@bitmagic/asset-core 0.1.1 → 0.1.3
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/favicon.d.ts +74 -0
- package/dist/favicon.d.ts.map +1 -0
- package/dist/favicon.js +130 -0
- package/dist/favicon.js.map +1 -0
- package/dist/generators/prop.d.ts +43 -0
- package/dist/generators/prop.d.ts.map +1 -0
- package/dist/generators/prop.js +80 -0
- package/dist/generators/prop.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/publish-source.d.ts +260 -0
- package/dist/publish-source.d.ts.map +1 -0
- package/dist/publish-source.js +258 -0
- package/dist/publish-source.js.map +1 -0
- package/package.json +9 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published game's browser-tab icon: the one `<link>` element, the one storage-key
|
|
3
|
+
* derivation, and the one function that swaps the default out for a game's own.
|
|
4
|
+
*
|
|
5
|
+
* Import path matters: this module is published as the `@bitmagic/asset-core/favicon`
|
|
6
|
+
* subpath so its consumers do not drag in the root barrel's zod / three / forger client.
|
|
7
|
+
* It is node-only (`node:crypto`) — the three consumers are game-play-agent (web publish
|
|
8
|
+
* lane), api-server (CLI publish lane) and the CLI itself. The Creator never imports it;
|
|
9
|
+
* it uploads a source image and lets publish do the deriving.
|
|
10
|
+
*
|
|
11
|
+
* Why this lives in a shared package rather than being duplicated per lane: the two
|
|
12
|
+
* publish lanes must agree on the marker id and on the exact `<link>` they emit, or a
|
|
13
|
+
* game's icon silently reverts to the Bitmagic mark in whichever lane drifted. The pieces
|
|
14
|
+
* where drift breaks correctness are shared here; the sharp resize is NOT (it would drag
|
|
15
|
+
* a heavy native dep into a package the browser-facing subpaths live beside), and is
|
|
16
|
+
* deliberately mirrored in each lane instead.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The id on the default `<link rel="icon">` baked into every game bundle — `game/index.html`
|
|
20
|
+
* for the web lane, `renderIndexHtml()` for CLI projects. Publish finds the element by this
|
|
21
|
+
* id and replaces it wholesale.
|
|
22
|
+
*
|
|
23
|
+
* Replacing rather than appending a second link is deliberate. Two `sizes`-less `rel="icon"`
|
|
24
|
+
* links is ambiguous input: "the last one wins" is browser convention, not spec, and Safari
|
|
25
|
+
* has historically picked differently from Chrome. After `applyFaviconLink` the document
|
|
26
|
+
* carries exactly one icon link, which is something a test can assert.
|
|
27
|
+
*/
|
|
28
|
+
export declare const FAVICON_LINK_ID = "bm-favicon";
|
|
29
|
+
/**
|
|
30
|
+
* Edge length of the derived icon. A retina browser tab renders a 32 CSS px icon at 2x, so
|
|
31
|
+
* 64 is the real ceiling that matters; it also covers bookmark and taskbar use at 48.
|
|
32
|
+
* Browsers downscale cleanly, so a single size beats emitting several `sizes=` variants.
|
|
33
|
+
*/
|
|
34
|
+
export declare const FAVICON_PX = 64;
|
|
35
|
+
/**
|
|
36
|
+
* 32 hex chars of sha256 over the SOURCE URL — not over the source bytes.
|
|
37
|
+
*
|
|
38
|
+
* Hashing the URL is what makes the cache free: the storage key is computable without
|
|
39
|
+
* fetching anything, so a cache hit costs one existence probe and no download. It is sound
|
|
40
|
+
* because every cover/upload URL in both lanes is already timestamp-unique per upload, so
|
|
41
|
+
* new art always produces a new URL and therefore a new key.
|
|
42
|
+
*/
|
|
43
|
+
export declare function faviconContentHash(sourceUrl: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Where the web lane stores an icon derived from `sourceUrl`, relative to the storage
|
|
46
|
+
* prefix (`worlds/v3`). The CLI lane composes its own game-scoped key from
|
|
47
|
+
* `faviconContentHash` instead, because its bucket layout is per-game rather than shared.
|
|
48
|
+
*
|
|
49
|
+
* The `v1` segment and the `-64` suffix are load-bearing: changing the crop or the size
|
|
50
|
+
* must produce a NEW key rather than silently serving the old bytes from an immutable
|
|
51
|
+
* `max-age=31536000` object. Same reasoning as the hand-rolled `-v2` on the watermark logo.
|
|
52
|
+
*/
|
|
53
|
+
export declare function faviconStorageKey(sourceUrl: string): string;
|
|
54
|
+
/** The exact element every lane emits. One definition so the lanes cannot disagree. */
|
|
55
|
+
export declare function renderFaviconLink(href: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Point the document's icon at `href`.
|
|
58
|
+
*
|
|
59
|
+
* Replaces the element carrying `id="bm-favicon"`. When that marker is absent — an older
|
|
60
|
+
* cached bundle, or a CLI project that has not run `bitmagic upgrade` since the marker
|
|
61
|
+
* shipped — falls back to inserting before `</head>`, which is best-effort but never worse
|
|
62
|
+
* than the un-marked status quo.
|
|
63
|
+
*
|
|
64
|
+
* Returns `html` unchanged when `href` is missing or not an allowed scheme, and never
|
|
65
|
+
* throws: a tab icon must not be able to fail a publish.
|
|
66
|
+
*
|
|
67
|
+
* `indexOf` rather than a regex on purpose. The default href is a ~700-char base64 data URI
|
|
68
|
+
* sitting inside a bundle that reaches 20 MB, and the surrounding code (`injectIntoHtml`)
|
|
69
|
+
* is string-insertion for the same reason. Anchored `indexOf` scans are O(n) with tiny
|
|
70
|
+
* constants and have obvious failure semantics; a backtracking regex over that string does
|
|
71
|
+
* not.
|
|
72
|
+
*/
|
|
73
|
+
export declare function applyFaviconLink(html: string, href: string | null | undefined): string;
|
|
74
|
+
//# sourceMappingURL=favicon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"favicon.d.ts","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,UAAU,KAAK,CAAC;AAqB7B;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAuBtF"}
|
package/dist/favicon.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published game's browser-tab icon: the one `<link>` element, the one storage-key
|
|
3
|
+
* derivation, and the one function that swaps the default out for a game's own.
|
|
4
|
+
*
|
|
5
|
+
* Import path matters: this module is published as the `@bitmagic/asset-core/favicon`
|
|
6
|
+
* subpath so its consumers do not drag in the root barrel's zod / three / forger client.
|
|
7
|
+
* It is node-only (`node:crypto`) — the three consumers are game-play-agent (web publish
|
|
8
|
+
* lane), api-server (CLI publish lane) and the CLI itself. The Creator never imports it;
|
|
9
|
+
* it uploads a source image and lets publish do the deriving.
|
|
10
|
+
*
|
|
11
|
+
* Why this lives in a shared package rather than being duplicated per lane: the two
|
|
12
|
+
* publish lanes must agree on the marker id and on the exact `<link>` they emit, or a
|
|
13
|
+
* game's icon silently reverts to the Bitmagic mark in whichever lane drifted. The pieces
|
|
14
|
+
* where drift breaks correctness are shared here; the sharp resize is NOT (it would drag
|
|
15
|
+
* a heavy native dep into a package the browser-facing subpaths live beside), and is
|
|
16
|
+
* deliberately mirrored in each lane instead.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
/**
|
|
20
|
+
* The id on the default `<link rel="icon">` baked into every game bundle — `game/index.html`
|
|
21
|
+
* for the web lane, `renderIndexHtml()` for CLI projects. Publish finds the element by this
|
|
22
|
+
* id and replaces it wholesale.
|
|
23
|
+
*
|
|
24
|
+
* Replacing rather than appending a second link is deliberate. Two `sizes`-less `rel="icon"`
|
|
25
|
+
* links is ambiguous input: "the last one wins" is browser convention, not spec, and Safari
|
|
26
|
+
* has historically picked differently from Chrome. After `applyFaviconLink` the document
|
|
27
|
+
* carries exactly one icon link, which is something a test can assert.
|
|
28
|
+
*/
|
|
29
|
+
export const FAVICON_LINK_ID = 'bm-favicon';
|
|
30
|
+
/**
|
|
31
|
+
* Edge length of the derived icon. A retina browser tab renders a 32 CSS px icon at 2x, so
|
|
32
|
+
* 64 is the real ceiling that matters; it also covers bookmark and taskbar use at 48.
|
|
33
|
+
* Browsers downscale cleanly, so a single size beats emitting several `sizes=` variants.
|
|
34
|
+
*/
|
|
35
|
+
export const FAVICON_PX = 64;
|
|
36
|
+
/** Attribute-safe. The href reaches a public page's `<head>` and may be creator-supplied. */
|
|
37
|
+
function escapeAttr(value) {
|
|
38
|
+
return value
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/"/g, '"')
|
|
41
|
+
.replace(/</g, '<')
|
|
42
|
+
.replace(/>/g, '>');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Only these two forms may become an icon href. Escaping alone is not the whole guard — a
|
|
46
|
+
* `javascript:` URL in a `<link href>` is inert in modern browsers, but this string is
|
|
47
|
+
* assembled from creator-controlled data and baked into a public page, so the allowlist is
|
|
48
|
+
* the guard and the escaping is defence in depth.
|
|
49
|
+
*/
|
|
50
|
+
function isAllowedHref(href) {
|
|
51
|
+
return /^https:\/\//i.test(href) || /^data:image\//i.test(href);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 32 hex chars of sha256 over the SOURCE URL — not over the source bytes.
|
|
55
|
+
*
|
|
56
|
+
* Hashing the URL is what makes the cache free: the storage key is computable without
|
|
57
|
+
* fetching anything, so a cache hit costs one existence probe and no download. It is sound
|
|
58
|
+
* because every cover/upload URL in both lanes is already timestamp-unique per upload, so
|
|
59
|
+
* new art always produces a new URL and therefore a new key.
|
|
60
|
+
*/
|
|
61
|
+
export function faviconContentHash(sourceUrl) {
|
|
62
|
+
return createHash('sha256').update(sourceUrl).digest('hex').slice(0, 32);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Where the web lane stores an icon derived from `sourceUrl`, relative to the storage
|
|
66
|
+
* prefix (`worlds/v3`). The CLI lane composes its own game-scoped key from
|
|
67
|
+
* `faviconContentHash` instead, because its bucket layout is per-game rather than shared.
|
|
68
|
+
*
|
|
69
|
+
* The `v1` segment and the `-64` suffix are load-bearing: changing the crop or the size
|
|
70
|
+
* must produce a NEW key rather than silently serving the old bytes from an immutable
|
|
71
|
+
* `max-age=31536000` object. Same reasoning as the hand-rolled `-v2` on the watermark logo.
|
|
72
|
+
*/
|
|
73
|
+
export function faviconStorageKey(sourceUrl) {
|
|
74
|
+
return `assets/favicons/v1/${faviconContentHash(sourceUrl)}-${FAVICON_PX}.png`;
|
|
75
|
+
}
|
|
76
|
+
/** The exact element every lane emits. One definition so the lanes cannot disagree. */
|
|
77
|
+
export function renderFaviconLink(href) {
|
|
78
|
+
return `<link id="${FAVICON_LINK_ID}" rel="icon" type="image/png" href="${escapeAttr(href)}">`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Point the document's icon at `href`.
|
|
82
|
+
*
|
|
83
|
+
* Replaces the element carrying `id="bm-favicon"`. When that marker is absent — an older
|
|
84
|
+
* cached bundle, or a CLI project that has not run `bitmagic upgrade` since the marker
|
|
85
|
+
* shipped — falls back to inserting before `</head>`, which is best-effort but never worse
|
|
86
|
+
* than the un-marked status quo.
|
|
87
|
+
*
|
|
88
|
+
* Returns `html` unchanged when `href` is missing or not an allowed scheme, and never
|
|
89
|
+
* throws: a tab icon must not be able to fail a publish.
|
|
90
|
+
*
|
|
91
|
+
* `indexOf` rather than a regex on purpose. The default href is a ~700-char base64 data URI
|
|
92
|
+
* sitting inside a bundle that reaches 20 MB, and the surrounding code (`injectIntoHtml`)
|
|
93
|
+
* is string-insertion for the same reason. Anchored `indexOf` scans are O(n) with tiny
|
|
94
|
+
* constants and have obvious failure semantics; a backtracking regex over that string does
|
|
95
|
+
* not.
|
|
96
|
+
*/
|
|
97
|
+
export function applyFaviconLink(html, href) {
|
|
98
|
+
if (!href || !isAllowedHref(href))
|
|
99
|
+
return html;
|
|
100
|
+
const link = renderFaviconLink(href);
|
|
101
|
+
const needle = `id="${FAVICON_LINK_ID}"`;
|
|
102
|
+
for (let at = html.indexOf(needle); at !== -1; at = html.indexOf(needle, at + needle.length)) {
|
|
103
|
+
const open = html.lastIndexOf('<', at);
|
|
104
|
+
const close = html.indexOf('>', at);
|
|
105
|
+
// A marker with no enclosing element is malformed input, not something to guess at.
|
|
106
|
+
if (open === -1 || close === -1)
|
|
107
|
+
continue;
|
|
108
|
+
// It has to be an actual <link> element. The id is also NAMED in prose — the explanatory
|
|
109
|
+
// comment above the default link in game/index.html, this doc block, docs — and matching the
|
|
110
|
+
// first occurrence anywhere replaced a comment instead of the link, which left the real
|
|
111
|
+
// default in place and shipped a page with two icon links. Verified the hard way.
|
|
112
|
+
if (!/^<link[\s/>]/i.test(html.slice(open, open + 6)))
|
|
113
|
+
continue;
|
|
114
|
+
if (isInsideComment(html, open))
|
|
115
|
+
continue;
|
|
116
|
+
return html.slice(0, open) + link + html.slice(close + 1);
|
|
117
|
+
}
|
|
118
|
+
const headClose = html.indexOf('</head>');
|
|
119
|
+
if (headClose === -1)
|
|
120
|
+
return html;
|
|
121
|
+
return html.slice(0, headClose) + link + '\n' + html.slice(headClose);
|
|
122
|
+
}
|
|
123
|
+
/** Is `at` inside an HTML comment? True when the nearest `<!--` behind it is not yet closed. */
|
|
124
|
+
function isInsideComment(html, at) {
|
|
125
|
+
const open = html.lastIndexOf('<!--', at);
|
|
126
|
+
if (open === -1)
|
|
127
|
+
return false;
|
|
128
|
+
return html.lastIndexOf('-->', at) < open;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=favicon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"favicon.js","sourceRoot":"","sources":["../src/favicon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AssetResultBase, GenerateDeps } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A 3D prop mesh, from a prompt.
|
|
5
|
+
*
|
|
6
|
+
* The odd one out among the generators: it returns NO world.json patches. Every other generator
|
|
7
|
+
* produces a finished asset the host can write straight away, but a prop is only half made when
|
|
8
|
+
* the Asset Forger hands back a GLB — the engine still has to voxelize it, and voxelizing happens
|
|
9
|
+
* in a browser with the game loaded, which is the caller's side of the seam in both lanes. So this
|
|
10
|
+
* returns the GLB URL and stops, and the host decides what to do with it.
|
|
11
|
+
*
|
|
12
|
+
* That is what makes the same code serve the CLI's `bitmagic generate prop` (voxelize in a
|
|
13
|
+
* headless Chrome, upsert the asset locally) and, in principle, the web lane's HQ job — which
|
|
14
|
+
* today reimplements this call inline in `hq-asset-jobs.ts`.
|
|
15
|
+
*
|
|
16
|
+
* The generation is billable the moment the Forger accepts the job: the vendor charges on
|
|
17
|
+
* acceptance, so a downstream failure to produce a URL still costs.
|
|
18
|
+
*/
|
|
19
|
+
export declare const propParamsSchema: z.ZodObject<{
|
|
20
|
+
prompt: z.ZodString;
|
|
21
|
+
/**
|
|
22
|
+
* `prop` matches what the HQ path has always sent. Named rather than hardcoded because the
|
|
23
|
+
* Forger distinguishes props from characters, and a mis-typed model produces a rigged humanoid
|
|
24
|
+
* where a rock was wanted.
|
|
25
|
+
*/
|
|
26
|
+
modelType: z.ZodDefault<z.ZodOptional<z.ZodEnum<["prop"]>>>;
|
|
27
|
+
}, "strict", z.ZodTypeAny, {
|
|
28
|
+
prompt: string;
|
|
29
|
+
modelType: "prop";
|
|
30
|
+
}, {
|
|
31
|
+
prompt: string;
|
|
32
|
+
modelType?: "prop" | undefined;
|
|
33
|
+
}>;
|
|
34
|
+
export type PropParams = z.input<typeof propParamsSchema>;
|
|
35
|
+
export type PropResult = AssetResultBase & {
|
|
36
|
+
/** Where the generated mesh landed. The host voxelizes this. */
|
|
37
|
+
glbUrl?: string;
|
|
38
|
+
billable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
export declare function generateProp(params: PropParams, deps: GenerateDeps & {
|
|
41
|
+
imageToMeshModel?: string;
|
|
42
|
+
}): Promise<PropResult>;
|
|
43
|
+
//# sourceMappingURL=prop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop.d.ts","sourceRoot":"","sources":["../../src/generators/prop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIjE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB;;IAI3B;;;;OAIG;;;;;;;;EAEM,CAAC;AAEZ,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG;IACzC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAcF,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,YAAY,GAAG;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,UAAU,CAAC,CAsDrB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { fail, ok } from '../result.js';
|
|
3
|
+
import { formatZodIssues } from '../validation.js';
|
|
4
|
+
/**
|
|
5
|
+
* A 3D prop mesh, from a prompt.
|
|
6
|
+
*
|
|
7
|
+
* The odd one out among the generators: it returns NO world.json patches. Every other generator
|
|
8
|
+
* produces a finished asset the host can write straight away, but a prop is only half made when
|
|
9
|
+
* the Asset Forger hands back a GLB — the engine still has to voxelize it, and voxelizing happens
|
|
10
|
+
* in a browser with the game loaded, which is the caller's side of the seam in both lanes. So this
|
|
11
|
+
* returns the GLB URL and stops, and the host decides what to do with it.
|
|
12
|
+
*
|
|
13
|
+
* That is what makes the same code serve the CLI's `bitmagic generate prop` (voxelize in a
|
|
14
|
+
* headless Chrome, upsert the asset locally) and, in principle, the web lane's HQ job — which
|
|
15
|
+
* today reimplements this call inline in `hq-asset-jobs.ts`.
|
|
16
|
+
*
|
|
17
|
+
* The generation is billable the moment the Forger accepts the job: the vendor charges on
|
|
18
|
+
* acceptance, so a downstream failure to produce a URL still costs.
|
|
19
|
+
*/
|
|
20
|
+
export const propParamsSchema = z.object({
|
|
21
|
+
prompt: z.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.describe('What the object should be (e.g. "a weathered stone archway covered in moss")'),
|
|
24
|
+
/**
|
|
25
|
+
* `prop` matches what the HQ path has always sent. Named rather than hardcoded because the
|
|
26
|
+
* Forger distinguishes props from characters, and a mis-typed model produces a rigged humanoid
|
|
27
|
+
* where a rock was wanted.
|
|
28
|
+
*/
|
|
29
|
+
modelType: z.enum(['prop']).optional().default('prop'),
|
|
30
|
+
}).strict();
|
|
31
|
+
const ENDPOINT = '/ai/v1/forge3d/jobs';
|
|
32
|
+
/**
|
|
33
|
+
* Kept in step with `hq-asset-jobs.ts`, which is the proven configuration for this endpoint.
|
|
34
|
+
* `rigging: 'false'` is a STRING because that is what the Forger's API accepts.
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_IMAGE_TO_MESH_MODEL = 'unified-trellis';
|
|
37
|
+
export async function generateProp(params, deps) {
|
|
38
|
+
const { forger, logger, onProgress } = deps;
|
|
39
|
+
const parsed = propParamsSchema.safeParse(params);
|
|
40
|
+
if (!parsed.success) {
|
|
41
|
+
logger.warn({ err: parsed.error }, '[prop] Invalid parameters');
|
|
42
|
+
return fail(formatZodIssues(parsed.error));
|
|
43
|
+
}
|
|
44
|
+
const input = parsed.data;
|
|
45
|
+
if (!forger.hasCredentials()) {
|
|
46
|
+
logger.error('[prop] Missing Asset Forger credentials');
|
|
47
|
+
return fail(forger.getMissingCredentialsMessage());
|
|
48
|
+
}
|
|
49
|
+
onProgress(`Generating a mesh for: ${input.prompt}`);
|
|
50
|
+
logger.info(`[prop] Starting forge3d for "${input.prompt}"`);
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await forger.postJobAndWait(ENDPOINT, {
|
|
54
|
+
model: 'lab',
|
|
55
|
+
prompt: input.prompt,
|
|
56
|
+
parameters: {
|
|
57
|
+
imageToMeshModel: deps.imageToMeshModel ?? DEFAULT_IMAGE_TO_MESH_MODEL,
|
|
58
|
+
modelType: input.modelType,
|
|
59
|
+
rigging: 'false',
|
|
60
|
+
},
|
|
61
|
+
}, 'prop');
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
65
|
+
logger.error({ err: error }, '[prop] Error during generation');
|
|
66
|
+
return fail(message);
|
|
67
|
+
}
|
|
68
|
+
const glbUrl = response.data?.fileUrl;
|
|
69
|
+
if (!glbUrl) {
|
|
70
|
+
// Billable: the Forger accepted and ran the job, and was paid for it, whatever it answered.
|
|
71
|
+
return { ...fail('Asset Forger returned no file URL for the generated mesh'), billable: true };
|
|
72
|
+
}
|
|
73
|
+
logger.info(`[prop] Generated ${glbUrl}`);
|
|
74
|
+
return ok(`Mesh generated: ${glbUrl}`,
|
|
75
|
+
// Deliberately empty — see the file header. The asset does not exist until the host has
|
|
76
|
+
// voxelized this, and writing a half-made entry would leave world.json describing an asset the
|
|
77
|
+
// engine cannot load.
|
|
78
|
+
[], { glbUrl, billable: true });
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=prop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop.js","sourceRoot":"","sources":["../../src/generators/prop.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export { generateImageAsset, imageAssetParamsSchema } from './generators/image.j
|
|
|
11
11
|
export type { ImageAssetParams, ImageAssetResult } from './generators/image.js';
|
|
12
12
|
export { generateCharacter, characterParamsSchema } from './generators/character.js';
|
|
13
13
|
export type { CharacterParams, CharacterResult } from './generators/character.js';
|
|
14
|
+
export { generateProp, propParamsSchema } from './generators/prop.js';
|
|
15
|
+
export type { PropParams, PropResult } from './generators/prop.js';
|
|
14
16
|
export { generateAnimationAsset, animationParamsSchema, getConfiguredAnimationModel, ANIMATION_MODEL_TEXT_TO_MOTION_3, ANIMATION_MODEL_TEXT_TO_MOTION_2, ANIMATION_MODEL_OLDER, } from './generators/animation.js';
|
|
15
17
|
export type { AnimationParams, AnimationResult } from './generators/animation.js';
|
|
16
18
|
export { fetchContentLength } from './http.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMnG,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACnF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAMnG,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { generateSkybox, skyboxParamsSchema } from './generators/skybox.js';
|
|
|
5
5
|
export { generateSoundEffect, soundEffectParamsSchema } from './generators/sound-effect.js';
|
|
6
6
|
export { generateImageAsset, imageAssetParamsSchema } from './generators/image.js';
|
|
7
7
|
export { generateCharacter, characterParamsSchema } from './generators/character.js';
|
|
8
|
+
export { generateProp, propParamsSchema } from './generators/prop.js';
|
|
8
9
|
export { generateAnimationAsset, animationParamsSchema, getConfiguredAnimationModel, ANIMATION_MODEL_TEXT_TO_MOTION_3, ANIMATION_MODEL_TEXT_TO_MOTION_2, ANIMATION_MODEL_OLDER, } from './generators/animation.js';
|
|
9
10
|
export { fetchContentLength } from './http.js';
|
|
10
11
|
export { PIXAR_VOXEL_STYLE, MAX_COVER_DESIGN_CHARS, buildCoverArtPrompt } from './cover-prompt.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEnG,oEAAoE;AACpE,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,2BAA2B,EAC3B,gCAAgC,EAChC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEnG,oEAAoE;AACpE,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,OAAO,EACL,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What "the source of a published game" means, shared by the two sides that must agree on it.
|
|
3
|
+
*
|
|
4
|
+
* The CLI walks a creator's project to produce a fingerprint and a source archive; api-server
|
|
5
|
+
* re-derives the fingerprint from that archive's manifest to check the two describe the same
|
|
6
|
+
* tree. If the exclusion lists, the path normalisation or the fold ever differ between them, a
|
|
7
|
+
* perfectly honest publish reports a mismatch. So they live here, once.
|
|
8
|
+
*
|
|
9
|
+
* Import path matters, twice over:
|
|
10
|
+
*
|
|
11
|
+
* - Published as the `@bitmagic/asset-core/publish-source` subpath, following `./favicon`'s
|
|
12
|
+
* precedent, so consumers do not drag in the root barrel's zod/three/forger client. This is
|
|
13
|
+
* not cosmetic: `api-server/src/cli/cli-routes.ts` documents that eagerly importing the
|
|
14
|
+
* three.js graph into api-server's tests OOM-killed the CI worker. Nothing here may import
|
|
15
|
+
* from `./index.js`.
|
|
16
|
+
* - No zip library, no `fs`. Zip *format* handling stays in each consumer (the CLI writes an
|
|
17
|
+
* archive, api-server streams one apart) and only the pure logic is shared, which keeps this
|
|
18
|
+
* package dependency-clean and keeps the npm blast radius of a change small.
|
|
19
|
+
*
|
|
20
|
+
* The CLI ships to npm with this package pinned, while api-server runs from `main`, so the two
|
|
21
|
+
* routinely run DIFFERENT copies of this file. That is why the fingerprint is versioned — see
|
|
22
|
+
* `SOURCE_INDEX_ALGO`.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The fingerprint algorithm's identity, sent by the CLI and dispatched on by the server.
|
|
26
|
+
*
|
|
27
|
+
* Without it, adding one entry to `EXCLUDED_FILES` — which has happened three times, each for a
|
|
28
|
+
* good reason documented below — would make every publish from an already-installed CLI report a
|
|
29
|
+
* fingerprint mismatch, because that CLI is still hashing the old file set. The server keeps every
|
|
30
|
+
* algorithm it has ever shipped and picks by this value; an unknown or absent one is reported as
|
|
31
|
+
* "cannot check", never as "mismatch". Bump it in lockstep with any change to the two sets below
|
|
32
|
+
* or to `fingerprintFromIndex`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SOURCE_INDEX_ALGO = "v1";
|
|
35
|
+
/**
|
|
36
|
+
* Directories whose contents never change what the bundle contains: our own artifacts
|
|
37
|
+
* (`.bitmagic/`, `dist/`), the dependency tree (`node_modules/` — pinned by the lockfile the
|
|
38
|
+
* scaffold writes), vite's cache, and git's object store. Including any of them would make the
|
|
39
|
+
* fingerprint change on something other than an edit, which would make the staleness gate reject
|
|
40
|
+
* a publish of an unchanged project.
|
|
41
|
+
*
|
|
42
|
+
* `.git` is the one that bit in practice. Hashing it means an EMPTY `git commit` — no source
|
|
43
|
+
* change at all — flips the fingerprint, because the commit writes new objects, a new ref value
|
|
44
|
+
* and a new reflog entry. That turns the ordinary `bitmagic verify` → `git commit` →
|
|
45
|
+
* `bitmagic publish` sequence into exit 3 ("The project has changed since it was last verified"),
|
|
46
|
+
* and `bitmagic upgrade` actively pushes creators into git by refusing a dirty tree. The
|
|
47
|
+
* scaffolded AGENTS.md's "do not edit any tracked file between verifying and publishing" cannot
|
|
48
|
+
* prevent it either: committing is not editing. It also made every publish rebuild, since the
|
|
49
|
+
* build manifest's own recorded fingerprint could never still match by the time publish ran.
|
|
50
|
+
*/
|
|
51
|
+
export declare const EXCLUDED_DIRS: ReadonlySet<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Files that can never change what the bundle contains, excluded at basename granularity.
|
|
54
|
+
*
|
|
55
|
+
* `.DS_Store`: on macOS, Finder writes it merely for opening the project folder, and no build tool
|
|
56
|
+
* ever reads it — so it is another way to flip the fingerprint without editing anything.
|
|
57
|
+
*
|
|
58
|
+
* `mechanics-plan.md`: the agent is told to rewrite it after every slice — moving shipped items
|
|
59
|
+
* out of the backlog and re-ranking the rest is the whole point of the file. Left in, the ordinary
|
|
60
|
+
* `bitmagic verify` → update the backlog → `bitmagic publish` sequence exits 3 over prose, which
|
|
61
|
+
* teaches agents to reach for `--force`, the one flag that also waves through a genuinely stale
|
|
62
|
+
* verify.
|
|
63
|
+
*
|
|
64
|
+
* `GAME-DESIGN.md`: same "no build tool reads it" test — nothing imports it,
|
|
65
|
+
* `vite.publish.config.js` cannot put prose in the bundle, and it exists to be REWRITTEN as the
|
|
66
|
+
* game evolves (it is the input `bitmagic cover` reads).
|
|
67
|
+
*
|
|
68
|
+
* The list is deliberately not a general ignore mechanism, because a false "fresh" ships an
|
|
69
|
+
* unverified bundle while a false "stale" only costs one `bitmagic verify`.
|
|
70
|
+
*
|
|
71
|
+
* NOTE the asymmetry with `SECRET_FILE_PATTERNS` below: these files are excluded from the
|
|
72
|
+
* FINGERPRINT but still travel in the source archive, because they are exactly what a human
|
|
73
|
+
* reading the archive wants. Secrets are the other way round.
|
|
74
|
+
*/
|
|
75
|
+
export declare const EXCLUDED_FILES: ReadonlySet<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Excluded from the fingerprint, but still carried in the source archive.
|
|
78
|
+
*
|
|
79
|
+
* The two sets answer different questions. The fingerprint asks "would changing this change the
|
|
80
|
+
* bundle?" — for a design document, no, which is why editing one must not invalidate a verify. The
|
|
81
|
+
* archive asks "would a human opening this want it?" — for a design document, emphatically yes: it
|
|
82
|
+
* is the single most useful file for understanding what the game was trying to be.
|
|
83
|
+
*
|
|
84
|
+
* `.DS_Store` is in neither, so it is deliberately absent here: nobody wants it and it is not
|
|
85
|
+
* evidence of anything.
|
|
86
|
+
*/
|
|
87
|
+
export declare const ARCHIVED_UNFINGERPRINTED_FILES: ReadonlySet<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Files that must never travel inside the source archive, however the fingerprint treats them.
|
|
90
|
+
*
|
|
91
|
+
* The archive lands in the portal bucket, and that bucket carries a BUCKET-level
|
|
92
|
+
* `allUsers:objectViewer` grant (see `.github/workflows/README.md`) — every object in it is
|
|
93
|
+
* world-readable. A creator's project is their working directory, and working directories collect
|
|
94
|
+
* credentials. Shipping one to a public URL because nobody thought about it is how an incident
|
|
95
|
+
* starts.
|
|
96
|
+
*
|
|
97
|
+
* These files are still hashed and still listed in the manifest (path + hash, no content), so the
|
|
98
|
+
* server can reconcile the fingerprint without ever receiving the bytes. That is the whole reason
|
|
99
|
+
* the manifest is separate from the payload.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately a deny-list of shapes rather than an allow-list of known-good paths: an allow-list
|
|
102
|
+
* would silently drop a creator's own source file the first time someone used an unusual layout,
|
|
103
|
+
* and dropping game code from the archive is a quieter failure than including a stray dotfile.
|
|
104
|
+
* The trade is accepted knowingly — this list will never be complete, which is why the archive's
|
|
105
|
+
* storage key is also unguessable rather than relying on this alone.
|
|
106
|
+
*/
|
|
107
|
+
export declare const SECRET_FILE_PATTERNS: readonly RegExp[];
|
|
108
|
+
/** Would including this path in the archive payload risk publishing a credential? */
|
|
109
|
+
export declare function isSecretPath(posixPath: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* A relative path as the fingerprint hashes it: always forward-slashed.
|
|
112
|
+
*
|
|
113
|
+
* `sep` is a parameter rather than a direct `path.sep` read so this is testable on any
|
|
114
|
+
* platform — the Windows behaviour that makes normalization necessary cannot otherwise be
|
|
115
|
+
* exercised on the POSIX runners CI uses, which is exactly how a vacuous test gets written.
|
|
116
|
+
*/
|
|
117
|
+
export declare function toPosixPath(relativePath: string, sep?: string): string;
|
|
118
|
+
/** One file as both sides see it: the project-relative posix path and the hash of its bytes. */
|
|
119
|
+
export interface SourceIndexEntry {
|
|
120
|
+
path: string;
|
|
121
|
+
sha256: string;
|
|
122
|
+
bytes: number;
|
|
123
|
+
/**
|
|
124
|
+
* For vendored engine files only: the same content hashed with line endings normalised, used
|
|
125
|
+
* exclusively to compare against the official release. See `engineComparisonHash` for why the
|
|
126
|
+
* raw `sha256` above cannot serve — in short, a Windows checkout rewrites LF to CRLF in the
|
|
127
|
+
* committed `engine/` tree and would otherwise report every engine file as modified.
|
|
128
|
+
*
|
|
129
|
+
* Computed here, by the side that actually has the bytes: the archive carries hashes rather than
|
|
130
|
+
* engine content, so the server can never normalise after the fact.
|
|
131
|
+
*/
|
|
132
|
+
engineSha256?: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every fingerprinted file in a project, plus the fingerprint they roll up to.
|
|
136
|
+
*
|
|
137
|
+
* Produced once per publish by the CLI (which owns the filesystem walk) and reconstructed by
|
|
138
|
+
* api-server from the archive's manifest. `files` is sorted by path — `fingerprintFromIndex`
|
|
139
|
+
* depends on it and sorts defensively rather than trusting the caller.
|
|
140
|
+
*/
|
|
141
|
+
export interface SourceIndex {
|
|
142
|
+
algo: string;
|
|
143
|
+
files: SourceIndexEntry[];
|
|
144
|
+
fingerprint: string;
|
|
145
|
+
/**
|
|
146
|
+
* Files the archive carries but the fingerprint ignores — see
|
|
147
|
+
* `ARCHIVED_UNFINGERPRINTED_FILES`. Kept apart from `files` so they can never reach
|
|
148
|
+
* `fingerprintFromIndex`, which is the whole reason they were excluded in the first place.
|
|
149
|
+
*/
|
|
150
|
+
archivedOnly: SourceIndexEntry[];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The fingerprint: a hash of everything that determines the published bundle.
|
|
154
|
+
*
|
|
155
|
+
* This is what lets `publish` decide whether a `verify` is too stale to trust, by CONTENT
|
|
156
|
+
* rather than by clock: a timestamp policy would accept an edit made one second after
|
|
157
|
+
* verifying and reject an untouched project that merely sat overnight.
|
|
158
|
+
*
|
|
159
|
+
* The path is hashed alongside the content so that moving a file — which changes what the
|
|
160
|
+
* bundle imports — changes the fingerprint even though no byte of any file did.
|
|
161
|
+
*
|
|
162
|
+
* Kept deliberately coarse (every tracked file, not a module graph): a false "stale" costs one
|
|
163
|
+
* `bitmagic verify`, while a false "fresh" ships an unverified bundle.
|
|
164
|
+
*
|
|
165
|
+
* The exact byte sequence folded here is load-bearing across two independently-released
|
|
166
|
+
* packages. Do not "clean up" the trailing newline or the separator without bumping
|
|
167
|
+
* `SOURCE_INDEX_ALGO`.
|
|
168
|
+
*/
|
|
169
|
+
export declare function fingerprintFromIndex(files: readonly SourceIndexEntry[]): string;
|
|
170
|
+
/** Where the archive's manifest lives inside the zip. */
|
|
171
|
+
export declare const SOURCE_MANIFEST_NAME = "bitmagic-source.json";
|
|
172
|
+
/**
|
|
173
|
+
* The archive's manifest: enough for the server to re-derive the fingerprint and to check the
|
|
174
|
+
* vendored engine, without the archive having to carry either the engine or any secret.
|
|
175
|
+
*
|
|
176
|
+
* `omitted` lists the paths that are in `files` but deliberately absent from the payload, so a
|
|
177
|
+
* reader (human or server) can tell "not shipped, on purpose" from "the archive is truncated".
|
|
178
|
+
*
|
|
179
|
+
* Deliberately NO `publishVersion`. The archive has to be built and hashed BEFORE `publish/begin`
|
|
180
|
+
* — that hash is what `begin` bakes into the meta block — and the version is not allocated until
|
|
181
|
+
* `begin` returns. A field that cannot be known when the file is written cannot be in the file.
|
|
182
|
+
* The version is recorded where it is actually available: in the archive's storage key and in the
|
|
183
|
+
* meta block's own `bm:publish-version`.
|
|
184
|
+
*/
|
|
185
|
+
export interface SourceManifest {
|
|
186
|
+
algo: string;
|
|
187
|
+
gameId: string;
|
|
188
|
+
engineVersion: string;
|
|
189
|
+
genre: string;
|
|
190
|
+
exportedAt: string;
|
|
191
|
+
fingerprint: string;
|
|
192
|
+
files: Record<string, string>;
|
|
193
|
+
omitted: string[];
|
|
194
|
+
/**
|
|
195
|
+
* Line-ending-normalised hashes for the vendored engine paths only, keyed the same way as
|
|
196
|
+
* `files`. Separate from `files` because the two answer different questions: `files` must stay
|
|
197
|
+
* byte-exact for the fingerprint, while this exists to survive a platform's checkout rules.
|
|
198
|
+
*/
|
|
199
|
+
engineHashes: Record<string, string>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Project-relative prefix of the vendored engine. `bitmagic init`/`upgrade` unpack the release
|
|
203
|
+
* tarball's `game/src/<root>` into `engine/<root>`, so every vendored file lives under this.
|
|
204
|
+
*/
|
|
205
|
+
export declare const VENDORED_ENGINE_PREFIX = "engine/";
|
|
206
|
+
/**
|
|
207
|
+
* Map a path as it appears in the engine release tarball to where it lands in a scaffolded
|
|
208
|
+
* project, or null when the tarball entry is not vendored into projects at all.
|
|
209
|
+
*
|
|
210
|
+
* The tarball stores three shapes at different depths (see `cli/src/scaffold/engine-download.ts`):
|
|
211
|
+
* `game/src/<root>/…` → `engine/<root>/…`, `game/agent-docs/…` → `engine/agent-docs/…`, and
|
|
212
|
+
* `game/sw-cache-buster.js` → `sw-cache-buster.js` at the project root. `templates/` ships in the
|
|
213
|
+
* tarball for the scaffolder to read and is never vendored, so it maps to null.
|
|
214
|
+
*
|
|
215
|
+
* Lives here rather than in api-server because it is a statement about what the CLI did to the
|
|
216
|
+
* tarball, and the CLI is the other consumer that has to agree.
|
|
217
|
+
*/
|
|
218
|
+
export declare function tarballPathToProjectPath(tarballPath: string): string | null;
|
|
219
|
+
/**
|
|
220
|
+
* Bytes every genuine published bundle carries, used to refuse a publish that is not an engine
|
|
221
|
+
* build at all.
|
|
222
|
+
*
|
|
223
|
+
* These survive the toolchain verbatim, which is the entire reason they were chosen over anything
|
|
224
|
+
* cleverer. The scaffold's `index.html` emits `<div id="game-container">` and a
|
|
225
|
+
* `<script type="importmap">` whose body Vite only RELOCATES (its import-map hook lifts the tag
|
|
226
|
+
* out and reinserts the matched text unchanged before the first module script) — so both land, byte
|
|
227
|
+
* for byte, a few KB into the single-file bundle, well inside the head range `complete` already
|
|
228
|
+
* reads for the meta block. Verified against a real `vite build` with `vite-plugin-singlefile`,
|
|
229
|
+
* not inferred.
|
|
230
|
+
*
|
|
231
|
+
* Why a SUBSET of the alias entries rather than all of them. A creator's installed CLI is an older
|
|
232
|
+
* npm release than the running server, so the server must only require markers that both agree on.
|
|
233
|
+
* The CDN entries are disqualified outright — they embed the pinned three.js version, which moves
|
|
234
|
+
* whenever the CLI bumps it. Of the local aliases these three are the ones that have to exist for
|
|
235
|
+
* a project to resolve its entry module at all, so requiring more would buy nothing and would turn
|
|
236
|
+
* any future alias change into a spurious refusal for every not-yet-upgraded project.
|
|
237
|
+
*
|
|
238
|
+
* What this is worth: it stops bitmagic.ai being used to host arbitrary HTML by someone who has an
|
|
239
|
+
* account and a game id. It is trivially copy-pasteable by anyone who looks, and is not claimed to
|
|
240
|
+
* be more than that.
|
|
241
|
+
*/
|
|
242
|
+
export declare const ENGINE_BUILD_MARKERS: readonly string[];
|
|
243
|
+
/** Does this bundle's head carry every marker a genuine engine build has? */
|
|
244
|
+
export declare function hasEngineBuildMarkers(head: string): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Hash used ONLY when comparing a vendored engine file against the official release.
|
|
247
|
+
*
|
|
248
|
+
* Line endings are normalised first, which the fingerprint deliberately never does. The reason is
|
|
249
|
+
* Windows: `renderGitignore()` does not ignore `engine/`, so the vendored tree is committed, and
|
|
250
|
+
* AGENTS.md pushes creators into git (`bitmagic upgrade` refuses a dirty tree). A Windows checkout
|
|
251
|
+
* with the default `core.autocrlf=true` rewrites LF to CRLF in every `.ts` file it restores — so
|
|
252
|
+
* without this, every Windows creator would see 100% of their engine files reported as modified,
|
|
253
|
+
* forever, on every publish. That is a false alarm about the one check most likely to be believed.
|
|
254
|
+
*
|
|
255
|
+
* Confined to this comparison on purpose. Normalising inside the fingerprint would instead make
|
|
256
|
+
* the fingerprint disagree with itself across platforms in the staleness gate, where a false
|
|
257
|
+
* "fresh" is the dangerous direction.
|
|
258
|
+
*/
|
|
259
|
+
export declare function engineComparisonHash(content: Uint8Array): string;
|
|
260
|
+
//# sourceMappingURL=publish-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-source.d.ts","sourceRoot":"","sources":["../src/publish-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAM5C,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAI7C,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,EAAE,WAAW,CAAC,MAAM,CAG7D,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EASjD,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,GAAE,MAAY,GAAG,MAAM,CAE3E;AAED,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,gBAAgB,EAAE,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,CAO/E;AAED,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAIjD,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAMhE"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What "the source of a published game" means, shared by the two sides that must agree on it.
|
|
3
|
+
*
|
|
4
|
+
* The CLI walks a creator's project to produce a fingerprint and a source archive; api-server
|
|
5
|
+
* re-derives the fingerprint from that archive's manifest to check the two describe the same
|
|
6
|
+
* tree. If the exclusion lists, the path normalisation or the fold ever differ between them, a
|
|
7
|
+
* perfectly honest publish reports a mismatch. So they live here, once.
|
|
8
|
+
*
|
|
9
|
+
* Import path matters, twice over:
|
|
10
|
+
*
|
|
11
|
+
* - Published as the `@bitmagic/asset-core/publish-source` subpath, following `./favicon`'s
|
|
12
|
+
* precedent, so consumers do not drag in the root barrel's zod/three/forger client. This is
|
|
13
|
+
* not cosmetic: `api-server/src/cli/cli-routes.ts` documents that eagerly importing the
|
|
14
|
+
* three.js graph into api-server's tests OOM-killed the CI worker. Nothing here may import
|
|
15
|
+
* from `./index.js`.
|
|
16
|
+
* - No zip library, no `fs`. Zip *format* handling stays in each consumer (the CLI writes an
|
|
17
|
+
* archive, api-server streams one apart) and only the pure logic is shared, which keeps this
|
|
18
|
+
* package dependency-clean and keeps the npm blast radius of a change small.
|
|
19
|
+
*
|
|
20
|
+
* The CLI ships to npm with this package pinned, while api-server runs from `main`, so the two
|
|
21
|
+
* routinely run DIFFERENT copies of this file. That is why the fingerprint is versioned — see
|
|
22
|
+
* `SOURCE_INDEX_ALGO`.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
// Imported rather than taken from the ambient global, matching `node:crypto` above: this file's
|
|
26
|
+
// eslint config enumerates its globals deliberately, and an explicit import says "node-only" as
|
|
27
|
+
// clearly as the crypto one does.
|
|
28
|
+
import { TextDecoder } from 'node:util';
|
|
29
|
+
/**
|
|
30
|
+
* The fingerprint algorithm's identity, sent by the CLI and dispatched on by the server.
|
|
31
|
+
*
|
|
32
|
+
* Without it, adding one entry to `EXCLUDED_FILES` — which has happened three times, each for a
|
|
33
|
+
* good reason documented below — would make every publish from an already-installed CLI report a
|
|
34
|
+
* fingerprint mismatch, because that CLI is still hashing the old file set. The server keeps every
|
|
35
|
+
* algorithm it has ever shipped and picks by this value; an unknown or absent one is reported as
|
|
36
|
+
* "cannot check", never as "mismatch". Bump it in lockstep with any change to the two sets below
|
|
37
|
+
* or to `fingerprintFromIndex`.
|
|
38
|
+
*/
|
|
39
|
+
export const SOURCE_INDEX_ALGO = 'v1';
|
|
40
|
+
/**
|
|
41
|
+
* Directories whose contents never change what the bundle contains: our own artifacts
|
|
42
|
+
* (`.bitmagic/`, `dist/`), the dependency tree (`node_modules/` — pinned by the lockfile the
|
|
43
|
+
* scaffold writes), vite's cache, and git's object store. Including any of them would make the
|
|
44
|
+
* fingerprint change on something other than an edit, which would make the staleness gate reject
|
|
45
|
+
* a publish of an unchanged project.
|
|
46
|
+
*
|
|
47
|
+
* `.git` is the one that bit in practice. Hashing it means an EMPTY `git commit` — no source
|
|
48
|
+
* change at all — flips the fingerprint, because the commit writes new objects, a new ref value
|
|
49
|
+
* and a new reflog entry. That turns the ordinary `bitmagic verify` → `git commit` →
|
|
50
|
+
* `bitmagic publish` sequence into exit 3 ("The project has changed since it was last verified"),
|
|
51
|
+
* and `bitmagic upgrade` actively pushes creators into git by refusing a dirty tree. The
|
|
52
|
+
* scaffolded AGENTS.md's "do not edit any tracked file between verifying and publishing" cannot
|
|
53
|
+
* prevent it either: committing is not editing. It also made every publish rebuild, since the
|
|
54
|
+
* build manifest's own recorded fingerprint could never still match by the time publish ran.
|
|
55
|
+
*/
|
|
56
|
+
export const EXCLUDED_DIRS = new Set([
|
|
57
|
+
'.bitmagic',
|
|
58
|
+
'node_modules',
|
|
59
|
+
'dist',
|
|
60
|
+
'.vite-cache',
|
|
61
|
+
'.git',
|
|
62
|
+
]);
|
|
63
|
+
/**
|
|
64
|
+
* Files that can never change what the bundle contains, excluded at basename granularity.
|
|
65
|
+
*
|
|
66
|
+
* `.DS_Store`: on macOS, Finder writes it merely for opening the project folder, and no build tool
|
|
67
|
+
* ever reads it — so it is another way to flip the fingerprint without editing anything.
|
|
68
|
+
*
|
|
69
|
+
* `mechanics-plan.md`: the agent is told to rewrite it after every slice — moving shipped items
|
|
70
|
+
* out of the backlog and re-ranking the rest is the whole point of the file. Left in, the ordinary
|
|
71
|
+
* `bitmagic verify` → update the backlog → `bitmagic publish` sequence exits 3 over prose, which
|
|
72
|
+
* teaches agents to reach for `--force`, the one flag that also waves through a genuinely stale
|
|
73
|
+
* verify.
|
|
74
|
+
*
|
|
75
|
+
* `GAME-DESIGN.md`: same "no build tool reads it" test — nothing imports it,
|
|
76
|
+
* `vite.publish.config.js` cannot put prose in the bundle, and it exists to be REWRITTEN as the
|
|
77
|
+
* game evolves (it is the input `bitmagic cover` reads).
|
|
78
|
+
*
|
|
79
|
+
* The list is deliberately not a general ignore mechanism, because a false "fresh" ships an
|
|
80
|
+
* unverified bundle while a false "stale" only costs one `bitmagic verify`.
|
|
81
|
+
*
|
|
82
|
+
* NOTE the asymmetry with `SECRET_FILE_PATTERNS` below: these files are excluded from the
|
|
83
|
+
* FINGERPRINT but still travel in the source archive, because they are exactly what a human
|
|
84
|
+
* reading the archive wants. Secrets are the other way round.
|
|
85
|
+
*/
|
|
86
|
+
export const EXCLUDED_FILES = new Set([
|
|
87
|
+
'.DS_Store',
|
|
88
|
+
'GAME-DESIGN.md',
|
|
89
|
+
'mechanics-plan.md',
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* Excluded from the fingerprint, but still carried in the source archive.
|
|
93
|
+
*
|
|
94
|
+
* The two sets answer different questions. The fingerprint asks "would changing this change the
|
|
95
|
+
* bundle?" — for a design document, no, which is why editing one must not invalidate a verify. The
|
|
96
|
+
* archive asks "would a human opening this want it?" — for a design document, emphatically yes: it
|
|
97
|
+
* is the single most useful file for understanding what the game was trying to be.
|
|
98
|
+
*
|
|
99
|
+
* `.DS_Store` is in neither, so it is deliberately absent here: nobody wants it and it is not
|
|
100
|
+
* evidence of anything.
|
|
101
|
+
*/
|
|
102
|
+
export const ARCHIVED_UNFINGERPRINTED_FILES = new Set([
|
|
103
|
+
'GAME-DESIGN.md',
|
|
104
|
+
'mechanics-plan.md',
|
|
105
|
+
]);
|
|
106
|
+
/**
|
|
107
|
+
* Files that must never travel inside the source archive, however the fingerprint treats them.
|
|
108
|
+
*
|
|
109
|
+
* The archive lands in the portal bucket, and that bucket carries a BUCKET-level
|
|
110
|
+
* `allUsers:objectViewer` grant (see `.github/workflows/README.md`) — every object in it is
|
|
111
|
+
* world-readable. A creator's project is their working directory, and working directories collect
|
|
112
|
+
* credentials. Shipping one to a public URL because nobody thought about it is how an incident
|
|
113
|
+
* starts.
|
|
114
|
+
*
|
|
115
|
+
* These files are still hashed and still listed in the manifest (path + hash, no content), so the
|
|
116
|
+
* server can reconcile the fingerprint without ever receiving the bytes. That is the whole reason
|
|
117
|
+
* the manifest is separate from the payload.
|
|
118
|
+
*
|
|
119
|
+
* Deliberately a deny-list of shapes rather than an allow-list of known-good paths: an allow-list
|
|
120
|
+
* would silently drop a creator's own source file the first time someone used an unusual layout,
|
|
121
|
+
* and dropping game code from the archive is a quieter failure than including a stray dotfile.
|
|
122
|
+
* The trade is accepted knowingly — this list will never be complete, which is why the archive's
|
|
123
|
+
* storage key is also unguessable rather than relying on this alone.
|
|
124
|
+
*/
|
|
125
|
+
export const SECRET_FILE_PATTERNS = [
|
|
126
|
+
/(^|\/)\.env($|\.)/,
|
|
127
|
+
/\.pem$/,
|
|
128
|
+
/\.key$/,
|
|
129
|
+
/(^|\/)id_rsa/,
|
|
130
|
+
/(^|\/)\.npmrc$/,
|
|
131
|
+
/(^|\/)\.git-credentials$/,
|
|
132
|
+
/service-account[^/]*\.json$/,
|
|
133
|
+
/(^|\/)\.claude\/settings\.local\.json$/,
|
|
134
|
+
];
|
|
135
|
+
/** Would including this path in the archive payload risk publishing a credential? */
|
|
136
|
+
export function isSecretPath(posixPath) {
|
|
137
|
+
return SECRET_FILE_PATTERNS.some((pattern) => pattern.test(posixPath));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A relative path as the fingerprint hashes it: always forward-slashed.
|
|
141
|
+
*
|
|
142
|
+
* `sep` is a parameter rather than a direct `path.sep` read so this is testable on any
|
|
143
|
+
* platform — the Windows behaviour that makes normalization necessary cannot otherwise be
|
|
144
|
+
* exercised on the POSIX runners CI uses, which is exactly how a vacuous test gets written.
|
|
145
|
+
*/
|
|
146
|
+
export function toPosixPath(relativePath, sep = '/') {
|
|
147
|
+
return relativePath.split(sep).join('/');
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The fingerprint: a hash of everything that determines the published bundle.
|
|
151
|
+
*
|
|
152
|
+
* This is what lets `publish` decide whether a `verify` is too stale to trust, by CONTENT
|
|
153
|
+
* rather than by clock: a timestamp policy would accept an edit made one second after
|
|
154
|
+
* verifying and reject an untouched project that merely sat overnight.
|
|
155
|
+
*
|
|
156
|
+
* The path is hashed alongside the content so that moving a file — which changes what the
|
|
157
|
+
* bundle imports — changes the fingerprint even though no byte of any file did.
|
|
158
|
+
*
|
|
159
|
+
* Kept deliberately coarse (every tracked file, not a module graph): a false "stale" costs one
|
|
160
|
+
* `bitmagic verify`, while a false "fresh" ships an unverified bundle.
|
|
161
|
+
*
|
|
162
|
+
* The exact byte sequence folded here is load-bearing across two independently-released
|
|
163
|
+
* packages. Do not "clean up" the trailing newline or the separator without bumping
|
|
164
|
+
* `SOURCE_INDEX_ALGO`.
|
|
165
|
+
*/
|
|
166
|
+
export function fingerprintFromIndex(files) {
|
|
167
|
+
const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
168
|
+
const hash = createHash('sha256');
|
|
169
|
+
for (const entry of sorted) {
|
|
170
|
+
hash.update(`${entry.path}:${entry.sha256}\n`);
|
|
171
|
+
}
|
|
172
|
+
return hash.digest('hex');
|
|
173
|
+
}
|
|
174
|
+
/** Where the archive's manifest lives inside the zip. */
|
|
175
|
+
export const SOURCE_MANIFEST_NAME = 'bitmagic-source.json';
|
|
176
|
+
/**
|
|
177
|
+
* Project-relative prefix of the vendored engine. `bitmagic init`/`upgrade` unpack the release
|
|
178
|
+
* tarball's `game/src/<root>` into `engine/<root>`, so every vendored file lives under this.
|
|
179
|
+
*/
|
|
180
|
+
export const VENDORED_ENGINE_PREFIX = 'engine/';
|
|
181
|
+
/**
|
|
182
|
+
* Map a path as it appears in the engine release tarball to where it lands in a scaffolded
|
|
183
|
+
* project, or null when the tarball entry is not vendored into projects at all.
|
|
184
|
+
*
|
|
185
|
+
* The tarball stores three shapes at different depths (see `cli/src/scaffold/engine-download.ts`):
|
|
186
|
+
* `game/src/<root>/…` → `engine/<root>/…`, `game/agent-docs/…` → `engine/agent-docs/…`, and
|
|
187
|
+
* `game/sw-cache-buster.js` → `sw-cache-buster.js` at the project root. `templates/` ships in the
|
|
188
|
+
* tarball for the scaffolder to read and is never vendored, so it maps to null.
|
|
189
|
+
*
|
|
190
|
+
* Lives here rather than in api-server because it is a statement about what the CLI did to the
|
|
191
|
+
* tarball, and the CLI is the other consumer that has to agree.
|
|
192
|
+
*/
|
|
193
|
+
export function tarballPathToProjectPath(tarballPath) {
|
|
194
|
+
if (tarballPath.startsWith('game/src/')) {
|
|
195
|
+
return `${VENDORED_ENGINE_PREFIX}${tarballPath.slice('game/src/'.length)}`;
|
|
196
|
+
}
|
|
197
|
+
if (tarballPath.startsWith('game/agent-docs/')) {
|
|
198
|
+
return `${VENDORED_ENGINE_PREFIX}agent-docs/${tarballPath.slice('game/agent-docs/'.length)}`;
|
|
199
|
+
}
|
|
200
|
+
if (tarballPath === 'game/sw-cache-buster.js') {
|
|
201
|
+
return 'sw-cache-buster.js';
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Bytes every genuine published bundle carries, used to refuse a publish that is not an engine
|
|
207
|
+
* build at all.
|
|
208
|
+
*
|
|
209
|
+
* These survive the toolchain verbatim, which is the entire reason they were chosen over anything
|
|
210
|
+
* cleverer. The scaffold's `index.html` emits `<div id="game-container">` and a
|
|
211
|
+
* `<script type="importmap">` whose body Vite only RELOCATES (its import-map hook lifts the tag
|
|
212
|
+
* out and reinserts the matched text unchanged before the first module script) — so both land, byte
|
|
213
|
+
* for byte, a few KB into the single-file bundle, well inside the head range `complete` already
|
|
214
|
+
* reads for the meta block. Verified against a real `vite build` with `vite-plugin-singlefile`,
|
|
215
|
+
* not inferred.
|
|
216
|
+
*
|
|
217
|
+
* Why a SUBSET of the alias entries rather than all of them. A creator's installed CLI is an older
|
|
218
|
+
* npm release than the running server, so the server must only require markers that both agree on.
|
|
219
|
+
* The CDN entries are disqualified outright — they embed the pinned three.js version, which moves
|
|
220
|
+
* whenever the CLI bumps it. Of the local aliases these three are the ones that have to exist for
|
|
221
|
+
* a project to resolve its entry module at all, so requiring more would buy nothing and would turn
|
|
222
|
+
* any future alias change into a spurious refusal for every not-yet-upgraded project.
|
|
223
|
+
*
|
|
224
|
+
* What this is worth: it stops bitmagic.ai being used to host arbitrary HTML by someone who has an
|
|
225
|
+
* account and a game id. It is trivially copy-pasteable by anyone who looks, and is not claimed to
|
|
226
|
+
* be more than that.
|
|
227
|
+
*/
|
|
228
|
+
export const ENGINE_BUILD_MARKERS = [
|
|
229
|
+
'id="game-container"',
|
|
230
|
+
'"engine/": "/dist/engine/engine/"',
|
|
231
|
+
'"types/": "/dist/engine/types/"',
|
|
232
|
+
];
|
|
233
|
+
/** Does this bundle's head carry every marker a genuine engine build has? */
|
|
234
|
+
export function hasEngineBuildMarkers(head) {
|
|
235
|
+
return ENGINE_BUILD_MARKERS.every((marker) => head.includes(marker));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Hash used ONLY when comparing a vendored engine file against the official release.
|
|
239
|
+
*
|
|
240
|
+
* Line endings are normalised first, which the fingerprint deliberately never does. The reason is
|
|
241
|
+
* Windows: `renderGitignore()` does not ignore `engine/`, so the vendored tree is committed, and
|
|
242
|
+
* AGENTS.md pushes creators into git (`bitmagic upgrade` refuses a dirty tree). A Windows checkout
|
|
243
|
+
* with the default `core.autocrlf=true` rewrites LF to CRLF in every `.ts` file it restores — so
|
|
244
|
+
* without this, every Windows creator would see 100% of their engine files reported as modified,
|
|
245
|
+
* forever, on every publish. That is a false alarm about the one check most likely to be believed.
|
|
246
|
+
*
|
|
247
|
+
* Confined to this comparison on purpose. Normalising inside the fingerprint would instead make
|
|
248
|
+
* the fingerprint disagree with itself across platforms in the staleness gate, where a false
|
|
249
|
+
* "fresh" is the dangerous direction.
|
|
250
|
+
*/
|
|
251
|
+
export function engineComparisonHash(content) {
|
|
252
|
+
// TextDecoder rather than Buffer: this module is imported by an npm-published CLI as well as by
|
|
253
|
+
// the server, and keeping Node's Buffer out of it means the file has no ambient-global
|
|
254
|
+
// dependency beyond `node:crypto`, which it imports explicitly.
|
|
255
|
+
const text = new TextDecoder('utf-8').decode(content);
|
|
256
|
+
return createHash('sha256').update(text.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=publish-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-source.js","sourceRoot":"","sources":["../src/publish-source.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitmagic/asset-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
"types": "./dist/terminal-failure.d.ts",
|
|
14
14
|
"default": "./dist/terminal-failure.js"
|
|
15
15
|
},
|
|
16
|
+
"./favicon": {
|
|
17
|
+
"types": "./dist/favicon.d.ts",
|
|
18
|
+
"default": "./dist/favicon.js"
|
|
19
|
+
},
|
|
20
|
+
"./publish-source": {
|
|
21
|
+
"types": "./dist/publish-source.d.ts",
|
|
22
|
+
"default": "./dist/publish-source.js"
|
|
23
|
+
},
|
|
16
24
|
"./animation/*": {
|
|
17
25
|
"default": "./src/animation/*"
|
|
18
26
|
},
|