@bitmagic/cli 0.1.22 → 0.1.24
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/README.md +53 -19
- package/dist/cli.d.ts +10 -6
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/dev.d.ts +5 -1
- package/dist/commands/dev.js +82 -13
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/generate.js +3 -3
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/reload.d.ts +10 -0
- package/dist/commands/reload.js +82 -0
- package/dist/commands/reload.js.map +1 -0
- package/dist/commands/upgrade.js +6 -0
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/editor/journal.js +2 -2
- package/dist/editor/reload-bus.d.ts +38 -0
- package/dist/editor/reload-bus.js +66 -0
- package/dist/editor/reload-bus.js.map +1 -0
- package/dist/editor/server.d.ts +7 -0
- package/dist/editor/server.js +51 -9
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.d.ts +24 -5
- package/dist/editor/shell-page.js +295 -36
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/editor/watch.d.ts +101 -0
- package/dist/editor/watch.js +228 -0
- package/dist/editor/watch.js.map +1 -0
- package/dist/local-port.d.ts +10 -0
- package/dist/local-port.js +23 -0
- package/dist/local-port.js.map +1 -1
- package/dist/project/dev-handle.d.ts +14 -0
- package/dist/project/dev-handle.js +53 -0
- package/dist/project/dev-handle.js.map +1 -0
- package/dist/scaffold/claude-settings.d.ts +41 -0
- package/dist/scaffold/claude-settings.js +98 -0
- package/dist/scaffold/claude-settings.js.map +1 -0
- package/dist/scaffold/project-files.js +20 -8
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.js +5 -0
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/upgrade-project.d.ts +6 -0
- package/dist/scaffold/upgrade-project.js +19 -1
- package/dist/scaffold/upgrade-project.js.map +1 -1
- package/dist/verify/browser.js +3 -1
- package/dist/verify/browser.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/edit.d.ts +0 -10
- package/dist/commands/edit.js +0 -148
- package/dist/commands/edit.js.map +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deciding *when* the browser should reload.
|
|
3
|
+
*
|
|
4
|
+
* The hard part is not noticing a change — `fs.watch` does that — it is not reloading at the wrong
|
|
5
|
+
* moment. Three wrong moments, each of which this file exists to avoid:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Mid-burst.** An agent editing five files produces five reloads unless the writes are let
|
|
8
|
+
* go quiet first. Hence the debounce.
|
|
9
|
+
* 2. **Mid-compile.** A `.ts` edit changes `src/` long before `tsc --watch` finishes emitting
|
|
10
|
+
* `dist/`, and every module the project loads resolves into `dist/`. Reloading on the source
|
|
11
|
+
* write shows the creator the *old* build and reads as "my change did nothing". Hence the
|
|
12
|
+
* compile gate: reloads are held while a compile is open and released when it lands clean.
|
|
13
|
+
* 3. **After a failed compile.** `dist/` still holds the last good emit, so a reload would show
|
|
14
|
+
* working code for broken source — the most confusing outcome of the three. The pending reload
|
|
15
|
+
* is dropped and the shell is told the build failed instead.
|
|
16
|
+
*
|
|
17
|
+
* `ReloadCoordinator` holds all of that and touches no I/O, so it is testable with fake timers and
|
|
18
|
+
* no filesystem. `watchProject` and `pipeTscWatch` are the thin I/O shells that feed it.
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from 'child_process';
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
/**
|
|
24
|
+
* How long the writes must stay quiet before a reload fires.
|
|
25
|
+
*
|
|
26
|
+
* Long enough that `tsc --watch` (which has its own ~250 ms debounce before it reports
|
|
27
|
+
* `File change detected`) has almost always opened its compile by the time this elapses, so the
|
|
28
|
+
* gate below actually gets to hold the reload. Too short and a `.ts` edit produces two reloads —
|
|
29
|
+
* one stale, one correct — instead of one.
|
|
30
|
+
*/
|
|
31
|
+
export const RELOAD_QUIET_MS = 700;
|
|
32
|
+
/**
|
|
33
|
+
* How long after one of our own writes to ignore file events.
|
|
34
|
+
*
|
|
35
|
+
* `/api/scene/save` and the HQ generation both write `world.json`, and a reload fired at the
|
|
36
|
+
* creator's own gizmo drag would throw away the drag that caused it. `fs.watch` delivers
|
|
37
|
+
* asynchronously but promptly, so this only has to outlast the delivery, not the write.
|
|
38
|
+
*/
|
|
39
|
+
export const OWN_WRITE_GRACE_MS = 400;
|
|
40
|
+
export class ReloadCoordinator {
|
|
41
|
+
options;
|
|
42
|
+
timer = null;
|
|
43
|
+
pendingReason = null;
|
|
44
|
+
compiling = false;
|
|
45
|
+
suppressUntil = 0;
|
|
46
|
+
stopped = false;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = { quietMs: RELOAD_QUIET_MS, ...options };
|
|
49
|
+
}
|
|
50
|
+
/** A file under a watched directory changed. Arms — or re-arms — the quiet window. */
|
|
51
|
+
noteFileChange(reason) {
|
|
52
|
+
if (this.stopped)
|
|
53
|
+
return;
|
|
54
|
+
if (Date.now() < this.suppressUntil)
|
|
55
|
+
return;
|
|
56
|
+
this.pendingReason = reason;
|
|
57
|
+
if (this.timer)
|
|
58
|
+
clearTimeout(this.timer);
|
|
59
|
+
this.timer = setTimeout(() => {
|
|
60
|
+
this.timer = null;
|
|
61
|
+
this.release();
|
|
62
|
+
}, this.options.quietMs);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Reload as soon as it is safe to — now, or the moment the compile in flight lands clean.
|
|
66
|
+
*
|
|
67
|
+
* The wait is the whole point for `bitmagic reload`: an agent's stop hook fires immediately after
|
|
68
|
+
* its last edit, which is precisely when `tsc` is still compiling it. Returns whether the reload
|
|
69
|
+
* was queued behind a compile rather than sent, so the command can say which happened.
|
|
70
|
+
*/
|
|
71
|
+
requestNow(reason) {
|
|
72
|
+
if (this.stopped)
|
|
73
|
+
return { queued: false };
|
|
74
|
+
if (this.timer) {
|
|
75
|
+
clearTimeout(this.timer);
|
|
76
|
+
this.timer = null;
|
|
77
|
+
}
|
|
78
|
+
this.pendingReason = reason;
|
|
79
|
+
this.release();
|
|
80
|
+
return { queued: this.pendingReason !== null };
|
|
81
|
+
}
|
|
82
|
+
/** The sidecar wrote `world.json` itself; the resulting file events are not news. */
|
|
83
|
+
noteOwnWrite() {
|
|
84
|
+
this.suppressUntil = Date.now() + OWN_WRITE_GRACE_MS;
|
|
85
|
+
}
|
|
86
|
+
noteCompileStart() {
|
|
87
|
+
if (this.stopped || this.compiling)
|
|
88
|
+
return;
|
|
89
|
+
this.compiling = true;
|
|
90
|
+
this.options.onBuild('building');
|
|
91
|
+
}
|
|
92
|
+
noteCompileEnd(errorCount) {
|
|
93
|
+
if (this.stopped)
|
|
94
|
+
return;
|
|
95
|
+
this.compiling = false;
|
|
96
|
+
if (errorCount > 0) {
|
|
97
|
+
// Dropped, not deferred: `dist/` holds the last good emit, so reloading would show working
|
|
98
|
+
// code for broken source. The shell says so instead, and the next clean compile reloads.
|
|
99
|
+
this.pendingReason = null;
|
|
100
|
+
this.options.onBuild('failed', `${errorCount} TypeScript error${errorCount === 1 ? '' : 's'} — the game was not rebuilt.`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.options.onBuild('ok');
|
|
104
|
+
this.release();
|
|
105
|
+
}
|
|
106
|
+
stop() {
|
|
107
|
+
this.stopped = true;
|
|
108
|
+
if (this.timer)
|
|
109
|
+
clearTimeout(this.timer);
|
|
110
|
+
this.timer = null;
|
|
111
|
+
this.pendingReason = null;
|
|
112
|
+
}
|
|
113
|
+
/** Send the pending reload, unless a compile is still open — then it waits for `noteCompileEnd`. */
|
|
114
|
+
release() {
|
|
115
|
+
if (this.pendingReason === null || this.compiling)
|
|
116
|
+
return;
|
|
117
|
+
const reason = this.pendingReason;
|
|
118
|
+
this.pendingReason = null;
|
|
119
|
+
this.options.onReload(reason);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Path segments that never mean "the project changed". */
|
|
123
|
+
const IGNORED_SEGMENTS = new Set(['node_modules', '.bitmagic', '.vite-cache', '.git']);
|
|
124
|
+
/**
|
|
125
|
+
* Editors write through temporary siblings — vim's `4913`/`~`, JetBrains' `___jb_tmp___`, an
|
|
126
|
+
* atomic-rename `.tmp`. Reloading on those means reloading on a file that no longer exists.
|
|
127
|
+
*/
|
|
128
|
+
function isIgnoredPath(relative) {
|
|
129
|
+
const segments = relative.split(path.sep);
|
|
130
|
+
if (segments.some((segment) => IGNORED_SEGMENTS.has(segment)))
|
|
131
|
+
return true;
|
|
132
|
+
const name = segments[segments.length - 1] ?? '';
|
|
133
|
+
return name.startsWith('.') || name.endsWith('~') || name.includes('___jb_') || name.endsWith('.tmp');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Watch the two directories that decide what the browser shows: `src/` (what the agent and the
|
|
137
|
+
* creator edit — including `work/world.json`) and `dist/` (what the page actually loads).
|
|
138
|
+
*
|
|
139
|
+
* `dist/` is watched as well as `src/` even though the compile gate already covers the normal
|
|
140
|
+
* `tsc --watch` path, because it is what a build run outside this process — a manual `tsc`, an
|
|
141
|
+
* editor's own compiler — lands in, and that should reach the browser too.
|
|
142
|
+
*
|
|
143
|
+
* A directory that cannot be watched is skipped with a note rather than failing the command: a
|
|
144
|
+
* missing `dist/` means "not built yet", and losing auto-reload is a smaller loss than losing
|
|
145
|
+
* `bitmagic dev`.
|
|
146
|
+
*/
|
|
147
|
+
export function watchProject(root, coordinator, log) {
|
|
148
|
+
const watchers = [];
|
|
149
|
+
for (const dir of ['src', 'dist']) {
|
|
150
|
+
const absolute = path.join(root, dir);
|
|
151
|
+
if (!fs.existsSync(absolute))
|
|
152
|
+
continue;
|
|
153
|
+
try {
|
|
154
|
+
const watcher = fs.watch(absolute, { recursive: true }, (_event, filename) => {
|
|
155
|
+
const relative = filename === null ? '' : String(filename);
|
|
156
|
+
if (relative !== '' && isIgnoredPath(relative))
|
|
157
|
+
return;
|
|
158
|
+
coordinator.noteFileChange(path.posix.join(dir, relative.split(path.sep).join('/')));
|
|
159
|
+
});
|
|
160
|
+
watcher.on('error', (error) => {
|
|
161
|
+
log(`[dev] stopped watching ${dir}/: ${error.message}`);
|
|
162
|
+
});
|
|
163
|
+
watchers.push(watcher);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
log(`[dev] could not watch ${dir}/ (${message}) — reload on change is off for it.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
close: () => {
|
|
172
|
+
for (const watcher of watchers)
|
|
173
|
+
watcher.close();
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** tsc colourises when it thinks it is on a terminal; the markers below must match either way. */
|
|
178
|
+
// eslint-disable-next-line no-control-regex
|
|
179
|
+
const ANSI = /\u001B\[[0-9;]*m/g;
|
|
180
|
+
const COMPILE_STARTED = /(File change detected|Starting compilation in watch mode)/;
|
|
181
|
+
const COMPILE_FINISHED = /Found (\d+) error/;
|
|
182
|
+
/**
|
|
183
|
+
* Feed one line of `tsc --watch` output to the coordinator. Exported for its test — the markers are
|
|
184
|
+
* tsc's own wording, and a version that changes them would otherwise break auto-reload silently.
|
|
185
|
+
*/
|
|
186
|
+
export function noteTscLine(line, coordinator) {
|
|
187
|
+
const plain = line.replace(ANSI, '');
|
|
188
|
+
const finished = COMPILE_FINISHED.exec(plain);
|
|
189
|
+
if (finished) {
|
|
190
|
+
coordinator.noteCompileEnd(Number(finished[1]));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (COMPILE_STARTED.test(plain))
|
|
194
|
+
coordinator.noteCompileStart();
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Spawn `tsc --watch` with its output piped through us instead of inherited.
|
|
198
|
+
*
|
|
199
|
+
* Piping is what makes the compile gate possible — the only signal that a build finished, and
|
|
200
|
+
* whether it finished clean, is tsc's own `Found N errors` line. Every line is re-echoed verbatim,
|
|
201
|
+
* so the creator sees exactly what an inherited stdio would have shown, and `--pretty` is forced
|
|
202
|
+
* back on when our own stdout is a terminal (tsc drops colour the moment it is piped).
|
|
203
|
+
*/
|
|
204
|
+
export function pipeTscWatch(tsc, cwd, coordinator) {
|
|
205
|
+
const args = ['--watch', '--preserveWatchOutput'];
|
|
206
|
+
if (process.stdout.isTTY)
|
|
207
|
+
args.push('--pretty');
|
|
208
|
+
const child = spawn(tsc, args, { cwd, stdio: ['inherit', 'pipe', 'pipe'] });
|
|
209
|
+
const consume = (stream, echo) => {
|
|
210
|
+
if (!stream)
|
|
211
|
+
return;
|
|
212
|
+
let buffered = '';
|
|
213
|
+
stream.setEncoding('utf-8');
|
|
214
|
+
stream.on('data', (chunk) => {
|
|
215
|
+
echo.write(chunk);
|
|
216
|
+
buffered += chunk;
|
|
217
|
+
const lines = buffered.split('\n');
|
|
218
|
+
// The last element is whatever came after the final newline — an incomplete line, held over.
|
|
219
|
+
buffered = lines.pop() ?? '';
|
|
220
|
+
for (const line of lines)
|
|
221
|
+
noteTscLine(line, coordinator);
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
consume(child.stdout, process.stdout);
|
|
225
|
+
consume(child.stderr, process.stderr);
|
|
226
|
+
return child;
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/editor/watch.ts"],"names":[],"mappings":"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"}
|
package/dist/local-port.d.ts
CHANGED
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export declare const CORS_ALLOWED_PORT_MIN = 3000;
|
|
18
18
|
export declare const CORS_ALLOWED_PORT_MAX = 3199;
|
|
19
|
+
/**
|
|
20
|
+
* A `--port`-style flag parsed and checked against the window above.
|
|
21
|
+
*
|
|
22
|
+
* Every port `bitmagic dev` binds goes through here, the game port for the reason documented above
|
|
23
|
+
* and the shell port because the two are adjacent by default: a creator who moves one will move the
|
|
24
|
+
* other, and being told the rule once, at the point of the mistake, beats discovering it as a
|
|
25
|
+
* terrain-less world. (The shell itself fetches nothing from the CDN, so its own check is purely
|
|
26
|
+
* that kindness.)
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseLocalPort(value: string | undefined, fallback: number, flag: string): number;
|
|
19
29
|
/**
|
|
20
30
|
* A free port the CDN will serve assets to, or `null` when the whole window is taken.
|
|
21
31
|
*
|
package/dist/local-port.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as net from 'net';
|
|
2
|
+
import { CliError } from './errors.js';
|
|
2
3
|
/**
|
|
3
4
|
* The port window the platform's bucket CORS policy allowlists as an origin.
|
|
4
5
|
*
|
|
@@ -17,6 +18,28 @@ import * as net from 'net';
|
|
|
17
18
|
*/
|
|
18
19
|
export const CORS_ALLOWED_PORT_MIN = 3000;
|
|
19
20
|
export const CORS_ALLOWED_PORT_MAX = 3199;
|
|
21
|
+
/**
|
|
22
|
+
* A `--port`-style flag parsed and checked against the window above.
|
|
23
|
+
*
|
|
24
|
+
* Every port `bitmagic dev` binds goes through here, the game port for the reason documented above
|
|
25
|
+
* and the shell port because the two are adjacent by default: a creator who moves one will move the
|
|
26
|
+
* other, and being told the rule once, at the point of the mistake, beats discovering it as a
|
|
27
|
+
* terrain-less world. (The shell itself fetches nothing from the CDN, so its own check is purely
|
|
28
|
+
* that kindness.)
|
|
29
|
+
*/
|
|
30
|
+
export function parseLocalPort(value, fallback, flag) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return fallback;
|
|
33
|
+
const port = Number(value);
|
|
34
|
+
if (!Number.isInteger(port)) {
|
|
35
|
+
throw new CliError(`\`${flag}\` must be a whole number, got "${value}".`);
|
|
36
|
+
}
|
|
37
|
+
if (port < CORS_ALLOWED_PORT_MIN || port > CORS_ALLOWED_PORT_MAX) {
|
|
38
|
+
throw new CliError(`\`${flag}\` must be between ${CORS_ALLOWED_PORT_MIN} and ${CORS_ALLOWED_PORT_MAX}. `
|
|
39
|
+
+ 'The asset CDN only allows those origins; outside that window the game loads with no terrain.');
|
|
40
|
+
}
|
|
41
|
+
return port;
|
|
42
|
+
}
|
|
20
43
|
function tryListen(port) {
|
|
21
44
|
return new Promise((resolve) => {
|
|
22
45
|
const server = net.createServer();
|
package/dist/local-port.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB,EAAE,QAAgB,EAAE,IAAY;IACtF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,QAAQ,CAAC,KAAK,IAAI,mCAAmC,KAAK,IAAI,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,IAAI,GAAG,qBAAqB,IAAI,IAAI,GAAG,qBAAqB,EAAE,CAAC;QACjE,MAAM,IAAI,QAAQ,CAChB,KAAK,IAAI,sBAAsB,qBAAqB,QAAQ,qBAAqB,IAAI;cACnF,8FAA8F,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YACpF,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,MAAM,IAAI,GAAG,qBAAqB,GAAG,qBAAqB,GAAG,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DevHandle {
|
|
2
|
+
/** The `--editor-port` value: where the tabbed Game/Editor shell is served — the URL to open. */
|
|
3
|
+
editorPort: number;
|
|
4
|
+
/** The `--port` value: where vite serves the game itself. */
|
|
5
|
+
gamePort: number;
|
|
6
|
+
/** The `bitmagic dev` process, for a human reading the file. Never used as a liveness check. */
|
|
7
|
+
pid: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function devHandlePath(root: string): string;
|
|
10
|
+
export declare function writeDevHandle(root: string, handle: DevHandle): void;
|
|
11
|
+
/** Best-effort: a handle we cannot remove is a stale file, never a failed shutdown. */
|
|
12
|
+
export declare function removeDevHandle(root: string): void;
|
|
13
|
+
/** The recorded handle, or null when there is no readable one. */
|
|
14
|
+
export declare function readDevHandle(root: string): DevHandle | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.bitmagic/dev.json` — how a second process finds the `bitmagic dev` already running here.
|
|
3
|
+
*
|
|
4
|
+
* `bitmagic reload` needs the shell's port, and hard-coding 3011 would break the moment a creator
|
|
5
|
+
* passes `--editor-port`. So `dev` writes what it bound the instant it is listening, and removes the
|
|
6
|
+
* file on shutdown. `.bitmagic/` is already gitignored (scaffold/project-files.ts renderGitignore),
|
|
7
|
+
* so this never reaches a commit.
|
|
8
|
+
*
|
|
9
|
+
* A stale file is expected, not exceptional: a `dev` killed with SIGKILL leaves one behind. Nothing
|
|
10
|
+
* here tries to detect that — `reload` simply fails to connect to the recorded port and reports that
|
|
11
|
+
* no dev server is running, which is the same outcome and the same message as no file at all. The
|
|
12
|
+
* pid is recorded for a human reading the file, not for liveness checks.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
export function devHandlePath(root) {
|
|
17
|
+
return path.join(root, '.bitmagic', 'dev.json');
|
|
18
|
+
}
|
|
19
|
+
export function writeDevHandle(root, handle) {
|
|
20
|
+
const file = devHandlePath(root);
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
fs.writeFileSync(file, `${JSON.stringify(handle, null, 2)}\n`);
|
|
23
|
+
}
|
|
24
|
+
/** Best-effort: a handle we cannot remove is a stale file, never a failed shutdown. */
|
|
25
|
+
export function removeDevHandle(root) {
|
|
26
|
+
try {
|
|
27
|
+
fs.rmSync(devHandlePath(root), { force: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// See above.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The recorded handle, or null when there is no readable one. */
|
|
34
|
+
export function readDevHandle(root) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(fs.readFileSync(devHandlePath(root), 'utf-8'));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
43
|
+
return null;
|
|
44
|
+
const record = parsed;
|
|
45
|
+
if (typeof record.editorPort !== 'number' || typeof record.gamePort !== 'number')
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
editorPort: record.editorPort,
|
|
49
|
+
gamePort: record.gamePort,
|
|
50
|
+
pid: typeof record.pid === 'number' ? record.pid : 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=dev-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-handle.js","sourceRoot":"","sources":["../../src/project/dev-handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAW7B,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAiB;IAC5D,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,aAAa;IACf,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC9F,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,EAAE,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACrD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.claude/settings.json` — the one line that makes the browser reload when the agent stops.
|
|
3
|
+
*
|
|
4
|
+
* `bitmagic dev` reloads on its own once writes go quiet, which is right for a human saving a file
|
|
5
|
+
* and a guess for an agent, because an agent pauses to think and a long enough pause reads as
|
|
6
|
+
* "finished". Claude Code's `Stop` hook removes the guess: it fires exactly once, when the turn
|
|
7
|
+
* ends, and running `bitmagic reload` there means the creator sees the finished work rather than a
|
|
8
|
+
* frame from the middle of it.
|
|
9
|
+
*
|
|
10
|
+
* ── Why this file is merged and not rendered ─────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* Every other file the CLI ships into `.claude/` is in PLATFORM_GENERATED_FILES, which `upgrade`
|
|
13
|
+
* overwrites wholesale — correct for a skill that documents the CLI, since a stale copy is worse
|
|
14
|
+
* than a lost edit. `settings.json` is the opposite: it is where a creator puts their permissions,
|
|
15
|
+
* their env, their own hooks. Overwriting it would delete work that was never ours, so `upgrade`
|
|
16
|
+
* adds our hook to whatever is there and touches nothing else, in the same read-modify-write shape
|
|
17
|
+
* `bitmagic.json` already uses.
|
|
18
|
+
*
|
|
19
|
+
* Anything that cannot be merged confidently is left alone entirely. A creator with a hand-written
|
|
20
|
+
* settings file that we cannot parse keeps their file; they lose auto-reload-on-stop, which the
|
|
21
|
+
* watcher in `editor/watch.ts` mostly covers anyway.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CLAUDE_SETTINGS_FILE = ".claude/settings.json";
|
|
24
|
+
/** What the hook runs. One definition, so the scaffold and the merge cannot disagree. */
|
|
25
|
+
export declare const RELOAD_HOOK_COMMAND = "bitmagic reload";
|
|
26
|
+
export interface ReloadHookMerge {
|
|
27
|
+
/** The text to write, or null to leave the file exactly as it is. */
|
|
28
|
+
contents: string | null;
|
|
29
|
+
/** Present only when `contents` is null *because* the file could not be merged. */
|
|
30
|
+
skipped?: string;
|
|
31
|
+
}
|
|
32
|
+
/** The settings a project with no `.claude/settings.json` at all gets. */
|
|
33
|
+
export declare function renderClaudeSettings(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Add the Stop hook to an existing settings file, preserving everything else in it.
|
|
36
|
+
*
|
|
37
|
+
* `existing` is the file's text, or undefined when there is no file. Returns `contents: null` when
|
|
38
|
+
* there is nothing to do — the hook is already there, or the file is not something we can safely
|
|
39
|
+
* rewrite.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mergeReloadHook(existing: string | undefined): ReloadHookMerge;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.claude/settings.json` — the one line that makes the browser reload when the agent stops.
|
|
3
|
+
*
|
|
4
|
+
* `bitmagic dev` reloads on its own once writes go quiet, which is right for a human saving a file
|
|
5
|
+
* and a guess for an agent, because an agent pauses to think and a long enough pause reads as
|
|
6
|
+
* "finished". Claude Code's `Stop` hook removes the guess: it fires exactly once, when the turn
|
|
7
|
+
* ends, and running `bitmagic reload` there means the creator sees the finished work rather than a
|
|
8
|
+
* frame from the middle of it.
|
|
9
|
+
*
|
|
10
|
+
* ── Why this file is merged and not rendered ─────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* Every other file the CLI ships into `.claude/` is in PLATFORM_GENERATED_FILES, which `upgrade`
|
|
13
|
+
* overwrites wholesale — correct for a skill that documents the CLI, since a stale copy is worse
|
|
14
|
+
* than a lost edit. `settings.json` is the opposite: it is where a creator puts their permissions,
|
|
15
|
+
* their env, their own hooks. Overwriting it would delete work that was never ours, so `upgrade`
|
|
16
|
+
* adds our hook to whatever is there and touches nothing else, in the same read-modify-write shape
|
|
17
|
+
* `bitmagic.json` already uses.
|
|
18
|
+
*
|
|
19
|
+
* Anything that cannot be merged confidently is left alone entirely. A creator with a hand-written
|
|
20
|
+
* settings file that we cannot parse keeps their file; they lose auto-reload-on-stop, which the
|
|
21
|
+
* watcher in `editor/watch.ts` mostly covers anyway.
|
|
22
|
+
*/
|
|
23
|
+
export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
|
|
24
|
+
/** What the hook runs. One definition, so the scaffold and the merge cannot disagree. */
|
|
25
|
+
export const RELOAD_HOOK_COMMAND = 'bitmagic reload';
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function stringify(settings) {
|
|
30
|
+
return `${JSON.stringify(settings, null, 2)}\n`;
|
|
31
|
+
}
|
|
32
|
+
/** The `Stop` matcher group this CLI owns. */
|
|
33
|
+
function reloadMatcher() {
|
|
34
|
+
return { hooks: [{ type: 'command', command: RELOAD_HOOK_COMMAND }] };
|
|
35
|
+
}
|
|
36
|
+
/** The settings a project with no `.claude/settings.json` at all gets. */
|
|
37
|
+
export function renderClaudeSettings() {
|
|
38
|
+
return stringify({ hooks: { Stop: [reloadMatcher()] } });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Does this `Stop` group already run our command?
|
|
42
|
+
*
|
|
43
|
+
* Matched by substring rather than equality on purpose: a creator who wrapped it — `cd app &&
|
|
44
|
+
* bitmagic reload`, `bitmagic reload --port 3021` — has the hook, and adding a second one would
|
|
45
|
+
* reload twice per turn.
|
|
46
|
+
*/
|
|
47
|
+
function runsReload(matcher) {
|
|
48
|
+
if (!isRecord(matcher))
|
|
49
|
+
return false;
|
|
50
|
+
const hooks = matcher.hooks;
|
|
51
|
+
if (!Array.isArray(hooks))
|
|
52
|
+
return false;
|
|
53
|
+
return hooks.some((hook) => {
|
|
54
|
+
if (!isRecord(hook))
|
|
55
|
+
return false;
|
|
56
|
+
const command = hook.command;
|
|
57
|
+
return typeof command === 'string' && command.includes(RELOAD_HOOK_COMMAND);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add the Stop hook to an existing settings file, preserving everything else in it.
|
|
62
|
+
*
|
|
63
|
+
* `existing` is the file's text, or undefined when there is no file. Returns `contents: null` when
|
|
64
|
+
* there is nothing to do — the hook is already there, or the file is not something we can safely
|
|
65
|
+
* rewrite.
|
|
66
|
+
*/
|
|
67
|
+
export function mergeReloadHook(existing) {
|
|
68
|
+
if (existing === undefined || existing.trim() === '') {
|
|
69
|
+
return { contents: renderClaudeSettings() };
|
|
70
|
+
}
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(existing);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} is not valid JSON` };
|
|
77
|
+
}
|
|
78
|
+
if (!isRecord(parsed)) {
|
|
79
|
+
return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} does not contain a settings object` };
|
|
80
|
+
}
|
|
81
|
+
const hooks = parsed.hooks === undefined ? {} : parsed.hooks;
|
|
82
|
+
if (!isRecord(hooks)) {
|
|
83
|
+
return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} has a "hooks" value we cannot merge into` };
|
|
84
|
+
}
|
|
85
|
+
const stop = hooks.Stop === undefined ? [] : hooks.Stop;
|
|
86
|
+
if (!Array.isArray(stop)) {
|
|
87
|
+
return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} has a "hooks.Stop" value we cannot merge into` };
|
|
88
|
+
}
|
|
89
|
+
if (stop.some(runsReload))
|
|
90
|
+
return { contents: null };
|
|
91
|
+
return {
|
|
92
|
+
contents: stringify({
|
|
93
|
+
...parsed,
|
|
94
|
+
hooks: { ...hooks, Stop: [...stop, reloadMatcher()] },
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=claude-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -307,6 +307,9 @@ export function renderAgentsMd() {
|
|
|
307
307
|
- \`.claude/skills/generating-assets/\` — **shipped by the CLI, re-rendered by \`bitmagic upgrade\`.**
|
|
308
308
|
It documents the CLI's own commands, so it stays current with them; edits to it are lost. Skills
|
|
309
309
|
you add yourself are never touched.
|
|
310
|
+
- \`.claude/settings.json\` — **yours**, seeded with one Stop hook that runs \`bitmagic reload\` so
|
|
311
|
+
the owner's browser refreshes when you finish a turn. \`upgrade\` adds that hook if it is missing
|
|
312
|
+
and changes nothing else, so your permissions and your own hooks are safe here.
|
|
310
313
|
- \`AGENTS.md\` — this file. Yours to edit, and \`upgrade\` will never overwrite your edits. It
|
|
311
314
|
refreshes this file only while it is still byte-identical to what the CLI wrote; once you change
|
|
312
315
|
anything, it stops and instead tells you which sections of the current template you are missing,
|
|
@@ -358,12 +361,20 @@ the two yaw conventions differ. Guessing produces code that compiles and moves t
|
|
|
358
361
|
|
|
359
362
|
\`\`\`
|
|
360
363
|
bitmagic check # typecheck against the vendored engine
|
|
361
|
-
bitmagic dev # build, then serve with
|
|
364
|
+
bitmagic dev # build, then serve the game + editor with auto-reload (leave this running)
|
|
365
|
+
bitmagic reload # tell the open browser you have finished a round of edits
|
|
362
366
|
bitmagic verify # boot the game in a real browser and report what broke
|
|
363
367
|
\`\`\`
|
|
364
368
|
|
|
365
369
|
The engine is large; \`check\` is the fast way to find out whether an edit holds together.
|
|
366
370
|
|
|
371
|
+
**Run \`bitmagic reload\` when you finish a round of edits.** \`bitmagic dev\` watches the project
|
|
372
|
+
and reloads the browser once writes go quiet, which is a guess about you — you pause to think, and
|
|
373
|
+
a long enough pause looks like "done". \`bitmagic reload\` replaces the guess with a fact, and it
|
|
374
|
+
waits for the build in flight rather than showing the previous one. It exits 0 and does nothing when
|
|
375
|
+
no \`bitmagic dev\` is running, so it is safe to run unconditionally. If this project has a Claude
|
|
376
|
+
Code Stop hook (\`.claude/settings.json\`), that already runs it for you.
|
|
377
|
+
|
|
367
378
|
**\`check\` passing does not mean the game runs.** The engine's most common failure is a clean
|
|
368
379
|
compile that dies on load — a missing asset, a bad \`world.json\` value, a null container. Run
|
|
369
380
|
\`bitmagic verify\` after a change you cannot eyeball: it loads the game in a headless browser and
|
|
@@ -371,9 +382,10 @@ fails with what actually broke, leaving a console log and a screenshot in \`.bit
|
|
|
371
382
|
|
|
372
383
|
## The human may be moving objects too
|
|
373
384
|
|
|
374
|
-
\`bitmagic
|
|
375
|
-
a gizmo
|
|
376
|
-
object touched. So \`world.json\` is shared ground, not yours
|
|
385
|
+
\`bitmagic dev\` serves one page with two tabs — **Game**, where the owner plays, and **Editor**,
|
|
386
|
+
where they click an object and drag a gizmo. A drag writes the new transform straight into
|
|
387
|
+
\`src/work/world.json\`, one entry per object touched. So \`world.json\` is shared ground, not yours
|
|
388
|
+
alone:
|
|
377
389
|
|
|
378
390
|
- **Re-read \`src/work/world.json\` before editing it.** The copy you read earlier in the session
|
|
379
391
|
may be stale, and a whole-file rewrite from that copy silently reverts what they just placed.
|
|
@@ -382,7 +394,7 @@ object touched. So \`world.json\` is shared ground, not yours alone:
|
|
|
382
394
|
|
|
383
395
|
### \`.bitmagic/edit/events.jsonl\`
|
|
384
396
|
|
|
385
|
-
Every editor action is appended there as one JSON object per line, and printed to \`bitmagic
|
|
397
|
+
Every editor action is appended there as one JSON object per line, and printed to \`bitmagic dev\`'s
|
|
386
398
|
own output. Read it to find out what the human did — and, for one event, what they want YOU to do.
|
|
387
399
|
|
|
388
400
|
\`\`\`
|
|
@@ -503,8 +515,8 @@ Four things worth knowing before you use these:
|
|
|
503
515
|
refuses before generating and tells you what it needed.
|
|
504
516
|
3. **It only works on the game this project points at.** \`bitmagic.json\` holds the \`gameId\`;
|
|
505
517
|
generation is refused for a game the logged-in account does not own.
|
|
506
|
-
4. **A running \`bitmagic dev\`
|
|
507
|
-
|
|
518
|
+
4. **A running \`bitmagic dev\` picks the new asset up on its own.** It reloads the browser once
|
|
519
|
+
the write settles; \`bitmagic reload\` forces it if you want it now.
|
|
508
520
|
|
|
509
521
|
**Not available as \`generate\` subcommands:** individual 3D models, voxel assets and block types.
|
|
510
522
|
Those need a browser to voxelize and upload. Do not work around it by hand-editing
|
|
@@ -600,7 +612,7 @@ running before you start implementing rather than after.
|
|
|
600
612
|
are holding it in memory.
|
|
601
613
|
2. Run \`bitmagic check\`, then \`bitmagic verify\` if the asset is load-bearing — a bad asset URL
|
|
602
614
|
typechecks fine and fails at runtime.
|
|
603
|
-
3.
|
|
615
|
+
3. A running \`bitmagic dev\` reloads the browser by itself once the write settles.
|
|
604
616
|
|
|
605
617
|
## When it refuses
|
|
606
618
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"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"}
|
package/dist/scaffold/project.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CliError } from '../errors.js';
|
|
|
4
4
|
import { aliasSourceDir } from './aliases.js';
|
|
5
5
|
import { renderPackageJson, renderTsconfig, renderIndexHtml, renderViteConfig, renderVitePublishConfig, renderGitignore, renderBitmagicJson, renderAgentsMd, renderGameDesignMd, renderClaudeMd, renderSkillsReadme, renderGenerateSkill, } from './project-files.js';
|
|
6
6
|
import { hashAgentsMd } from './agents-md.js';
|
|
7
|
+
import { CLAUDE_SETTINGS_FILE, renderClaudeSettings } from './claude-settings.js';
|
|
7
8
|
/**
|
|
8
9
|
* Directories moved out of the extracted tree into the project's vendored engine/.
|
|
9
10
|
*
|
|
@@ -146,6 +147,10 @@ export function scaffoldProject(options) {
|
|
|
146
147
|
['GAME-DESIGN.md', renderGameDesignMd(idea)],
|
|
147
148
|
['CLAUDE.md', renderClaudeMd()],
|
|
148
149
|
['.gitignore', renderGitignore()],
|
|
150
|
+
// Also creator-owned, and merged rather than rewritten by `upgrade` for that reason — this is
|
|
151
|
+
// the only place it is written whole, because the project is new and there is nothing to keep.
|
|
152
|
+
// See scaffold/claude-settings.ts.
|
|
153
|
+
[CLAUDE_SETTINGS_FILE, renderClaudeSettings()],
|
|
149
154
|
];
|
|
150
155
|
for (const [name, contents] of files) {
|
|
151
156
|
const filePath = path.join(targetDir, name);
|