diff --git a/package.json b/package.json index dfca0b9..0886d3a 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "chalk": "^5.3.0", "change-case": "^5.4.3", + "execa": "^8.0.1", "fast-glob": "^3.3.2", "fs-extra": "^11.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fee1f4..a0dd031 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: change-case: specifier: ^5.4.3 version: 5.4.3 + execa: + specifier: ^8.0.1 + version: 8.0.1 fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -1525,7 +1528,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} @@ -2066,7 +2068,6 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: true /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} @@ -2249,7 +2250,6 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: true /get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} @@ -2531,7 +2531,6 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: true /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -2845,7 +2844,6 @@ packages: /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} @@ -2906,7 +2904,6 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true /issue-parser@6.0.0: resolution: {integrity: sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==} @@ -3250,7 +3247,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -3458,7 +3454,6 @@ packages: /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: true /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -3652,7 +3647,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 - dev: true /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -3696,7 +3690,6 @@ packages: engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 - dev: true /open@10.0.3: resolution: {integrity: sha512-dtbI5oW7987hwC9qjJTyABldTaa19SuyJse1QboWv3b0qCcrrLNVDqBx1XgELAjh9QTVQaP/C5b1nhQebd1H2A==} @@ -3893,12 +3886,10 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4395,12 +4386,10 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} @@ -4437,7 +4426,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /slash@1.0.0: resolution: {integrity: sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==} @@ -4614,7 +4602,6 @@ packages: /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -5108,7 +5095,6 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true /why-is-node-running@2.2.2: resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} diff --git a/src/generate-template-registry.ts b/src/generate-template-registry.ts index 9559293..607dd84 100644 --- a/src/generate-template-registry.ts +++ b/src/generate-template-registry.ts @@ -1,5 +1,6 @@ import chalk from "chalk"; import { camelCase, pascalCase } from "change-case"; +import { execa } from "execa"; import glob from "fast-glob"; import { pathExists, readJson } from "fs-extra/esm"; import { writeFile } from "node:fs/promises"; @@ -29,11 +30,15 @@ export async function generateTemplateRegistry(cwd = processCwd()) { const helpers = await getEntries(join(cwd, srcDir, "helpers")); const modifiers = await getEntries(join(cwd, srcDir, "modifiers")); + const identifiers = new Identifiers(); + let templateRegistryContent = ""; if (components.length > 0) { const componentImports = components.map((name) => { - return `import type ${pascalCase(name)}Component from "${importRoot}/components/${name}";`; + const identifier = identifiers.generate(name, pascalCase, "Component"); + + return `import type ${identifier} from "${importRoot}/components/${name}";`; }); templateRegistryContent += "// Components:\n"; @@ -42,7 +47,9 @@ export async function generateTemplateRegistry(cwd = processCwd()) { if (helpers.length > 0) { const helperImports = helpers.map((name) => { - return `import type ${camelCase(name)}Helper from "${importRoot}/helpers/${name}";`; + const identifier = identifiers.generate(name, camelCase, "Helper"); + + return `import type ${identifier} from "${importRoot}/helpers/${name}";`; }); templateRegistryContent += "// Helpers:\n"; @@ -51,7 +58,9 @@ export async function generateTemplateRegistry(cwd = processCwd()) { if (modifiers.length > 0) { const modifierImports = modifiers.map((name) => { - return `import type ${camelCase(name)}Modifier from "${importRoot}/modifiers/${name}";`; + const identifier = identifiers.generate(name, camelCase, "Modifier"); + + return `import type ${identifier} from "${importRoot}/modifiers/${name}";`; }); templateRegistryContent += "// Modifiers:\n"; @@ -66,8 +75,8 @@ export async function generateTemplateRegistry(cwd = processCwd()) { let componentsContent = " // Components:\n"; components.forEach((name) => { - componentsContent += ` ${angleBracketNotation(name)}: typeof ${pascalCase(name)}Component;\n`; - componentsContent += ` "${name}": typeof ${pascalCase(name)}Component;\n`; + componentsContent += ` ${angleBracketNotation(name)}: typeof ${identifiers.for(name)};\n`; + componentsContent += ` "${name}": typeof ${identifiers.for(name)};\n`; }); entriesContent.push(componentsContent); @@ -77,7 +86,7 @@ export async function generateTemplateRegistry(cwd = processCwd()) { let helpersContent = " // Helpers:\n"; helpers.forEach((name) => { - helpersContent += ` "${name}": typeof ${camelCase(name)}Helper;\n`; + helpersContent += ` "${name}": typeof ${identifiers.for(name)};\n`; }); entriesContent.push(helpersContent); @@ -87,7 +96,7 @@ export async function generateTemplateRegistry(cwd = processCwd()) { let modifiersContent = " // Modifiers:\n"; modifiers.forEach((name) => { - modifiersContent += ` "${name}": typeof ${camelCase(name)}Modifier;\n`; + modifiersContent += ` "${name}": typeof ${identifiers.for(name)};\n`; }); entriesContent.push(modifiersContent); @@ -99,6 +108,12 @@ export async function generateTemplateRegistry(cwd = processCwd()) { await writeFile(templateRegistryPath, templateRegistryContent); + try { + await execa("npx", ["prettier", "--write", templateRegistryPath]); + } catch { + // Move on. + } + console.log( chalk.green(`Template registry generated at ${templateRegistryPath}`), ); @@ -150,3 +165,24 @@ function angleBracketNotation(name: string) { return result; } + +class Identifiers { + map: Record = {}; + seen: Record = {}; + + for(name: string) { + return this.map[name]; + } + + generate(name: string, transform: typeof camelCase | typeof pascalCase, suffix: string) { + let identifier = `${transform(name)}${suffix}`; + + this.seen[identifier] = (this.seen[identifier] || 0) + 1; + + identifier += this.seen[identifier] > 1 ? this.seen[identifier] : ""; + + this.map[name] = identifier; + + return identifier; + } +} diff --git a/test/__snapshots__/generate-template-registry.test.ts.snap b/test/__snapshots__/generate-template-registry.test.ts.snap index ba3eb5d..0d51ab2 100644 --- a/test/__snapshots__/generate-template-registry.test.ts.snap +++ b/test/__snapshots__/generate-template-registry.test.ts.snap @@ -3,17 +3,20 @@ exports[`generates a template registry for a v1 addon 1`] = ` "// Components: import type BarComponent from "v1-addon/components/bar"; +import type FooBarComponent from "v1-addon/components/foo-bar"; import type FooComponent from "v1-addon/components/foo"; -import type FooBarComponent from "v1-addon/components/foo/bar"; +import type FooBarComponent2 from "v1-addon/components/foo/bar"; export default interface Registry { // Components: Bar: typeof BarComponent; "bar": typeof BarComponent; + FooBar: typeof FooBarComponent; + "foo-bar": typeof FooBarComponent; Foo: typeof FooComponent; "foo": typeof FooComponent; - "Foo::Bar": typeof FooBarComponent; - "foo/bar": typeof FooBarComponent; + "Foo::Bar": typeof FooBarComponent2; + "foo/bar": typeof FooBarComponent2; } " `; @@ -21,17 +24,20 @@ export default interface Registry { exports[`generates a template registry for a v2 addon 1`] = ` "// Components: import type BarComponent from "./components/bar"; +import type FooBarComponent from "./components/foo-bar"; import type FooComponent from "./components/foo"; -import type FooBarComponent from "./components/foo/bar"; +import type FooBarComponent2 from "./components/foo/bar"; export default interface Registry { // Components: Bar: typeof BarComponent; "bar": typeof BarComponent; + FooBar: typeof FooBarComponent; + "foo-bar": typeof FooBarComponent; Foo: typeof FooComponent; "foo": typeof FooComponent; - "Foo::Bar": typeof FooBarComponent; - "foo/bar": typeof FooBarComponent; + "Foo::Bar": typeof FooBarComponent2; + "foo/bar": typeof FooBarComponent2; } " `; @@ -39,17 +45,20 @@ export default interface Registry { exports[`generates a template registry for an app 1`] = ` "// Components: import type BarComponent from "app/components/bar"; +import type FooBarComponent from "app/components/foo-bar"; import type FooComponent from "app/components/foo"; -import type FooBarComponent from "app/components/foo/bar"; +import type FooBarComponent2 from "app/components/foo/bar"; export default interface Registry { // Components: Bar: typeof BarComponent; "bar": typeof BarComponent; + FooBar: typeof FooBarComponent; + "foo-bar": typeof FooBarComponent; Foo: typeof FooComponent; "foo": typeof FooComponent; - "Foo::Bar": typeof FooBarComponent; - "foo/bar": typeof FooBarComponent; + "Foo::Bar": typeof FooBarComponent2; + "foo/bar": typeof FooBarComponent2; } " `; diff --git a/test/blueprints/app/app/components/foo-bar.hbs b/test/blueprints/app/app/components/foo-bar.hbs new file mode 100644 index 0000000..e69de29 diff --git a/test/blueprints/app/app/components/foo-bar.ts b/test/blueprints/app/app/components/foo-bar.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/blueprints/v1-addon/addon/components/foo-bar.hbs b/test/blueprints/v1-addon/addon/components/foo-bar.hbs new file mode 100644 index 0000000..e69de29 diff --git a/test/blueprints/v1-addon/addon/components/foo-bar.ts b/test/blueprints/v1-addon/addon/components/foo-bar.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/blueprints/v2-addon/src/components/foo-bar.hbs b/test/blueprints/v2-addon/src/components/foo-bar.hbs new file mode 100644 index 0000000..e69de29 diff --git a/test/blueprints/v2-addon/src/components/foo-bar.ts b/test/blueprints/v2-addon/src/components/foo-bar.ts new file mode 100644 index 0000000..e69de29