@nanoforge-dev/schematics 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/collection.json +5 -0
  3. package/dist/index.cjs +4 -2
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +2 -2
  6. package/dist/index.d.cts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/libs/component/component.factory.d.ts +1 -4
  12. package/dist/libs/component/component.factory.d.ts.map +1 -1
  13. package/dist/libs/component/component.factory.js +2 -4
  14. package/dist/libs/component/component.factory.js.map +1 -1
  15. package/dist/libs/plugin/files/README.md +23 -0
  16. package/dist/libs/plugin/files/__dot__gitignore +2 -0
  17. package/dist/libs/plugin/files/nanoforge.manifest.json.template +49 -0
  18. package/dist/libs/plugin/files/package.json.template +21 -0
  19. package/dist/libs/plugin/files/src/Panel.svelte +35 -0
  20. package/dist/libs/plugin/files/src/counter.ts +4 -0
  21. package/dist/libs/plugin/files/src/index.ts +52 -0
  22. package/dist/libs/plugin/files/svelte.config.js +2 -0
  23. package/dist/libs/plugin/files/tsconfig.json +14 -0
  24. package/dist/libs/plugin/files/vite.config.ts +12 -0
  25. package/dist/libs/plugin/plugin.factory.d.ts +18 -0
  26. package/dist/libs/plugin/plugin.factory.d.ts.map +1 -0
  27. package/dist/libs/plugin/plugin.factory.js +111 -0
  28. package/dist/libs/plugin/plugin.factory.js.map +1 -0
  29. package/dist/libs/plugin/schema.json +25 -0
  30. package/dist/libs/plugin/theme-files/README.md +21 -0
  31. package/dist/libs/plugin/theme-files/__dot__gitignore +2 -0
  32. package/dist/libs/plugin/theme-files/build.mjs +29 -0
  33. package/dist/libs/plugin/theme-files/icons/leaf.svg +1 -0
  34. package/dist/libs/plugin/theme-files/nanoforge.manifest.json.template +30 -0
  35. package/dist/libs/plugin/theme-files/package.json.template +11 -0
  36. package/dist/libs/project/files/js/AGENTS.md.template +126 -0
  37. package/dist/libs/project/files/js/CLAUDE.md.template +1 -0
  38. package/dist/libs/project/files/js/__dot__gitignore +4 -0
  39. package/dist/libs/project/files/js/nanoforge.config.js +1 -1
  40. package/dist/libs/project/files/js/package.json.template +8 -3
  41. package/dist/libs/project/files/js/src/components/drawable-circle-2d.component.js +1 -1
  42. package/dist/libs/project/files/js/src/main.js +2 -2
  43. package/dist/libs/project/files/js/src/systems/draw-2d.system.js +1 -1
  44. package/dist/libs/project/files/js/src/systems/move-2d.system.js +1 -1
  45. package/dist/libs/project/files/js/src/systems/position-sync.system.js +1 -1
  46. package/dist/libs/project/files/ts/AGENTS.md.template +114 -0
  47. package/dist/libs/project/files/ts/CLAUDE.md.template +1 -0
  48. package/dist/libs/project/files/ts/__dot__gitignore +4 -0
  49. package/dist/libs/project/files/ts/nanoforge.config.ts +1 -1
  50. package/dist/libs/project/files/ts/package.json.template +8 -3
  51. package/dist/libs/project/files/ts/src/components/drawable-circle-2d.component.ts +1 -1
  52. package/dist/libs/project/files/ts/src/main.ts +2 -1
  53. package/dist/libs/project/files/ts/src/systems/draw-2d.system.ts +1 -1
  54. package/dist/libs/project/files/ts/src/systems/move-2d.system.ts +1 -1
  55. package/dist/libs/project/files/ts/src/systems/position-sync.system.ts +1 -1
  56. package/dist/libs/project/project.factory.d.ts +5 -4
  57. package/dist/libs/project/project.factory.d.ts.map +1 -1
  58. package/dist/libs/project/project.factory.js +14 -10
  59. package/dist/libs/project/project.factory.js.map +1 -1
  60. package/dist/libs/project/schema.json +5 -0
  61. package/dist/libs/system/system.factory.d.ts +1 -4
  62. package/dist/libs/system/system.factory.d.ts.map +1 -1
  63. package/dist/libs/system/system.factory.js +2 -4
  64. package/dist/libs/system/system.factory.js.map +1 -1
  65. package/dist/libs/workspace/files/js/AGENTS.md.template +49 -0
  66. package/dist/libs/workspace/files/js/CLAUDE.md.template +1 -0
  67. package/dist/libs/workspace/files/js/__dot__gitignore +4 -0
  68. package/dist/libs/workspace/files/js/nanoforge.config.js +1 -1
  69. package/dist/libs/workspace/files/js/package.json.template +1 -1
  70. package/dist/libs/workspace/files/ts/AGENTS.md.template +49 -0
  71. package/dist/libs/workspace/files/ts/CLAUDE.md.template +1 -0
  72. package/dist/libs/workspace/files/ts/__dot__gitignore +4 -0
  73. package/dist/libs/workspace/files/ts/nanoforge.config.ts +1 -1
  74. package/dist/libs/workspace/files/ts/package.json.template +1 -1
  75. package/dist/libs/workspace/schema.json +5 -0
  76. package/dist/libs/workspace/workspace.factory.d.ts +5 -4
  77. package/dist/libs/workspace/workspace.factory.d.ts.map +1 -1
  78. package/dist/libs/workspace/workspace.factory.js +7 -8
  79. package/dist/libs/workspace/workspace.factory.js.map +1 -1
  80. package/package.json +15 -16
@@ -1 +1 @@
1
- {"version":3,"file":"project.factory.js","names":[],"sources":["../../../src/utils/formatting.ts","../../../src/defaults.ts","../../../src/utils/registry.ts","../../../src/libs/project/project.factory.ts"],"sourcesContent":["const toWords = (str: string): string[] => {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n};\n\nexport const toKebabCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.toLowerCase())\n .join(\"-\");\n};\n\nexport const toPascalCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n};\n\nexport const toCamelCase = (str: string): string => {\n const words = toWords(str);\n return words\n .map((word, i) =>\n i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join(\"\");\n};\n","export const DEFAULT_APP_NAME = \"nanoforge-app\";\nexport const DEFAULT_LANGUAGE = \"ts\";\nexport const DEFAULT_PACKAGE_MANAGER = \"npm\";\nexport const DEFAULT_NANOFORGE_DEPENDENCY_VERSION = \"^2\";\nexport const DEFAULT_ENGINE_VERSION = \"^2\";\n\n/** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */\nexport const DEFAULT_CLI_DEPENDENCY_VERSION = \"latest\";\n\n/**\n * Fallback used when the registry lookup for `typescript` fails. A specific\n * version, not a range — non-nanoforge packages are pinned exactly.\n */\nexport const DEFAULT_TYPESCRIPT_VERSION = \"6.0.3\";\n\n/**\n * Mirrors pnpm's own `minimumReleaseAge` supply-chain protection: when\n * resolving a pinned dependency version at generation time, only consider\n * versions published at least this long ago, so a freshly (and potentially\n * maliciously) published release isn't picked up immediately.\n */\nexport const MIN_RELEASE_AGE_HOURS = 48;\n","import { MIN_RELEASE_AGE_HOURS } from \"~/defaults\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_TIMEOUT_MS = 3000;\n\nconst RELEASE_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\n\nconst compareVersions = (a: string, b: string): number => {\n const partsA = a.split(\".\").map(Number);\n const partsB = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) {\n return diff;\n }\n }\n return 0;\n};\n\nexport interface FetchTrustedVersionOptions {\n /**\n * Only consider versions published at least this many hours ago, mirroring\n * pnpm's `minimumReleaseAge` supply-chain protection for a freshly\n * published (and potentially compromised) release.\n * @default MIN_RELEASE_AGE_HOURS\n */\n minAgeHours?: number;\n\n /**\n * Restrict candidates to this major version (e.g. `6` only considers\n * `6.x.x`). Useful to avoid silently jumping a generated project onto a\n * new major release.\n */\n major?: number;\n\n /** Network timeout in milliseconds. @default 3000 */\n timeoutMs?: number;\n}\n\n/**\n * Resolves the newest published version of an npm package that is at least\n * `minAgeHours` old, so freshly (and potentially maliciously) published\n * versions aren't picked up immediately. Falls back to `fallback` on any\n * network error, timeout, missing package, or when no version satisfies the\n * constraints, so callers never have to handle a rejected promise.\n */\nexport const fetchTrustedVersion = async (\n packageName: string,\n fallback: string,\n options: FetchTrustedVersionOptions = {},\n): Promise<string> => {\n const { minAgeHours = MIN_RELEASE_AGE_HOURS, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n return fallback;\n }\n\n const data = (await response.json()) as { time?: Record<string, string> };\n const time = data.time;\n if (!time) {\n return fallback;\n }\n\n const cutoff = Date.now() - minAgeHours * 60 * 60 * 1000;\n const eligible = Object.entries(time)\n .filter(([version]) => RELEASE_VERSION_PATTERN.test(version))\n .filter(([version]) => major === undefined || Number(version.split(\".\")[0]) === major)\n .filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff)\n .map(([version]) => version)\n .sort(compareVersions);\n\n return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;\n } catch {\n return fallback;\n } finally {\n clearTimeout(timeout);\n }\n};\n\n/**\n * Resolves several packages' trusted versions in parallel. Keys of `specs`\n * are the package name to query the registry for; values are the fallback\n * to use if that lookup fails. Returns a map from the *same* keys to the\n * resolved (or fallback) version — typed so callers don't get `| undefined`\n * back for a key they just passed in.\n */\nexport const fetchTrustedVersions = async <\n T extends Record<string, { fallback: string; major?: number }>,\n>(\n specs: T,\n): Promise<{ [K in keyof T]: string }> => {\n const entries = await Promise.all(\n Object.entries(specs).map(async ([packageName, { fallback, major }]) => {\n const version = await fetchTrustedVersion(packageName, fallback, { major });\n return [packageName, version] as const;\n }),\n );\n return Object.fromEntries(entries) as { [K in keyof T]: string };\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n filter,\n mergeWith,\n move,\n renameTemplateFiles,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { toKebabCase } from \"@utils/formatting\";\nimport { fetchTrustedVersion } from \"@utils/registry\";\n\nimport {\n DEFAULT_APP_NAME,\n DEFAULT_LANGUAGE,\n DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n DEFAULT_PACKAGE_MANAGER,\n} from \"~/defaults\";\n\nimport { type ProjectOptions } from \"./project.options\";\nimport { type ProjectSchema } from \"./project.schema\";\n\ninterface DepVersions {\n ecs: string;\n graphics2d: string;\n input: string;\n network: string;\n nanoforge: string;\n}\n\nconst resolveDepVersions = async (part: ProjectSchema[\"part\"]): Promise<DepVersions> => {\n const isClient = part === \"client\";\n\n const [nanoforge, ecs, network, graphics2d, input] = await Promise.all([\n fetchTrustedVersion(\"nanoforge\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),\n fetchTrustedVersion(\"@nanoforge-dev/ecs\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),\n fetchTrustedVersion(\"@nanoforge-dev/network\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),\n isClient\n ? fetchTrustedVersion(\"@nanoforge-dev/graphics-2d\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)\n : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n isClient\n ? fetchTrustedVersion(\"@nanoforge-dev/input\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)\n : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n ]);\n\n return {\n ecs,\n graphics2d,\n input,\n network,\n nanoforge,\n };\n};\n\nconst transform = async (schema: ProjectSchema): Promise<ProjectOptions> => {\n const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;\n const workspace = schema.workspace ?? true;\n const depVersions = await resolveDepVersions(schema.part);\n\n const name = schema.name ?? schema.part;\n const packageName = schema.workspaceName\n ? `${toKebabCase(schema.workspaceName)}-${toKebabCase(name)}`\n : toKebabCase(schema.name ?? DEFAULT_APP_NAME);\n\n return {\n part: schema.part,\n appClass: schema.part === \"client\" ? \"NanoforgeClient\" : \"NanoforgeServer\",\n packageName,\n language: schema.language ?? DEFAULT_LANGUAGE,\n strict: schema.strict ?? true,\n packageManager,\n workspace,\n ecsVersion: depVersions.ecs,\n graphics2dVersion: depVersions.graphics2d,\n inputVersion: depVersions.input,\n networkVersion: depVersions.network,\n nanoforgeVersion: depVersions.nanoforge,\n hasServer: schema.hasServer,\n docker: schema.docker ?? false,\n editor: schema.editor ?? false,\n allowBuilds: workspace ? [] : [\"bun\"],\n };\n};\n\nconst generate = (options: ProjectOptions, path: string): Source => {\n return apply(url(join(\"./files\" as Path, options.language)), [\n template({\n dot: \".\",\n ...strings,\n ...options,\n }),\n filter(\n (path) =>\n (options.docker && !options.workspace) ||\n ![\"Dockerfile\", \".dockerignore\"].includes(path.split(\"/\").at(-1) ?? \"\"),\n ),\n filter(\n (path) =>\n (options.packageManager === \"pnpm\" && options.allowBuilds.length > 0) ||\n !(path.split(\"/\").at(-1) ?? \"\").startsWith(\"pnpm-workspace.yaml\"),\n ),\n filter((path) => {\n const filename = path.split(\"/\").at(-1) ?? \"\";\n if (\n filename.startsWith(\"drawable-circle-2d.component\") ||\n filename.startsWith(\"draw-2d.system\")\n ) {\n return options.part === \"client\";\n }\n if (filename.startsWith(\"move-2d.system\")) {\n return options.part === \"server\";\n }\n if (filename.startsWith(\"position-sync.system\")) {\n return options.part === \"client\" && options.hasServer;\n }\n return true;\n }),\n renameTemplateFiles(),\n move(normalize(path)),\n ]);\n};\n\nexport const main = (schema: ProjectSchema): Rule => {\n return async () => {\n const options = await transform(schema);\n\n return mergeWith(generate(options, schema.directory));\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"project.factory.js","names":[],"sources":["../../../src/utils/formatting.ts","../../../src/defaults.ts","../../../src/utils/registry.ts","../../../src/libs/project/project.factory.ts"],"sourcesContent":["const toWords = (str: string): string[] => {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n};\n\nexport const toKebabCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.toLowerCase())\n .join(\"-\");\n};\n\nexport const toPascalCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n};\n\nexport const toCamelCase = (str: string): string => {\n const words = toWords(str);\n return words\n .map((word, i) =>\n i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join(\"\");\n};\n","export const DEFAULT_APP_NAME = \"nanoforge-app\";\nexport const DEFAULT_LANGUAGE = \"ts\";\nexport const DEFAULT_PACKAGE_MANAGER = \"npm\";\nexport const DEFAULT_NANOFORGE_DEPENDENCY_VERSION = \"^2\";\nexport const DEFAULT_ENGINE_VERSION = \"^2\";\n\n/** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */\nexport const DEFAULT_CLI_DEPENDENCY_VERSION = \"latest\";\n\n/**\n * Fallback used when the registry lookup for `typescript` fails. A specific\n * version, not a range — non-nanoforge packages are pinned exactly.\n */\nexport const DEFAULT_TYPESCRIPT_VERSION = \"6.0.3\";\n\n/**\n * Mirrors pnpm's own `minimumReleaseAge` supply-chain protection: when\n * resolving a pinned dependency version at generation time, only consider\n * versions published at least this long ago, so a freshly (and potentially\n * maliciously) published release isn't picked up immediately.\n */\nexport const MIN_RELEASE_AGE_HOURS = 48;\n\n/** Fallback for the editor packages (`@nanoforge-dev/editor-*`), which share one version line. */\nexport const DEFAULT_EDITOR_VERSION = \"^1.0.0\";\n","import { MIN_RELEASE_AGE_HOURS } from \"~/defaults\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_TIMEOUT_MS = 3000;\n\nconst RELEASE_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\n\nconst compareVersions = (a: string, b: string): number => {\n const partsA = a.split(\".\").map(Number);\n const partsB = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) {\n return diff;\n }\n }\n return 0;\n};\n\nexport interface FetchTrustedVersionOptions {\n /**\n * Only consider versions published at least this many hours ago, mirroring\n * pnpm's `minimumReleaseAge` supply-chain protection for a freshly\n * published (and potentially compromised) release.\n * @default MIN_RELEASE_AGE_HOURS\n */\n minAgeHours?: number;\n\n /**\n * Restrict candidates to this major version (e.g. `6` only considers\n * `6.x.x`). Useful to avoid silently jumping a generated project onto a\n * new major release.\n */\n major?: number;\n\n /** Network timeout in milliseconds. @default 3000 */\n timeoutMs?: number;\n}\n\n/**\n * Resolves the newest published version of an npm package that is at least\n * `minAgeHours` old, so freshly (and potentially maliciously) published\n * versions aren't picked up immediately. Falls back to `fallback` on any\n * network error, timeout, missing package, or when no version satisfies the\n * constraints, so callers never have to handle a rejected promise.\n */\nexport const fetchTrustedVersion = async (\n packageName: string,\n fallback: string,\n options: FetchTrustedVersionOptions = {},\n): Promise<string> => {\n const { minAgeHours = MIN_RELEASE_AGE_HOURS, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n return fallback;\n }\n\n const data = (await response.json()) as { time?: Record<string, string> };\n const time = data.time;\n if (!time) {\n return fallback;\n }\n\n const cutoff = Date.now() - minAgeHours * 60 * 60 * 1000;\n const eligible = Object.entries(time)\n .filter(([version]) => RELEASE_VERSION_PATTERN.test(version))\n .filter(([version]) => major === undefined || Number(version.split(\".\")[0]) === major)\n .filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff)\n .map(([version]) => version)\n .sort(compareVersions);\n\n return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;\n } catch {\n return fallback;\n } finally {\n clearTimeout(timeout);\n }\n};\n\n/**\n * Resolves several packages' trusted versions in parallel. Keys of `specs`\n * are the package name to query the registry for; values are the fallback\n * to use if that lookup fails. Returns a map from the *same* keys to the\n * resolved (or fallback) version — typed so callers don't get `| undefined`\n * back for a key they just passed in.\n */\nexport const fetchTrustedVersions = async <\n T extends Record<string, { fallback: string; major?: number }>,\n>(\n specs: T,\n): Promise<{ [K in keyof T]: string }> => {\n const entries = await Promise.all(\n Object.entries(specs).map(async ([packageName, { fallback, major }]) => {\n const version = await fetchTrustedVersion(packageName, fallback, { major });\n return [packageName, version] as const;\n }),\n );\n return Object.fromEntries(entries) as { [K in keyof T]: string };\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n filter,\n mergeWith,\n move,\n renameTemplateFiles,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { toKebabCase } from \"@utils/formatting\";\nimport { fetchTrustedVersion } from \"@utils/registry\";\n\nimport {\n DEFAULT_APP_NAME,\n DEFAULT_CLI_DEPENDENCY_VERSION,\n DEFAULT_ENGINE_VERSION,\n DEFAULT_LANGUAGE,\n DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n DEFAULT_PACKAGE_MANAGER,\n} from \"~/defaults\";\n\nimport { type ProjectOptions } from \"./project.options\";\nimport { type ProjectSchema } from \"./project.schema\";\n\ninterface DepVersions {\n ecs: string;\n graphics2d: string;\n input: string;\n network: string;\n engine: string;\n cli: string;\n}\n\nconst resolveDepVersions = async (part: ProjectSchema[\"part\"]): Promise<DepVersions> => {\n const isClient = part === \"client\";\n\n const [engine, cli, ecs, network, graphics2d, input] = await Promise.all([\n fetchTrustedVersion(\"@nanoforge-dev/core\", DEFAULT_ENGINE_VERSION),\n fetchTrustedVersion(\"@nanoforge-dev/cli\", DEFAULT_CLI_DEPENDENCY_VERSION),\n fetchTrustedVersion(\"@nanoforge-dev/ecs\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),\n fetchTrustedVersion(\"@nanoforge-dev/network\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION),\n isClient\n ? fetchTrustedVersion(\"@nanoforge-dev/graphics-2d\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)\n : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n isClient\n ? fetchTrustedVersion(\"@nanoforge-dev/input\", DEFAULT_NANOFORGE_DEPENDENCY_VERSION)\n : DEFAULT_NANOFORGE_DEPENDENCY_VERSION,\n ]);\n\n return {\n ecs,\n graphics2d,\n input,\n network,\n engine,\n cli,\n };\n};\n\nconst transform = async (schema: ProjectSchema): Promise<ProjectOptions> => {\n const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;\n const workspace = schema.workspace ?? true;\n const depVersions = await resolveDepVersions(schema.part);\n\n const name = schema.name ?? schema.part;\n const packageName = schema.workspaceName\n ? `${toKebabCase(schema.workspaceName)}-${toKebabCase(name)}`\n : toKebabCase(schema.name ?? DEFAULT_APP_NAME);\n\n return {\n part: schema.part,\n appClass: schema.part === \"client\" ? \"NanoforgeClient\" : \"NanoforgeServer\",\n packageName,\n language: schema.language ?? DEFAULT_LANGUAGE,\n strict: schema.strict ?? true,\n packageManager,\n workspace,\n ecsVersion: depVersions.ecs,\n graphics2dVersion: depVersions.graphics2d,\n inputVersion: depVersions.input,\n networkVersion: depVersions.network,\n engineVersion: depVersions.engine,\n cliVersion: depVersions.cli,\n hasServer: schema.hasServer,\n docker: schema.docker ?? false,\n editor: schema.editor ?? false,\n agents: schema.agents ?? true,\n allowBuilds: workspace ? [] : [\"bun\"],\n };\n};\n\nconst generate = (options: ProjectOptions, path: string): Source => {\n return apply(url(join(\"./files\" as Path, options.language)), [\n template({\n dot: \".\",\n ...strings,\n ...options,\n }),\n filter(\n (path) =>\n (options.docker && !options.workspace) ||\n ![\"Dockerfile\", \".dockerignore\"].includes(path.split(\"/\").at(-1) ?? \"\"),\n ),\n filter(\n (path) =>\n (options.packageManager === \"pnpm\" && options.allowBuilds.length > 0) ||\n !(path.split(\"/\").at(-1) ?? \"\").startsWith(\"pnpm-workspace.yaml\"),\n ),\n filter(\n (path) =>\n options.agents ||\n ![\"AGENTS.md\", \"CLAUDE.md\"].some((name) => (path.split(\"/\").at(-1) ?? \"\").startsWith(name)),\n ),\n filter((path) => {\n const filename = path.split(\"/\").at(-1) ?? \"\";\n if (\n filename.startsWith(\"drawable-circle-2d.component\") ||\n filename.startsWith(\"draw-2d.system\")\n ) {\n return options.part === \"client\";\n }\n if (filename.startsWith(\"move-2d.system\")) {\n return options.part === \"server\";\n }\n if (filename.startsWith(\"position-sync.system\")) {\n return options.part === \"client\" && options.hasServer;\n }\n return true;\n }),\n renameTemplateFiles(),\n move(normalize(path)),\n ]);\n};\n\nexport const main = (schema: ProjectSchema): Rule => {\n return async () => {\n const options = await transform(schema);\n\n return mergeWith(generate(options, schema.directory));\n };\n};\n"],"mappings":"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"}
@@ -66,6 +66,11 @@
66
66
  "description": "Add editor dependencies",
67
67
  "default": false
68
68
  },
69
+ "agents": {
70
+ "type": "boolean",
71
+ "description": "Generate an AGENTS.md file documenting the project for AI coding agents, and a CLAUDE.md that imports it",
72
+ "default": true
73
+ },
69
74
  "libs": {
70
75
  "type": "array",
71
76
  "items": { "type": "string" },
@@ -1,5 +1,4 @@
1
1
  import { Rule } from "@angular-devkit/schematics";
2
- //#region src/libs/system/system.schema.d.ts
3
2
  interface SystemSchema {
4
3
  /**
5
4
  * System destination directory
@@ -18,8 +17,6 @@ interface SystemSchema {
18
17
  */
19
18
  language: "js" | "ts";
20
19
  }
21
- //#endregion
22
- //#region src/libs/system/system.factory.d.ts
23
20
  export declare const main: (schema: SystemSchema) => Rule;
24
- //#endregion
21
+ export {};
25
22
  //# sourceMappingURL=system.factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"system.factory.d.ts","names":[],"sources":["../../../src/libs/system/system.schema.d.ts","../../../src/libs/system/system.factory.ts"],"mappings":";;UAAiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;;;qBCiBW,OAAQ,QAAQ,iBAAe"}
1
+ {"version":3,"file":"system.factory.d.ts","names":[],"sources":["../../../src/libs/system/system.schema.d.ts","../../../src/libs/system/system.factory.ts"],"mappings":";UAAiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;qBCiBW,OAAQ,QAAQ,iBAAe"}
@@ -1,6 +1,6 @@
1
1
  import { join, normalize, strings } from "@angular-devkit/core";
2
2
  import { apply, mergeWith, move, template, url } from "@angular-devkit/schematics";
3
- //#region src/utils/formatting.ts
3
+ // src/utils/formatting.ts
4
4
  const toWords = (str) => {
5
5
  return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[-_]+/g, " ").trim().split(/\s+/).filter(Boolean);
6
6
  };
@@ -10,8 +10,7 @@ const toKebabCase = (str) => {
10
10
  const toCamelCase = (str) => {
11
11
  return toWords(str).map((word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
12
12
  };
13
- //#endregion
14
- //#region src/libs/system/system.factory.ts
13
+ // src/libs/system/system.factory.ts
15
14
  const transform = (schema) => {
16
15
  return {
17
16
  part: schema.part,
@@ -30,7 +29,6 @@ const main = (schema) => {
30
29
  const options = transform(schema);
31
30
  return mergeWith(generate(options, schema.directory, schema.language));
32
31
  };
33
- //#endregion
34
32
  export { main };
35
33
 
36
34
  //# sourceMappingURL=system.factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"system.factory.js","names":[],"sources":["../../../src/utils/formatting.ts","../../../src/libs/system/system.factory.ts"],"sourcesContent":["const toWords = (str: string): string[] => {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n};\n\nexport const toKebabCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.toLowerCase())\n .join(\"-\");\n};\n\nexport const toPascalCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n};\n\nexport const toCamelCase = (str: string): string => {\n const words = toWords(str);\n return words\n .map((word, i) =>\n i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join(\"\");\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n mergeWith,\n move,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { toCamelCase, toKebabCase } from \"@utils/formatting\";\n\nimport { type SystemOptions } from \"./system.options\";\nimport { type SystemSchema } from \"./system.schema\";\n\nconst transform = (schema: SystemSchema): SystemOptions => {\n return {\n part: schema.part,\n functionName: toCamelCase(schema.name),\n fileName: toKebabCase(schema.name),\n };\n};\n\nconst generate = (options: SystemOptions, path: string, language: \"ts\" | \"js\"): Source => {\n const rules = [\n template({\n ...strings,\n ...options,\n }),\n move(normalize(path)),\n ];\n\n return apply(url(join(\"./files\" as Path, language)), rules);\n};\n\nexport const main = (schema: SystemSchema): Rule => {\n const options = transform(schema);\n\n return mergeWith(generate(options, schema.directory, schema.language));\n};\n"],"mappings":";;;AAAA,MAAM,WAAW,QAA0B;CACzC,OAAO,IACJ,QAAQ,mBAAmB,OAAO,CAAC,CACnC,QAAQ,yBAAyB,OAAO,CAAC,CACzC,QAAQ,UAAU,GAAG,CAAC,CACtB,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO;AACnB;AAEA,MAAa,eAAe,QAAwB;CAClD,OAAO,QAAQ,GAAG,CAAC,CAChB,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,CACjC,KAAK,GAAG;AACb;AAQA,MAAa,eAAe,QAAwB;CAElD,OADc,QAAQ,GACf,CAAA,CACJ,KAAK,MAAM,MACV,MAAM,IAAI,KAAK,YAAY,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAC1F,CAAC,CACA,KAAK,EAAE;AACZ;;;ACbA,MAAM,aAAa,WAAwC;CACzD,OAAO;EACL,MAAM,OAAO;EACb,cAAc,YAAY,OAAO,IAAI;EACrC,UAAU,YAAY,OAAO,IAAI;CACnC;AACF;AAEA,MAAM,YAAY,SAAwB,MAAc,aAAkC;CACxF,MAAM,QAAQ,CACZ,SAAS;EACP,GAAG;EACH,GAAG;CACL,CAAC,GACD,KAAK,UAAU,IAAI,CAAC,CACtB;CAEA,OAAO,MAAM,IAAI,KAAK,WAAmB,QAAQ,CAAC,GAAG,KAAK;AAC5D;AAEA,MAAa,QAAQ,WAA+B;CAClD,MAAM,UAAU,UAAU,MAAM;CAEhC,OAAO,UAAU,SAAS,SAAS,OAAO,WAAW,OAAO,QAAQ,CAAC;AACvE"}
1
+ {"version":3,"file":"system.factory.js","names":[],"sources":["../../../src/utils/formatting.ts","../../../src/libs/system/system.factory.ts"],"sourcesContent":["const toWords = (str: string): string[] => {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n};\n\nexport const toKebabCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.toLowerCase())\n .join(\"-\");\n};\n\nexport const toPascalCase = (str: string): string => {\n return toWords(str)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n};\n\nexport const toCamelCase = (str: string): string => {\n const words = toWords(str);\n return words\n .map((word, i) =>\n i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),\n )\n .join(\"\");\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n mergeWith,\n move,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { toCamelCase, toKebabCase } from \"@utils/formatting\";\n\nimport { type SystemOptions } from \"./system.options\";\nimport { type SystemSchema } from \"./system.schema\";\n\nconst transform = (schema: SystemSchema): SystemOptions => {\n return {\n part: schema.part,\n functionName: toCamelCase(schema.name),\n fileName: toKebabCase(schema.name),\n };\n};\n\nconst generate = (options: SystemOptions, path: string, language: \"ts\" | \"js\"): Source => {\n const rules = [\n template({\n ...strings,\n ...options,\n }),\n move(normalize(path)),\n ];\n\n return apply(url(join(\"./files\" as Path, language)), rules);\n};\n\nexport const main = (schema: SystemSchema): Rule => {\n const options = transform(schema);\n\n return mergeWith(generate(options, schema.directory, schema.language));\n};\n"],"mappings":";;;AAAA,MAAM,WAAW,QAA0B;CACzC,OAAO,IACJ,QAAQ,mBAAmB,OAAO,CAAC,CACnC,QAAQ,yBAAyB,OAAO,CAAC,CACzC,QAAQ,UAAU,GAAG,CAAC,CACtB,KAAK,CAAC,CACN,MAAM,KAAK,CAAC,CACZ,OAAO,OAAO;AACnB;AAEA,MAAa,eAAe,QAAwB;CAClD,OAAO,QAAQ,GAAG,CAAC,CAChB,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,CACjC,KAAK,GAAG;AACb;AAQA,MAAa,eAAe,QAAwB;CAElD,OADc,QAAQ,GACf,CAAA,CACJ,KAAK,MAAM,MACV,MAAM,IAAI,KAAK,YAAY,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAC1F,CAAC,CACA,KAAK,EAAE;AACZ;;ACbA,MAAM,aAAa,WAAwC;CACzD,OAAO;EACL,MAAM,OAAO;EACb,cAAc,YAAY,OAAO,IAAI;EACrC,UAAU,YAAY,OAAO,IAAI;CACnC;AACF;AAEA,MAAM,YAAY,SAAwB,MAAc,aAAkC;CACxF,MAAM,QAAQ,CACZ,SAAS;EACP,GAAG;EACH,GAAG;CACL,CAAC,GACD,KAAK,UAAU,IAAI,CAAC,CACtB;CAEA,OAAO,MAAM,IAAI,KAAK,WAAmB,QAAQ,CAAC,GAAG,KAAK;AAC5D;AAEA,MAAa,QAAQ,WAA+B;CAClD,MAAM,UAAU,UAAU,MAAM;CAEhC,OAAO,UAAU,SAAS,SAAS,OAAO,WAAW,OAAO,QAAQ,CAAC;AACvE"}
@@ -0,0 +1,49 @@
1
+ <% var nf = { npm: "npm exec --no -- nf", pnpm: "pnpm exec nf", yarn: "yarn nf", bun: "bun run nf" }[packageManager]; %># <%= name %>
2
+
3
+ Notes for AI coding agents. This is a NanoForge game workspace, written in JavaScript (ES
4
+ modules, with types in JSDoc comments): a client app that runs in the browser and a server app
5
+ that runs in Bun. NanoForge is a game engine built on an entity component system (ECS).
6
+
7
+ ## Files
8
+
9
+ - `nanoforge.config.js`: `type: "workspace"`, with `packages: ["apps/*"]`. Each folder it matches
10
+ is an app with its own `nanoforge.config.js`.
11
+ - `apps/client/`, `apps/server/`: the two apps. Each has its own `AGENTS.md`: read the one of the
12
+ app you change.
13
+ - `.env`: the `NANOFORGE_*` variables that connect the client to the server (address, TCP and UDP
14
+ ports). Change both sides together. Git ignores it.
15
+ - `jsconfig.json`: the options your code editor reads the JSDoc types with, extended by each app.
16
+ - `package.json`<% if (packageManager === "pnpm") { %> and `pnpm-workspace.yaml`<% } %>: the <%= packageManager %> workspace. Install every app with
17
+ `<%= packageManager %> install` here.
18
+
19
+ ## Commands
20
+
21
+ `nf` comes from the `@nanoforge-dev/cli` dev dependency. Run it from this folder, as
22
+ `<%= nf %> <command>`.
23
+
24
+ - `nf dev`: runs `nf build --watch` and `nf start --watch` together. The game is on
25
+ http://localhost:3000, with the server running.
26
+ - `nf build`: bundles every app with Bun into its `dist/`. It does not check the JSDoc
27
+ types. `-d apps/client` or `-d apps/server` builds one app.
28
+ - `nf start`: runs the last build of both apps.
29
+
30
+ `nf dev` and `nf start` read the `.env` of their `-d` folder (the current folder by default), so
31
+ run them here.
32
+
33
+ ## Client and server
34
+
35
+ - They connect over TCP, a WebSocket, and over UDP, a WebRTC data channel set up through a
36
+ WebSocket. `nf start` gives `NANOFORGE_CLIENT_X` to the client and `NANOFORGE_SERVER_X` to the
37
+ server, as `X` in the `options.env` of `main`. A plain `NANOFORGE_X` goes to both. Everything
38
+ the client gets reaches the browser: never put a secret there.
39
+ https://docs.nanoforge.eu/docs/cli/configuration/environment-variables
40
+ - In the generated game, the server moves the entity and sends its position to every client
41
+ (`apps/server/src/systems/move-2d.system.js`), and the client sets the position it receives
42
+ (`apps/client/src/systems/position-sync.system.js`).
43
+ - The apps share no code: each defines its own `Position2D`.
44
+
45
+ ## Docs
46
+
47
+ - `nf` commands: https://docs.nanoforge.eu/docs/cli/commands/overview
48
+ - The workspace config: https://docs.nanoforge.eu/docs/cli/configuration/configuration-file
49
+ - Everything else: https://docs.nanoforge.eu
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -82,6 +82,10 @@ build/Release
82
82
 
83
83
  # Dependency directories
84
84
  node_modules/
85
+
86
+ # NanoForge registry packages (nf install restores them from nanoforge.packages.lock.json)
87
+ nf_modules/
88
+
85
89
  jspm_packages/
86
90
 
87
91
  # Snowpack dependency directory (https://snowpack.dev/)
@@ -1,4 +1,4 @@
1
- import { defineConfig } from "nanoforge/config";
1
+ import { defineConfig } from "@nanoforge-dev/config";
2
2
 
3
3
  export default defineConfig({
4
4
  type: "workspace",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "devDependencies": {
10
10
  "@nanoforge-dev/cli": "<%= cliVersion %>",
11
- "nanoforge": "<%= engineVersion %>"
11
+ "@nanoforge-dev/config": "<%= engineVersion %>"
12
12
  },<% if (packageManager !== "pnpm") { %>
13
13
  "workspaces": ["apps/*"],<% } %><% if (packageManager === "npm" && allowBuilds.length > 0) { %>
14
14
  "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
@@ -0,0 +1,49 @@
1
+ <% var nf = { npm: "npm exec --no -- nf", pnpm: "pnpm exec nf", yarn: "yarn nf", bun: "bun run nf" }[packageManager]; %># <%= name %>
2
+
3
+ Notes for AI coding agents. This is a NanoForge game workspace, written in TypeScript: a client
4
+ app that runs in the browser and a server app that runs in Bun. NanoForge is a game engine built
5
+ on an entity component system (ECS).
6
+
7
+ ## Files
8
+
9
+ - `nanoforge.config.ts`: `type: "workspace"`, with `packages: ["apps/*"]`. Each folder it matches
10
+ is an app with its own `nanoforge.config.ts`.
11
+ - `apps/client/`, `apps/server/`: the two apps. Each has its own `AGENTS.md`: read the one of the
12
+ app you change.
13
+ - `.env`: the `NANOFORGE_*` variables that connect the client to the server (address, TCP and UDP
14
+ ports). Change both sides together. Git ignores it.
15
+ - `tsconfig.json`: the compiler options, extended by each app.
16
+ - `package.json`<% if (packageManager === "pnpm") { %> and `pnpm-workspace.yaml`<% } %>: the <%= packageManager %> workspace. Install every app with
17
+ `<%= packageManager %> install` here.
18
+
19
+ ## Commands
20
+
21
+ `nf` comes from the `@nanoforge-dev/cli` dev dependency. Run it from this folder, as
22
+ `<%= nf %> <command>`.
23
+
24
+ - `nf dev`: runs `nf build --watch` and `nf start --watch` together. The game is on
25
+ http://localhost:3000, with the server running.
26
+ - `nf build`: bundles every app with Bun into its `dist/`. It does not type-check.
27
+ `-d apps/client` or `-d apps/server` builds one app.
28
+ - `nf start`: runs the last build of both apps.
29
+
30
+ `nf dev` and `nf start` read the `.env` of their `-d` folder (the current folder by default), so
31
+ run them here.
32
+
33
+ ## Client and server
34
+
35
+ - They connect over TCP, a WebSocket, and over UDP, a WebRTC data channel set up through a
36
+ WebSocket. `nf start` gives `NANOFORGE_CLIENT_X` to the client and `NANOFORGE_SERVER_X` to the
37
+ server, as `X` in the `options.env` of `main`. A plain `NANOFORGE_X` goes to both. Everything
38
+ the client gets reaches the browser: never put a secret there.
39
+ https://docs.nanoforge.eu/docs/cli/configuration/environment-variables
40
+ - In the generated game, the server moves the entity and sends its position to every client
41
+ (`apps/server/src/systems/move-2d.system.ts`), and the client sets the position it receives
42
+ (`apps/client/src/systems/position-sync.system.ts`).
43
+ - The apps share no code: each defines its own `Position2D`.
44
+
45
+ ## Docs
46
+
47
+ - `nf` commands: https://docs.nanoforge.eu/docs/cli/commands/overview
48
+ - The workspace config: https://docs.nanoforge.eu/docs/cli/configuration/configuration-file
49
+ - Everything else: https://docs.nanoforge.eu
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -82,6 +82,10 @@ build/Release
82
82
 
83
83
  # Dependency directories
84
84
  node_modules/
85
+
86
+ # NanoForge registry packages (nf install restores them from nanoforge.packages.lock.json)
87
+ nf_modules/
88
+
85
89
  jspm_packages/
86
90
 
87
91
  # Snowpack dependency directory (https://snowpack.dev/)
@@ -1,4 +1,4 @@
1
- import { defineConfig } from "nanoforge/config";
1
+ import { defineConfig } from "@nanoforge-dev/config";
2
2
 
3
3
  export default defineConfig({
4
4
  type: "workspace",
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "devDependencies": {
10
10
  "@nanoforge-dev/cli": "<%= cliVersion %>",
11
- "nanoforge": "<%= engineVersion %>",
11
+ "@nanoforge-dev/config": "<%= engineVersion %>",
12
12
  "typescript": "<%= typescriptVersion %>"
13
13
  },<% if (packageManager !== "pnpm") { %>
14
14
  "workspaces": ["apps/*"],<% } %><% if (packageManager === "npm" && allowBuilds.length > 0) { %>
@@ -40,6 +40,11 @@
40
40
  "type": "boolean",
41
41
  "description": "Generate a Dockerfile building every app in the workspace into one image",
42
42
  "default": false
43
+ },
44
+ "agents": {
45
+ "type": "boolean",
46
+ "description": "Generate an AGENTS.md file documenting the project for AI coding agents, and a CLAUDE.md that imports it",
47
+ "default": true
43
48
  }
44
49
  }
45
50
  }
@@ -1,5 +1,4 @@
1
1
  import { Rule } from "@angular-devkit/schematics";
2
- //#region src/libs/workspace/workspace.schema.d.ts
3
2
  interface WorkspaceSchema {
4
3
  /**
5
4
  * NanoForge workspace name
@@ -34,9 +33,11 @@ interface WorkspaceSchema {
34
33
  * Generate a Dockerfile building every app in the workspace into one image
35
34
  */
36
35
  docker: boolean;
36
+ /**
37
+ * Generate an AGENTS.md file documenting the project for AI coding agents, and a CLAUDE.md that imports it
38
+ */
39
+ agents: boolean;
37
40
  }
38
- //#endregion
39
- //#region src/libs/workspace/workspace.factory.d.ts
40
41
  export declare const main: (schema: WorkspaceSchema) => Rule;
41
- //#endregion
42
+ export {};
42
43
  //# sourceMappingURL=workspace.factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.factory.d.ts","names":[],"sources":["../../../src/libs/workspace/workspace.schema.d.ts","../../../src/libs/workspace/workspace.factory.ts"],"mappings":";;UAAiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;;;EAKA;;;;;;;;;EAUA;;;;EAKA;;;;qBCqCW,OAAQ,QAAQ,oBAAkB"}
1
+ {"version":3,"file":"workspace.factory.d.ts","names":[],"sources":["../../../src/libs/workspace/workspace.schema.d.ts","../../../src/libs/workspace/workspace.factory.ts"],"mappings":";UAAiB;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA;;;;EAKA;;;;;;;;;EAUA;;;;EAKA;;;;EAKA;;qBCsCW,OAAQ,QAAQ,oBAAkB"}
@@ -7,8 +7,7 @@ const DEFAULT_CLI_DEPENDENCY_VERSION = "latest";
7
7
  * version, not a range — non-nanoforge packages are pinned exactly.
8
8
  */
9
9
  const DEFAULT_TYPESCRIPT_VERSION = "6.0.3";
10
- //#endregion
11
- //#region src/utils/registry.ts
10
+ // src/utils/registry.ts
12
11
  const REGISTRY_URL = "https://registry.npmjs.org";
13
12
  const DEFAULT_TIMEOUT_MS = 3e3;
14
13
  const RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
@@ -59,8 +58,7 @@ const fetchTrustedVersions = async (specs) => {
59
58
  }));
60
59
  return Object.fromEntries(entries);
61
60
  };
62
- //#endregion
63
- //#region src/libs/workspace/workspace.factory.ts
61
+ // src/libs/workspace/workspace.factory.ts
64
62
  /**
65
63
  * bun ships as a postinstall-driven binary download (its own package.json
66
64
  * declares `"postinstall": "node install.js"`), and @nanoforge-dev/cli
@@ -72,7 +70,7 @@ const transform = async (schema) => {
72
70
  const packageManager = schema.packageManager ?? "npm";
73
71
  const versions = await fetchTrustedVersions({
74
72
  "@nanoforge-dev/cli": { fallback: DEFAULT_CLI_DEPENDENCY_VERSION },
75
- nanoforge: { fallback: "^2" },
73
+ "@nanoforge-dev/core": { fallback: "^2" },
76
74
  typescript: {
77
75
  fallback: DEFAULT_TYPESCRIPT_VERSION,
78
76
  major: 6
@@ -80,14 +78,15 @@ const transform = async (schema) => {
80
78
  });
81
79
  return {
82
80
  name: schema.name,
83
- engineVersion: versions.nanoforge,
81
+ engineVersion: versions["@nanoforge-dev/core"],
84
82
  cliVersion: versions["@nanoforge-dev/cli"],
85
83
  typescriptVersion: versions.typescript,
86
84
  language: schema.language,
87
85
  strict: schema.strict,
88
86
  packageManager,
89
87
  allowBuilds: Array.from(/* @__PURE__ */ new Set([...ALWAYS_ALLOWED_BUILDS, ...schema.allowBuilds ?? []])),
90
- docker: schema.docker ?? false
88
+ docker: schema.docker ?? false,
89
+ agents: schema.agents ?? true
91
90
  };
92
91
  };
93
92
  const generate = (options, path) => {
@@ -99,6 +98,7 @@ const generate = (options, path) => {
99
98
  }),
100
99
  filter((path) => options.packageManager === "pnpm" || !(path.split("/").at(-1) ?? "").startsWith("pnpm-workspace.yaml")),
101
100
  filter((path) => options.docker || !["Dockerfile", ".dockerignore"].includes(path.split("/").at(-1) ?? "")),
101
+ filter((path) => options.agents || !["AGENTS.md", "CLAUDE.md"].some((name) => (path.split("/").at(-1) ?? "").startsWith(name))),
102
102
  renameTemplateFiles(),
103
103
  move(normalize(path))
104
104
  ]);
@@ -110,7 +110,6 @@ const main = (schema) => {
110
110
  return mergeWith(generate(options, path));
111
111
  };
112
112
  };
113
- //#endregion
114
113
  export { main };
115
114
 
116
115
  //# sourceMappingURL=workspace.factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.factory.js","names":[],"sources":["../../../src/defaults.ts","../../../src/utils/registry.ts","../../../src/libs/workspace/workspace.factory.ts"],"sourcesContent":["export const DEFAULT_APP_NAME = \"nanoforge-app\";\nexport const DEFAULT_LANGUAGE = \"ts\";\nexport const DEFAULT_PACKAGE_MANAGER = \"npm\";\nexport const DEFAULT_NANOFORGE_DEPENDENCY_VERSION = \"^2\";\nexport const DEFAULT_ENGINE_VERSION = \"^2\";\n\n/** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */\nexport const DEFAULT_CLI_DEPENDENCY_VERSION = \"latest\";\n\n/**\n * Fallback used when the registry lookup for `typescript` fails. A specific\n * version, not a range — non-nanoforge packages are pinned exactly.\n */\nexport const DEFAULT_TYPESCRIPT_VERSION = \"6.0.3\";\n\n/**\n * Mirrors pnpm's own `minimumReleaseAge` supply-chain protection: when\n * resolving a pinned dependency version at generation time, only consider\n * versions published at least this long ago, so a freshly (and potentially\n * maliciously) published release isn't picked up immediately.\n */\nexport const MIN_RELEASE_AGE_HOURS = 48;\n","import { MIN_RELEASE_AGE_HOURS } from \"~/defaults\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_TIMEOUT_MS = 3000;\n\nconst RELEASE_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\n\nconst compareVersions = (a: string, b: string): number => {\n const partsA = a.split(\".\").map(Number);\n const partsB = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) {\n return diff;\n }\n }\n return 0;\n};\n\nexport interface FetchTrustedVersionOptions {\n /**\n * Only consider versions published at least this many hours ago, mirroring\n * pnpm's `minimumReleaseAge` supply-chain protection for a freshly\n * published (and potentially compromised) release.\n * @default MIN_RELEASE_AGE_HOURS\n */\n minAgeHours?: number;\n\n /**\n * Restrict candidates to this major version (e.g. `6` only considers\n * `6.x.x`). Useful to avoid silently jumping a generated project onto a\n * new major release.\n */\n major?: number;\n\n /** Network timeout in milliseconds. @default 3000 */\n timeoutMs?: number;\n}\n\n/**\n * Resolves the newest published version of an npm package that is at least\n * `minAgeHours` old, so freshly (and potentially maliciously) published\n * versions aren't picked up immediately. Falls back to `fallback` on any\n * network error, timeout, missing package, or when no version satisfies the\n * constraints, so callers never have to handle a rejected promise.\n */\nexport const fetchTrustedVersion = async (\n packageName: string,\n fallback: string,\n options: FetchTrustedVersionOptions = {},\n): Promise<string> => {\n const { minAgeHours = MIN_RELEASE_AGE_HOURS, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n return fallback;\n }\n\n const data = (await response.json()) as { time?: Record<string, string> };\n const time = data.time;\n if (!time) {\n return fallback;\n }\n\n const cutoff = Date.now() - minAgeHours * 60 * 60 * 1000;\n const eligible = Object.entries(time)\n .filter(([version]) => RELEASE_VERSION_PATTERN.test(version))\n .filter(([version]) => major === undefined || Number(version.split(\".\")[0]) === major)\n .filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff)\n .map(([version]) => version)\n .sort(compareVersions);\n\n return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;\n } catch {\n return fallback;\n } finally {\n clearTimeout(timeout);\n }\n};\n\n/**\n * Resolves several packages' trusted versions in parallel. Keys of `specs`\n * are the package name to query the registry for; values are the fallback\n * to use if that lookup fails. Returns a map from the *same* keys to the\n * resolved (or fallback) version — typed so callers don't get `| undefined`\n * back for a key they just passed in.\n */\nexport const fetchTrustedVersions = async <\n T extends Record<string, { fallback: string; major?: number }>,\n>(\n specs: T,\n): Promise<{ [K in keyof T]: string }> => {\n const entries = await Promise.all(\n Object.entries(specs).map(async ([packageName, { fallback, major }]) => {\n const version = await fetchTrustedVersion(packageName, fallback, { major });\n return [packageName, version] as const;\n }),\n );\n return Object.fromEntries(entries) as { [K in keyof T]: string };\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n filter,\n mergeWith,\n move,\n renameTemplateFiles,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { fetchTrustedVersions } from \"@utils/registry\";\n\nimport {\n DEFAULT_CLI_DEPENDENCY_VERSION,\n DEFAULT_ENGINE_VERSION,\n DEFAULT_PACKAGE_MANAGER,\n DEFAULT_TYPESCRIPT_VERSION,\n} from \"~/defaults\";\n\nimport { type WorkspaceOptions } from \"./workspace.options\";\nimport { type WorkspaceSchema } from \"./workspace.schema\";\n\n/**\n * bun ships as a postinstall-driven binary download (its own package.json\n * declares `\"postinstall\": \"node install.js\"`), and @nanoforge-dev/cli\n * depends on it unconditionally, so every generated workspace needs it\n * approved to run its build script regardless of what the caller passes.\n */\nconst ALWAYS_ALLOWED_BUILDS = [\"bun\"];\n\nconst transform = async (schema: WorkspaceSchema): Promise<WorkspaceOptions> => {\n const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;\n\n const versions = await fetchTrustedVersions({\n \"@nanoforge-dev/cli\": { fallback: DEFAULT_CLI_DEPENDENCY_VERSION },\n nanoforge: { fallback: DEFAULT_ENGINE_VERSION },\n typescript: { fallback: DEFAULT_TYPESCRIPT_VERSION, major: 6 },\n });\n\n return {\n name: schema.name,\n engineVersion: versions.nanoforge,\n cliVersion: versions[\"@nanoforge-dev/cli\"],\n typescriptVersion: versions.typescript,\n language: schema.language,\n strict: schema.strict,\n packageManager,\n allowBuilds: Array.from(new Set([...ALWAYS_ALLOWED_BUILDS, ...(schema.allowBuilds ?? [])])),\n docker: schema.docker ?? false,\n };\n};\n\nconst generate = (options: WorkspaceOptions, path: string): Source => {\n return apply(url(join(\"./files\" as Path, options.language)), [\n template({\n dot: \".\",\n ...strings,\n ...options,\n }),\n filter(\n (path) =>\n options.packageManager === \"pnpm\" ||\n !(path.split(\"/\").at(-1) ?? \"\").startsWith(\"pnpm-workspace.yaml\"),\n ),\n filter(\n (path) =>\n options.docker || ![\"Dockerfile\", \".dockerignore\"].includes(path.split(\"/\").at(-1) ?? \"\"),\n ),\n renameTemplateFiles(),\n move(normalize(path)),\n ]);\n};\n\nexport const main = (schema: WorkspaceSchema): Rule => {\n return async () => {\n const options = await transform(schema);\n const path = schema.directory ?? options.name;\n\n return mergeWith(generate(options, path));\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace.factory.js","names":[],"sources":["../../../src/defaults.ts","../../../src/utils/registry.ts","../../../src/libs/workspace/workspace.factory.ts"],"sourcesContent":["export const DEFAULT_APP_NAME = \"nanoforge-app\";\nexport const DEFAULT_LANGUAGE = \"ts\";\nexport const DEFAULT_PACKAGE_MANAGER = \"npm\";\nexport const DEFAULT_NANOFORGE_DEPENDENCY_VERSION = \"^2\";\nexport const DEFAULT_ENGINE_VERSION = \"^2\";\n\n/** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */\nexport const DEFAULT_CLI_DEPENDENCY_VERSION = \"latest\";\n\n/**\n * Fallback used when the registry lookup for `typescript` fails. A specific\n * version, not a range — non-nanoforge packages are pinned exactly.\n */\nexport const DEFAULT_TYPESCRIPT_VERSION = \"6.0.3\";\n\n/**\n * Mirrors pnpm's own `minimumReleaseAge` supply-chain protection: when\n * resolving a pinned dependency version at generation time, only consider\n * versions published at least this long ago, so a freshly (and potentially\n * maliciously) published release isn't picked up immediately.\n */\nexport const MIN_RELEASE_AGE_HOURS = 48;\n\n/** Fallback for the editor packages (`@nanoforge-dev/editor-*`), which share one version line. */\nexport const DEFAULT_EDITOR_VERSION = \"^1.0.0\";\n","import { MIN_RELEASE_AGE_HOURS } from \"~/defaults\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org\";\nconst DEFAULT_TIMEOUT_MS = 3000;\n\nconst RELEASE_VERSION_PATTERN = /^\\d+\\.\\d+\\.\\d+$/;\n\nconst compareVersions = (a: string, b: string): number => {\n const partsA = a.split(\".\").map(Number);\n const partsB = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) {\n const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);\n if (diff !== 0) {\n return diff;\n }\n }\n return 0;\n};\n\nexport interface FetchTrustedVersionOptions {\n /**\n * Only consider versions published at least this many hours ago, mirroring\n * pnpm's `minimumReleaseAge` supply-chain protection for a freshly\n * published (and potentially compromised) release.\n * @default MIN_RELEASE_AGE_HOURS\n */\n minAgeHours?: number;\n\n /**\n * Restrict candidates to this major version (e.g. `6` only considers\n * `6.x.x`). Useful to avoid silently jumping a generated project onto a\n * new major release.\n */\n major?: number;\n\n /** Network timeout in milliseconds. @default 3000 */\n timeoutMs?: number;\n}\n\n/**\n * Resolves the newest published version of an npm package that is at least\n * `minAgeHours` old, so freshly (and potentially maliciously) published\n * versions aren't picked up immediately. Falls back to `fallback` on any\n * network error, timeout, missing package, or when no version satisfies the\n * constraints, so callers never have to handle a rejected promise.\n */\nexport const fetchTrustedVersion = async (\n packageName: string,\n fallback: string,\n options: FetchTrustedVersionOptions = {},\n): Promise<string> => {\n const { minAgeHours = MIN_RELEASE_AGE_HOURS, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {\n signal: controller.signal,\n });\n if (!response.ok) {\n return fallback;\n }\n\n const data = (await response.json()) as { time?: Record<string, string> };\n const time = data.time;\n if (!time) {\n return fallback;\n }\n\n const cutoff = Date.now() - minAgeHours * 60 * 60 * 1000;\n const eligible = Object.entries(time)\n .filter(([version]) => RELEASE_VERSION_PATTERN.test(version))\n .filter(([version]) => major === undefined || Number(version.split(\".\")[0]) === major)\n .filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff)\n .map(([version]) => version)\n .sort(compareVersions);\n\n return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;\n } catch {\n return fallback;\n } finally {\n clearTimeout(timeout);\n }\n};\n\n/**\n * Resolves several packages' trusted versions in parallel. Keys of `specs`\n * are the package name to query the registry for; values are the fallback\n * to use if that lookup fails. Returns a map from the *same* keys to the\n * resolved (or fallback) version — typed so callers don't get `| undefined`\n * back for a key they just passed in.\n */\nexport const fetchTrustedVersions = async <\n T extends Record<string, { fallback: string; major?: number }>,\n>(\n specs: T,\n): Promise<{ [K in keyof T]: string }> => {\n const entries = await Promise.all(\n Object.entries(specs).map(async ([packageName, { fallback, major }]) => {\n const version = await fetchTrustedVersion(packageName, fallback, { major });\n return [packageName, version] as const;\n }),\n );\n return Object.fromEntries(entries) as { [K in keyof T]: string };\n};\n","import { type Path, join, normalize, strings } from \"@angular-devkit/core\";\nimport {\n type Rule,\n type Source,\n apply,\n filter,\n mergeWith,\n move,\n renameTemplateFiles,\n template,\n url,\n} from \"@angular-devkit/schematics\";\n\nimport { fetchTrustedVersions } from \"@utils/registry\";\n\nimport {\n DEFAULT_CLI_DEPENDENCY_VERSION,\n DEFAULT_ENGINE_VERSION,\n DEFAULT_PACKAGE_MANAGER,\n DEFAULT_TYPESCRIPT_VERSION,\n} from \"~/defaults\";\n\nimport { type WorkspaceOptions } from \"./workspace.options\";\nimport { type WorkspaceSchema } from \"./workspace.schema\";\n\n/**\n * bun ships as a postinstall-driven binary download (its own package.json\n * declares `\"postinstall\": \"node install.js\"`), and @nanoforge-dev/cli\n * depends on it unconditionally, so every generated workspace needs it\n * approved to run its build script regardless of what the caller passes.\n */\nconst ALWAYS_ALLOWED_BUILDS = [\"bun\"];\n\nconst transform = async (schema: WorkspaceSchema): Promise<WorkspaceOptions> => {\n const packageManager = schema.packageManager ?? DEFAULT_PACKAGE_MANAGER;\n\n const versions = await fetchTrustedVersions({\n \"@nanoforge-dev/cli\": { fallback: DEFAULT_CLI_DEPENDENCY_VERSION },\n \"@nanoforge-dev/core\": { fallback: DEFAULT_ENGINE_VERSION },\n typescript: { fallback: DEFAULT_TYPESCRIPT_VERSION, major: 6 },\n });\n\n return {\n name: schema.name,\n engineVersion: versions[\"@nanoforge-dev/core\"],\n cliVersion: versions[\"@nanoforge-dev/cli\"],\n typescriptVersion: versions.typescript,\n language: schema.language,\n strict: schema.strict,\n packageManager,\n allowBuilds: Array.from(new Set([...ALWAYS_ALLOWED_BUILDS, ...(schema.allowBuilds ?? [])])),\n docker: schema.docker ?? false,\n agents: schema.agents ?? true,\n };\n};\n\nconst generate = (options: WorkspaceOptions, path: string): Source => {\n return apply(url(join(\"./files\" as Path, options.language)), [\n template({\n dot: \".\",\n ...strings,\n ...options,\n }),\n filter(\n (path) =>\n options.packageManager === \"pnpm\" ||\n !(path.split(\"/\").at(-1) ?? \"\").startsWith(\"pnpm-workspace.yaml\"),\n ),\n filter(\n (path) =>\n options.docker || ![\"Dockerfile\", \".dockerignore\"].includes(path.split(\"/\").at(-1) ?? \"\"),\n ),\n filter(\n (path) =>\n options.agents ||\n ![\"AGENTS.md\", \"CLAUDE.md\"].some((name) => (path.split(\"/\").at(-1) ?? \"\").startsWith(name)),\n ),\n renameTemplateFiles(),\n move(normalize(path)),\n ]);\n};\n\nexport const main = (schema: WorkspaceSchema): Rule => {\n return async () => {\n const options = await transform(schema);\n const path = schema.directory ?? options.name;\n\n return mergeWith(generate(options, path));\n };\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@nanoforge-dev/schematics",
4
- "version": "2.3.0",
4
+ "version": "3.0.0",
5
5
  "description": "NanoForge Schematics",
6
6
  "keywords": [
7
7
  "nanoforge",
@@ -49,29 +49,28 @@
49
49
  },
50
50
  "funding": "https://github.com/NanoForge-dev/CLI?sponsor",
51
51
  "dependencies": {
52
- "@angular-devkit/core": "^22.0.4",
53
- "@angular-devkit/schematics": "^22.1.8",
52
+ "@angular-devkit/core": "^22.2.6",
53
+ "@angular-devkit/schematics": "^22.2.6",
54
54
  "rxjs": "^7.8.2"
55
55
  },
56
56
  "devDependencies": {
57
- "@angular-devkit/schematics-cli": "^22.1.8",
58
- "@commitlint/cli": "^21.2.3",
59
- "@commitlint/config-conventional": "^21.2.3",
57
+ "@angular-devkit/schematics-cli": "^22.2.6",
58
+ "@commitlint/cli": "^21.2.4",
59
+ "@commitlint/config-conventional": "^21.2.4",
60
60
  "@favware/cliff-jumper": "^6.1.0",
61
- "@nanoforge-dev/actions": "^2.1.5",
62
61
  "@nanoforge-dev/utils-eslint-config": "^1.0.2",
63
62
  "@nanoforge-dev/utils-prettier-config": "^1.0.2",
64
63
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
65
- "@types/node": "^26.6.2",
66
- "@vitest/coverage-v8": "^5.0.1",
67
- "cpx2": "^9.0.0",
68
- "eslint": "^10.11.0",
64
+ "@types/node": "^26.6.6",
65
+ "@vitest/coverage-v8": "^5.1.0",
66
+ "cpx2": "^9.0.3",
67
+ "eslint": "^10.14.0",
69
68
  "husky": "^9.1.7",
70
- "lint-staged": "^17.5.1",
71
- "prettier": "^3.9.9",
72
- "tsdown": "^0.23.0",
69
+ "lint-staged": "^17.6.0",
70
+ "prettier": "^3.9.10",
71
+ "tsdown": "^0.23.7",
73
72
  "typescript": "^6.0.3",
74
- "vitest": "^5.0.1"
73
+ "vitest": "^5.1.0"
75
74
  },
76
75
  "engines": {
77
76
  "node": "26"
@@ -92,7 +91,7 @@
92
91
  "build": "tsc --noEmit && tsdown",
93
92
  "postbuild": "pnpm run copy:collection && pnpm run copy:lib",
94
93
  "copy:collection": "cpx src/collection.json dist && cpx 'src/libs/**/schema.json' dist/libs",
95
- "copy:lib": "cpx 'src/libs/**/files/**/{.,}*' dist/libs",
94
+ "copy:lib": "cpx 'src/libs/**/{files,theme-files}/**/{.,}*' dist/libs",
96
95
  "lint": "prettier --check . && eslint --format=pretty src",
97
96
  "format": "prettier --write . && eslint --fix --format=pretty src",
98
97
  "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'libs/schematics/*'",