Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommended variables templating #135

Merged
merged 15 commits into from
Oct 16, 2024
Merged

Recommended variables templating #135

merged 15 commits into from
Oct 16, 2024

Conversation

rin-st
Copy link
Member

@rin-st rin-st commented Oct 9, 2024

Added recommended ways to handle complex variables.

Despite most of those variables is not even possible in our current templates, I decided to consider most of the possible cases since it's easier to make all of them once than return and fix them whenever needed.

Changes comparing with #132 (comment)

  • added structuredClone( ) when adding new entries to objects and when adding new items to array. ...[ ] and ...{ } disappeared from resulting code
  • added two possible cases when working with objects/arrays with bigints inside

Resulting table

To test:

Change page.tsx.template.mjs to

Code

import { withDefaults } from "../../../../utils.js";

const contents = ({ imports,
  externalExtensionName,
  description,
  replacedObj,
  replacedArr,
  objToMerge,
  arrayToSpread,
  someBigInt,
  simpleObjectWithBigInt,
  objWithBigInt
}) => {
  return `
"use client";

import { useAccount } from "wagmi";
import { Address } from "~~/components/scaffold-eth";
import type { NextPage } from "next";
import Link from "next/link";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { bigintReviver } from "~~/utils/scaffold-eth/bigint-reviver";
${imports}

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();
  const replacedObj = ${JSON.stringify(replacedObj[0])};
  console.log(replacedObj, 'replacedObj');
  const replacedArr = ${JSON.stringify(replacedArr[0])};
  console.log(replacedArr, 'replacedArr');
  const mergedObj = ${JSON.stringify(structuredClone({key1: 'value1', key2: 'value2', ...objToMerge[0]}))};
  console.log(mergedObj, 'mergedObj');
  const arrWithAdditionalItems = ${JSON.stringify(structuredClone(['a', 'b', ...arrayToSpread[0]]))};
  console.log(arrWithAdditionalItems, 'arrWithAdditionalItems');
  const bigInt = BigInt("${someBigInt[0]}");
  console.log(bigInt, 'bigInt');
  console.log(typeof bigInt, 'typeof bigInt');
  const simpleObjectWithBigInt = { key1: BigInt("${simpleObjectWithBigInt[0].key1}") };
  console.log(simpleObjectWithBigInt, 'simpleObjectWithBigInt');
  const objWithBigInt = JSON.parse('${JSON.stringify(objWithBigInt[0])}', bigintReviver);
  console.log(objWithBigInt, 'objWithBigInt');
  const mergeObjectWithBigInt = JSON.parse('${JSON.stringify(structuredClone({key1: 'value1', key2: 'value2', ...objWithBigInt[0]}))}', bigintReviver);
  console.log(mergeObjectWithBigInt, 'mergeObjectWithBigInt');

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
            ${externalExtensionName[0] ? `<span className="block text-xl font-bold">(${externalExtensionName[0]} extension)</span>` : ''}
          </h1>
          <div className="flex justify-center items-center space-x-2 flex-col sm:flex-row">
            <p className="my-2 font-medium">Connected Address:</p>
            <Address address={connectedAddress} />
          </div>
          ${description}
        </div>

        <div className="flex-grow bg-base-300 w-full mt-16 px-8 py-12">
          <div className="flex justify-center items-center gap-12 flex-col sm:flex-row">
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <BugAntIcon className="h-8 w-8 fill-secondary" />
              <p>
                Tinker with your smart contract using the{" "}
                <Link href="/debug" passHref className="link">
                  Debug Contracts
                </Link>{" "}
                tab.
              </p>
            </div>
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <MagnifyingGlassIcon className="h-8 w-8 fill-secondary" />
              <p>
                Explore your local transactions with the{" "}
                <Link href="/blockexplorer" passHref className="link">
                  Block Explorer
                </Link>{" "}
                tab.
              </p>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;
`;
};

export default withDefaults(contents, {
  imports: ``,
  description: `
<p className="text-center text-lg">
  Get started by editing{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/nextjs/app/page.tsx
  </code>
</p>
<p className="text-center text-lg">
  Edit your smart contract{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    YourContract.sol
  </code>{" "}
  in{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/hardhat/contracts
  </code>
</p>
`,
  externalExtensionName: "",
  replacedObj: { key1: "Replaced", key2: "Object" },
  replacedArr: ["Replaced", "Array"],
  objToMerge: { key3: "Merged", key4: "Object" },
  arrayToSpread: ["Spread", "This"],
  someBigInt: 1233939399393939393939393912339393993939393939393939n,
  simpleObjectWithBigInt: { key1: 123n },
  objWithBigInt: { key1: 1233939399393939393939393912339393993939393939393939n }
});

run

yarn cli

and check resulting app/page.tsx in your instance and console.logs

Fixes #132

@rin-st
Copy link
Member Author

rin-st commented Oct 11, 2024

Added new lines to rules table about merging objects and adding new items to array. Marked previous versions as v2. V2s requires to wrap full object/array, and looks a bit more complex than V1s. Probably V2s should be removed from the table. V1s can work with part of the object/array, and don't need structuredClone but require simple utils for stringifying content, I also added it 71989f7

Updated page.tsx.template.mjs for testing

Details

import { getStringifiedArrayContent, getStringifiedObjectContent, withDefaults } from "../../../../utils.js";

const contents = ({ imports,
  externalExtensionName,
  description,
  replacedObj,
  replacedArr,
  objToMerge,
  arrayToSpread,
  someBigInt,
  simpleObjectWithBigInt,
  objWithBigInt
}) => {
  return `
"use client";

import { useAccount } from "wagmi";
import { Address } from "~~/components/scaffold-eth";
import type { NextPage } from "next";
import Link from "next/link";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { bigintReviver } from "~~/utils/scaffold-eth/bigint-reviver";
${imports}

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();
  const replacedObj = ${JSON.stringify(replacedObj[0])};
  console.log(replacedObj, 'replacedObj');
  const replacedArr = ${JSON.stringify(replacedArr[0])};
  console.log(replacedArr, 'replacedArr');
  const mergedObj1 = {key1: 'value1', key2: 'value2', ${getStringifiedObjectContent(objToMerge[0])}};
  console.log(mergedObj1, 'mergedObj1');
  const arrWithAdditionalItems1 = ['a', 'b', ${getStringifiedArrayContent(arrayToSpread[0])}];
  console.log(arrWithAdditionalItems1, 'arrWithAdditionalItems1');
  const mergedObj2 = ${JSON.stringify(structuredClone({key1: 'value1', key2: 'value2', ...objToMerge[0]}))};
  console.log(mergedObj2, 'mergedObj2');
  const arrWithAdditionalItems2 = ${JSON.stringify(structuredClone(['a', 'b', ...arrayToSpread[0]]))};
  console.log(arrWithAdditionalItems2, 'arrWithAdditionalItems2');
  const bigInt = BigInt("${someBigInt[0]}");
  console.log(bigInt, 'bigInt');
  console.log(typeof bigInt, 'typeof bigInt');
  const simpleObjectWithBigInt = { key1: BigInt("${simpleObjectWithBigInt[0].key1}") };
  console.log(simpleObjectWithBigInt, 'simpleObjectWithBigInt');
  const objWithBigInt = JSON.parse('${JSON.stringify(objWithBigInt[0])}', bigintReviver);
  console.log(objWithBigInt, 'objWithBigInt');
  const mergeObjectWithBigInt = JSON.parse('${JSON.stringify(structuredClone({key1: 'value1', key2: 'value2', ...objWithBigInt[0]}))}', bigintReviver);
  console.log(mergeObjectWithBigInt, 'mergeObjectWithBigInt');

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
            ${externalExtensionName[0] ? `<span className="block text-xl font-bold">(${externalExtensionName[0]} extension)</span>` : ''}
          </h1>
          <div className="flex justify-center items-center space-x-2 flex-col sm:flex-row">
            <p className="my-2 font-medium">Connected Address:</p>
            <Address address={connectedAddress} />
          </div>
          ${description}
        </div>

        <div className="flex-grow bg-base-300 w-full mt-16 px-8 py-12">
          <div className="flex justify-center items-center gap-12 flex-col sm:flex-row">
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <BugAntIcon className="h-8 w-8 fill-secondary" />
              <p>
                Tinker with your smart contract using the{" "}
                <Link href="/debug" passHref className="link">
                  Debug Contracts
                </Link>{" "}
                tab.
              </p>
            </div>
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <MagnifyingGlassIcon className="h-8 w-8 fill-secondary" />
              <p>
                Explore your local transactions with the{" "}
                <Link href="/blockexplorer" passHref className="link">
                  Block Explorer
                </Link>{" "}
                tab.
              </p>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;
`;
};

export default withDefaults(contents, {
  imports: ``,
  description: `
<p className="text-center text-lg">
  Get started by editing{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/nextjs/app/page.tsx
  </code>
</p>
<p className="text-center text-lg">
  Edit your smart contract{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    YourContract.sol
  </code>{" "}
  in{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/hardhat/contracts
  </code>
</p>
`,
  externalExtensionName: "",
  replacedObj: { key1: "Replaced", key2: "Object" },
  replacedArr: ["Replaced", "Array"],
  objToMerge: { key3: "Merged", key4: "Object" },
  arrayToSpread: ["Spread", "This"],
  someBigInt: 1233939399393939393939393912339393993939393939393939n,
  simpleObjectWithBigInt: { key1: 123n },
  objWithBigInt: { key1: 1233939399393939393939393912339393993939393939393939n }
});

@technophile-04
Copy link
Collaborator

Tyms @rin-st !! for all the research and PR! While I was researching some better ways to do it found this nice perfect node util for our usecase called inspect and it seems to nicely handle all the cases like merging objects without spread in final sourcecode, handling bigInts, handling bigint in nested object as well! TLDR; of it can a better JSON.stringify(). I think people use it for logging the data on servers.

But here is its woking example:

  1. Copy this in page.tsx.template.mjs :
import { inspect } from "util";
import { getStringifiedArrayContent, withDefaults } from "../../../../utils.js";

const contents = ({
  imports,
  externalExtensionName,
  description,
  replacedObj,
  replacedArr,
  objToMerge,
  arrayToSpread,
  someBigInt,
  simpleObjectWithBigInt,
  objWithBigInt,
}) => {
  return `
"use client";

import { useAccount } from "wagmi";
import { Address } from "~~/components/scaffold-eth";
import type { NextPage } from "next";
import Link from "next/link";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { bigintReviver } from "~~/utils/scaffold-eth/bigint-reviver";
${imports}

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();
  const replacedObj = ${JSON.stringify(replacedObj[0])};
  console.log(replacedObj, 'replacedObj');
  const replacedArr = ${JSON.stringify(replacedArr[0])};
  console.log(replacedArr, 'replacedArr');
  const mergedObj1 = ${inspect({ key1: "value1", key2: "value2", ...objToMerge[0] }, { depth: null, compact: true })} 
  console.log(mergedObj1, 'mergedObj1');
  const arrWithAdditionalItems1 = ['a', 'b', ${getStringifiedArrayContent(arrayToSpread[0])}];
  console.log(arrWithAdditionalItems1, 'arrWithAdditionalItems1');
  const mergedObj2 = ${JSON.stringify(structuredClone({ key1: "value1", key2: "value2", ...objToMerge[0] }))};
  console.log(mergedObj2, 'mergedObj2');
  const arrWithAdditionalItems2 = ${JSON.stringify(structuredClone(["a", "b", ...arrayToSpread[0]]))};
  console.log(arrWithAdditionalItems2, 'arrWithAdditionalItems2');
  const bigInt = ${inspect(someBigInt[0])};
  console.log(bigInt, 'bigInt');
  console.log(typeof bigInt, 'typeof bigInt');
  const simpleObjectWithBigInt = { key1: BigInt("${simpleObjectWithBigInt[0].key1}") };
  console.log(simpleObjectWithBigInt, 'simpleObjectWithBigInt');
  const objWithBigInt = JSON.parse('${JSON.stringify(objWithBigInt[0])}', bigintReviver);
  console.log(objWithBigInt, 'objWithBigInt');
  const mergeObjectWithBigInt = ${inspect({ key1: "value1", key2: "value2", ...objWithBigInt[0] }, { depth: null, compact: true })};
  console.log(mergeObjectWithBigInt, 'mergeObjectWithBigInt');

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
            ${externalExtensionName[0] ? `<span className="block text-xl font-bold">(${externalExtensionName[0]} extension)</span>` : ""}
          </h1>
          <div className="flex justify-center items-center space-x-2 flex-col sm:flex-row">
            <p className="my-2 font-medium">Connected Address:</p>
            <Address address={connectedAddress} />
          </div>
          ${description}
        </div>

        <div className="flex-grow bg-base-300 w-full mt-16 px-8 py-12">
          <div className="flex justify-center items-center gap-12 flex-col sm:flex-row">
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <BugAntIcon className="h-8 w-8 fill-secondary" />
              <p>
                Tinker with your smart contract using the{" "}
                <Link href="/debug" passHref className="link">
                  Debug Contracts
                </Link>{" "}
                tab.
              </p>
            </div>
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <MagnifyingGlassIcon className="h-8 w-8 fill-secondary" />
              <p>
                Explore your local transactions with the{" "}
                <Link href="/blockexplorer" passHref className="link">
                  Block Explorer
                </Link>{" "}
                tab.
              </p>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;
`;
};

export default withDefaults(contents, {
  imports: ``,
  description: `
<p className="text-center text-lg">
  Get started by editing{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/nextjs/app/page.tsx
  </code>
</p>
<p className="text-center text-lg">
  Edit your smart contract{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    YourContract.sol
  </code>{" "}
  in{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/hardhat/contracts
  </code>
</p>
`,
  externalExtensionName: "",
  replacedObj: { key1: "Replaced", key2: "Object" },
  replacedArr: ["Replaced", "Array"],
  objToMerge: { key3: "Merged", key4: "Object" },
  arrayToSpread: ["Spread", "This"],
  someBigInt: 1233939399393939393939393912339393993939393939393939n,
  simpleObjectWithBigInt: { key1: 123n },
  objWithBigInt: { key1: 1233939399393939393939393912339393993939393939393939n },
});
  1. Checkout line 33, 41, 48 for how to use it.

And in final result this is how it looks 🙌
Screenshot 2024-10-14 at 7 54 09 AM

So I was thinking lets remove all the utils getStringifiedObjectContent, getStringifiedArrayContent , other big int handling.

single util for objects and array and we could use it as mergeObjectOrArray({a: 3, ...object[0]}) (we can name it better)

and for single bigInt variables we just them using const bigInt = ${inspect(someBigInt[0])}; nested bigints in objects are already handled by mergeObjectOrArray

@rin-st
Copy link
Member Author

rin-st commented Oct 14, 2024

Great find!! So if add simple util

export const deepStringify = val => inspect(val, { depth: null, compact: true, maxArrayLength: null, maxStringLength: null })

everything could just be changed to deepStringify, so we don't need getStringifiedObjectContent , getStringifiedArrayContent and bigintReviver

page.tsx.template.mjs

import { deepStringify, withDefaults } from "../../../../utils.js";

const contents = ({
  imports,
  externalExtensionName,
  description,
  replacedObj,
  replacedArr,
  objToMerge,
  arrayToSpread,
  someBigInt,
  simpleObjectWithBigInt,
  objWithBigInt,
}) => {
  return `
"use client";

import { useAccount } from "wagmi";
import { Address } from "~~/components/scaffold-eth";
import type { NextPage } from "next";
import Link from "next/link";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
${imports}

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();
  const replacedObj = ${deepStringify(replacedObj[0])};
  console.log(replacedObj, 'replacedObj');
  const replacedArr = ${deepStringify(replacedArr[0])};
  console.log(replacedArr, 'replacedArr');
  const mergedObj1 = ${deepStringify({ key1: "value1", key2: "value2", ...objToMerge[0] })};
  console.log(mergedObj1, 'mergedObj1');
  const arrWithAdditionalItems1 = ${deepStringify(['a', 'b', ...arrayToSpread[0]])};
  console.log(arrWithAdditionalItems1, 'arrWithAdditionalItems1');
  const mergedObj2 = ${deepStringify({ key1: "value1", key2: "value2", ...objToMerge[0] })};
  console.log(mergedObj2, 'mergedObj2');
  const arrWithAdditionalItems2 = ${deepStringify(["a", "b", ...arrayToSpread[0]])};
  console.log(arrWithAdditionalItems2, 'arrWithAdditionalItems2');
  const bigInt = ${deepStringify(someBigInt[0])};
  console.log(bigInt, 'bigInt');
  console.log(typeof bigInt, 'typeof bigInt');
  const simpleObjectWithBigInt = ${deepStringify(simpleObjectWithBigInt[0])};
  console.log(simpleObjectWithBigInt, 'simpleObjectWithBigInt');
  const objWithBigInt = ${deepStringify(objWithBigInt[0])};
  console.log(objWithBigInt, 'objWithBigInt');
  const mergeObjectWithBigInt = ${deepStringify({ key1: "value1", key2: "value2", ...objWithBigInt[0] })};
  console.log(mergeObjectWithBigInt, 'mergeObjectWithBigInt');

  return (
    <>
      <div className="flex items-center flex-col flex-grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
            ${externalExtensionName[0] ? `<span className="block text-xl font-bold">(${externalExtensionName[0]} extension)</span>` : ""}
          </h1>
          <div className="flex justify-center items-center space-x-2 flex-col sm:flex-row">
            <p className="my-2 font-medium">Connected Address:</p>
            <Address address={connectedAddress} />
          </div>
          ${description}
        </div>

        <div className="flex-grow bg-base-300 w-full mt-16 px-8 py-12">
          <div className="flex justify-center items-center gap-12 flex-col sm:flex-row">
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <BugAntIcon className="h-8 w-8 fill-secondary" />
              <p>
                Tinker with your smart contract using the{" "}
                <Link href="/debug" passHref className="link">
                  Debug Contracts
                </Link>{" "}
                tab.
              </p>
            </div>
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <MagnifyingGlassIcon className="h-8 w-8 fill-secondary" />
              <p>
                Explore your local transactions with the{" "}
                <Link href="/blockexplorer" passHref className="link">
                  Block Explorer
                </Link>{" "}
                tab.
              </p>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;
`;
};

export default withDefaults(contents, {
  imports: ``,
  description: `
<p className="text-center text-lg">
  Get started by editing{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/nextjs/app/page.tsx
  </code>
</p>
<p className="text-center text-lg">
  Edit your smart contract{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    YourContract.sol
  </code>{" "}
  in{" "}
  <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
    packages/hardhat/contracts
  </code>
</p>
`,
  externalExtensionName: "",
  replacedObj: { key1: "Replaced", key2: "Object" },
  replacedArr: ["Replaced", "Array"],
  objToMerge: { key3: "Merged", key4: "Object" },
  arrayToSpread: ["Spread", "This"],
  someBigInt: 1233939399393939393939393912339393993939393939393939n,
  simpleObjectWithBigInt: { key1: 123n },
  objWithBigInt: { key1: 1233939399393939393939393912339393993939393939393939n },
});

Result:
image

I think it's the easiest way. Probably in some places using getStringifiedObjectContent or getStringifiedArrayContent is more convenient when used doesn't want to touch outer object, but looks like it's more natural to wrap full object/array with deepStringify and use real objects/array inside.

I mean, if compare
before ({key1, key2 and ['a', 'b' were already stringified part):

const mergedObj1 = {key1: 'value1', key2: 'value2', ${getStringifiedObjectContent(objToMerge[0])}};
const arrWithAdditionalItems1 = ['a', 'b', ${getStringifiedArrayContent(arrayToSpread[0])}];

after (everything is array/object):

const mergedObj1 = ${deepStringify({ key1: "value1", key2: "value2", ...objToMerge[0] })};
const arrWithAdditionalItems1 = ${deepStringify(['a', 'b', ...arrayToSpread[0]])};

What do you think?

@rin-st
Copy link
Member Author

rin-st commented Oct 14, 2024

updated pr with deepStringify eeea6df

templates/utils.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@technophile-04 technophile-04 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rin-st for updating it! Added a couple of comments but looks ready to be merged!!

@technophile-04
Copy link
Collaborator

Thanks @rin-st! Merging this!

@technophile-04 technophile-04 merged commit ba4e12e into main Oct 16, 2024
1 check passed
@technophile-04 technophile-04 deleted the templating-rules branch October 16, 2024 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Describe templating rules
2 participants