@bitmagic/cli 0.1.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/dist/auth/device-flow.d.ts +18 -0
- package/dist/auth/device-flow.js +130 -0
- package/dist/auth/device-flow.js.map +1 -0
- package/dist/auth/session.d.ts +3 -0
- package/dist/auth/session.js +19 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +18 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +70 -0
- package/dist/cli.js +96 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +22 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/dev.d.ts +6 -0
- package/dist/commands/dev.js +89 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/generate.d.ts +62 -0
- package/dist/commands/generate.js +348 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +23 -0
- package/dist/commands/init.js +123 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.js +36 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +6 -0
- package/dist/commands/logout.js +18 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/verify.d.ts +6 -0
- package/dist/commands/verify.js +124 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/whoami.d.ts +11 -0
- package/dist/commands/whoami.js +29 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config/credentials.d.ts +13 -0
- package/dist/config/credentials.js +114 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/environments.d.ts +21 -0
- package/dist/config/environments.js +67 -0
- package/dist/config/environments.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/generate/apply-patches.d.ts +7 -0
- package/dist/generate/apply-patches.js +97 -0
- package/dist/generate/apply-patches.js.map +1 -0
- package/dist/generate/stream.d.ts +33 -0
- package/dist/generate/stream.js +185 -0
- package/dist/generate/stream.js.map +1 -0
- package/dist/http/client.d.ts +6 -0
- package/dist/http/client.js +49 -0
- package/dist/http/client.js.map +1 -0
- package/dist/project/context.d.ts +18 -0
- package/dist/project/context.js +67 -0
- package/dist/project/context.js.map +1 -0
- package/dist/scaffold/aliases.d.ts +27 -0
- package/dist/scaffold/aliases.js +51 -0
- package/dist/scaffold/aliases.js.map +1 -0
- package/dist/scaffold/engine-download.d.ts +27 -0
- package/dist/scaffold/engine-download.js +78 -0
- package/dist/scaffold/engine-download.js.map +1 -0
- package/dist/scaffold/project-files.d.ts +15 -0
- package/dist/scaffold/project-files.js +225 -0
- package/dist/scaffold/project-files.js.map +1 -0
- package/dist/scaffold/project.d.ts +28 -0
- package/dist/scaffold/project.js +118 -0
- package/dist/scaffold/project.js.map +1 -0
- package/dist/verify/browser.d.ts +13 -0
- package/dist/verify/browser.js +75 -0
- package/dist/verify/browser.js.map +1 -0
- package/dist/verify/classify.d.ts +29 -0
- package/dist/verify/classify.js +59 -0
- package/dist/verify/classify.js.map +1 -0
- package/dist/verify/wait-for-server.d.ts +7 -0
- package/dist/verify/wait-for-server.js +25 -0
- package/dist/verify/wait-for-server.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { CliError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The unit of `event:`/`data:` lines terminated by a blank line, per the SSE wire format
|
|
4
|
+
* `api-server/src/cli/cli-assets.ts` writes. Splits on `\n`, joining multiple `data:` lines (the
|
|
5
|
+
* spec allows more than one) with `\n` before parsing as JSON. A leading `:` line (the server's
|
|
6
|
+
* `: keepalive` comment) carries no `data:`, so it naturally produces no event.
|
|
7
|
+
*/
|
|
8
|
+
function parseFrame(frame) {
|
|
9
|
+
let eventName = 'message';
|
|
10
|
+
const dataLines = [];
|
|
11
|
+
for (const line of frame.split('\n')) {
|
|
12
|
+
if (line.startsWith('event:')) {
|
|
13
|
+
eventName = line.slice('event:'.length).trim();
|
|
14
|
+
}
|
|
15
|
+
else if (line.startsWith('data:')) {
|
|
16
|
+
dataLines.push(line.slice('data:'.length).trim());
|
|
17
|
+
}
|
|
18
|
+
// Any other line (including a `:`-prefixed comment, or blank) carries no event data.
|
|
19
|
+
}
|
|
20
|
+
if (dataLines.length === 0)
|
|
21
|
+
return null;
|
|
22
|
+
return { event: eventName, data: JSON.parse(dataLines.join('\n')) };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Incremental frame buffer: SSE frames arrive in arbitrary chunks over the wire — one frame can
|
|
26
|
+
* span two reads, and two frames can arrive in one — so this accumulates bytes and only emits
|
|
27
|
+
* events once a full `\n\n`-delimited frame is available. The trailing, not-yet-delimited
|
|
28
|
+
* remainder stays in `buffer` for the next push. Shared by both `parseSseChunks` (fed the whole
|
|
29
|
+
* chunk array at once) and `requestGeneration` (fed one chunk per network read), so the two
|
|
30
|
+
* cannot drift apart.
|
|
31
|
+
*/
|
|
32
|
+
function createSseFrameBuffer() {
|
|
33
|
+
let buffer = '';
|
|
34
|
+
return {
|
|
35
|
+
push(chunk) {
|
|
36
|
+
buffer += chunk;
|
|
37
|
+
const frames = buffer.split('\n\n');
|
|
38
|
+
// The last element is either '' (buffer ended exactly on a delimiter) or an incomplete
|
|
39
|
+
// trailing frame — either way it is not yet a complete frame, so keep it buffered.
|
|
40
|
+
buffer = frames.pop() ?? '';
|
|
41
|
+
const events = [];
|
|
42
|
+
for (const frame of frames) {
|
|
43
|
+
const event = parseFrame(frame);
|
|
44
|
+
if (event)
|
|
45
|
+
events.push(event);
|
|
46
|
+
}
|
|
47
|
+
return events;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Pure, synchronous parse of a full set of chunks — cheap and exhaustive to test. Production
|
|
53
|
+
* streaming uses the same underlying buffer (`createSseFrameBuffer`) one network read at a time,
|
|
54
|
+
* via `requestGeneration`, so this function is not itself on that path.
|
|
55
|
+
*/
|
|
56
|
+
export function parseSseChunks(chunks) {
|
|
57
|
+
const frameBuffer = createSseFrameBuffer();
|
|
58
|
+
const events = [];
|
|
59
|
+
for (const chunk of chunks) {
|
|
60
|
+
events.push(...frameBuffer.push(chunk));
|
|
61
|
+
}
|
|
62
|
+
return events;
|
|
63
|
+
}
|
|
64
|
+
function isRecord(value) {
|
|
65
|
+
return typeof value === 'object' && value !== null;
|
|
66
|
+
}
|
|
67
|
+
function asProgressMessage(data) {
|
|
68
|
+
return isRecord(data) && typeof data.message === 'string' ? data.message : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `patches` is trusted structurally here (an array), not deeply validated — the server's own
|
|
72
|
+
* `WorldPatch[]` contract is re-validated field-by-field later by
|
|
73
|
+
* `apply-patches.ts`'s `validateWorldPatch` before anything is written to disk.
|
|
74
|
+
*/
|
|
75
|
+
function asGenerationOutcome(data) {
|
|
76
|
+
if (!isRecord(data))
|
|
77
|
+
return null;
|
|
78
|
+
const { success, message, patches } = data;
|
|
79
|
+
if (typeof success !== 'boolean' || typeof message !== 'string' || !Array.isArray(patches)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return { success, message, patches: patches };
|
|
83
|
+
}
|
|
84
|
+
async function readJsonBody(response) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = await response.json();
|
|
87
|
+
return isRecord(parsed) ? parsed : {};
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function gameIdFrom(body) {
|
|
94
|
+
return typeof body.gameId === 'string' && body.gameId.length > 0 ? body.gameId : '(unknown game)';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Maps the pre-stream failure shapes documented on `api-server/src/cli/cli-assets.ts`'s handler
|
|
98
|
+
* into a message an agent can act on: whether to stop (403, insufficient sparks) or fix its
|
|
99
|
+
* request (400). Never includes the access token — only the game id and the server's own text.
|
|
100
|
+
*/
|
|
101
|
+
async function toCliError(response, type, body) {
|
|
102
|
+
const errorBody = await readJsonBody(response);
|
|
103
|
+
const serverMessage = typeof errorBody.error === 'string' ? errorBody.error : undefined;
|
|
104
|
+
if (response.status === 403) {
|
|
105
|
+
return new CliError(`You do not own game "${gameIdFrom(body)}", so it cannot generate a ${type} asset for it.`);
|
|
106
|
+
}
|
|
107
|
+
if (response.status === 402) {
|
|
108
|
+
const balance = errorBody.balance;
|
|
109
|
+
const required = errorBody.required;
|
|
110
|
+
const detail = typeof balance === 'number' && typeof required === 'number'
|
|
111
|
+
? ` You have ${balance} sparks; generating this ${type} asset requires ${required}.`
|
|
112
|
+
: '';
|
|
113
|
+
return new CliError(`Not enough sparks to generate this ${type} asset.${detail}`);
|
|
114
|
+
}
|
|
115
|
+
if (response.status === 400) {
|
|
116
|
+
return new CliError(serverMessage ?? `The server rejected this ${type} generation request.`);
|
|
117
|
+
}
|
|
118
|
+
return new CliError(`api-server returned HTTP ${response.status} generating a ${type} asset` +
|
|
119
|
+
(serverMessage ? `: ${serverMessage}` : '.'));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* POSTs to `/api/cli/v1/assets/<type>/stream` and consumes the SSE response.
|
|
123
|
+
*
|
|
124
|
+
* - A non-2xx response fails before any stream opens; see `toCliError` for the status mapping.
|
|
125
|
+
* - On 200, each `progress` event invokes `deps.onProgress`; the terminal `result` or `error`
|
|
126
|
+
* event resolves this call with the outcome it carried (an `error` event's `success: false`
|
|
127
|
+
* is a legitimate generation failure, not a thrown CliError — the caller decides how to
|
|
128
|
+
* present it).
|
|
129
|
+
* - A stream that closes with **no terminal event** is a truncated connection, not a success
|
|
130
|
+
* with zero patches, and fails with a CliError.
|
|
131
|
+
*/
|
|
132
|
+
export async function requestGeneration(environment, accessToken, type, body, deps) {
|
|
133
|
+
const url = `${environment.apiUrl}/api/cli/v1/assets/${type}/stream`;
|
|
134
|
+
let response;
|
|
135
|
+
try {
|
|
136
|
+
response = await deps.fetch(url, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${accessToken}`,
|
|
140
|
+
Accept: 'text/event-stream',
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify(body),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// A transport failure and an error status send people to very different places.
|
|
148
|
+
throw new CliError(`Cannot reach api-server at ${environment.apiUrl}.`);
|
|
149
|
+
}
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
throw await toCliError(response, type, body);
|
|
152
|
+
}
|
|
153
|
+
if (!response.body) {
|
|
154
|
+
throw new CliError('The server accepted the request but returned no stream body.');
|
|
155
|
+
}
|
|
156
|
+
const reader = response.body.getReader();
|
|
157
|
+
const decoder = new TextDecoder();
|
|
158
|
+
const frameBuffer = createSseFrameBuffer();
|
|
159
|
+
for (;;) {
|
|
160
|
+
const { value, done } = await reader.read();
|
|
161
|
+
if (done)
|
|
162
|
+
break;
|
|
163
|
+
// `{ stream: true }` retains any trailing partial multi-byte UTF-8 sequence inside the
|
|
164
|
+
// decoder itself, so a character split across two network chunks decodes correctly once its
|
|
165
|
+
// remaining bytes arrive in the next read, instead of emitting U+FFFD.
|
|
166
|
+
const text = decoder.decode(value, { stream: true });
|
|
167
|
+
for (const event of frameBuffer.push(text)) {
|
|
168
|
+
if (event.event === 'progress') {
|
|
169
|
+
const message = asProgressMessage(event.data);
|
|
170
|
+
if (message !== null)
|
|
171
|
+
deps.onProgress(message);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (event.event === 'result' || event.event === 'error') {
|
|
175
|
+
const outcome = asGenerationOutcome(event.data);
|
|
176
|
+
if (outcome)
|
|
177
|
+
return outcome;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw new CliError(`The connection to api-server ended before the ${type} generation finished. ` +
|
|
182
|
+
'This is not a confirmed failure: check the game before retrying, since the server ' +
|
|
183
|
+
'may have already produced (and billed for) an asset before the stream broke.');
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/generate/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Environment } from '../config/environments.js';
|
|
2
|
+
export interface ApiDeps {
|
|
3
|
+
fetch: typeof globalThis.fetch;
|
|
4
|
+
}
|
|
5
|
+
export declare function apiGet<T>(environment: Environment, path: string, accessToken: string, deps: ApiDeps): Promise<T>;
|
|
6
|
+
export declare function apiPost<T>(environment: Environment, path: string, accessToken: string, body: unknown, deps: ApiDeps): Promise<T>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CliError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared status handling for every api-server call: the transport-failure/error-status split,
|
|
4
|
+
* and the 401/403/503/other mapping. `apiGet` and `apiPost` differ only in the fetch `init` they
|
|
5
|
+
* pass in — keeping the mapping here means the two cannot drift apart.
|
|
6
|
+
*/
|
|
7
|
+
async function request(environment, path, accessToken, deps, init) {
|
|
8
|
+
const url = `${environment.apiUrl}${path}`;
|
|
9
|
+
let response;
|
|
10
|
+
try {
|
|
11
|
+
response = await deps.fetch(url, init);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// A transport failure and an error status send people to very different places.
|
|
15
|
+
throw new CliError(`Cannot reach api-server at ${environment.apiUrl}.`);
|
|
16
|
+
}
|
|
17
|
+
if (response.ok) {
|
|
18
|
+
return (await response.json());
|
|
19
|
+
}
|
|
20
|
+
if (response.status === 401) {
|
|
21
|
+
throw new CliError('Your session has expired. Run `bitmagic login`.');
|
|
22
|
+
}
|
|
23
|
+
if (response.status === 403) {
|
|
24
|
+
throw new CliError('Your account is authenticated but does not have the Admin role, which the ' +
|
|
25
|
+
'Bitmagic CLI requires. Ask a Bitmagic admin to grant it.');
|
|
26
|
+
}
|
|
27
|
+
if (response.status === 503) {
|
|
28
|
+
throw new CliError(`api-server reported this feature is unavailable in the "${environment.name}" environment.`);
|
|
29
|
+
}
|
|
30
|
+
throw new CliError(`api-server returned HTTP ${response.status} for ${path}.`);
|
|
31
|
+
}
|
|
32
|
+
export async function apiGet(environment, path, accessToken, deps) {
|
|
33
|
+
return request(environment, path, accessToken, deps, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function apiPost(environment, path, accessToken, body, deps) {
|
|
39
|
+
return request(environment, path, accessToken, deps, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${accessToken}`,
|
|
43
|
+
Accept: 'application/json',
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/http/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAaxC;;;;GAIG;AACH,KAAK,UAAU,OAAO,CACpB,WAAwB,EACxB,IAAY,EACZ,WAAmB,EACnB,IAAa,EACb,IAA4C;IAE5C,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;IAE3C,IAAI,QAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,gFAAgF;QAChF,MAAM,IAAI,QAAQ,CAAC,8BAA8B,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;IACtC,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,QAAQ,CAAC,iDAAiD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,QAAQ,CAChB,4EAA4E;YAC1E,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,QAAQ,CAChB,2DAA2D,WAAW,CAAC,IAAI,gBAAgB,CAC5F,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,QAAQ,CAAC,4BAA4B,QAAQ,CAAC,MAAM,QAAQ,IAAI,GAAG,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,WAAwB,EACxB,IAAY,EACZ,WAAmB,EACnB,IAAa;IAEb,OAAO,OAAO,CAAI,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE;QACtD,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;KAChF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,WAAwB,EACxB,IAAY,EACZ,WAAmB,EACnB,IAAa,EACb,IAAa;IAEb,OAAO,OAAO,CAAI,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE;QACtD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,MAAM,EAAE,kBAAkB;YAC1B,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ProjectMetadata {
|
|
2
|
+
gameId: string;
|
|
3
|
+
engineVersion: string;
|
|
4
|
+
genre: string;
|
|
5
|
+
template: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProjectContext {
|
|
8
|
+
root: string;
|
|
9
|
+
metadata: ProjectMetadata;
|
|
10
|
+
}
|
|
11
|
+
/** Walks up from `startDir` looking for bitmagic.json, so commands work from any subdirectory. */
|
|
12
|
+
export declare function findProjectRoot(startDir?: string): string;
|
|
13
|
+
export declare function loadProjectContext(startDir?: string): ProjectContext;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a tool from the PROJECT's node_modules, never the CLI's. The scaffold pins its own
|
|
16
|
+
* typescript and vite; running a different copy would type-check against the wrong compiler.
|
|
17
|
+
*/
|
|
18
|
+
export declare function projectBin(root: string, name: string): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { CliError } from '../errors.js';
|
|
4
|
+
const MARKER = 'bitmagic.json';
|
|
5
|
+
const REQUIRED_STRING_FIELDS = [
|
|
6
|
+
'gameId',
|
|
7
|
+
'engineVersion',
|
|
8
|
+
'genre',
|
|
9
|
+
'template',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Runtime guard: does `value` have every required ProjectMetadata field as a non-empty string?
|
|
13
|
+
* Returns the name of the first missing/invalid field so the caller can report a message that
|
|
14
|
+
* names the culprit, or undefined once `value` is well-formed. Follows the same guard-over-cast
|
|
15
|
+
* pattern as `isStoredCredentials` in ../config/credentials.ts, added there after a similar
|
|
16
|
+
* review found a bare cast let malformed data through unnoticed.
|
|
17
|
+
*/
|
|
18
|
+
function findMissingField(value) {
|
|
19
|
+
return REQUIRED_STRING_FIELDS.find((field) => typeof value[field] !== 'string' || value[field].length === 0);
|
|
20
|
+
}
|
|
21
|
+
/** Walks up from `startDir` looking for bitmagic.json, so commands work from any subdirectory. */
|
|
22
|
+
export function findProjectRoot(startDir = process.cwd()) {
|
|
23
|
+
let dir = path.resolve(startDir);
|
|
24
|
+
for (;;) {
|
|
25
|
+
if (fs.existsSync(path.join(dir, MARKER)))
|
|
26
|
+
return dir;
|
|
27
|
+
const parent = path.dirname(dir);
|
|
28
|
+
if (parent === dir) {
|
|
29
|
+
throw new CliError(`No ${MARKER} found in ${path.resolve(startDir)} or any parent directory. ` +
|
|
30
|
+
'Run this from inside a Bitmagic project, or create one with `bitmagic init`.');
|
|
31
|
+
}
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function loadProjectContext(startDir = process.cwd()) {
|
|
36
|
+
const root = findProjectRoot(startDir);
|
|
37
|
+
const file = path.join(root, MARKER);
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Distinct from "no project": the marker exists but cannot be read, and telling the user to
|
|
44
|
+
// run `bitmagic init` here would be wrong advice.
|
|
45
|
+
throw new CliError(`${file} is not valid JSON. The project metadata is unreadable.`);
|
|
46
|
+
}
|
|
47
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
48
|
+
throw new CliError(`${file} does not contain a project object.`);
|
|
49
|
+
}
|
|
50
|
+
const missingField = findMissingField(parsed);
|
|
51
|
+
if (missingField) {
|
|
52
|
+
throw new CliError(`${file} is missing ${missingField}.`);
|
|
53
|
+
}
|
|
54
|
+
return { root, metadata: parsed };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolves a tool from the PROJECT's node_modules, never the CLI's. The scaffold pins its own
|
|
58
|
+
* typescript and vite; running a different copy would type-check against the wrong compiler.
|
|
59
|
+
*/
|
|
60
|
+
export function projectBin(root, name) {
|
|
61
|
+
const binary = path.join(root, 'node_modules', '.bin', name);
|
|
62
|
+
if (!fs.existsSync(binary)) {
|
|
63
|
+
throw new CliError(`Cannot find \`${name}\` in this project's dependencies. Run \`npm install\` in ${root} first.`);
|
|
64
|
+
}
|
|
65
|
+
return binary;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/project/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAcxC,MAAM,MAAM,GAAG,eAAe,CAAC;AAE/B,MAAM,sBAAsB,GAA8B;IACxD,QAAQ;IACR,eAAe;IACf,OAAO;IACP,UAAU;CACX,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,KAA8B;IACtD,OAAO,sBAAsB,CAAC,IAAI,CAChC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAK,KAAK,CAAC,KAAK,CAAY,CAAC,MAAM,KAAK,CAAC,CACrF,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,eAAe,CAAC,WAAmB,OAAO,CAAC,GAAG,EAAE;IAC9D,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,SAAS,CAAC;QACR,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,MAAM,IAAI,QAAQ,CAChB,MAAM,MAAM,aAAa,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,4BAA4B;gBACzE,8EAA8E,CACjF,CAAC;QACJ,CAAC;QACD,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB,OAAO,CAAC,GAAG,EAAE;IACjE,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,4FAA4F;QAC5F,kDAAkD;QAClD,MAAM,IAAI,QAAQ,CAAC,GAAG,IAAI,yDAAyD,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,QAAQ,CAAC,GAAG,IAAI,qCAAqC,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAiC,CAAC,CAAC;IACzE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,IAAI,QAAQ,CAAC,GAAG,IAAI,eAAe,YAAY,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAyB,EAAE,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,IAAY;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,QAAQ,CAChB,iBAAiB,IAAI,6DAA6D,IAAI,SAAS,CAChG,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine resolves every import through a path alias. The same set has to appear in three
|
|
3
|
+
* places — tsconfig paths, the browser import map, and vite's resolve.alias — so it is defined
|
|
4
|
+
* once here and rendered into each form. Hand-maintaining three copies is the most likely way a
|
|
5
|
+
* scaffolded project silently stops compiling.
|
|
6
|
+
*/
|
|
7
|
+
export interface AliasRoot {
|
|
8
|
+
/** Alias prefix as written in imports, without a trailing slash. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Directory the alias resolves to in the project, relative to the project root. */
|
|
11
|
+
sourceDir: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const ALIASES: AliasRoot[];
|
|
14
|
+
/**
|
|
15
|
+
* Where one named alias resolves to in the project. Callers that need a specific alias's
|
|
16
|
+
* directory (the scaffolder and its tests) go through this rather than repeating the lookup and
|
|
17
|
+
* its not-found guard, so the "no such alias" message stays in one place.
|
|
18
|
+
*/
|
|
19
|
+
export declare function aliasSourceDir(name: string): string;
|
|
20
|
+
/** tsconfig `paths`, resolved against a baseUrl of ".". */
|
|
21
|
+
export declare function tsconfigPaths(): Record<string, string[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Import-map and vite targets. tsc runs with rootDir "." so it mirrors the source tree into
|
|
24
|
+
* dist/, meaning engine/engine/X.ts emits to dist/engine/engine/X.js.
|
|
25
|
+
*/
|
|
26
|
+
export declare function importMapEntries(): Record<string, string>;
|
|
27
|
+
export declare function viteAliasEntries(): Record<string, string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CliError } from '../errors.js';
|
|
2
|
+
export const ALIASES = [
|
|
3
|
+
{ name: 'engine', sourceDir: 'engine/engine' },
|
|
4
|
+
{ name: 'types', sourceDir: 'engine/types' },
|
|
5
|
+
{ name: 'bundle', sourceDir: 'engine/bundle' },
|
|
6
|
+
{ name: 'debug', sourceDir: 'engine/debug' },
|
|
7
|
+
{ name: 'editor', sourceDir: 'engine/editor' },
|
|
8
|
+
// The entry module (GameTemplate.ts) does `import 'core/utils/AutoCacheBuster.js'` as a
|
|
9
|
+
// side-effect-only import. tsc does not flag an unresolvable bare side-effect import (TS2307
|
|
10
|
+
// is only raised for named imports), so a missing core/ alias typechecks clean and fails only
|
|
11
|
+
// at runtime with "Failed to resolve module specifier" on the entry module itself.
|
|
12
|
+
{ name: 'core', sourceDir: 'engine/core' },
|
|
13
|
+
// The genre baseline, vendored for the engine's own debug tooling (e.g.
|
|
14
|
+
// debug/AnimationPreview.ts imports genres/voxel/BitmagicPlayerCharacter.js). Distinct from
|
|
15
|
+
// src/, which is the creator's game derived from this same baseline — see project.ts.
|
|
16
|
+
{ name: 'genres', sourceDir: 'engine/genres' },
|
|
17
|
+
// The creator's own game. engine/types/work-game.d.ts declares module 'work/Game.js', which
|
|
18
|
+
// is how the engine loads it. sourceDir MUST be 'src/work', not 'src': worldDataLoader.ts
|
|
19
|
+
// fetches the literal relative paths 'src/work/game.json' and 'src/work/world.json' as plain
|
|
20
|
+
// fetch URLs, not module specifiers — no import map or vite alias can redirect a fetch URL.
|
|
21
|
+
// Moving this directory typechecks clean (tsc never sees a fetch string) but breaks the game
|
|
22
|
+
// at runtime with "world.json not found", so don't "simplify" it back to 'src'.
|
|
23
|
+
{ name: 'work', sourceDir: 'src/work' },
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Where one named alias resolves to in the project. Callers that need a specific alias's
|
|
27
|
+
* directory (the scaffolder and its tests) go through this rather than repeating the lookup and
|
|
28
|
+
* its not-found guard, so the "no such alias" message stays in one place.
|
|
29
|
+
*/
|
|
30
|
+
export function aliasSourceDir(name) {
|
|
31
|
+
const alias = ALIASES.find((a) => a.name === name);
|
|
32
|
+
if (!alias) {
|
|
33
|
+
throw new CliError(`The alias model has no "${name}" entry.`);
|
|
34
|
+
}
|
|
35
|
+
return alias.sourceDir;
|
|
36
|
+
}
|
|
37
|
+
/** tsconfig `paths`, resolved against a baseUrl of ".". */
|
|
38
|
+
export function tsconfigPaths() {
|
|
39
|
+
return Object.fromEntries(ALIASES.map((a) => [`${a.name}/*`, [`${a.sourceDir}/*`]]));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Import-map and vite targets. tsc runs with rootDir "." so it mirrors the source tree into
|
|
43
|
+
* dist/, meaning engine/engine/X.ts emits to dist/engine/engine/X.js.
|
|
44
|
+
*/
|
|
45
|
+
export function importMapEntries() {
|
|
46
|
+
return Object.fromEntries(ALIASES.map((a) => [`${a.name}/`, `/dist/${a.sourceDir}/`]));
|
|
47
|
+
}
|
|
48
|
+
export function viteAliasEntries() {
|
|
49
|
+
return importMapEntries();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.js","sourceRoot":"","sources":["../../src/scaffold/aliases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAexC,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE;IAC9C,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE;IAC9C,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE;IAC5C,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE;IAC9C,wFAAwF;IACxF,6FAA6F;IAC7F,8FAA8F;IAC9F,mFAAmF;IACnF,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE;IAC1C,wEAAwE;IACxE,4FAA4F;IAC5F,sFAAsF;IACtF,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE;IAC9C,4FAA4F;IAC5F,0FAA0F;IAC1F,6FAA6F;IAC7F,4FAA4F;IAC5F,6FAA6F;IAC7F,gFAAgF;IAChF,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE;CACxC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAAC,2BAA2B,IAAI,UAAU,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAC,SAAS,CAAC;AACzB,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,EAAE,SAAS,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,gBAAgB,EAAE,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Environment } from '../config/environments.js';
|
|
2
|
+
import { type ApiDeps } from '../http/client.js';
|
|
3
|
+
export interface EngineManifest {
|
|
4
|
+
version: string;
|
|
5
|
+
tarballUrl: string;
|
|
6
|
+
sha256: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchEngineManifest(environment: Environment, accessToken: string, deps: ApiDeps): Promise<EngineManifest>;
|
|
9
|
+
export interface DownloadDeps {
|
|
10
|
+
fetch: typeof globalThis.fetch;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Downloads the tarball and verifies its digest before it is written. A signed URL is a
|
|
14
|
+
* credential, so it never appears in an error message.
|
|
15
|
+
*/
|
|
16
|
+
export declare function downloadAndVerify(tarballUrl: string, expectedSha256: string, targetPath: string, deps: DownloadDeps): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* The tarball stores three prefixes at different depths — game/src/<root>, game/docs (plus the
|
|
19
|
+
* sibling game/sw-cache-buster.js, which shares game/docs's strip depth) and templates — so it
|
|
20
|
+
* takes three passes. A single pass cannot express them: `strip` is a fixed depth, and stripping
|
|
21
|
+
* 2 would turn game/docs/x.md into a bare x.md.
|
|
22
|
+
*
|
|
23
|
+
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/docs,
|
|
24
|
+
* <engineDir>/sw-cache-buster.js, and templates unpacked separately so the scaffolder can read
|
|
25
|
+
* templates/index.json.
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractEngine(tarballPath: string, engineDir: string, templatesDir: string): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
5
|
+
import * as tar from 'tar';
|
|
6
|
+
import { CliError } from '../errors.js';
|
|
7
|
+
import { apiGet } from '../http/client.js';
|
|
8
|
+
export async function fetchEngineManifest(environment, accessToken, deps) {
|
|
9
|
+
return apiGet(environment, '/api/cli/v1/engine/manifest', accessToken, deps);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Downloads the tarball and verifies its digest before it is written. A signed URL is a
|
|
13
|
+
* credential, so it never appears in an error message.
|
|
14
|
+
*/
|
|
15
|
+
export async function downloadAndVerify(tarballUrl, expectedSha256, targetPath, deps) {
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await deps.fetch(tarballUrl, { method: 'GET' });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new CliError('Cannot download the engine. Check your network connection.');
|
|
22
|
+
}
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new CliError(`Downloading the engine failed with HTTP ${response.status}.`);
|
|
25
|
+
}
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = Buffer.from(await response.arrayBuffer());
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new CliError('The engine download was interrupted while reading its data. Check your network connection and try again.');
|
|
32
|
+
}
|
|
33
|
+
const actual = crypto.createHash('sha256').update(body).digest('hex');
|
|
34
|
+
if (actual !== expectedSha256) {
|
|
35
|
+
throw new CliError('The downloaded engine does not match the checksum the server published. ' +
|
|
36
|
+
'Nothing has been written. Try again; if it persists, report it.');
|
|
37
|
+
}
|
|
38
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
39
|
+
fs.writeFileSync(targetPath, body);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The tarball stores three prefixes at different depths — game/src/<root>, game/docs (plus the
|
|
43
|
+
* sibling game/sw-cache-buster.js, which shares game/docs's strip depth) and templates — so it
|
|
44
|
+
* takes three passes. A single pass cannot express them: `strip` is a fixed depth, and stripping
|
|
45
|
+
* 2 would turn game/docs/x.md into a bare x.md.
|
|
46
|
+
*
|
|
47
|
+
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/docs,
|
|
48
|
+
* <engineDir>/sw-cache-buster.js, and templates unpacked separately so the scaffolder can read
|
|
49
|
+
* templates/index.json.
|
|
50
|
+
*/
|
|
51
|
+
export async function extractEngine(tarballPath, engineDir, templatesDir) {
|
|
52
|
+
fs.mkdirSync(engineDir, { recursive: true });
|
|
53
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
54
|
+
// game/src/engine/A.ts -> <engineDir>/engine/A.ts
|
|
55
|
+
await tar.extract({
|
|
56
|
+
file: tarballPath,
|
|
57
|
+
cwd: engineDir,
|
|
58
|
+
strip: 2,
|
|
59
|
+
filter: (entryPath) => entryPath.startsWith('game/src/'),
|
|
60
|
+
});
|
|
61
|
+
// game/docs/coordinate-system.md -> <engineDir>/docs/coordinate-system.md
|
|
62
|
+
// game/sw-cache-buster.js -> <engineDir>/sw-cache-buster.js (rides this pass because it needs
|
|
63
|
+
// the same strip depth as game/docs/, even though it isn't under game/docs/ itself)
|
|
64
|
+
await tar.extract({
|
|
65
|
+
file: tarballPath,
|
|
66
|
+
cwd: engineDir,
|
|
67
|
+
strip: 1,
|
|
68
|
+
filter: (entryPath) => entryPath.startsWith('game/docs/') || entryPath === 'game/sw-cache-buster.js',
|
|
69
|
+
});
|
|
70
|
+
// templates/index.json -> <templatesDir>/index.json
|
|
71
|
+
await tar.extract({
|
|
72
|
+
file: tarballPath,
|
|
73
|
+
cwd: templatesDir,
|
|
74
|
+
strip: 1,
|
|
75
|
+
filter: (entryPath) => entryPath.startsWith('templates/'),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=engine-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-download.js","sourceRoot":"","sources":["../../src/scaffold/engine-download.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function renderPackageJson(name: string): string;
|
|
2
|
+
export declare function renderTsconfig(): string;
|
|
3
|
+
export declare function renderIndexHtml(): string;
|
|
4
|
+
export declare function renderViteConfig(): string;
|
|
5
|
+
export declare function renderGitignore(): string;
|
|
6
|
+
export interface ProjectMetadata {
|
|
7
|
+
gameId: string;
|
|
8
|
+
engineVersion: string;
|
|
9
|
+
genre: string;
|
|
10
|
+
template: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function renderBitmagicJson(metadata: ProjectMetadata): string;
|
|
13
|
+
export declare function renderAgentsMd(): string;
|
|
14
|
+
export declare function renderClaudeMd(): string;
|
|
15
|
+
export declare function renderSkillsReadme(): string;
|