@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.
- package/CHANGELOG.md +15 -0
- package/dist/collection.json +5 -0
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/libs/component/component.factory.d.ts +1 -4
- package/dist/libs/component/component.factory.d.ts.map +1 -1
- package/dist/libs/component/component.factory.js +2 -4
- package/dist/libs/component/component.factory.js.map +1 -1
- package/dist/libs/plugin/files/README.md +23 -0
- package/dist/libs/plugin/files/__dot__gitignore +2 -0
- package/dist/libs/plugin/files/nanoforge.manifest.json.template +49 -0
- package/dist/libs/plugin/files/package.json.template +21 -0
- package/dist/libs/plugin/files/src/Panel.svelte +35 -0
- package/dist/libs/plugin/files/src/counter.ts +4 -0
- package/dist/libs/plugin/files/src/index.ts +52 -0
- package/dist/libs/plugin/files/svelte.config.js +2 -0
- package/dist/libs/plugin/files/tsconfig.json +14 -0
- package/dist/libs/plugin/files/vite.config.ts +12 -0
- package/dist/libs/plugin/plugin.factory.d.ts +18 -0
- package/dist/libs/plugin/plugin.factory.d.ts.map +1 -0
- package/dist/libs/plugin/plugin.factory.js +111 -0
- package/dist/libs/plugin/plugin.factory.js.map +1 -0
- package/dist/libs/plugin/schema.json +25 -0
- package/dist/libs/plugin/theme-files/README.md +21 -0
- package/dist/libs/plugin/theme-files/__dot__gitignore +2 -0
- package/dist/libs/plugin/theme-files/build.mjs +29 -0
- package/dist/libs/plugin/theme-files/icons/leaf.svg +1 -0
- package/dist/libs/plugin/theme-files/nanoforge.manifest.json.template +30 -0
- package/dist/libs/plugin/theme-files/package.json.template +11 -0
- package/dist/libs/project/files/js/AGENTS.md.template +126 -0
- package/dist/libs/project/files/js/CLAUDE.md.template +1 -0
- package/dist/libs/project/files/js/__dot__gitignore +4 -0
- package/dist/libs/project/files/js/nanoforge.config.js +1 -1
- package/dist/libs/project/files/js/package.json.template +8 -3
- package/dist/libs/project/files/js/src/components/drawable-circle-2d.component.js +1 -1
- package/dist/libs/project/files/js/src/main.js +2 -2
- package/dist/libs/project/files/js/src/systems/draw-2d.system.js +1 -1
- package/dist/libs/project/files/js/src/systems/move-2d.system.js +1 -1
- package/dist/libs/project/files/js/src/systems/position-sync.system.js +1 -1
- package/dist/libs/project/files/ts/AGENTS.md.template +114 -0
- package/dist/libs/project/files/ts/CLAUDE.md.template +1 -0
- package/dist/libs/project/files/ts/__dot__gitignore +4 -0
- package/dist/libs/project/files/ts/nanoforge.config.ts +1 -1
- package/dist/libs/project/files/ts/package.json.template +8 -3
- package/dist/libs/project/files/ts/src/components/drawable-circle-2d.component.ts +1 -1
- package/dist/libs/project/files/ts/src/main.ts +2 -1
- package/dist/libs/project/files/ts/src/systems/draw-2d.system.ts +1 -1
- package/dist/libs/project/files/ts/src/systems/move-2d.system.ts +1 -1
- package/dist/libs/project/files/ts/src/systems/position-sync.system.ts +1 -1
- package/dist/libs/project/project.factory.d.ts +5 -4
- package/dist/libs/project/project.factory.d.ts.map +1 -1
- package/dist/libs/project/project.factory.js +14 -10
- package/dist/libs/project/project.factory.js.map +1 -1
- package/dist/libs/project/schema.json +5 -0
- package/dist/libs/system/system.factory.d.ts +1 -4
- package/dist/libs/system/system.factory.d.ts.map +1 -1
- package/dist/libs/system/system.factory.js +2 -4
- package/dist/libs/system/system.factory.js.map +1 -1
- package/dist/libs/workspace/files/js/AGENTS.md.template +49 -0
- package/dist/libs/workspace/files/js/CLAUDE.md.template +1 -0
- package/dist/libs/workspace/files/js/__dot__gitignore +4 -0
- package/dist/libs/workspace/files/js/nanoforge.config.js +1 -1
- package/dist/libs/workspace/files/js/package.json.template +1 -1
- package/dist/libs/workspace/files/ts/AGENTS.md.template +49 -0
- package/dist/libs/workspace/files/ts/CLAUDE.md.template +1 -0
- package/dist/libs/workspace/files/ts/__dot__gitignore +4 -0
- package/dist/libs/workspace/files/ts/nanoforge.config.ts +1 -1
- package/dist/libs/workspace/files/ts/package.json.template +1 -1
- package/dist/libs/workspace/schema.json +5 -0
- package/dist/libs/workspace/workspace.factory.d.ts +5 -4
- package/dist/libs/workspace/workspace.factory.d.ts.map +1 -1
- package/dist/libs/workspace/workspace.factory.js +7 -8
- package/dist/libs/workspace/workspace.factory.js.map +1 -1
- 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
|
-
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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/)
|
|
@@ -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/)
|
|
@@ -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
|
-
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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": "
|
|
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.
|
|
53
|
-
"@angular-devkit/schematics": "^22.
|
|
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.
|
|
58
|
-
"@commitlint/cli": "^21.2.
|
|
59
|
-
"@commitlint/config-conventional": "^21.2.
|
|
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.
|
|
66
|
-
"@vitest/coverage-v8": "^5.0
|
|
67
|
-
"cpx2": "^9.0.
|
|
68
|
-
"eslint": "^10.
|
|
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.
|
|
71
|
-
"prettier": "^3.9.
|
|
72
|
-
"tsdown": "^0.23.
|
|
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
|
|
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/*'",
|