@nanoforge-dev/schematics 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/collection.json +5 -0
  3. package/dist/index.cjs +4 -2
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +2 -2
  6. package/dist/index.d.cts.map +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/libs/component/component.factory.d.ts +1 -4
  12. package/dist/libs/component/component.factory.d.ts.map +1 -1
  13. package/dist/libs/component/component.factory.js +2 -4
  14. package/dist/libs/component/component.factory.js.map +1 -1
  15. package/dist/libs/plugin/files/README.md +23 -0
  16. package/dist/libs/plugin/files/__dot__gitignore +2 -0
  17. package/dist/libs/plugin/files/nanoforge.manifest.json.template +49 -0
  18. package/dist/libs/plugin/files/package.json.template +21 -0
  19. package/dist/libs/plugin/files/src/Panel.svelte +35 -0
  20. package/dist/libs/plugin/files/src/counter.ts +4 -0
  21. package/dist/libs/plugin/files/src/index.ts +52 -0
  22. package/dist/libs/plugin/files/svelte.config.js +2 -0
  23. package/dist/libs/plugin/files/tsconfig.json +14 -0
  24. package/dist/libs/plugin/files/vite.config.ts +12 -0
  25. package/dist/libs/plugin/plugin.factory.d.ts +18 -0
  26. package/dist/libs/plugin/plugin.factory.d.ts.map +1 -0
  27. package/dist/libs/plugin/plugin.factory.js +111 -0
  28. package/dist/libs/plugin/plugin.factory.js.map +1 -0
  29. package/dist/libs/plugin/schema.json +25 -0
  30. package/dist/libs/plugin/theme-files/README.md +21 -0
  31. package/dist/libs/plugin/theme-files/__dot__gitignore +2 -0
  32. package/dist/libs/plugin/theme-files/build.mjs +29 -0
  33. package/dist/libs/plugin/theme-files/icons/leaf.svg +1 -0
  34. package/dist/libs/plugin/theme-files/nanoforge.manifest.json.template +30 -0
  35. package/dist/libs/plugin/theme-files/package.json.template +11 -0
  36. package/dist/libs/project/files/js/AGENTS.md.template +126 -0
  37. package/dist/libs/project/files/js/CLAUDE.md.template +1 -0
  38. package/dist/libs/project/files/js/__dot__gitignore +4 -0
  39. package/dist/libs/project/files/js/nanoforge.config.js +1 -1
  40. package/dist/libs/project/files/js/package.json.template +8 -3
  41. package/dist/libs/project/files/js/src/components/drawable-circle-2d.component.js +1 -1
  42. package/dist/libs/project/files/js/src/main.js +2 -2
  43. package/dist/libs/project/files/js/src/systems/draw-2d.system.js +1 -1
  44. package/dist/libs/project/files/js/src/systems/move-2d.system.js +1 -1
  45. package/dist/libs/project/files/js/src/systems/position-sync.system.js +1 -1
  46. package/dist/libs/project/files/ts/AGENTS.md.template +114 -0
  47. package/dist/libs/project/files/ts/CLAUDE.md.template +1 -0
  48. package/dist/libs/project/files/ts/__dot__gitignore +4 -0
  49. package/dist/libs/project/files/ts/nanoforge.config.ts +1 -1
  50. package/dist/libs/project/files/ts/package.json.template +8 -3
  51. package/dist/libs/project/files/ts/src/components/drawable-circle-2d.component.ts +1 -1
  52. package/dist/libs/project/files/ts/src/main.ts +2 -1
  53. package/dist/libs/project/files/ts/src/systems/draw-2d.system.ts +1 -1
  54. package/dist/libs/project/files/ts/src/systems/move-2d.system.ts +1 -1
  55. package/dist/libs/project/files/ts/src/systems/position-sync.system.ts +1 -1
  56. package/dist/libs/project/project.factory.d.ts +5 -4
  57. package/dist/libs/project/project.factory.d.ts.map +1 -1
  58. package/dist/libs/project/project.factory.js +14 -10
  59. package/dist/libs/project/project.factory.js.map +1 -1
  60. package/dist/libs/project/schema.json +5 -0
  61. package/dist/libs/system/system.factory.d.ts +1 -4
  62. package/dist/libs/system/system.factory.d.ts.map +1 -1
  63. package/dist/libs/system/system.factory.js +2 -4
  64. package/dist/libs/system/system.factory.js.map +1 -1
  65. package/dist/libs/workspace/files/js/AGENTS.md.template +49 -0
  66. package/dist/libs/workspace/files/js/CLAUDE.md.template +1 -0
  67. package/dist/libs/workspace/files/js/__dot__gitignore +4 -0
  68. package/dist/libs/workspace/files/js/nanoforge.config.js +1 -1
  69. package/dist/libs/workspace/files/js/package.json.template +1 -1
  70. package/dist/libs/workspace/files/ts/AGENTS.md.template +49 -0
  71. package/dist/libs/workspace/files/ts/CLAUDE.md.template +1 -0
  72. package/dist/libs/workspace/files/ts/__dot__gitignore +4 -0
  73. package/dist/libs/workspace/files/ts/nanoforge.config.ts +1 -1
  74. package/dist/libs/workspace/files/ts/package.json.template +1 -1
  75. package/dist/libs/workspace/schema.json +5 -0
  76. package/dist/libs/workspace/workspace.factory.d.ts +5 -4
  77. package/dist/libs/workspace/workspace.factory.d.ts.map +1 -1
  78. package/dist/libs/workspace/workspace.factory.js +7 -8
  79. package/dist/libs/workspace/workspace.factory.js.map +1 -1
  80. package/package.json +15 -16
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The theme has no code: its build copies the manifest and the icons into dist/, the folder the
3
+ * editor loads. With --watch, it copies them again on every change (the editor then reloads it).
4
+ */
5
+ import { cpSync, existsSync, mkdirSync, rmSync, watch } from "node:fs";
6
+
7
+ const build = () => {
8
+ rmSync("dist", { recursive: true, force: true });
9
+ mkdirSync("dist");
10
+ cpSync("nanoforge.manifest.json", "dist/nanoforge.manifest.json");
11
+ if (existsSync("icons")) cpSync("icons", "dist/icons", { recursive: true });
12
+ console.log("Built dist/");
13
+ };
14
+
15
+ // An editor saving mid-rename makes cpSync throw ENOENT: log it and wait for the next change
16
+ // rather than killing the watcher.
17
+ const rebuild = () => {
18
+ try {
19
+ build();
20
+ } catch (error) {
21
+ console.error(`Build failed: ${error.message}`);
22
+ }
23
+ };
24
+
25
+ build();
26
+ if (process.argv.includes("--watch")) {
27
+ for (const target of ["nanoforge.manifest.json", "icons"])
28
+ if (existsSync(target)) watch(target, { recursive: true }, rebuild);
29
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21c0-9 6-15 16-16-1 10-7 16-16 16Z"/><path d="M5 21 14 12"/></svg>
@@ -0,0 +1,30 @@
1
+ {
2
+ "type": "plugin",
3
+ "name": "<%= name %>",
4
+ "version": "0.1.0",
5
+ "displayName": "<%= title %>",
6
+ "description": "A color theme for the NanoForge editor.",
7
+ "keywords": ["theme"],
8
+ "engines": {
9
+ "editor": ">=0.1.0"
10
+ },
11
+ "contributes": {
12
+ "themes": [
13
+ {
14
+ "id": "<%= short %>",
15
+ "label": "<%= title %>",
16
+ "extends": "nanoforge-dark",
17
+ "colors": {
18
+ "accent": "#2f7d6d",
19
+ "accent-hover": "#286a5d",
20
+ "accent-text": "#7fd6c2"
21
+ },
22
+ "syntax": {
23
+ "keyword": "#7fd6c2",
24
+ "string": "#e6c07b"
25
+ }
26
+ }
27
+ ],
28
+ "icons": [{ "id": "<%= short %>-leaf", "path": "icons/leaf.svg" }]
29
+ }
30
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "<%= short %>-editor-theme",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "<%= title %>: a NanoForge editor theme",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "node build.mjs",
9
+ "dev": "node build.mjs --watch"
10
+ }
11
+ }
@@ -0,0 +1,126 @@
1
+ <% var nf = { npm: "npm exec --no -- nf", pnpm: "pnpm exec nf", yarn: "yarn nf", bun: "bun run nf" }[packageManager]; var add = { npm: "npm install", pnpm: "pnpm add", yarn: "yarn add", bun: "bun add" }[packageManager]; var networked = part === "server" || hasServer; %># <%= packageName %>
2
+
3
+ Notes for AI coding agents. This is a NanoForge <%= part %> app, written in JavaScript: ES
4
+ modules, with types in JSDoc comments. NanoForge is a game engine built on an entity component
5
+ system (ECS).
6
+ <% if (part === "client") { %>The game runs in the browser.<% } else { %>This app runs in Bun: it has no graphics, input or DOM.<% } %>
7
+ <% if (workspace) { %>
8
+ This app is part of a workspace: read `../../AGENTS.md` first.
9
+ <% } %>
10
+ ## Files
11
+
12
+ - `nanoforge.config.js`: the app config that `nf` reads (`type: "<%= part %>"`). Keep its
13
+ `entryFile`, `editor.entryFile` and `language: "js"`: the defaults are for TypeScript. Fields
14
+ and defaults: https://docs.nanoforge.eu/docs/cli/configuration/configuration-file
15
+ - `src/main.js`: the entry file. The loader calls its exported `main(options)` from the bundled
16
+ `main.js`, so keep the file name and the export.
17
+ - `src/components/`, `src/systems/`: one component or system per file,
18
+ `<kebab-name>.component.js` and `<kebab-name>.system.js`.
19
+ - `assets/`: copied to the root of `dist/` on each build, so `assets/hero.png` is served as
20
+ `/hero.png`. Read it with `ctx.assets.getAsset("/hero.png")`, an `NfFile` whose `path` is its URL.
21
+ - `dist/` is the build output, emptied on every build. `.nanoforge/` belongs to the NanoForge
22
+ tools: don't edit it.
23
+
24
+ ## Commands
25
+
26
+ `nf` comes from the `@nanoforge-dev/cli` dev dependency: run it as `<%= nf %> <command>`.
27
+ <% if (workspace) { %>
28
+ Run `nf dev` and `nf start` from the workspace root: they run this app with the other one, and
29
+ they read the `.env` of their `-d` folder (the current folder by default). To build this app
30
+ alone, run `<%= nf %> build -d apps/<%= part %>` at the root.
31
+ <% } else { %>
32
+ - `nf dev`: runs `nf build --watch` and `nf start --watch` together.<% if (part === "client") { %> The game is on
33
+ http://localhost:3000 (`port` in the config).<% } %>
34
+ - `nf build`: bundles `src/main.js` with Bun into `dist/`. It does not check the JSDoc types.
35
+ - `nf start`: <% if (part === "client") { %>serves<% } else { %>runs<% } %> the last build.
36
+ <% } %>
37
+ <% if (workspace && packageManager === "npm") { %>Add a package with `npm install <package> -w apps/<%= part %>` at the workspace root.<% } else { %>Add a package with `<%= add %> <package>` in this folder.<% } %>
38
+
39
+ The `@nanoforge-dev/*` packages are on `^2`. The online docs follow the engine's development
40
+ branch and can describe APIs that the installed release lacks: the installed types
41
+ (`@nanoforge-dev/<package>/dist` in `node_modules`) are the reference.
42
+
43
+ ## Game code
44
+
45
+ - `main` creates the app with `NanoforgeFactory.<%= part === "client" ? "createClient" : "createServer" %>`, registers each library with
46
+ `app.use(...)`, then calls `await app.init(options)`. `app.use` throws after `init`, and
47
+ `ecs.registry` throws before it. `await app.run()` starts the tick loop and returns.
48
+ - Components are plain classes. The registry keys them by their `name` field: give each class a
49
+ `name` equal to its class name (the generated ones use `this.constructor.name`), and never store
50
+ game data in `name`. Names must be unique in the app, and `entity` and `id` are reserved. An
51
+ entity has one component per class: adding a second one replaces the first.
52
+ - Systems are `(registry, ctx) => void` functions. They run once per tick, in the order of the
53
+ `registry.addSystem` calls. Their result is ignored, so an async system is not awaited.
54
+ - `registry.getZipper([A, B])` returns one object per entity that has both, keyed by component
55
+ name: `({ A, B }) => …`. The values are the live components: change them in place.
56
+ `getIndexedZipper` adds the entity `id`, and `registry.entityFromIndex(id)` turns it back into an
57
+ entity for `killEntity` or `removeComponent`.
58
+ - `ctx.app.delta` is the time since the last tick, in milliseconds. `tickRate` in `main` is the
59
+ number of ticks per second. `ctx.app.requestStop()` stops the game.<% if (part === "client") { %>
60
+ - Graphics use Konva: `@nanoforge-dev/graphics-2d` re-exports its shapes (`Circle`, `Rect`,
61
+ `Layer`…), and `ctx.graphics.baseLayer` is a layer already on the stage. Coordinates are in a
62
+ 1920×1080 design space scaled to the window (the `viewport` option of `createClient`).
63
+ - Keys: `ctx.input.isKeyPressed(InputEnum.KeyW)`, with `InputEnum` from `@nanoforge-dev/input`. Its
64
+ values are `KeyboardEvent.code` names.<% } %>
65
+ <% if (networked) { %>
66
+ ## Network
67
+
68
+ - The client and the server connect over TCP, a WebSocket, and over UDP, a WebRTC data channel
69
+ set up through a WebSocket. Their address and ports are `NANOFORGE_*` variables, which `nf start`
70
+ reads from the `.env` of <%= workspace ? "the workspace root" : "its `-d` folder" %>. It gives
71
+ `NANOFORGE_CLIENT_X` to the client and `NANOFORGE_SERVER_X` to the server, as `X` in
72
+ `options.env`. Everything the client gets reaches the browser: never put a secret there.
73
+ https://docs.nanoforge.eu/docs/cli/configuration/environment-variables
74
+ - The generated systems use `ctx.network.tcp` and encode JSON with `TextEncoder`. Check the
75
+ installed `@nanoforge-dev/network` types before using another network API.
76
+ <% } %>
77
+ ## Example
78
+
79
+ A component and a system, as the rules above want them:
80
+
81
+ ```js
82
+ // src/components/velocity-2d.component.js
83
+ /** Speed of an entity, in pixels per second. */
84
+ export class Velocity2D {
85
+ name = "Velocity2D";
86
+
87
+ /**
88
+ * @param {number} [x]
89
+ * @param {number} [y]
90
+ */
91
+ constructor(x = 0, y = 0) {
92
+ this.x = x;
93
+ this.y = y;
94
+ }
95
+ }
96
+
97
+ // src/systems/apply-velocity-2d.system.js
98
+ import { Position2D } from "../components/position-2d.component";
99
+ import { Velocity2D } from "../components/velocity-2d.component";
100
+
101
+ /**
102
+ * @typedef {import("@nanoforge-dev/common").Context} Context
103
+ * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
104
+ */
105
+
106
+ /**
107
+ * Moves every entity that has a position and a velocity.
108
+ * @param {Registry} registry
109
+ * @param {Context} ctx
110
+ */
111
+ export const applyVelocity2D = (registry, ctx) => {
112
+ const seconds = ctx.app.delta / 1000;
113
+
114
+ registry.getZipper([Position2D, Velocity2D]).forEach(({ Position2D, Velocity2D }) => {
115
+ Position2D.x += Velocity2D.x * seconds;
116
+ Position2D.y += Velocity2D.y * seconds;
117
+ });
118
+ };
119
+ ```
120
+
121
+ Register it in `main` with `registry.addSystem(applyVelocity2D)`.
122
+
123
+ ## Docs
124
+
125
+ - `nf` commands: https://docs.nanoforge.eu/docs/cli/commands/overview
126
+ - Everything else: https://docs.nanoforge.eu
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -82,6 +82,10 @@ build/Release
82
82
 
83
83
  # Dependency directories
84
84
  node_modules/
85
+
86
+ # NanoForge registry packages (nf install restores them from nanoforge.packages.lock.json)
87
+ nf_modules/
88
+
85
89
  jspm_packages/
86
90
 
87
91
  # Snowpack dependency directory (https://snowpack.dev/)
@@ -1,4 +1,4 @@
1
- import { defineConfig } from "nanoforge/config";
1
+ import { defineConfig } from "@nanoforge-dev/config";
2
2
 
3
3
  export default defineConfig({
4
4
  type: "<%= part %>",
@@ -10,11 +10,16 @@
10
10
  "@nanoforge-dev/network": "<%= networkVersion %>"
11
11
  },<% } %>
12
12
  "devDependencies": {
13
- "@nanoforge-dev/ecs": "<%= ecsVersion %>",<% if (part === "client") { %>
13
+ "@nanoforge-dev/asset": "<%= engineVersion %>",
14
+ "@nanoforge-dev/cli": "<%= cliVersion %>",
15
+ "@nanoforge-dev/common": "<%= engineVersion %>",
16
+ "@nanoforge-dev/core": "<%= engineVersion %>",
17
+ "@nanoforge-dev/config": "<%= engineVersion %>",
18
+ "@nanoforge-dev/ecs": "<%= ecsVersion %>"<% if (part === "client") { %>,
19
+ "@nanoforge-dev/env": "<%= engineVersion %>",
14
20
  "@nanoforge-dev/graphics-2d": "<%= graphics2dVersion %>",
15
21
  "@nanoforge-dev/input": "<%= inputVersion %>",
16
- "@nanoforge-dev/network": "<%= networkVersion %>",<% } %>
17
- "nanoforge": "<%= nanoforgeVersion %>"
22
+ "@nanoforge-dev/network": "<%= networkVersion %>"<% } %>
18
23
  },<% if (packageManager === "npm" && allowBuilds.length > 0) { %>
19
24
  "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
20
25
  "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
@@ -1,7 +1,7 @@
1
1
  /**
2
+ * @typedef {import("@nanoforge-dev/common").NfFile} NfFile
2
3
  * @typedef {import("@nanoforge-dev/ecs").EditorComponentManifest} EditorComponentManifest
3
4
  * @typedef {import("@nanoforge-dev/graphics-2d").Layer} Layer
4
- * @typedef {import("nanoforge").NfFile} NfFile
5
5
  */
6
6
 
7
7
  import { Circle } from "@nanoforge-dev/graphics-2d";
@@ -1,4 +1,4 @@
1
- import { NanoforgeFactory } from "nanoforge";
1
+ import { NanoforgeFactory } from "@nanoforge-dev/core";
2
2
  import { EcsLibrary } from "@nanoforge-dev/ecs/<%= part %>";<% if (part === "client") { %>
3
3
  import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
4
4
  import { InputLibrary } from "@nanoforge-dev/input";<% } %>
@@ -11,7 +11,7 @@ import { positionSync } from "./systems/position-sync.system";<% } %><% } else {
11
11
  import { move2D } from "./systems/move-2d.system";<% } %>
12
12
 
13
13
  /**
14
- * @param {import("nanoforge").<%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %>} options
14
+ * @param {import("@nanoforge-dev/common").<%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %>} options
15
15
  * @returns {Promise<void>}
16
16
  */
17
17
  export const main = async (options) => {
@@ -1,7 +1,7 @@
1
1
  /**
2
+ * @typedef {import("@nanoforge-dev/common").Context} Context
2
3
  * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
3
4
  * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
4
- * @typedef {import("nanoforge").Context} Context
5
5
  */
6
6
 
7
7
  import { DrawableCircle2D } from "../components/drawable-circle-2d.component";
@@ -1,7 +1,7 @@
1
1
  /**
2
+ * @typedef {import("@nanoforge-dev/common").Context} Context
2
3
  * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
3
4
  * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
4
- * @typedef {import("nanoforge").Context} Context
5
5
  */
6
6
 
7
7
  import { Position2D } from "../components/position-2d.component";
@@ -1,7 +1,7 @@
1
1
  /**
2
+ * @typedef {import("@nanoforge-dev/common").Context} Context
2
3
  * @typedef {import("@nanoforge-dev/ecs").EditorSystemManifest} EditorSystemManifest
3
4
  * @typedef {import("@nanoforge-dev/ecs").Registry} Registry
4
- * @typedef {import("nanoforge").Context} Context
5
5
  */
6
6
 
7
7
  import { Position2D } from "../components/position-2d.component";
@@ -0,0 +1,114 @@
1
+ <% var nf = { npm: "npm exec --no -- nf", pnpm: "pnpm exec nf", yarn: "yarn nf", bun: "bun run nf" }[packageManager]; var add = { npm: "npm install", pnpm: "pnpm add", yarn: "yarn add", bun: "bun add" }[packageManager]; var networked = part === "server" || hasServer; %># <%= packageName %>
2
+
3
+ Notes for AI coding agents. This is a NanoForge <%= part %> app, written in TypeScript.
4
+ NanoForge is a game engine built on an entity component system (ECS).
5
+ <% if (part === "client") { %>The game runs in the browser.<% } else { %>This app runs in Bun: it has no graphics, input or DOM.<% } %>
6
+ <% if (workspace) { %>
7
+ This app is part of a workspace: read `../../AGENTS.md` first.
8
+ <% } %>
9
+ ## Files
10
+
11
+ - `nanoforge.config.ts`: the app config that `nf` reads (`type: "<%= part %>"`). Fields and defaults:
12
+ https://docs.nanoforge.eu/docs/cli/configuration/configuration-file
13
+ - `src/main.ts`: the entry file. The loader calls its exported `main(options)` from the bundled
14
+ `main.js`, so keep the file name and the export.
15
+ - `src/components/`, `src/systems/`: one component or system per file,
16
+ `<kebab-name>.component.ts` and `<kebab-name>.system.ts`.
17
+ - `assets/`: copied to the root of `dist/` on each build, so `assets/hero.png` is served as
18
+ `/hero.png`. Read it with `ctx.assets.getAsset("/hero.png")`, an `NfFile` whose `path` is its URL.
19
+ - `dist/` is the build output, emptied on every build. `.nanoforge/` belongs to the NanoForge
20
+ tools: don't edit it.
21
+
22
+ ## Commands
23
+
24
+ `nf` comes from the `@nanoforge-dev/cli` dev dependency: run it as `<%= nf %> <command>`.
25
+ <% if (workspace) { %>
26
+ Run `nf dev` and `nf start` from the workspace root: they run this app with the other one, and
27
+ they read the `.env` of their `-d` folder (the current folder by default). To build this app
28
+ alone, run `<%= nf %> build -d apps/<%= part %>` at the root.
29
+ <% } else { %>
30
+ - `nf dev`: runs `nf build --watch` and `nf start --watch` together.<% if (part === "client") { %> The game is on
31
+ http://localhost:3000 (`port` in the config).<% } %>
32
+ - `nf build`: bundles `src/main.ts` with Bun into `dist/`. It does not type-check.
33
+ - `nf start`: <% if (part === "client") { %>serves<% } else { %>runs<% } %> the last build.
34
+ <% } %>
35
+ <% if (workspace && packageManager === "npm") { %>Add a package with `npm install <package> -w apps/<%= part %>` at the workspace root.<% } else { %>Add a package with `<%= add %> <package>` in this folder.<% } %>
36
+
37
+ The `@nanoforge-dev/*` packages are on `^2`. The online docs follow the engine's development
38
+ branch and can describe APIs that the installed release lacks: the installed types
39
+ (`@nanoforge-dev/<package>/dist` in `node_modules`) are the reference.
40
+
41
+ ## Game code
42
+
43
+ - `main` creates the app with `NanoforgeFactory.<%= part === "client" ? "createClient" : "createServer" %>`, registers each library with
44
+ `app.use(...)`, then calls `await app.init(options)`. `app.use` throws after `init`, and
45
+ `ecs.registry` throws before it. `await app.run()` starts the tick loop and returns.
46
+ - Components are plain classes. The registry keys them by their `name` field: give each class a
47
+ `name` equal to its class name (the generated ones use `this.constructor.name`), and never store
48
+ game data in `name`. Names must be unique in the app, and `entity` and `id` are reserved. An
49
+ entity has one component per class: adding a second one replaces the first.
50
+ - Systems are `(registry, ctx) => void` functions. They run once per tick, in the order of the
51
+ `registry.addSystem` calls. Their result is ignored, so an async system is not awaited.
52
+ - `registry.getZipper([A, B])` returns one object per entity that has both, keyed by component
53
+ name: `({ A, B }) => …`. The values are the live components: change them in place.
54
+ `getIndexedZipper` adds the entity `id`, and `registry.entityFromIndex(id)` turns it back into an
55
+ entity for `killEntity` or `removeComponent`.
56
+ - `ctx.app.delta` is the time since the last tick, in milliseconds. `tickRate` in `main` is the
57
+ number of ticks per second. `ctx.app.requestStop()` stops the game.<% if (part === "client") { %>
58
+ - Graphics use Konva: `@nanoforge-dev/graphics-2d` re-exports its shapes (`Circle`, `Rect`,
59
+ `Layer`…), and `ctx.graphics.baseLayer` is a layer already on the stage. Coordinates are in a
60
+ 1920×1080 design space scaled to the window (the `viewport` option of `createClient`).
61
+ - Keys: `ctx.input.isKeyPressed(InputEnum.KeyW)`, with `InputEnum` from `@nanoforge-dev/input`. Its
62
+ values are `KeyboardEvent.code` names.<% } %>
63
+ <% if (networked) { %>
64
+ ## Network
65
+
66
+ - The client and the server connect over TCP, a WebSocket, and over UDP, a WebRTC data channel
67
+ set up through a WebSocket. Their address and ports are `NANOFORGE_*` variables, which `nf start`
68
+ reads from the `.env` of <%= workspace ? "the workspace root" : "its `-d` folder" %>. It gives
69
+ `NANOFORGE_CLIENT_X` to the client and `NANOFORGE_SERVER_X` to the server, as `X` in
70
+ `options.env`. Everything the client gets reaches the browser: never put a secret there.
71
+ https://docs.nanoforge.eu/docs/cli/configuration/environment-variables
72
+ - The generated systems use `ctx.network.tcp` and encode JSON with `TextEncoder`. Check the
73
+ installed `@nanoforge-dev/network` types before using another network API.
74
+ <% } %>
75
+ ## Example
76
+
77
+ A component and a system, as the rules above want them:
78
+
79
+ ```ts
80
+ // src/components/velocity-2d.component.ts
81
+ /** Speed of an entity, in pixels per second. */
82
+ export class Velocity2D {
83
+ name = "Velocity2D";
84
+
85
+ constructor(
86
+ public x = 0,
87
+ public y = 0,
88
+ ) {}
89
+ }
90
+
91
+ // src/systems/apply-velocity-2d.system.ts
92
+ import { type Context } from "@nanoforge-dev/common";
93
+ import { type Registry } from "@nanoforge-dev/ecs";
94
+
95
+ import { Position2D } from "../components/position-2d.component";
96
+ import { Velocity2D } from "../components/velocity-2d.component";
97
+
98
+ /** Moves every entity that has a position and a velocity. */
99
+ export const applyVelocity2D = (registry: Registry, ctx: Context): void => {
100
+ const seconds = ctx.app.delta / 1000;
101
+
102
+ registry.getZipper([Position2D, Velocity2D]).forEach(({ Position2D, Velocity2D }) => {
103
+ Position2D.x += Velocity2D.x * seconds;
104
+ Position2D.y += Velocity2D.y * seconds;
105
+ });
106
+ };
107
+ ```
108
+
109
+ Register it in `main` with `registry.addSystem(applyVelocity2D)`.
110
+
111
+ ## Docs
112
+
113
+ - `nf` commands: https://docs.nanoforge.eu/docs/cli/commands/overview
114
+ - Everything else: https://docs.nanoforge.eu
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -82,6 +82,10 @@ build/Release
82
82
 
83
83
  # Dependency directories
84
84
  node_modules/
85
+
86
+ # NanoForge registry packages (nf install restores them from nanoforge.packages.lock.json)
87
+ nf_modules/
88
+
85
89
  jspm_packages/
86
90
 
87
91
  # Snowpack dependency directory (https://snowpack.dev/)
@@ -1,4 +1,4 @@
1
- import { defineConfig } from "nanoforge/config";
1
+ import { defineConfig } from "@nanoforge-dev/config";
2
2
 
3
3
  export default defineConfig({
4
4
  type: "<%= part %>",
@@ -10,11 +10,16 @@
10
10
  "@nanoforge-dev/network": "<%= networkVersion %>"
11
11
  },<% } %>
12
12
  "devDependencies": {
13
- "@nanoforge-dev/ecs": "<%= ecsVersion %>",<% if (part === "client") { %>
13
+ "@nanoforge-dev/asset": "<%= engineVersion %>",
14
+ "@nanoforge-dev/cli": "<%= cliVersion %>",
15
+ "@nanoforge-dev/common": "<%= engineVersion %>",
16
+ "@nanoforge-dev/core": "<%= engineVersion %>",
17
+ "@nanoforge-dev/config": "<%= engineVersion %>",
18
+ "@nanoforge-dev/ecs": "<%= ecsVersion %>"<% if (part === "client") { %>,
19
+ "@nanoforge-dev/env": "<%= engineVersion %>",
14
20
  "@nanoforge-dev/graphics-2d": "<%= graphics2dVersion %>",
15
21
  "@nanoforge-dev/input": "<%= inputVersion %>",
16
- "@nanoforge-dev/network": "<%= networkVersion %>",<% } %>
17
- "nanoforge": "<%= nanoforgeVersion %>"
22
+ "@nanoforge-dev/network": "<%= networkVersion %>"<% } %>
18
23
  },<% if (packageManager === "npm" && allowBuilds.length > 0) { %>
19
24
  "allowScripts": {<% allowBuilds.forEach(function (pkg, i) { %>
20
25
  "<%= pkg %>": true<% if (i < allowBuilds.length - 1) { %>,<% } %><% }); %>
@@ -1,6 +1,6 @@
1
+ import type { NfFile } from "@nanoforge-dev/common";
1
2
  import type { EditorComponentManifest } from "@nanoforge-dev/ecs";
2
3
  import { Circle, type Layer } from "@nanoforge-dev/graphics-2d";
3
- import type { NfFile } from "nanoforge";
4
4
 
5
5
  export class DrawableCircle2D {
6
6
  public readonly name: string = this.constructor.name;
@@ -1,4 +1,5 @@
1
- import { NanoforgeFactory, type <%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %> } from "nanoforge";
1
+ import { type <%= part === "client" ? "ClientRunOptions" : "ServerRunOptions" %> } from "@nanoforge-dev/common";
2
+ import { NanoforgeFactory } from "@nanoforge-dev/core";
2
3
  import { EcsLibrary } from "@nanoforge-dev/ecs/<%= part %>";<% if (part === "client") { %>
3
4
  import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d";
4
5
  import { InputLibrary } from "@nanoforge-dev/input";<% } %>
@@ -1,5 +1,5 @@
1
+ import type { Context } from "@nanoforge-dev/common";
1
2
  import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
2
- import { type Context } from "nanoforge";
3
3
 
4
4
  import { DrawableCircle2D } from "../components/drawable-circle-2d.component";
5
5
  import { Position2D } from "../components/position-2d.component";
@@ -1,5 +1,5 @@
1
+ import type { Context } from "@nanoforge-dev/common";
1
2
  import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
2
- import { type Context } from "nanoforge";
3
3
 
4
4
  import { Position2D } from "../components/position-2d.component";
5
5
 
@@ -1,5 +1,5 @@
1
+ import type { Context } from "@nanoforge-dev/common";
1
2
  import type { EditorSystemManifest, Registry } from "@nanoforge-dev/ecs";
2
- import { type Context } from "nanoforge";
3
3
 
4
4
  import { Position2D } from "../components/position-2d.component";
5
5
 
@@ -1,5 +1,4 @@
1
1
  import { Rule } from "@angular-devkit/schematics";
2
- //#region src/libs/project/project.schema.d.ts
3
2
  interface ProjectSchema {
4
3
  /**
5
4
  * The type of project to generate
@@ -56,9 +55,11 @@ interface ProjectSchema {
56
55
  * Add editor dependencies
57
56
  */
58
57
  editor: boolean;
58
+ /**
59
+ * Generate an AGENTS.md file documenting the project for AI coding agents, and a CLAUDE.md that imports it
60
+ */
61
+ agents: boolean;
59
62
  }
60
- //#endregion
61
- //#region src/libs/project/project.factory.d.ts
62
63
  export declare const main: (schema: ProjectSchema) => Rule;
63
- //#endregion
64
+ export {};
64
65
  //# sourceMappingURL=project.factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project.factory.d.ts","names":[],"sources":["../../../src/libs/project/project.schema.d.ts","../../../src/libs/project/project.factory.ts"],"mappings":";;UAAiB;;;;EAIf;;;;;;EAOA;;;;;;;EAQA;;;;;;EAOA;;;;EAKA;;;;EAKA;;;;EAKA;;;;EAKA;;;;;EAMA;;;;;;;EAQA;;;;EAKA;;;;qBC6DW,OAAQ,QAAQ,kBAAgB"}
1
+ {"version":3,"file":"project.factory.d.ts","names":[],"sources":["../../../src/libs/project/project.schema.d.ts","../../../src/libs/project/project.factory.ts"],"mappings":";UAAiB;;;;EAIf;;;;;;EAOA;;;;;;;EAQA;;;;;;EAOA;;;;EAKA;;;;EAKA;;;;EAKA;;;;EAKA;;;;;EAMA;;;;;;;EAQA;;;;EAKA;;;;EAKA;;qBCoEW,OAAQ,QAAQ,kBAAgB"}
@@ -1,14 +1,15 @@
1
1
  import { join, normalize, strings } from "@angular-devkit/core";
2
2
  import { apply, filter, mergeWith, move, renameTemplateFiles, template, url } from "@angular-devkit/schematics";
3
- //#region src/utils/formatting.ts
3
+ // src/utils/formatting.ts
4
4
  const toWords = (str) => {
5
5
  return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[-_]+/g, " ").trim().split(/\s+/).filter(Boolean);
6
6
  };
7
7
  const toKebabCase = (str) => {
8
8
  return toWords(str).map((word) => word.toLowerCase()).join("-");
9
9
  };
10
- //#endregion
11
- //#region src/utils/registry.ts
10
+ /** Fallback used when the registry lookup for `@nanoforge-dev/cli` fails. */
11
+ const DEFAULT_CLI_DEPENDENCY_VERSION = "latest";
12
+ // src/utils/registry.ts
12
13
  const REGISTRY_URL = "https://registry.npmjs.org";
13
14
  const DEFAULT_TIMEOUT_MS = 3e3;
14
15
  const RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
@@ -46,12 +47,12 @@ const fetchTrustedVersion = async (packageName, fallback, options = {}) => {
46
47
  clearTimeout(timeout);
47
48
  }
48
49
  };
49
- //#endregion
50
- //#region src/libs/project/project.factory.ts
50
+ // src/libs/project/project.factory.ts
51
51
  const resolveDepVersions = async (part) => {
52
52
  const isClient = part === "client";
53
- const [nanoforge, ecs, network, graphics2d, input] = await Promise.all([
54
- fetchTrustedVersion("nanoforge", "^2"),
53
+ const [engine, cli, ecs, network, graphics2d, input] = await Promise.all([
54
+ fetchTrustedVersion("@nanoforge-dev/core", "^2"),
55
+ fetchTrustedVersion("@nanoforge-dev/cli", DEFAULT_CLI_DEPENDENCY_VERSION),
55
56
  fetchTrustedVersion("@nanoforge-dev/ecs", "^2"),
56
57
  fetchTrustedVersion("@nanoforge-dev/network", "^2"),
57
58
  isClient ? fetchTrustedVersion("@nanoforge-dev/graphics-2d", "^2") : "^2",
@@ -62,7 +63,8 @@ const resolveDepVersions = async (part) => {
62
63
  graphics2d,
63
64
  input,
64
65
  network,
65
- nanoforge
66
+ engine,
67
+ cli
66
68
  };
67
69
  };
68
70
  const transform = async (schema) => {
@@ -83,10 +85,12 @@ const transform = async (schema) => {
83
85
  graphics2dVersion: depVersions.graphics2d,
84
86
  inputVersion: depVersions.input,
85
87
  networkVersion: depVersions.network,
86
- nanoforgeVersion: depVersions.nanoforge,
88
+ engineVersion: depVersions.engine,
89
+ cliVersion: depVersions.cli,
87
90
  hasServer: schema.hasServer,
88
91
  docker: schema.docker ?? false,
89
92
  editor: schema.editor ?? false,
93
+ agents: schema.agents ?? true,
90
94
  allowBuilds: workspace ? [] : ["bun"]
91
95
  };
92
96
  };
@@ -99,6 +103,7 @@ const generate = (options, path) => {
99
103
  }),
100
104
  filter((path) => options.docker && !options.workspace || !["Dockerfile", ".dockerignore"].includes(path.split("/").at(-1) ?? "")),
101
105
  filter((path) => options.packageManager === "pnpm" && options.allowBuilds.length > 0 || !(path.split("/").at(-1) ?? "").startsWith("pnpm-workspace.yaml")),
106
+ filter((path) => options.agents || !["AGENTS.md", "CLAUDE.md"].some((name) => (path.split("/").at(-1) ?? "").startsWith(name))),
102
107
  filter((path) => {
103
108
  const filename = path.split("/").at(-1) ?? "";
104
109
  if (filename.startsWith("drawable-circle-2d.component") || filename.startsWith("draw-2d.system")) return options.part === "client";
@@ -116,7 +121,6 @@ const main = (schema) => {
116
121
  return mergeWith(generate(options, schema.directory));
117
122
  };
118
123
  };
119
- //#endregion
120
124
  export { main };
121
125
 
122
126
  //# sourceMappingURL=project.factory.js.map