diff --git a/README.md b/README.md index b9cada3..15c4cf9 100644 --- a/README.md +++ b/README.md @@ -139,39 +139,155 @@ export default async () => ({}); export type Config = { generators?: { component?: { + // Generate a `class-based` component, instead of a `template-only` component: classBased?: boolean; + // Copy the generated component to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated component to the console, instead of writing it to disk: + log?: boolean; + // The component's name: + name?: string; + // Generate a nested colocated component, e.g. `foo/bar/index.gts`: nested?: boolean; + // Generate a component at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` component, instead of a `.gjs` component: typescript?: boolean; }; "component-test"?: { + // Copy the generated component-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated component-test to the console, instead of writing it to disk: + log?: boolean; + // The component-test's name: + name?: string; + // Generate a component-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` component-test, instead of a `.gjs` component-test: typescript?: boolean; }; helper?: { + // Generate a `class-based` helper, instead of a `function-based` helper: classBased?: boolean; + // Copy the generated helper to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated helper to the console, instead of writing it to disk: + log?: boolean; + // The helper's name: + name?: string; + // Generate a helper at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` helper, instead of a `.js` helper: typescript?: boolean; }; "helper-test"?: { + // Copy the generated helper-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated helper-test to the console, instead of writing it to disk: + log?: boolean; + // The helper-test's name: + name?: string; + // Generate a helper-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` helper-test, instead of a `.gjs` helper-test: typescript?: boolean; }; modifier?: { + // Generate a `class-based` modifier, instead of a `function-based` modifier: classBased?: boolean; + // Copy the generated modifier to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated modifier to the console, instead of writing it to disk: + log?: boolean; + // The modifier's name: + name?: string; + // Generate a modifier at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` modifier, instead of a `.js` modifier: typescript?: boolean; }; "modifier-test"?: { + // Copy the generated modifier-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated modifier-test to the console, instead of writing it to disk: + log?: boolean; + // The modifier-test's name: + name?: string; + // Generate a modifier-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` modifier-test, instead of a `.gjs` modifier-test: typescript?: boolean; }; service?: { + // Copy the generated service to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated service to the console, instead of writing it to disk: + log?: boolean; + // The service's name: + name?: string; + // Generate a service at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` service, instead of a `.js` service: typescript?: boolean; }; "service-test"?: { + // Copy the generated service-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated service-test to the console, instead of writing it to disk: + log?: boolean; + // The service-test's name: + name?: string; + // Generate a service-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` service-test, instead of a `.js` service-test: + typescript?: boolean; + }; + "acceptance-test"?: { + // Copy the generated acceptance-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated acceptance-test to the console, instead of writing it to disk: + log?: boolean; + // The acceptance-test's name: + name?: string; + // Generate a acceptance-test at a custom path, e.g. `--path=src/-private`: + path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` acceptance-test, instead of a `.js` acceptance-test: typescript?: boolean; }; }; diff --git a/dev/generators-type.js b/dev/generators-type.js new file mode 100644 index 0000000..414b36f --- /dev/null +++ b/dev/generators-type.js @@ -0,0 +1,17 @@ +// eslint-disable-next-line n/no-missing-import +import { generators } from "../dist/generators.js"; + +let generatorsType = ""; + +for (const generator of generators) { + generatorsType += `${generator.name.includes("-") ? `"${generator.name}"` : generator.name}?: {\n`; + + for (const arg of generator.args) { + generatorsType += ` // ${arg.description}:\n`; + generatorsType += ` ${arg.name}?: ${arg.type === "positional" ? "string" : arg.type};\n`; + } + + generatorsType += `};\n`; +} + +console.log(generatorsType); diff --git a/src/config.ts b/src/config.ts index 372e7b0..1cb1c6a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,39 +6,155 @@ import type { GeneratorFile } from "./types.js"; export type Config = { generators?: { component?: { + // Generate a `class-based` component, instead of a `template-only` component: classBased?: boolean; + // Copy the generated component to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated component to the console, instead of writing it to disk: + log?: boolean; + // The component's name: + name?: string; + // Generate a nested colocated component, e.g. `foo/bar/index.gts`: nested?: boolean; + // Generate a component at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` component, instead of a `.gjs` component: typescript?: boolean; }; "component-test"?: { + // Copy the generated component-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated component-test to the console, instead of writing it to disk: + log?: boolean; + // The component-test's name: + name?: string; + // Generate a component-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` component-test, instead of a `.gjs` component-test: typescript?: boolean; }; helper?: { + // Generate a `class-based` helper, instead of a `function-based` helper: classBased?: boolean; + // Copy the generated helper to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated helper to the console, instead of writing it to disk: + log?: boolean; + // The helper's name: + name?: string; + // Generate a helper at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` helper, instead of a `.js` helper: typescript?: boolean; }; "helper-test"?: { + // Copy the generated helper-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated helper-test to the console, instead of writing it to disk: + log?: boolean; + // The helper-test's name: + name?: string; + // Generate a helper-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` helper-test, instead of a `.gjs` helper-test: typescript?: boolean; }; modifier?: { + // Generate a `class-based` modifier, instead of a `function-based` modifier: classBased?: boolean; + // Copy the generated modifier to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated modifier to the console, instead of writing it to disk: + log?: boolean; + // The modifier's name: + name?: string; + // Generate a modifier at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` modifier, instead of a `.js` modifier: typescript?: boolean; }; "modifier-test"?: { + // Copy the generated modifier-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated modifier-test to the console, instead of writing it to disk: + log?: boolean; + // The modifier-test's name: + name?: string; + // Generate a modifier-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.gts` modifier-test, instead of a `.gjs` modifier-test: typescript?: boolean; }; service?: { + // Copy the generated service to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated service to the console, instead of writing it to disk: + log?: boolean; + // The service's name: + name?: string; + // Generate a service at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` service, instead of a `.js` service: typescript?: boolean; }; "service-test"?: { + // Copy the generated service-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated service-test to the console, instead of writing it to disk: + log?: boolean; + // The service-test's name: + name?: string; + // Generate a service-test at a custom path, e.g. `--path=src/-private`: path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` service-test, instead of a `.js` service-test: + typescript?: boolean; + }; + "acceptance-test"?: { + // Copy the generated acceptance-test to the clipboard, instead of writing it to disk: + copy?: boolean; + // Log the generated acceptance-test to the console, instead of writing it to disk: + log?: boolean; + // The acceptance-test's name: + name?: string; + // Generate a acceptance-test at a custom path, e.g. `--path=src/-private`: + path?: string; + // Custom template content: + templateContent?: string; + // Custom template path: + templatePath?: string; + // Generate a `.ts` acceptance-test, instead of a `.js` acceptance-test: typescript?: boolean; }; }; diff --git a/src/generator.ts b/src/generator.ts index c6dc836..0abe2e2 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -8,6 +8,7 @@ import { dirname, join, relative } from "node:path"; import { cwd } from "node:process"; import { fileURLToPath } from "node:url"; import { resolveConfig, type Config } from "./config.js"; +import { GemberError } from "./errors.js"; import { FileReference } from "./file-reference.js"; import { isV1Addon, isV2Addon } from "./helpers.js"; import type { EmberPackageJson, GeneratorFile } from "./types.js"; @@ -60,7 +61,13 @@ export function defineGenerator({ name, }: GeneratorOptions): Generator { const generatorName = name; - const generatorArgs = [copy(), log(), ...args] + const generatorArgs = [ + copy(), + log(), + templateContent(), + templatePath(), + ...args, + ] .map((argFactory) => argFactory(generatorName)) .sort((a, b) => a.name.localeCompare(b.name)); @@ -88,15 +95,7 @@ export function defineGenerator({ subDir: entityPath ?? "", }); - const templateFile = new FileReference({ - ext: ".ts", - name: generatorName, - rootDir: join(dirname(fileURLToPath(import.meta.url)), "..", "templates"), - subDir: generatorName, - }); - await modifyTargetFile?.(targetFile, resolvedArgs); - await modifyTemplateFile?.(templateFile, resolvedArgs); if (targetFile.subDir === "") { targetFile.subDir = join(getSrcDir(packageJson), generatorName + "s"); @@ -104,10 +103,44 @@ export function defineGenerator({ for (const arg of generatorArgs) { await arg.modifyTargetFile?.(targetFile, resolvedArgs); - await arg.modifyTemplateFile?.(templateFile, resolvedArgs); } - const templateContent = await readFile(templateFile.path(), "utf-8"); + let templateContent; + + if (args.templateContent) { + templateContent = args.templateContent; + } else if (args.templatePath) { + try { + templateContent = await readFile( + join(packagePath, args.templatePath), + "utf-8", + ); + } catch (cause) { + throw new GemberError(`Could not read file \`${args.templatePath}\`.`, { + cause, + }); + } + } else { + const templateFile = new FileReference({ + ext: ".ts", + name: generatorName, + rootDir: join( + dirname(fileURLToPath(import.meta.url)), + "..", + "templates", + ), + subDir: generatorName, + }); + + await modifyTemplateFile?.(templateFile, resolvedArgs); + + for (const arg of generatorArgs) { + await arg.modifyTemplateFile?.(templateFile, resolvedArgs); + } + + templateContent = await readFile(templateFile.path(), "utf-8"); + } + const template = Handlebars.compile(templateContent); const entityNameCases = { @@ -116,21 +149,27 @@ export function defineGenerator({ path: pathCase(entityName), }; - const templateCompiled = template({ - name: { - ...entityNameCases, - pathMaybeQuoted: /(-|\/)/.test(entityNameCases.path) - ? `"${entityNameCases.path}"` - : entityNameCases.path, - signature: entityNameCases.pascal + "Signature", - }, - package: packageJson, - testHelpersImportPath: - (await pathExists(join(packagePath, "tests", "helpers.js"))) || - (await pathExists(join(packagePath, "tests", "helpers.ts"))) - ? `${packageJson.name}/tests/helpers` - : "ember-qunit", - }); + let templateCompiled; + + try { + templateCompiled = template({ + name: { + ...entityNameCases, + pathMaybeQuoted: /(-|\/)/.test(entityNameCases.path) + ? `"${entityNameCases.path}"` + : entityNameCases.path, + signature: entityNameCases.pascal + "Signature", + }, + package: packageJson, + testHelpersImportPath: + (await pathExists(join(packagePath, "tests", "helpers.js"))) || + (await pathExists(join(packagePath, "tests", "helpers.ts"))) + ? `${packageJson.name}/tests/helpers` + : "ember-qunit", + }); + } catch (cause) { + throw new GemberError("Could not compile template.", { cause }); + } if (resolvedArgs.copy) { const clipboard = new Clipboard(); @@ -282,6 +321,22 @@ export function path(): GeneratorArgFactory { }); } +export function templateContent(): GeneratorArgFactory { + return () => ({ + description: "Custom template content", + name: "templateContent", + type: "string", + }); +} + +export function templatePath(): GeneratorArgFactory { + return () => ({ + description: "Custom template path", + name: "templatePath", + type: "string", + }); +} + export function typescript({ gts = false, }: {