create-shallot 0.5.1 → 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 +157 -157
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,27 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
4
|
-
import { join, resolve } from "path";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
|
+
|
|
6
|
+
export interface TemplateOptions {
|
|
7
|
+
/**
|
|
8
|
+
* dependency spec for `@dylanebert/shallot`. `"latest"` for the published `bun create shallot`
|
|
9
|
+
* scaffold; `"workspace:*"` when `bun run build` regenerates examples/templates/* in-repo.
|
|
10
|
+
*/
|
|
11
|
+
shallot?: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* the project files keyed by relative path, with the project name interpolated. single source of
|
|
16
|
+
* truth for both `bun create shallot <name>` and the in-repo examples/templates/starter example
|
|
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).
|
|
22
|
+
*/
|
|
23
|
+
export function template(name: string, opts: TemplateOptions = {}): Record<string, string> {
|
|
24
|
+
const shallot = opts.shallot ?? "latest";
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
"public/icon.svg": ICON,
|
|
28
|
+
".gitignore": "node_modules/\ndist/\nbuild/\n",
|
|
29
|
+
"package.json":
|
|
30
|
+
JSON.stringify(
|
|
31
|
+
{
|
|
32
|
+
name,
|
|
33
|
+
version: "0.0.0",
|
|
34
|
+
private: true,
|
|
35
|
+
type: "module",
|
|
36
|
+
dependencies: { "@dylanebert/shallot": shallot },
|
|
37
|
+
devDependencies: { typescript: "^5.9.3" },
|
|
38
|
+
},
|
|
39
|
+
null,
|
|
40
|
+
2,
|
|
41
|
+
) + "\n",
|
|
42
|
+
"tsconfig.json":
|
|
43
|
+
JSON.stringify(
|
|
44
|
+
{
|
|
45
|
+
compilerOptions: {
|
|
46
|
+
target: "ESNext",
|
|
47
|
+
module: "ESNext",
|
|
48
|
+
moduleResolution: "bundler",
|
|
49
|
+
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
50
|
+
types: ["@webgpu/types"],
|
|
51
|
+
strict: true,
|
|
52
|
+
noEmit: true,
|
|
53
|
+
skipLibCheck: true,
|
|
54
|
+
},
|
|
55
|
+
include: ["src"],
|
|
56
|
+
},
|
|
57
|
+
null,
|
|
58
|
+
2,
|
|
59
|
+
) + "\n",
|
|
60
|
+
"shallot.json": MANIFEST,
|
|
61
|
+
"src/spin.ts": SPIN,
|
|
62
|
+
"public/scenes/scene.scene": SCENE,
|
|
63
|
+
"README.md": readme(name),
|
|
64
|
+
};
|
|
16
65
|
}
|
|
17
66
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
|
|
73
|
+
/** write a template file map under dir, creating parent directories as needed. */
|
|
74
|
+
export function scaffold(dir: string, files: Record<string, string>): void {
|
|
75
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
76
|
+
const path = join(dir, rel);
|
|
77
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
78
|
+
writeFileSync(path, content);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
21
81
|
|
|
22
|
-
|
|
23
|
-
join(dir, "public/icon.svg"),
|
|
24
|
-
`<svg id="Shallot" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
|
|
82
|
+
const ICON = `<svg id="Shallot" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
|
|
25
83
|
<title>shallot icon</title>
|
|
26
84
|
<defs>
|
|
27
85
|
<radialGradient id="baseGradient" cx="35%" cy="30%" r="70%" fx="25%" fy="20%">
|
|
@@ -39,148 +97,74 @@ writeFileSync(
|
|
|
39
97
|
<path d="M40,2 C44,10 66,28 66,46 C66,60 48,70 40,78 C32,70 14,60 14,46 C14,28 36,10 40,2 Z" fill="none" stroke="#6B4230" stroke-width="2"/>
|
|
40
98
|
</g>
|
|
41
99
|
</svg>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
2,
|
|
92
|
-
) + "\n",
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
writeFileSync(
|
|
96
|
-
join(dir, "index.html"),
|
|
97
|
-
`<!doctype html>
|
|
98
|
-
<html lang="en">
|
|
99
|
-
<head>
|
|
100
|
-
<meta charset="UTF-8" />
|
|
101
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
102
|
-
<link rel="icon" type="image/svg+xml" href="./icon.svg" />
|
|
103
|
-
<title>${name}</title>
|
|
104
|
-
<style>
|
|
105
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
106
|
-
body { background: #0c0a09; overflow: hidden; }
|
|
107
|
-
canvas { display: block; width: 100vw; height: 100vh; }
|
|
108
|
-
</style>
|
|
109
|
-
</head>
|
|
110
|
-
<body>
|
|
111
|
-
<canvas id="canvas"></canvas>
|
|
112
|
-
<script type="module" src="./src/main.ts"></script>
|
|
113
|
-
</body>
|
|
114
|
-
</html>
|
|
115
|
-
`,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
writeFileSync(
|
|
119
|
-
join(dir, "vite.config.ts"),
|
|
120
|
-
`import { defineConfig } from "vite";
|
|
121
|
-
|
|
122
|
-
export default defineConfig({
|
|
123
|
-
base: "./",
|
|
124
|
-
server: { port: 3000 },
|
|
125
|
-
build: {
|
|
126
|
-
target: "esnext",
|
|
127
|
-
outDir: "dist",
|
|
128
|
-
emptyOutDir: true,
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
`,
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
writeFileSync(
|
|
135
|
-
join(dir, "src/lib.ts"),
|
|
136
|
-
`import { type Config, Part, quat, type State, type System, Transform } from "@dylanebert/shallot";
|
|
137
|
-
|
|
138
|
-
// A system is plain data: an \`update\` the engine runs each frame. This one spins
|
|
139
|
-
// every Part around Y. It runs in the standalone app but not the editor — \`shallot .\`
|
|
140
|
-
// builds in edit mode, where unannotated systems are skipped, so the cube stays put
|
|
141
|
-
// and editable there. Delete it (and the plugin entry below) for a static scene.
|
|
142
|
-
let angle = 0;
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const readme = (name: string) => `# ${name}
|
|
103
|
+
|
|
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).
|
|
125
|
+
`;
|
|
126
|
+
|
|
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
|
+
`;
|
|
141
|
+
|
|
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.
|
|
143
149
|
const SpinSystem: System = {
|
|
144
150
|
group: "simulation",
|
|
145
151
|
update(state: State) {
|
|
146
|
-
|
|
147
|
-
|
|
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);
|
|
148
155
|
for (const eid of state.query([Part, Transform])) {
|
|
149
156
|
Transform.rot.set(eid, q.x, q.y, q.z, q.w);
|
|
150
157
|
}
|
|
151
158
|
},
|
|
152
159
|
};
|
|
153
160
|
|
|
154
|
-
// The
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
plugins: [{ name: "spin", systems: [SpinSystem] }],
|
|
160
|
-
scene: "scenes/scene.scene",
|
|
161
|
-
};
|
|
162
|
-
`,
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
writeFileSync(
|
|
166
|
-
join(dir, "src/main.ts"),
|
|
167
|
-
`import { run } from "@dylanebert/shallot";
|
|
168
|
-
import { config } from "./lib";
|
|
169
|
-
|
|
170
|
-
// One config source (src/lib.ts) drives both this standalone build and the editor. The camera
|
|
171
|
-
// auto-binds to the page's <canvas>.
|
|
172
|
-
const { dispose } = await run(config);
|
|
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;
|
|
165
|
+
`;
|
|
173
166
|
|
|
174
|
-
|
|
175
|
-
if (import.meta.hot) {
|
|
176
|
-
import.meta.hot.dispose(() => dispose());
|
|
177
|
-
}
|
|
178
|
-
`,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
writeFileSync(
|
|
182
|
-
join(dir, "public/scenes/scene.scene"),
|
|
183
|
-
`<scene>
|
|
167
|
+
const SCENE = `<scene>
|
|
184
168
|
<a ambient-light="color: 0xd0dcec; intensity: 0.5" />
|
|
185
169
|
<a directional-light="direction: -0.4 -1 -0.55; color: 0xfff4e0; intensity: 1.1" />
|
|
186
170
|
|
|
@@ -190,12 +174,28 @@ writeFileSync(
|
|
|
190
174
|
<!-- a Part is the engine's drop-in renderable: a mesh (default "cube") wearing a surface (default "default", lit) -->
|
|
191
175
|
<a part transform="pos: 0 0 0" color="rgba: 0.85 0.55 0.35 1" />
|
|
192
176
|
</scene>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
console.
|
|
200
|
-
|
|
201
|
-
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
if (import.meta.main) {
|
|
180
|
+
const args = process.argv.slice(2);
|
|
181
|
+
const name = args.find((a) => !a.startsWith("--"));
|
|
182
|
+
if (!name) {
|
|
183
|
+
console.error("Usage: bun create shallot <project-name>");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const dir = resolve(name);
|
|
188
|
+
if (existsSync(dir)) {
|
|
189
|
+
console.error(`Directory "${name}" already exists`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
scaffold(dir, template(name));
|
|
194
|
+
|
|
195
|
+
console.log(`Created ${name}/`);
|
|
196
|
+
console.log();
|
|
197
|
+
console.log("Next steps:");
|
|
198
|
+
console.log(` cd ${name}`);
|
|
199
|
+
console.log(" bun install");
|
|
200
|
+
console.log(" bunx shallot dev");
|
|
201
|
+
}
|