@nanoforge-dev/schematics 2.2.1 → 2.3.1

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 (138) hide show
  1. package/CHANGELOG.md +474 -0
  2. package/README.md +12 -15
  3. package/dist/collection.json +8 -23
  4. package/dist/index.cjs +21 -6
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +19 -7
  7. package/dist/index.d.cts.map +1 -1
  8. package/dist/index.d.ts +19 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +17 -4
  11. package/dist/index.js.map +1 -1
  12. package/dist/libs/component/component.factory.d.ts +1 -3
  13. package/dist/libs/component/component.factory.d.ts.map +1 -1
  14. package/dist/libs/component/component.factory.js +2 -1
  15. package/dist/libs/component/component.factory.js.map +1 -1
  16. package/dist/libs/project/files/js/.dockerignore +5 -0
  17. package/dist/libs/project/files/js/Dockerfile +36 -0
  18. package/dist/libs/project/files/js/README.md +53 -0
  19. package/dist/libs/project/files/js/assets/__dot__gitkeep +0 -0
  20. package/dist/libs/project/files/js/jsconfig.json +36 -0
  21. package/dist/libs/project/files/js/nanoforge.config.js +10 -0
  22. package/dist/libs/project/files/js/package.json.template +29 -0
  23. package/dist/libs/project/files/js/pnpm-workspace.yaml.template +3 -0
  24. package/dist/libs/project/files/js/src/components/drawable-circle-2d.component.js +404 -0
  25. package/dist/libs/project/files/js/src/components/position-2d.component.js +42 -0
  26. package/dist/libs/project/files/js/src/main.js +43 -0
  27. package/dist/libs/project/files/js/src/systems/draw-2d.system.js +33 -0
  28. package/dist/libs/project/files/js/src/systems/move-2d.system.js +34 -0
  29. package/dist/libs/project/files/js/src/systems/position-sync.system.js +35 -0
  30. package/dist/libs/project/files/ts/.dockerignore +5 -0
  31. package/dist/libs/project/files/ts/Dockerfile +36 -0
  32. package/dist/libs/project/files/ts/README.md +53 -0
  33. package/dist/libs/project/files/ts/assets/__dot__gitkeep +0 -0
  34. package/dist/libs/project/files/ts/nanoforge.config.ts +5 -0
  35. package/dist/libs/project/files/ts/package.json.template +29 -0
  36. package/dist/libs/project/files/ts/pnpm-workspace.yaml.template +3 -0
  37. package/dist/libs/project/files/ts/src/components/drawable-circle-2d.component.ts +367 -0
  38. package/dist/libs/project/files/ts/src/components/position-2d.component.ts +35 -0
  39. package/dist/libs/project/files/ts/src/main.ts +40 -0
  40. package/dist/libs/project/files/ts/src/systems/draw-2d.system.ts +25 -0
  41. package/dist/libs/project/files/ts/src/systems/move-2d.system.ts +26 -0
  42. package/dist/libs/project/files/ts/src/systems/position-sync.system.ts +27 -0
  43. package/dist/libs/project/files/ts/tsconfig.json +42 -0
  44. package/dist/libs/project/project.factory.d.ts +64 -0
  45. package/dist/libs/project/project.factory.d.ts.map +1 -0
  46. package/dist/libs/project/project.factory.js +127 -0
  47. package/dist/libs/project/project.factory.js.map +1 -0
  48. package/dist/libs/project/schema.json +83 -0
  49. package/dist/libs/system/system.factory.d.ts +1 -3
  50. package/dist/libs/system/system.factory.d.ts.map +1 -1
  51. package/dist/libs/system/system.factory.js +2 -1
  52. package/dist/libs/system/system.factory.js.map +1 -1
  53. package/dist/libs/workspace/files/js/.dockerignore +5 -0
  54. package/dist/libs/workspace/files/js/Dockerfile +36 -0
  55. package/dist/libs/workspace/files/js/README.md +51 -0
  56. package/dist/libs/workspace/files/js/__dot__gitignore +137 -0
  57. package/dist/libs/{application → workspace}/files/js/jsconfig.json +1 -2
  58. package/dist/libs/workspace/files/js/nanoforge.config.js +6 -0
  59. package/dist/libs/workspace/files/js/package.json.template +19 -0
  60. package/dist/libs/workspace/files/js/pnpm-workspace.yaml.template +6 -0
  61. package/dist/libs/workspace/files/ts/.dockerignore +5 -0
  62. package/dist/libs/workspace/files/ts/Dockerfile +36 -0
  63. package/dist/libs/workspace/files/ts/README.md +51 -0
  64. package/dist/libs/workspace/files/ts/__dot__gitignore +134 -0
  65. package/dist/libs/workspace/files/ts/nanoforge.config.ts +6 -0
  66. package/dist/libs/workspace/files/ts/package.json.template +20 -0
  67. package/dist/libs/workspace/files/ts/pnpm-workspace.yaml.template +6 -0
  68. package/dist/libs/{application → workspace}/files/ts/tsconfig.json +1 -2
  69. package/dist/libs/workspace/schema.json +45 -0
  70. package/dist/libs/workspace/workspace.factory.d.ts +42 -0
  71. package/dist/libs/workspace/workspace.factory.d.ts.map +1 -0
  72. package/dist/libs/workspace/workspace.factory.js +116 -0
  73. package/dist/libs/workspace/workspace.factory.js.map +1 -0
  74. package/package.json +23 -21
  75. package/dist/libs/application/application.factory.d.ts +0 -59
  76. package/dist/libs/application/application.factory.d.ts.map +0 -1
  77. package/dist/libs/application/application.factory.js +0 -57
  78. package/dist/libs/application/application.factory.js.map +0 -1
  79. package/dist/libs/application/files/js/.prettierignore +0 -8
  80. package/dist/libs/application/files/js/README.md +0 -166
  81. package/dist/libs/application/files/js/eslint.config.js +0 -11
  82. package/dist/libs/application/files/js/package.json.template +0 -48
  83. package/dist/libs/application/files/js/pnpm-workspace.yaml.template +0 -3
  84. package/dist/libs/application/files/js/prettier.config.js +0 -6
  85. package/dist/libs/application/files/ts/.prettierignore +0 -8
  86. package/dist/libs/application/files/ts/README.md +0 -166
  87. package/dist/libs/application/files/ts/eslint.config.js +0 -15
  88. package/dist/libs/application/files/ts/package.json.template +0 -52
  89. package/dist/libs/application/files/ts/pnpm-workspace.yaml.template +0 -3
  90. package/dist/libs/application/files/ts/prettier.config.js +0 -6
  91. package/dist/libs/application/schema.json +0 -70
  92. package/dist/libs/configuration/configuration.factory.d.ts +0 -31
  93. package/dist/libs/configuration/configuration.factory.d.ts.map +0 -1
  94. package/dist/libs/configuration/configuration.factory.js +0 -97
  95. package/dist/libs/configuration/configuration.factory.js.map +0 -1
  96. package/dist/libs/configuration/files/nanoforge.config.json +0 -1
  97. package/dist/libs/configuration/schema.json +0 -34
  98. package/dist/libs/docker/docker.factory.d.ts +0 -19
  99. package/dist/libs/docker/docker.factory.d.ts.map +0 -1
  100. package/dist/libs/docker/docker.factory.js +0 -20
  101. package/dist/libs/docker/docker.factory.js.map +0 -1
  102. package/dist/libs/docker/files/.dockerignore +0 -1
  103. package/dist/libs/docker/files/Dockerfile +0 -25
  104. package/dist/libs/docker/schema.json +0 -19
  105. package/dist/libs/part-base/files/js/__nanoforgeFolder__/client.save.json +0 -77
  106. package/dist/libs/part-base/files/js/__nanoforgeFolder__/server.save.json +0 -49
  107. package/dist/libs/part-base/files/js/__part__/components/example.component.js +0 -72
  108. package/dist/libs/part-base/files/js/__part__/init/after-init.js +0 -12
  109. package/dist/libs/part-base/files/js/__part__/init/after-registry-init.js +0 -14
  110. package/dist/libs/part-base/files/js/__part__/init/after-run.js +0 -12
  111. package/dist/libs/part-base/files/js/__part__/init/before-init.js +0 -13
  112. package/dist/libs/part-base/files/js/__part__/init/before-registry-init.js +0 -14
  113. package/dist/libs/part-base/files/js/__part__/init/before-run.js +0 -12
  114. package/dist/libs/part-base/files/js/__part__/systems/example.system.js +0 -44
  115. package/dist/libs/part-base/files/ts/__nanoforgeFolder__/client.save.json +0 -77
  116. package/dist/libs/part-base/files/ts/__nanoforgeFolder__/server.save.json +0 -49
  117. package/dist/libs/part-base/files/ts/__part__/components/example.component.ts +0 -55
  118. package/dist/libs/part-base/files/ts/__part__/init/after-init.ts +0 -5
  119. package/dist/libs/part-base/files/ts/__part__/init/after-registry-init.ts +0 -6
  120. package/dist/libs/part-base/files/ts/__part__/init/after-run.ts +0 -5
  121. package/dist/libs/part-base/files/ts/__part__/init/before-init.ts +0 -5
  122. package/dist/libs/part-base/files/ts/__part__/init/before-registry-init.ts +0 -6
  123. package/dist/libs/part-base/files/ts/__part__/init/before-run.ts +0 -5
  124. package/dist/libs/part-base/files/ts/__part__/systems/example.system.ts +0 -31
  125. package/dist/libs/part-base/part-base.factory.d.ts +0 -31
  126. package/dist/libs/part-base/part-base.factory.d.ts.map +0 -1
  127. package/dist/libs/part-base/part-base.factory.js +0 -33
  128. package/dist/libs/part-base/part-base.factory.js.map +0 -1
  129. package/dist/libs/part-base/schema.json +0 -35
  130. package/dist/libs/part-main/part-main.factory.d.ts +0 -39
  131. package/dist/libs/part-main/part-main.factory.d.ts.map +0 -1
  132. package/dist/libs/part-main/part-main.factory.js +0 -252
  133. package/dist/libs/part-main/part-main.factory.js.map +0 -1
  134. package/dist/libs/part-main/schema.json +0 -43
  135. /package/dist/libs/{application → project}/files/js/__dot__gitignore +0 -0
  136. /package/dist/libs/{application → project}/files/ts/__dot__gitignore +0 -0
  137. /package/dist/libs/{application → workspace}/files/js/__dot__env +0 -0
  138. /package/dist/libs/{application → workspace}/files/ts/__dot__env +0 -0
@@ -0,0 +1,127 @@
1
+ import { join, normalize, strings } from "@angular-devkit/core";
2
+ import { apply, filter, mergeWith, move, renameTemplateFiles, template, url } from "@angular-devkit/schematics";
3
+ //#region src/utils/formatting.ts
4
+ const toWords = (str) => {
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
+ };
7
+ const toKebabCase = (str) => {
8
+ return toWords(str).map((word) => word.toLowerCase()).join("-");
9
+ };
10
+ /** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */
11
+ const DEFAULT_CLI_DEPENDENCY_VERSION = "latest";
12
+ //#endregion
13
+ //#region src/utils/registry.ts
14
+ const REGISTRY_URL = "https://registry.npmjs.org";
15
+ const DEFAULT_TIMEOUT_MS = 3e3;
16
+ const RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
17
+ const compareVersions = (a, b) => {
18
+ const partsA = a.split(".").map(Number);
19
+ const partsB = b.split(".").map(Number);
20
+ for (let i = 0; i < 3; i++) {
21
+ const diff = (partsA[i] ?? 0) - (partsB[i] ?? 0);
22
+ if (diff !== 0) return diff;
23
+ }
24
+ return 0;
25
+ };
26
+ /**
27
+ * Resolves the newest published version of an npm package that is at least
28
+ * `minAgeHours` old, so freshly (and potentially maliciously) published
29
+ * versions aren't picked up immediately. Falls back to `fallback` on any
30
+ * network error, timeout, missing package, or when no version satisfies the
31
+ * constraints, so callers never have to handle a rejected promise.
32
+ */
33
+ const fetchTrustedVersion = async (packageName, fallback, options = {}) => {
34
+ const { minAgeHours = 48, major, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
35
+ const controller = new AbortController();
36
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
37
+ try {
38
+ const response = await fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, { signal: controller.signal });
39
+ if (!response.ok) return fallback;
40
+ const time = (await response.json()).time;
41
+ if (!time) return fallback;
42
+ const cutoff = Date.now() - minAgeHours * 60 * 60 * 1e3;
43
+ const eligible = Object.entries(time).filter(([version]) => RELEASE_VERSION_PATTERN.test(version)).filter(([version]) => major === void 0 || Number(version.split(".")[0]) === major).filter(([, publishedAt]) => new Date(publishedAt).getTime() <= cutoff).map(([version]) => version).sort(compareVersions);
44
+ return eligible.at(-1) ? `^${eligible.at(-1)}` : fallback;
45
+ } catch {
46
+ return fallback;
47
+ } finally {
48
+ clearTimeout(timeout);
49
+ }
50
+ };
51
+ //#endregion
52
+ //#region src/libs/project/project.factory.ts
53
+ const resolveDepVersions = async (part) => {
54
+ const isClient = part === "client";
55
+ const [engine, cli, ecs, network, graphics2d, input] = await Promise.all([
56
+ fetchTrustedVersion("@nanoforge-dev/core", "^2"),
57
+ fetchTrustedVersion("@nanoforge-dev/cli", DEFAULT_CLI_DEPENDENCY_VERSION),
58
+ fetchTrustedVersion("@nanoforge-dev/ecs", "^2"),
59
+ fetchTrustedVersion("@nanoforge-dev/network", "^2"),
60
+ isClient ? fetchTrustedVersion("@nanoforge-dev/graphics-2d", "^2") : "^2",
61
+ isClient ? fetchTrustedVersion("@nanoforge-dev/input", "^2") : "^2"
62
+ ]);
63
+ return {
64
+ ecs,
65
+ graphics2d,
66
+ input,
67
+ network,
68
+ engine,
69
+ cli
70
+ };
71
+ };
72
+ const transform = async (schema) => {
73
+ const packageManager = schema.packageManager ?? "npm";
74
+ const workspace = schema.workspace ?? true;
75
+ const depVersions = await resolveDepVersions(schema.part);
76
+ const name = schema.name ?? schema.part;
77
+ const packageName = schema.workspaceName ? `${toKebabCase(schema.workspaceName)}-${toKebabCase(name)}` : toKebabCase(schema.name ?? "nanoforge-app");
78
+ return {
79
+ part: schema.part,
80
+ appClass: schema.part === "client" ? "NanoforgeClient" : "NanoforgeServer",
81
+ packageName,
82
+ language: schema.language ?? "ts",
83
+ strict: schema.strict ?? true,
84
+ packageManager,
85
+ workspace,
86
+ ecsVersion: depVersions.ecs,
87
+ graphics2dVersion: depVersions.graphics2d,
88
+ inputVersion: depVersions.input,
89
+ networkVersion: depVersions.network,
90
+ engineVersion: depVersions.engine,
91
+ cliVersion: depVersions.cli,
92
+ hasServer: schema.hasServer,
93
+ docker: schema.docker ?? false,
94
+ editor: schema.editor ?? false,
95
+ allowBuilds: workspace ? [] : ["bun"]
96
+ };
97
+ };
98
+ const generate = (options, path) => {
99
+ return apply(url(join("./files", options.language)), [
100
+ template({
101
+ dot: ".",
102
+ ...strings,
103
+ ...options
104
+ }),
105
+ filter((path) => options.docker && !options.workspace || !["Dockerfile", ".dockerignore"].includes(path.split("/").at(-1) ?? "")),
106
+ filter((path) => options.packageManager === "pnpm" && options.allowBuilds.length > 0 || !(path.split("/").at(-1) ?? "").startsWith("pnpm-workspace.yaml")),
107
+ filter((path) => {
108
+ const filename = path.split("/").at(-1) ?? "";
109
+ if (filename.startsWith("drawable-circle-2d.component") || filename.startsWith("draw-2d.system")) return options.part === "client";
110
+ if (filename.startsWith("move-2d.system")) return options.part === "server";
111
+ if (filename.startsWith("position-sync.system")) return options.part === "client" && options.hasServer;
112
+ return true;
113
+ }),
114
+ renameTemplateFiles(),
115
+ move(normalize(path))
116
+ ]);
117
+ };
118
+ const main = (schema) => {
119
+ return async () => {
120
+ const options = await transform(schema);
121
+ return mergeWith(generate(options, schema.directory));
122
+ };
123
+ };
124
+ //#endregion
125
+ export { main };
126
+
127
+ //# sourceMappingURL=project.factory.js.map
@@ -0,0 +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_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 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":";;;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;;ACPA,MAAa,iCAAiC;;;ACL9C,MAAM,eAAe;AACrB,MAAM,qBAAqB;AAE3B,MAAM,0BAA0B;AAEhC,MAAM,mBAAmB,GAAW,MAAsB;CACxD,MAAM,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACtC,MAAM,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,QAAQ,OAAO,MAAM,MAAM,OAAO,MAAM;EAC9C,IAAI,SAAS,GACX,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;;AA6BA,MAAa,sBAAsB,OACjC,aACA,UACA,UAAsC,CAAC,MACnB;CACpB,MAAM,EAAE,cAAA,IAAqC,OAAO,YAAY,uBAAuB;CAEvF,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,SAAS;CAE9D,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,aAAa,GAAG,mBAAmB,WAAW,KAAK,EACjF,QAAQ,WAAW,OACrB,CAAC;EACD,IAAI,CAAC,SAAS,IACZ,OAAO;EAIT,MAAM,QAAO,MADO,SAAS,KAAK,EACrB,CAAK;EAClB,IAAI,CAAC,MACH,OAAO;EAGT,MAAM,SAAS,KAAK,IAAI,IAAI,cAAc,KAAK,KAAK;EACpD,MAAM,WAAW,OAAO,QAAQ,IAAI,CAAC,CAClC,QAAQ,CAAC,aAAa,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAC5D,QAAQ,CAAC,aAAa,UAAU,UAAa,OAAO,QAAQ,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,CACrF,QAAQ,GAAG,iBAAiB,IAAI,KAAK,WAAW,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CACtE,KAAK,CAAC,aAAa,OAAO,CAAC,CAC3B,KAAK,eAAe;EAEvB,OAAO,SAAS,GAAG,EAAE,IAAI,IAAI,SAAS,GAAG,EAAE,MAAM;CACnD,QAAQ;EACN,OAAO;CACT,UAAU;EACR,aAAa,OAAO;CACtB;AACF;;;AC/CA,MAAM,qBAAqB,OAAO,SAAsD;CACtF,MAAM,WAAW,SAAS;CAE1B,MAAM,CAAC,QAAQ,KAAK,KAAK,SAAS,YAAY,SAAS,MAAM,QAAQ,IAAI;EACvE,oBAAoB,uBAAA,IAA6C;EACjE,oBAAoB,sBAAsB,8BAA8B;EACxE,oBAAoB,sBAAA,IAA0D;EAC9E,oBAAoB,0BAAA,IAA8D;EAClF,WACI,oBAAoB,8BAAA,IAAkE,IAAA;EAE1F,WACI,oBAAoB,wBAAA,IAA4D,IAAA;CAEtF,CAAC;CAED,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAEA,MAAM,YAAY,OAAO,WAAmD;CAC1E,MAAM,iBAAiB,OAAO,kBAAA;CAC9B,MAAM,YAAY,OAAO,aAAa;CACtC,MAAM,cAAc,MAAM,mBAAmB,OAAO,IAAI;CAExD,MAAM,OAAO,OAAO,QAAQ,OAAO;CACnC,MAAM,cAAc,OAAO,gBACvB,GAAG,YAAY,OAAO,aAAa,EAAE,GAAG,YAAY,IAAI,MACxD,YAAY,OAAO,QAAA,eAAwB;CAE/C,OAAO;EACL,MAAM,OAAO;EACb,UAAU,OAAO,SAAS,WAAW,oBAAoB;EACzD;EACA,UAAU,OAAO,YAAA;EACjB,QAAQ,OAAO,UAAU;EACzB;EACA;EACA,YAAY,YAAY;EACxB,mBAAmB,YAAY;EAC/B,cAAc,YAAY;EAC1B,gBAAgB,YAAY;EAC5B,eAAe,YAAY;EAC3B,YAAY,YAAY;EACxB,WAAW,OAAO;EAClB,QAAQ,OAAO,UAAU;EACzB,QAAQ,OAAO,UAAU;EACzB,aAAa,YAAY,CAAC,IAAI,CAAC,KAAK;CACtC;AACF;AAEA,MAAM,YAAY,SAAyB,SAAyB;CAClE,OAAO,MAAM,IAAI,KAAK,WAAmB,QAAQ,QAAQ,CAAC,GAAG;EAC3D,SAAS;GACP,KAAK;GACL,GAAG;GACH,GAAG;EACL,CAAC;EACD,QACG,SACE,QAAQ,UAAU,CAAC,QAAQ,aAC5B,CAAC,CAAC,cAAc,eAAe,CAAC,CAAC,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAC1E;EACA,QACG,SACE,QAAQ,mBAAmB,UAAU,QAAQ,YAAY,SAAS,KACnE,EAAE,KAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,GAAA,CAAI,WAAW,qBAAqB,CACpE;EACA,QAAQ,SAAS;GACf,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK;GAC3C,IACE,SAAS,WAAW,8BAA8B,KAClD,SAAS,WAAW,gBAAgB,GAEpC,OAAO,QAAQ,SAAS;GAE1B,IAAI,SAAS,WAAW,gBAAgB,GACtC,OAAO,QAAQ,SAAS;GAE1B,IAAI,SAAS,WAAW,sBAAsB,GAC5C,OAAO,QAAQ,SAAS,YAAY,QAAQ;GAE9C,OAAO;EACT,CAAC;EACD,oBAAoB;EACpB,KAAK,UAAU,IAAI,CAAC;CACtB,CAAC;AACH;AAEA,MAAa,QAAQ,WAAgC;CACnD,OAAO,YAAY;EACjB,MAAM,UAAU,MAAM,UAAU,MAAM;EAEtC,OAAO,UAAU,SAAS,SAAS,OAAO,SAAS,CAAC;CACtD;AACF"}
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "SchematicsNanoForgeProject",
4
+ "title": "NanoForge Project Options Schema",
5
+ "type": "object",
6
+ "properties": {
7
+ "part": {
8
+ "type": "string",
9
+ "enum": ["client", "server"],
10
+ "description": "The type of project to generate"
11
+ },
12
+ "workspaceName": {
13
+ "type": ["string", "null"],
14
+ "description": "Name of the parent workspace, used to derive the project package name. Optional - when omitted, `name` is used as the whole package name instead of being appended to the workspace name.",
15
+ "default": null
16
+ },
17
+ "name": {
18
+ "type": ["string", "null"],
19
+ "description": "Name used to derive the project package name. When `workspaceName` is set, the package name is `${workspaceName}-${name}` (replacing the previous `${workspaceName}-${part}` suffix). When `workspaceName` is absent, `name` becomes the whole package name. Defaults to `part`.",
20
+ "default": null
21
+ },
22
+ "directory": {
23
+ "type": "string",
24
+ "description": "Full destination path for this project (e.g. \"apps/client\", \"packages/player-client\"). The caller is responsible for including any part-specific segment - it is not appended automatically.",
25
+ "default": "apps"
26
+ },
27
+ "language": {
28
+ "type": "string",
29
+ "enum": ["ts", "js"],
30
+ "description": "NanoForge project language",
31
+ "default": "ts"
32
+ },
33
+ "strict": {
34
+ "type": "boolean",
35
+ "description": "With strict mode",
36
+ "default": true
37
+ },
38
+ "packageManager": {
39
+ "type": "string",
40
+ "enum": ["npm", "yarn", "pnpm", "bun"],
41
+ "description": "The used package manager",
42
+ "default": "npm"
43
+ },
44
+ "workspace": {
45
+ "type": "boolean",
46
+ "description": "Whether this project is generated inside an existing NanoForge workspace",
47
+ "default": true
48
+ },
49
+ "docker": {
50
+ "type": "boolean",
51
+ "description": "Generate a Dockerfile for the project. Ignored when `workspace` is true - a project inside a workspace is built and run by the workspace's own Dockerfile instead.",
52
+ "default": false
53
+ },
54
+ "initFunctions": {
55
+ "type": "boolean",
56
+ "description": "Add init functions to the project",
57
+ "default": false
58
+ },
59
+ "hasServer": {
60
+ "type": "boolean",
61
+ "description": "Only meaningful when part is \"client\": whether the client syncs its entity position from a companion server instead of owning it locally. A server project always simulates and broadcasts its own position, regardless of this flag.",
62
+ "default": false
63
+ },
64
+ "editor": {
65
+ "type": "boolean",
66
+ "description": "Add editor dependencies",
67
+ "default": false
68
+ },
69
+ "libs": {
70
+ "type": "array",
71
+ "items": { "type": "string" },
72
+ "description": "Relative paths to shared libs this project depends on",
73
+ "default": []
74
+ },
75
+ "allowBuilds": {
76
+ "type": "array",
77
+ "items": { "type": "string" },
78
+ "description": "Package names allowed to run install/build scripts. Only applies to a standalone project (workspace: false); ignored otherwise since the workspace root already handles it.",
79
+ "default": []
80
+ }
81
+ },
82
+ "required": ["part"]
83
+ }
@@ -1,5 +1,4 @@
1
1
  import { Rule } from "@angular-devkit/schematics";
2
-
3
2
  //#region src/libs/system/system.schema.d.ts
4
3
  interface SystemSchema {
5
4
  /**
@@ -21,7 +20,6 @@ interface SystemSchema {
21
20
  }
22
21
  //#endregion
23
22
  //#region src/libs/system/system.factory.d.ts
24
- declare const main: (schema: SystemSchema) => Rule;
23
+ export declare const main: (schema: SystemSchema) => Rule;
25
24
  //#endregion
26
- export { main };
27
25
  //# sourceMappingURL=system.factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"system.factory.d.ts","names":["name","directory","part","language"],"sources":["../../../src/libs/system/system.schema.d.ts","../../../src/libs/system/system.factory.ts"],"mappings":";;;UAAiB,YAAA;;;AAAjB;EAIEA,IAAAA;EAJ2B;;;EAS3BC,SAAAA;EAUQ;AAAA;;EALRC,IAAAA;ECsBF;;;EDjBEC,QAAAA;AAAAA;;;cCiBW,IAAA,GAAQ,MAAA,EAAQ,YAAA,KAAe,IAI3C"}
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"}
@@ -27,7 +27,8 @@ const generate = (options, path, language) => {
27
27
  return apply(url(join("./files", language)), rules);
28
28
  };
29
29
  const main = (schema) => {
30
- return mergeWith(generate(transform(schema), schema.directory, schema.language));
30
+ const options = transform(schema);
31
+ return mergeWith(generate(options, schema.directory, schema.language));
31
32
  };
32
33
  //#endregion
33
34
  export { main };
@@ -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;CAGlD,OAAO,UAAU,SAFD,UAAU,MAEA,GAAS,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,5 @@
1
+ **/node_modules/
2
+ **/dist/
3
+ **/.nanoforge/
4
+ **/.env
5
+ .git/
@@ -0,0 +1,36 @@
1
+ <% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
2
+ <% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
3
+ RUN pnpm runtime set node 26 -g
4
+ <% } else { %>FROM node:26-slim AS base
5
+ <% } %><% if (packageManager === "yarn") { %>
6
+ RUN npm install -g yarn
7
+ <% } %>
8
+ WORKDIR /app
9
+
10
+ FROM base AS deps
11
+ <% if (packageManager === "pnpm") { %>
12
+ COPY pnpm-lock.yaml ./
13
+ RUN pnpm fetch
14
+
15
+ FROM deps AS build
16
+ COPY . .
17
+ RUN pnpm install --frozen-lockfile --offline
18
+ <% } else if (packageManager === "npm") { %>
19
+ FROM deps AS build
20
+ COPY . .
21
+ RUN npm ci
22
+ <% } else { %>
23
+ FROM deps AS build
24
+ COPY . .
25
+ RUN <%= packageManager %> install --frozen-lockfile
26
+ <% } %>
27
+ RUN <%= packageManager %> run build
28
+
29
+ <% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
30
+ WORKDIR /app
31
+
32
+ COPY --from=build /app .
33
+ <% if (packageManager !== "pnpm") { %>
34
+ USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
35
+ <% } %>EXPOSE 3000
36
+ CMD ["<%= packageManager %>", "run", "start"]
@@ -0,0 +1,51 @@
1
+ # <%= name %>
2
+
3
+ A [NanoForge](https://nanoforge.eu) game workspace.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js and <%= packageManager %>
8
+ - The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ <%= packageManager %> install
14
+ ```
15
+
16
+ ## Structure
17
+
18
+ This is the root of your NanoForge monorepo. `nanoforge.config.js` lists the app packages, and each
19
+ client or server app lives under `apps/<part>` (generated separately with the `project` schematic).
20
+
21
+ ## Development
22
+
23
+ ```bash
24
+ nf dev -r
25
+ ```
26
+
27
+ `-r` runs the command across every app in the workspace. `nf dev` serves the game and watches your
28
+ files, so changes are picked up as you work. Target a single app instead with `-d apps/<part>`.
29
+
30
+ ## Build & start
31
+
32
+ ```bash
33
+ nf build -r
34
+ nf start -r
35
+ ```
36
+
37
+ `nf build` compiles every app for production, and `nf start` serves the build.
38
+
39
+ ## CLI reference
40
+
41
+ | Command | Description |
42
+ | ---------- | ------------------------------------- |
43
+ | `nf dev` | Develop with hot reload |
44
+ | `nf build` | Build for production |
45
+ | `nf start` | Run a built app |
46
+ | `-r` | Apply the command to every app |
47
+
48
+ ## More info
49
+
50
+ - [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
51
+ - [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge
@@ -0,0 +1,137 @@
1
+ ### VisualStudioCode template
2
+ .vscode/*
3
+ !.vscode/settings.json
4
+ !.vscode/tasks.json
5
+ !.vscode/launch.json
6
+ !.vscode/extensions.json
7
+ !.vscode/*.code-snippets
8
+
9
+ # Local History for Visual Studio Code
10
+ .history/
11
+
12
+ # Built Visual Studio Code Extensions
13
+ *.vsix
14
+
15
+ ### JetBrains template
16
+
17
+ # User-specific stuff
18
+ .idea/**/workspace.xml
19
+ .idea/**/tasks.xml
20
+ .idea/**/usage.statistics.xml
21
+ .idea/**/dictionaries
22
+ .idea/**/shelf
23
+
24
+ # Generated files
25
+ .idea/**/contentModel.xml
26
+
27
+ # Sensitive or high-churn files
28
+ .idea/**/dataSources/
29
+ .idea/**/dataSources.ids
30
+ .idea/**/dataSources.local.xml
31
+ .idea/**/sqlDataSources.xml
32
+ .idea/**/dynamic.xml
33
+ .idea/**/uiDesigner.xml
34
+ .idea/**/dbnavigator.xml
35
+
36
+ # File-based project format
37
+ *.iws
38
+
39
+ # Editor-based Rest Client
40
+ .idea/httpRequests
41
+
42
+ ### Node template
43
+ # Logs
44
+ logs
45
+ *.log
46
+ npm-debug.log*
47
+ yarn-debug.log*
48
+ yarn-error.log*
49
+ lerna-debug.log*
50
+ .pnpm-debug.log*
51
+
52
+ # Diagnostic reports (https://nodejs.org/api/report.html)
53
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
54
+
55
+ # Runtime data
56
+ pids
57
+ *.pid
58
+ *.seed
59
+ *.pid.lock
60
+
61
+ # Directory for instrumented libs generated by jscoverage/JSCover
62
+ lib-cov
63
+
64
+ # Coverage directory used by tools like istanbul
65
+ coverage
66
+ *.lcov
67
+
68
+ # nyc test coverage
69
+ .nyc_output
70
+
71
+ # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
72
+ .grunt
73
+
74
+ # Bower dependency directory (https://bower.io/)
75
+ bower_components
76
+
77
+ # node-waf configuration
78
+ .lock-wscript
79
+
80
+ # Compiled binary addons (https://nodejs.org/api/addons.html)
81
+ build/Release
82
+
83
+ # Dependency directories
84
+ node_modules/
85
+ jspm_packages/
86
+
87
+ # Snowpack dependency directory (https://snowpack.dev/)
88
+ web_modules/
89
+
90
+ # TypeScript cache
91
+ *.tsbuildinfo
92
+
93
+ # Optional npm cache directory
94
+ .npm
95
+
96
+ # Optional eslint cache
97
+ .eslintcache
98
+
99
+ # Optional stylelint cache
100
+ .stylelintcache
101
+
102
+ # Microbundle cache
103
+ .rpt2_cache/
104
+ .rts2_cache_cjs/
105
+ .rts2_cache_es/
106
+ .rts2_cache_umd/
107
+
108
+ # Optional REPL history
109
+ .node_repl_history
110
+
111
+ # Output of 'npm pack'
112
+ *.tgz
113
+
114
+ # Yarn Integrity file
115
+ .yarn-integrity
116
+
117
+ # dotenv environment variable files
118
+ .env
119
+ .env.development.local
120
+ .env.test.local
121
+ .env.production.local
122
+ .env.local
123
+
124
+ # yarn v2
125
+ .yarn/cache
126
+ .yarn/unplugged
127
+ .yarn/build-state.yml
128
+ .yarn/install-state.gz
129
+ .pnp.*
130
+
131
+ # Turborepo
132
+ .turbo/
133
+
134
+ ### NanoForge template
135
+ # Build/compilation output
136
+ .nanoforge/
137
+ dist/
@@ -30,6 +30,5 @@
30
30
  "target": "ESNext",
31
31
  "useDefineForClassFields": true
32
32
  },
33
- "include": ["client/**/*.js", "server/**/*.js"],
34
- "exclude": ["node_modules", ".nanoforge"]
33
+ "exclude": ["node_modules", ".nanoforge", "dist"]
35
34
  }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "@nanoforge-dev/config";
2
+
3
+ export default defineConfig({
4
+ type: "workspace",
5
+ packages: ["apps/*"],
6
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "<%= name %>",
4
+ "scripts": {
5
+ "dev": "nf dev -r",
6
+ "build": "nf build -r",
7
+ "start": "nf start -r"
8
+ },
9
+ "devDependencies": {
10
+ "@nanoforge-dev/cli": "<%= cliVersion %>",
11
+ "@nanoforge-dev/config": "<%= engineVersion %>"
12
+ },<% if (packageManager !== "pnpm") { %>
13
+ "workspaces": ["apps/*"],<% } %><% if (packageManager === "npm" && allowBuilds.length > 0) { %>
14
+ "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
15
+ "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
16
+ },<% } %><% if (packageManager === "bun" && allowBuilds.length > 0) { %>
17
+ "trustedDependencies": [<% allowBuilds.forEach(function (pkg, i) { %>"<%= pkg %>"<% if (i < allowBuilds.length - 1) { %>, <% } %><% }); %>],<% } %>
18
+ "private": true
19
+ }
@@ -0,0 +1,6 @@
1
+ packages:
2
+ - "apps/*"
3
+ <% if (allowBuilds.length > 0) { %>
4
+ allowBuilds:<% allowBuilds.forEach(function (pkg) { %>
5
+ <%= pkg %>: true<% }); %>
6
+ <% } %>
@@ -0,0 +1,5 @@
1
+ **/node_modules/
2
+ **/dist/
3
+ **/.nanoforge/
4
+ **/.env
5
+ .git/
@@ -0,0 +1,36 @@
1
+ <% if (packageManager === "bun") { %>FROM oven/bun:1.3 AS base
2
+ <% } else if (packageManager === "pnpm") { %>FROM ghcr.io/pnpm/pnpm:12 AS base
3
+ RUN pnpm runtime set node 26 -g
4
+ <% } else { %>FROM node:26-slim AS base
5
+ <% } %><% if (packageManager === "yarn") { %>
6
+ RUN npm install -g yarn
7
+ <% } %>
8
+ WORKDIR /app
9
+
10
+ FROM base AS deps
11
+ <% if (packageManager === "pnpm") { %>
12
+ COPY pnpm-lock.yaml ./
13
+ RUN pnpm fetch
14
+
15
+ FROM deps AS build
16
+ COPY . .
17
+ RUN pnpm install --frozen-lockfile --offline
18
+ <% } else if (packageManager === "npm") { %>
19
+ FROM deps AS build
20
+ COPY . .
21
+ RUN npm ci
22
+ <% } else { %>
23
+ FROM deps AS build
24
+ COPY . .
25
+ RUN <%= packageManager %> install --frozen-lockfile
26
+ <% } %>
27
+ RUN <%= packageManager %> run build
28
+
29
+ <% if (packageManager === "bun") { %>FROM oven/bun:1.3-slim<% } else { %>FROM base<% } %>
30
+ WORKDIR /app
31
+
32
+ COPY --from=build /app .
33
+ <% if (packageManager !== "pnpm") { %>
34
+ USER <% if (packageManager === "bun") { %>bun<% } else { %>node<% } %>
35
+ <% } %>EXPOSE 3000
36
+ CMD ["<%= packageManager %>", "run", "start"]
@@ -0,0 +1,51 @@
1
+ # <%= name %>
2
+
3
+ A [NanoForge](https://nanoforge.eu) game workspace.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js and <%= packageManager %>
8
+ - The [NanoForge CLI](https://www.npmjs.com/package/@nanoforge-dev/cli): `npm install -g @nanoforge-dev/cli`
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ <%= packageManager %> install
14
+ ```
15
+
16
+ ## Structure
17
+
18
+ This is the root of your NanoForge monorepo. `nanoforge.config.ts` lists the app packages, and each
19
+ client or server app lives under `apps/<part>` (generated separately with the `project` schematic).
20
+
21
+ ## Development
22
+
23
+ ```bash
24
+ nf dev -r
25
+ ```
26
+
27
+ `-r` runs the command across every app in the workspace. `nf dev` serves the game and watches your
28
+ files, so changes are picked up as you work. Target a single app instead with `-d apps/<part>`.
29
+
30
+ ## Build & start
31
+
32
+ ```bash
33
+ nf build -r
34
+ nf start -r
35
+ ```
36
+
37
+ `nf build` compiles every app for production, and `nf start` serves the build.
38
+
39
+ ## CLI reference
40
+
41
+ | Command | Description |
42
+ | ---------- | ------------------------------------- |
43
+ | `nf dev` | Develop with hot reload |
44
+ | `nf build` | Build for production |
45
+ | `nf start` | Run a built app |
46
+ | `-r` | Apply the command to every app |
47
+
48
+ ## More info
49
+
50
+ - [Documentation](https://docs.nanoforge.eu) - CLI commands, configuration, and guides
51
+ - [nanoforge.eu](https://nanoforge.eu) - learn more about NanoForge