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.
- package/index.ts +70 -67
- 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
|
|
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"
|
|
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"
|
|
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
|
-
"
|
|
57
|
-
|
|
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
|
|
102
|
+
const readme = (name: string) => `# ${name}
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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("
|
|
200
|
+
console.log(" bunx shallot dev");
|
|
198
201
|
}
|