diff --git a/.eslintignore b/.eslintignore index 178135c..f5bb9fd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ /dist/ +/test/output/ diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 61f423d..c2b58f0 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,8 +2,13 @@ module.exports = { env: { node: true }, - extends: ["eslint:recommended", "plugin:n/recommended"], + extends: [ + "eslint:recommended", + "plugin:n/recommended", + "plugin:@typescript-eslint/recommended", + ], parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: "latest" }, + plugins: ["@typescript-eslint"], root: true, }; diff --git a/.prettierignore b/.prettierignore index d28376b..8ed4f00 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ /coverage/ /dist/ +/test/output/ /CHANGELOG.md /pnpm-lock.yaml diff --git a/README.md b/README.md index 6921bfc..be16eb1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/bertdeblock/generate-template-registry/workflows/CI/badge.svg)](https://github.com/bertdeblock/generate-template-registry/actions?query=workflow%3ACI) -Generate a template registry for [Glint](https://github.com/typed-ember/glint). +Generate a [template registry](https://typed-ember.gitbook.io/glint/environments/ember/template-registry) for [Glint](https://github.com/typed-ember/glint). ## Usage diff --git a/package.json b/package.json index 168f08c..ed3e616 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@types/fs-extra": "^11.0.4", "@types/node": "^20.11.20", "@types/recursive-readdir": "^2.2.4", + "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "@vitest/coverage-v8": "^1.2.2", "concurrently": "^8.2.2", @@ -47,6 +48,7 @@ "prettier": "^3.2.5", "recursive-copy": "^2.0.14", "release-it": "^17.0.3", + "type-fest": "^4.10.3", "typescript": "^5.3.3", "vitest": "^1.2.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0dd031..5358710 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ devDependencies: '@types/recursive-readdir': specifier: ^2.2.4 version: 2.2.4 + '@typescript-eslint/eslint-plugin': + specifier: ^7.1.0 + version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/parser': specifier: ^7.1.0 version: 7.1.0(eslint@8.57.0)(typescript@5.3.3) @@ -58,6 +61,9 @@ devDependencies: release-it: specifier: ^17.0.3 version: 17.1.1(typescript@5.3.3) + type-fest: + specifier: ^4.10.3 + version: 4.10.3 typescript: specifier: ^5.3.3 version: 5.3.3 @@ -792,6 +798,10 @@ packages: resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} dev: true + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + /@types/jsonfile@6.1.4: resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} dependencies: @@ -820,10 +830,43 @@ packages: '@types/node': 20.11.20 dev: true + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + dev: true + /@types/unist@2.0.10: resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} dev: true + /@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/type-utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.1.0 + debug: 4.3.4 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.2.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==} engines: {node: ^16.0.0 || >=18.0.0} @@ -853,6 +896,26 @@ packages: '@typescript-eslint/visitor-keys': 7.1.0 dev: true + /@typescript-eslint/type-utils@7.1.0(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.57.0 + ts-api-utils: 1.2.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@7.1.0: resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -880,6 +943,25 @@ packages: - supports-color dev: true + /@typescript-eslint/utils@7.1.0(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + eslint: 8.57.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@7.1.0: resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -4772,6 +4854,11 @@ packages: engines: {node: '>=12.20'} dev: true + /type-fest@4.10.3: + resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==} + engines: {node: '>=16'} + dev: true + /typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} diff --git a/src/generate-template-registry.ts b/src/generate-template-registry.ts index 836c6b6..4b49e9f 100644 --- a/src/generate-template-registry.ts +++ b/src/generate-template-registry.ts @@ -13,10 +13,16 @@ import { toAngleBracketNotation, toRegistryKey, } from "./helpers.js"; -import type { EntriesResult } from "./types.js"; +import { + EntryType, + type EmberPackageJson, + type EntriesResult, +} from "./types.js"; + +const TAB = " "; export async function generateTemplateRegistry(cwd = processCwd()) { - let packageJson: any; + let packageJson: EmberPackageJson; try { packageJson = await readJson(join(cwd, "package.json")); @@ -26,11 +32,14 @@ export async function generateTemplateRegistry(cwd = processCwd()) { ); } + if (typeof packageJson.name === "undefined") { + throw new Error(`The found "package.json" file is missing a "name" entry.`); + } + if (isEmberPackage(packageJson) === false) { throw new Error("The current package is not an Ember app or addon."); } - const importRoot = isV2Addon(packageJson) ? "." : packageJson.name; const entriesDir = isAddon(packageJson) ? isV2Addon(packageJson) ? "src" @@ -39,6 +48,20 @@ export async function generateTemplateRegistry(cwd = processCwd()) { const entries = await getEntries(join(cwd, entriesDir)); + if ( + entries.components.length === 0 && + entries.helpers.length === 0 && + entries.modifiers.length === 0 + ) { + console.warn( + chalk.yellow( + "No component, helper or modifier gts/ts files found. An empty template registry will be generated.", + ), + ); + } + + const importRoot = isV2Addon(packageJson) ? "." : packageJson.name; + let templateRegistryContent = ""; for (const type in entries) { @@ -63,7 +86,7 @@ export async function generateTemplateRegistry(cwd = processCwd()) { templateRegistryContent += `export default interface ${pascalCase(packageJson.name)}Registry {\n`; - let entriesContent: string[] = []; + const entriesContent: string[] = []; for (const type in entries) { const typeEntries = entries[type as keyof EntriesResult]; @@ -72,13 +95,13 @@ export async function generateTemplateRegistry(cwd = processCwd()) { continue; } - let content = ` // ${type}\n`; + let content = `${TAB}// ${type}\n`; typeEntries.forEach((entry) => { - content += ` ${toRegistryKey(entry.name)}: typeof ${entry.identifier};\n`; + content += `${TAB}${toRegistryKey(entry.name)}: typeof ${entry.identifier};\n`; - if (type === "components") { - content += ` ${toRegistryKey(toAngleBracketNotation(entry.name))}: typeof ${entry.identifier};\n`; + if (type === EntryType.Components) { + content += `${TAB}${toRegistryKey(toAngleBracketNotation(entry.name))}: typeof ${entry.identifier};\n`; } }); diff --git a/src/helpers.ts b/src/helpers.ts index d1397a0..ab9c4a3 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -2,7 +2,11 @@ import { pascalCase } from "change-case"; import glob from "fast-glob"; import { pathExists } from "fs-extra/esm"; import { join, parse } from "node:path"; -import type { EntriesResult } from "./types.js"; +import { + EntryType, + type EmberPackageJson, + type EntriesResult, +} from "./types.js"; export async function getEntries(directory: string): Promise { const seenIdentifiers: Record = {}; @@ -12,11 +16,11 @@ export async function getEntries(directory: string): Promise { modifiers: [], }; - for (const type in entriesResult) { - const path = join(directory, type); + for (const entryType of Object.values(EntryType)) { + const path = join(directory, entryType); if (await pathExists(path)) { - const files = await glob("**/*.{gjs,gts,ts}", { cwd: path }); + const files = await glob("**/*.{gts,ts}", { cwd: path }); const filesSorted = files.sort(); for (let index = 0; index < filesSorted.length; index++) { @@ -40,7 +44,7 @@ export async function getEntries(directory: string): Promise { seenIdentifiers[identifier] = 1; } - entriesResult[type as keyof EntriesResult].push({ + entriesResult[entryType as keyof EntriesResult].push({ extension: fileParsed.ext, identifier, name, @@ -52,15 +56,19 @@ export async function getEntries(directory: string): Promise { return entriesResult; } -export function isAddon(packageJson: any): boolean { - return packageJson.keywords?.includes("ember-addon"); +export function isAddon(packageJson: EmberPackageJson): boolean { + if (Array.isArray(packageJson.keywords)) { + return packageJson.keywords.includes("ember-addon"); + } + + return false; } -export function isEmberPackage(packageJson: any): boolean { +export function isEmberPackage(packageJson: EmberPackageJson): boolean { return typeof packageJson.ember === "object"; } -export function isV2Addon(packageJson: any): boolean { +export function isV2Addon(packageJson: EmberPackageJson): boolean { return packageJson["ember-addon"]?.version === 2; } diff --git a/src/types.ts b/src/types.ts index 62248b1..9716622 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,12 @@ -export interface EntriesResult { - components: Entry[]; - helpers: Entry[]; - modifiers: Entry[]; +// eslint-disable-next-line n/no-missing-import +import type { PackageJson } from "type-fest"; + +export type EntriesResult = Record; + +export enum EntryType { + Components = "components", + Helpers = "helpers", + Modifiers = "modifiers", } export interface Entry { @@ -9,3 +14,12 @@ export interface Entry { identifier: string; name: string; } + +export type EmberPackageJson = PackageJson & { + ember?: { + edition?: string; + }; + "ember-addon"?: { + version?: number; + }; +};