create-shallot 0.5.2 → 0.6.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 (2) hide show
  1. package/index.ts +70 -67
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -6,7 +6,7 @@ import { dirname, join, resolve } from "path";
6
6
  export interface TemplateOptions {
7
7
  /**
8
8
  * dependency spec for `@dylanebert/shallot`. `"latest"` for the published `bun create shallot`
9
- * scaffold; `"workspace:*"` when `bun run build` regenerates examples/templates/starter in-repo.
9
+ * scaffold; `"workspace:*"` when `bun run build` regenerates examples/templates/* in-repo.
10
10
  */
11
11
  shallot?: string;
12
12
  }
@@ -15,9 +15,14 @@ export interface TemplateOptions {
15
15
  * the project files keyed by relative path, with the project name interpolated. single source of
16
16
  * truth for both `bun create shallot <name>` and the in-repo examples/templates/starter example
17
17
  * (regenerated by `bun run build`).
18
+ *
19
+ * the project is pure data — a `shallot.json` manifest + plugin modules + `public/`, no vite
20
+ * boilerplate. the CLI provides every harness over it: `shallot dev` runs it standalone (`bun dev`),
21
+ * `shallot .` opens it in the editor, `shallot build` ships it (web + native targets).
18
22
  */
19
23
  export function template(name: string, opts: TemplateOptions = {}): Record<string, string> {
20
24
  const shallot = opts.shallot ?? "latest";
25
+
21
26
  return {
22
27
  "public/icon.svg": ICON,
23
28
  ".gitignore": "node_modules/\ndist/\nbuild/\n",
@@ -28,9 +33,8 @@ export function template(name: string, opts: TemplateOptions = {}): Record<strin
28
33
  version: "0.0.0",
29
34
  private: true,
30
35
  type: "module",
31
- scripts: { dev: "vite", build: "vite build", preview: "vite preview" },
32
36
  dependencies: { "@dylanebert/shallot": shallot },
33
- devDependencies: { typescript: "^5.9.3", vite: "^8.0.0" },
37
+ devDependencies: { typescript: "^5.9.3" },
34
38
  },
35
39
  null,
36
40
  2,
@@ -43,7 +47,7 @@ export function template(name: string, opts: TemplateOptions = {}): Record<strin
43
47
  module: "ESNext",
44
48
  moduleResolution: "bundler",
45
49
  lib: ["ESNext", "DOM", "DOM.Iterable"],
46
- types: ["@webgpu/types", "vite/client"],
50
+ types: ["@webgpu/types"],
47
51
  strict: true,
48
52
  noEmit: true,
49
53
  skipLibCheck: true,
@@ -53,32 +57,19 @@ export function template(name: string, opts: TemplateOptions = {}): Record<strin
53
57
  null,
54
58
  2,
55
59
  ) + "\n",
56
- "index.html": `<!doctype html>
57
- <html lang="en">
58
- <head>
59
- <meta charset="UTF-8" />
60
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
61
- <link rel="icon" type="image/svg+xml" href="./icon.svg" />
62
- <title>${name}</title>
63
- <style>
64
- * { box-sizing: border-box; margin: 0; padding: 0; }
65
- body { background: #0c0a09; overflow: hidden; }
66
- canvas { display: block; width: 100vw; height: 100vh; }
67
- </style>
68
- </head>
69
- <body>
70
- <canvas id="canvas"></canvas>
71
- <script type="module" src="./src/main.ts"></script>
72
- </body>
73
- </html>
74
- `,
75
- "vite.config.ts": VITE,
76
- "src/lib.ts": LIB,
77
- "src/main.ts": MAIN,
60
+ "shallot.json": MANIFEST,
61
+ "src/spin.ts": SPIN,
78
62
  "public/scenes/scene.scene": SCENE,
63
+ "README.md": readme(name),
79
64
  };
80
65
  }
81
66
 
67
+ /**
68
+ * the committed in-repo template under examples/templates/<dir>. the source of truth for what
69
+ * `bun run build` regenerates and `bun check` gates for staleness.
70
+ */
71
+ export const TEMPLATES: { dir: string }[] = [{ dir: "starter" }];
72
+
82
73
  /** write a template file map under dir, creating parent directories as needed. */
83
74
  export function scaffold(dir: string, files: Record<string, string>): void {
84
75
  for (const [rel, content] of Object.entries(files)) {
@@ -108,58 +99,69 @@ const ICON = `<svg id="Shallot" xmlns="http://www.w3.org/2000/svg" viewBox="0 0
108
99
  </svg>
109
100
  `;
110
101
 
111
- const VITE = `import { defineConfig } from "vite";
102
+ const readme = (name: string) => `# ${name}
112
103
 
113
- export default defineConfig({
114
- base: "./",
115
- server: { port: 3000 },
116
- build: {
117
- target: "esnext",
118
- outDir: "dist",
119
- emptyOutDir: true,
120
- },
121
- });
104
+ A shallot project.
105
+
106
+ ## Develop
107
+
108
+ \`\`\`bash
109
+ bun install
110
+ bunx shallot dev
111
+ \`\`\`
112
+
113
+ \`bun install\` fetches the engine. \`bunx shallot dev\` runs the project with hot
114
+ reload; \`bunx shallot\` opens it in the editor. Edit \`src/spin.ts\` (a plugin) and
115
+ \`shallot.json\` (the manifest: scene + plugin enablement) in your IDE.
116
+
117
+ ## Ship
118
+
119
+ \`\`\`bash
120
+ bunx shallot build
121
+ \`\`\`
122
+
123
+ Builds a web bundle to \`dist/\`. For native targets:
124
+ \`bunx shallot build --target windows|mac|linux\` (add \`--release\` for an optimized build).
122
125
  `;
123
126
 
124
- const LIB = `import { type Config, Part, quat, type State, type System, Transform } from "@dylanebert/shallot";
127
+ // The project manifest: the editor (\`shallot .\`) and a shipped build both read it. \`scene\`
128
+ // is the scene to open; \`plugins\` is enablement — "Orbit": true turns on the orbit camera the
129
+ // scene uses, and "Spin": "./src/spin" declares our own plugin by its module path. The default
130
+ // plugins (render, lit surface) are on unless you set one false. Toggle these in the editor's
131
+ // plugin menu and it rewrites this file, so the editor and a build always agree on what loads.
132
+ const MANIFEST = `{
133
+ "$schema": "./node_modules/@dylanebert/shallot/shallot.schema.json",
134
+ "scene": "scenes/scene.scene",
135
+ "plugins": {
136
+ "Orbit": true,
137
+ "Spin": "./src/spin"
138
+ }
139
+ }
140
+ `;
125
141
 
126
- // A system is plain data: an \`update\` the engine runs each frame. This one spins
127
- // every Part around Y. It runs in the standalone app but not the editor — \`shallot .\`
128
- // builds in edit mode, where unannotated systems are skipped, so the cube stays put
129
- // and editable there. Delete it (and the plugin entry below) for a static scene.
130
- let angle = 0;
142
+ const SPIN = `import { type Plugin, type State, type System, Part, quat, Transform } from "@dylanebert/shallot";
143
+
144
+ // A plugin is plain data: components + systems the engine runs. This system spins every Part
145
+ // around Y. It runs when the project plays (the editor's preview, or a shipped build) but not
146
+ // while you edit — the editor (\`bunx shallot\`) builds in edit mode, where unannotated systems
147
+ // are skipped, so the cube stays put and editable there. Delete this file (and its
148
+ // \`shallot.json\` entry) for a static scene.
131
149
  const SpinSystem: System = {
132
150
  group: "simulation",
133
151
  update(state: State) {
134
- angle = (angle + state.time.deltaTime * 45) % 360;
135
- const q = quat(0, angle, 0);
152
+ // Derive the angle from elapsed time, not a module-level accumulator, so it stays
153
+ // correct after a hot reload or State rebuild rather than carrying stale rotation.
154
+ const q = quat(0, (state.time.elapsed * 45) % 360, 0);
136
155
  for (const eid of state.query([Part, Transform])) {
137
156
  Transform.rot.set(eid, q.x, q.y, q.z, q.w);
138
157
  }
139
158
  },
140
159
  };
141
160
 
142
- // The project definition, shared by the standalone app (src/main.ts) and the shallot
143
- // editor (\`shallot .\`). The default plugins (render, lit surface, orbit camera) stay
144
- // on; the spin plugin adds our one system. The scene lives here too, so main.ts stays
145
- // untouched as the project grows.
146
- export const config: Config = {
147
- plugins: [{ name: "spin", systems: [SpinSystem] }],
148
- scene: "scenes/scene.scene",
149
- };
150
- `;
151
-
152
- const MAIN = `import { run } from "@dylanebert/shallot";
153
- import { config } from "./lib";
154
-
155
- // One config source (src/lib.ts) drives both this standalone build and the editor. The camera
156
- // auto-binds to the page's <canvas>.
157
- const { dispose } = await run(config);
158
-
159
- // HMR re-runs this module — dispose the old State + RAF loop or each reload stacks another.
160
- if (import.meta.hot) {
161
- import.meta.hot.dispose(() => dispose());
162
- }
161
+ // The default export is the plugin — \`shallot.json\` references this file by path and imports
162
+ // its default. The name ("Spin") is how the plugin menu lists it.
163
+ const SpinPlugin: Plugin = { name: "Spin", systems: [SpinSystem] };
164
+ export default SpinPlugin;
163
165
  `;
164
166
 
165
167
  const SCENE = `<scene>
@@ -175,7 +177,8 @@ const SCENE = `<scene>
175
177
  `;
176
178
 
177
179
  if (import.meta.main) {
178
- const name = process.argv[2];
180
+ const args = process.argv.slice(2);
181
+ const name = args.find((a) => !a.startsWith("--"));
179
182
  if (!name) {
180
183
  console.error("Usage: bun create shallot <project-name>");
181
184
  process.exit(1);
@@ -194,5 +197,5 @@ if (import.meta.main) {
194
197
  console.log("Next steps:");
195
198
  console.log(` cd ${name}`);
196
199
  console.log(" bun install");
197
- console.log(" bun dev");
200
+ console.log(" bunx shallot dev");
198
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-shallot",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "bin": "./index.ts",
6
6
  "files": ["index.ts"],