@bitmagic/asset-core 0.2.38-dev.3 → 0.2.38-dev.5

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.
@@ -0,0 +1,88 @@
1
+ import type * as TS from 'typescript';
2
+ /** Which syntactic form named the module. */
3
+ export type SpecifierKind = 'static' | 'dynamic' | 'importType' | 'jest' | 'require' | 'url' | 'ambient';
4
+ export declare const SPECIFIER_KINDS: readonly SpecifierKind[];
5
+ /** A string literal that names a module, with its position in the file's text. */
6
+ export interface FoundSpecifier {
7
+ kind: SpecifierKind;
8
+ /** The literal's value: `engine/Door.js`. */
9
+ value: string;
10
+ /** Offset of the opening quote. */
11
+ start: number;
12
+ /** Offset just past the closing quote. */
13
+ end: number;
14
+ /** The quote character the literal is written with (`'`, `"` or a backtick). */
15
+ quote: string;
16
+ }
17
+ export interface SpecifierScan {
18
+ specifiers: FoundSpecifier[];
19
+ /** `import(<expr>)` calls whose specifier is computed, as `import(<expr text>)`: no tool can rewrite them. */
20
+ computedDynamic: string[];
21
+ }
22
+ /** The `jest.*` calls whose first argument is a module path. */
23
+ export declare const JEST_PATH_CALLS: ReadonlySet<string>;
24
+ /**
25
+ * Every module specifier in a TypeScript file: static imports and re-exports, dynamic `import()`,
26
+ * inline `import('…')` types, `jest.mock` and the other `jest.*` path calls, `require()`,
27
+ * `new URL('…', import.meta.url)` and `declare module '…'`.
28
+ */
29
+ export declare function findModuleSpecifiers(ts: typeof TS, fileName: string, text: string): SpecifierScan;
30
+ /** A path without its source/output extension: `engine/Door.ts` → `engine/Door`. */
31
+ export declare function stemOf(p: string): string;
32
+ /** The extension a specifier is written with (`.js`, `.ts`, `.mjs`), or '' for none. */
33
+ export declare function extOf(spec: string): string;
34
+ export interface ResolveOptions {
35
+ /** The directory paths under the source root are relative to (absolute). */
36
+ srcRoot: string;
37
+ /** Alias prefixes that name a directory directly under the source root (`engine`, `types`, …). */
38
+ aliasRoots: readonly string[];
39
+ /** Whether a file exists, given its absolute path. */
40
+ exists: (abs: string) => boolean;
41
+ }
42
+ export interface ResolvedSpecifier {
43
+ /** The file under the source root, posix, with `.ts`: `engine/Door.ts`. */
44
+ rel: string;
45
+ /** True when the specifier names a directory and resolves to its `index.ts`. */
46
+ viaIndex: boolean;
47
+ }
48
+ /**
49
+ * The file under the source root an alias (`engine/Door.js`) or relative (`../engine/Door.js`)
50
+ * specifier written in `importerAbs` names, or null when it names something else (a package, a
51
+ * file outside the root, a file that does not exist).
52
+ */
53
+ export declare function resolveSourceSpecifier(spec: string, importerAbs: string, options: ResolveOptions): ResolvedSpecifier | null;
54
+ export interface SpecifierTarget {
55
+ /** Where the named file is after the move (its current path when it does not move), under the source root. */
56
+ newRel: string;
57
+ /** As resolved: the specifier named a directory's index. */
58
+ viaIndex: boolean;
59
+ /** Whether the named file itself moves (false when only the importer does). */
60
+ targetMoves: boolean;
61
+ }
62
+ /**
63
+ * The specifier to write for a target, in the style of the original: an alias stays an alias,
64
+ * a relative specifier stays relative (to the importer's new folder), the extension it was
65
+ * written with is kept, and an index import of a directory that does not move keeps naming the
66
+ * directory.
67
+ */
68
+ export declare function formatSpecifier(spec: string, target: SpecifierTarget, importerNewAbs: string, srcRoot: string): string;
69
+ /** One moved module, as `engine/compat/aliases.ts` lists it (paths under the source root). */
70
+ export interface MovedModuleEntry {
71
+ from: string;
72
+ to: string;
73
+ }
74
+ /**
75
+ * The `MOVED_MODULES` list out of the text of `engine/compat/aliases.ts`. The file is generated
76
+ * by `engine-move.mjs` in one fixed format (`{ from: '…', to: '…', removeIn: '…' }` per line), so
77
+ * this reads it with the same pattern the `no-internal-shim-imports` lint rule uses rather than
78
+ * evaluating TypeScript.
79
+ */
80
+ export declare function parseMovedModules(aliasesText: string): MovedModuleEntry[];
81
+ export interface TextEdit {
82
+ start: number;
83
+ end: number;
84
+ text: string;
85
+ }
86
+ /** Apply position edits (non-overlapping) to a text, last first so earlier offsets stay valid. */
87
+ export declare function applyTextEdits(text: string, edits: readonly TextEdit[]): string;
88
+ //# sourceMappingURL=engine-specifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-specifiers.d.ts","sourceRoot":"","sources":["../src/engine-specifiers.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtC,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAEzG,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAA6E,CAAC;AAElI,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,8GAA8G;IAC9G,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAE9C,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CA6CjG;AAED,oFAAoF;AACpF,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wFAAwF;AACxF,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1C;AAID,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,sDAAsD;IACtD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,iBAAiB,GAAG,IAAI,CAY3H;AAED,MAAM,WAAW,eAAe;IAC9B,8GAA8G;IAC9G,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUtH;AAED,8FAA8F;AAC9F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAQzE;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAM/E"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Finding and rewriting the module specifiers that name engine files — the one core that both
3
+ * `game/scripts/engine-move.mjs` (moving a file inside the repo) and `bitmagic upgrade` (moving
4
+ * a creator project's imports onto an upgraded engine) run, so the two cannot come to disagree on
5
+ * which specifiers count or how a rewritten one is spelt.
6
+ *
7
+ * It takes the TypeScript compiler as a parameter instead of importing it: the published CLI has
8
+ * no TypeScript of its own (it borrows the creator project's), and the move script hands in the
9
+ * copy ts-morph bundles. Nothing here reads the disk; whether a path exists is the caller's
10
+ * `exists` callback.
11
+ *
12
+ * Paths "under the source root" are the engine's own layout: `engine/Door.ts`, `types/game.ts`.
13
+ * The source root is `game/src` in the repo and a project's vendored `engine/` directory in a
14
+ * creator project, where `engine/engine/Door.ts` is the same file.
15
+ */
16
+ import * as path from 'node:path';
17
+ export const SPECIFIER_KINDS = ['static', 'dynamic', 'importType', 'jest', 'require', 'url', 'ambient'];
18
+ /** The `jest.*` calls whose first argument is a module path. */
19
+ export const JEST_PATH_CALLS = new Set([
20
+ 'mock', 'doMock', 'unmock', 'dontMock', 'requireActual', 'requireMock', 'unstable_mockModule', 'createMockFromModule', 'setMock',
21
+ ]);
22
+ /**
23
+ * Every module specifier in a TypeScript file: static imports and re-exports, dynamic `import()`,
24
+ * inline `import('…')` types, `jest.mock` and the other `jest.*` path calls, `require()`,
25
+ * `new URL('…', import.meta.url)` and `declare module '…'`.
26
+ */
27
+ export function findModuleSpecifiers(ts, fileName, text) {
28
+ const kind = /\.tsx$/.test(fileName) ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
29
+ const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, kind);
30
+ const specifiers = [];
31
+ const computedDynamic = [];
32
+ const literalOf = (node) => node !== undefined && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node : null;
33
+ const add = (lit, k) => {
34
+ if (!lit)
35
+ return;
36
+ const start = lit.getStart(sf);
37
+ specifiers.push({ kind: k, value: lit.text, start, end: lit.end, quote: text[start] ?? "'" });
38
+ };
39
+ const visit = (node) => {
40
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
41
+ add(literalOf(node.moduleSpecifier), 'static');
42
+ }
43
+ else if (ts.isImportTypeNode(node)) {
44
+ const arg = node.argument;
45
+ if (ts.isLiteralTypeNode(arg))
46
+ add(literalOf(arg.literal), 'importType');
47
+ }
48
+ else if (ts.isModuleDeclaration(node)) {
49
+ if (ts.isStringLiteral(node.name))
50
+ add(node.name, 'ambient');
51
+ }
52
+ else if (ts.isCallExpression(node)) {
53
+ const callee = node.expression;
54
+ const first = node.arguments[0];
55
+ if (callee.kind === ts.SyntaxKind.ImportKeyword) {
56
+ const lit = literalOf(first);
57
+ if (lit)
58
+ add(lit, 'dynamic');
59
+ else if (first)
60
+ computedDynamic.push(`import(${first.getText(sf)})`);
61
+ }
62
+ else if (ts.isPropertyAccessExpression(callee) && callee.expression.getText(sf) === 'jest'
63
+ && JEST_PATH_CALLS.has(callee.name.text)) {
64
+ add(literalOf(first), 'jest');
65
+ }
66
+ else if (ts.isIdentifier(callee) && callee.text === 'require') {
67
+ add(literalOf(first), 'require');
68
+ }
69
+ }
70
+ else if (ts.isNewExpression(node)) {
71
+ const args = node.arguments ?? [];
72
+ if (node.expression.getText(sf) === 'URL' && args[1] !== undefined && args[1].getText(sf) === 'import.meta.url') {
73
+ add(literalOf(args[0]), 'url');
74
+ }
75
+ }
76
+ ts.forEachChild(node, visit);
77
+ };
78
+ visit(sf);
79
+ return { specifiers, computedDynamic };
80
+ }
81
+ /** A path without its source/output extension: `engine/Door.ts` → `engine/Door`. */
82
+ export function stemOf(p) {
83
+ return p.replace(/\.(d\.ts|ts|js|mjs|cjs)$/, '');
84
+ }
85
+ /** The extension a specifier is written with (`.js`, `.ts`, `.mjs`), or '' for none. */
86
+ export function extOf(spec) {
87
+ return /\.(ts|js|mjs)$/.exec(spec)?.[0] ?? '';
88
+ }
89
+ const toPosix = (p) => p.split(path.sep).join('/');
90
+ /**
91
+ * The file under the source root an alias (`engine/Door.js`) or relative (`../engine/Door.js`)
92
+ * specifier written in `importerAbs` names, or null when it names something else (a package, a
93
+ * file outside the root, a file that does not exist).
94
+ */
95
+ export function resolveSourceSpecifier(spec, importerAbs, options) {
96
+ let base;
97
+ if (spec.startsWith('.'))
98
+ base = path.resolve(path.dirname(importerAbs), spec);
99
+ else if (options.aliasRoots.includes(spec.split('/')[0] ?? ''))
100
+ base = path.join(options.srcRoot, spec);
101
+ else
102
+ return null;
103
+ base = stemOf(base);
104
+ for (const [candidate, viaIndex] of [[`${base}.ts`, false], [path.join(base, 'index.ts'), true]]) {
105
+ const rel = toPosix(path.relative(options.srcRoot, candidate));
106
+ if (rel.startsWith('..') || path.isAbsolute(rel))
107
+ return null;
108
+ if (options.exists(candidate))
109
+ return { rel, viaIndex };
110
+ }
111
+ return null;
112
+ }
113
+ /**
114
+ * The specifier to write for a target, in the style of the original: an alias stays an alias,
115
+ * a relative specifier stays relative (to the importer's new folder), the extension it was
116
+ * written with is kept, and an index import of a directory that does not move keeps naming the
117
+ * directory.
118
+ */
119
+ export function formatSpecifier(spec, target, importerNewAbs, srcRoot) {
120
+ const ext = extOf(spec);
121
+ const keepDir = target.viaIndex && !target.targetMoves;
122
+ let targetStem = stemOf(target.newRel);
123
+ if (keepDir)
124
+ targetStem = path.posix.dirname(targetStem);
125
+ const withExt = keepDir ? targetStem : `${targetStem}${ext}`;
126
+ if (!spec.startsWith('.'))
127
+ return withExt;
128
+ let rel = toPosix(path.relative(path.dirname(importerNewAbs), path.join(srcRoot, withExt)));
129
+ if (!rel.startsWith('.'))
130
+ rel = `./${rel}`;
131
+ return rel;
132
+ }
133
+ /**
134
+ * The `MOVED_MODULES` list out of the text of `engine/compat/aliases.ts`. The file is generated
135
+ * by `engine-move.mjs` in one fixed format (`{ from: '…', to: '…', removeIn: '…' }` per line), so
136
+ * this reads it with the same pattern the `no-internal-shim-imports` lint rule uses rather than
137
+ * evaluating TypeScript.
138
+ */
139
+ export function parseMovedModules(aliasesText) {
140
+ const out = [];
141
+ for (const m of aliasesText.matchAll(/\{\s*from:\s*'([^']+)',\s*to:\s*'([^']+)'/g)) {
142
+ const from = m[1];
143
+ const to = m[2];
144
+ if (from !== undefined && to !== undefined)
145
+ out.push({ from, to });
146
+ }
147
+ return out;
148
+ }
149
+ /** Apply position edits (non-overlapping) to a text, last first so earlier offsets stay valid. */
150
+ export function applyTextEdits(text, edits) {
151
+ let out = text;
152
+ for (const edit of [...edits].sort((a, b) => b.start - a.start)) {
153
+ out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
154
+ }
155
+ return out;
156
+ }
157
+ //# sourceMappingURL=engine-specifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-specifiers.js","sourceRoot":"","sources":["../src/engine-specifiers.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@
24
24
  import { z } from 'zod';
25
25
  /**
26
26
  * The engine's closed vocabulary (`VoxelMaterialClassName` in
27
- * `game/src/engine/VoxelMaterialClass.ts`), mirrored because this package cannot
27
+ * `game/src/engine/voxel/materials/VoxelMaterialClass.ts`), mirrored because this package cannot
28
28
  * import the engine. A test pins the two lists to each other.
29
29
  */
30
30
  export declare const VOXEL_MATERIAL_CLASS_NAMES: readonly ["matte", "cloth", "fur", "leather", "wood", "stone", "plastic", "paint", "metal", "gold", "chrome", "gem", "glass", "filament", "neon", "lava"];
@@ -24,7 +24,7 @@
24
24
  import { z } from 'zod';
25
25
  /**
26
26
  * The engine's closed vocabulary (`VoxelMaterialClassName` in
27
- * `game/src/engine/VoxelMaterialClass.ts`), mirrored because this package cannot
27
+ * `game/src/engine/voxel/materials/VoxelMaterialClass.ts`), mirrored because this package cannot
28
28
  * import the engine. A test pins the two lists to each other.
29
29
  */
30
30
  export const VOXEL_MATERIAL_CLASS_NAMES = [
@@ -0,0 +1,29 @@
1
+ /** One layer of the merge. */
2
+ export interface OverlayLayer {
3
+ /** The directory whose files the layer contributes. A missing directory contributes nothing. */
4
+ dir: string;
5
+ /** A folder (posix) the layer's files land under in the result: `source` puts `Game.ts` at `source/Game.ts`. */
6
+ prefix?: string;
7
+ /** Keep only the files this accepts, given their posix path relative to `dir`. */
8
+ include?: (rel: string) => boolean;
9
+ }
10
+ /** Every file under `dir`, as posix paths relative to it, sorted. */
11
+ export declare function listFilesRecursive(dir: string): string[];
12
+ /**
13
+ * The merged file map: result path (posix, relative to the destination) → the absolute source
14
+ * file that wins it. Later layers win, so pass the baseline first and the overlay after it.
15
+ */
16
+ export declare function collectLayeredFiles(layers: readonly OverlayLayer[]): Map<string, string>;
17
+ /** Copy a merged file map into `destDir`, creating folders as needed. Returns the number of files written. */
18
+ export declare function writeLayeredFiles(files: ReadonlyMap<string, string>, destDir: string): number;
19
+ export interface BaselineOverlayMerge {
20
+ baselineDir: string;
21
+ /** The template's overlay; absent or missing means the baseline alone. */
22
+ overlayDir?: string;
23
+ destDir: string;
24
+ /** Applied to both layers. */
25
+ include?: (rel: string) => boolean;
26
+ }
27
+ /** Baseline first, overlay wins, written to `destDir`. Returns the number of files written. */
28
+ export declare function mergeBaselineAndOverlay(merge: BaselineOverlayMerge): number;
29
+ //# sourceMappingURL=template-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-overlay.d.ts","sourceRoot":"","sources":["../src/template-overlay.ts"],"names":[],"mappings":"AAYA,8BAA8B;AAC9B,MAAM,WAAW,YAAY;IAC3B,gGAAgG;IAChG,GAAG,EAAE,MAAM,CAAC;IACZ,gHAAgH;IAChH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAYxD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxF;AAED,8GAA8G;AAC9G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAS7F;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC;AAED,+FAA+F;AAC/F,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI3E"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The baseline-plus-overlay merge every new game is built from: the baseline's files first, then
3
+ * a community template's overlay on top, the overlay winning wherever both have a file.
4
+ *
5
+ * One implementation for the three places that do it — `bitmagic init` (cli/src/scaffold), the
6
+ * agent's template import and in-place template switch (ExportImportController) and the template
7
+ * type-check (game/scripts/check-templates.cjs) — so the game a creator gets, the game the
8
+ * Creator gets and the game `pnpm run check` compiles are the same merge.
9
+ */
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ /** Every file under `dir`, as posix paths relative to it, sorted. */
13
+ export function listFilesRecursive(dir) {
14
+ const out = [];
15
+ const walk = (abs, rel) => {
16
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
17
+ const childAbs = path.join(abs, entry.name);
18
+ const childRel = rel === '' ? entry.name : `${rel}/${entry.name}`;
19
+ if (entry.isDirectory())
20
+ walk(childAbs, childRel);
21
+ else
22
+ out.push(childRel);
23
+ }
24
+ };
25
+ if (fs.existsSync(dir))
26
+ walk(dir, '');
27
+ return out.sort();
28
+ }
29
+ /**
30
+ * The merged file map: result path (posix, relative to the destination) → the absolute source
31
+ * file that wins it. Later layers win, so pass the baseline first and the overlay after it.
32
+ */
33
+ export function collectLayeredFiles(layers) {
34
+ const files = new Map();
35
+ for (const layer of layers) {
36
+ for (const rel of listFilesRecursive(layer.dir)) {
37
+ if (layer.include && !layer.include(rel))
38
+ continue;
39
+ const dest = layer.prefix ? `${layer.prefix.replace(/\/+$/, '')}/${rel}` : rel;
40
+ files.set(dest, path.join(layer.dir, ...rel.split('/')));
41
+ }
42
+ }
43
+ return files;
44
+ }
45
+ /** Copy a merged file map into `destDir`, creating folders as needed. Returns the number of files written. */
46
+ export function writeLayeredFiles(files, destDir) {
47
+ let written = 0;
48
+ for (const [rel, abs] of files) {
49
+ const dest = path.join(destDir, ...rel.split('/'));
50
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
51
+ fs.copyFileSync(abs, dest);
52
+ written++;
53
+ }
54
+ return written;
55
+ }
56
+ /** Baseline first, overlay wins, written to `destDir`. Returns the number of files written. */
57
+ export function mergeBaselineAndOverlay(merge) {
58
+ const dirs = merge.overlayDir === undefined ? [merge.baselineDir] : [merge.baselineDir, merge.overlayDir];
59
+ const layers = dirs.map((dir) => ({ dir, include: merge.include }));
60
+ return writeLayeredFiles(collectLayeredFiles(layers), merge.destDir);
61
+ }
62
+ //# sourceMappingURL=template-overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-overlay.js","sourceRoot":"","sources":["../src/template-overlay.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  * Two sinks run it: the CLI's `bitmagic dev` WebSocket (cli/src/editor/recording-stream.ts) and
5
5
  * game-play-agent's chunked-HTTP sink for the web Creator.
6
6
  *
7
- * The recorder's worker encodes every frame with WebCodecs (see game/src/debug/RecordStream.ts)
7
+ * The recorder's worker encodes every frame with WebCodecs (see game/src/engine/recording/RecordStream.ts)
8
8
  * and sends `[frameIndex u32 LE][keyframe u8][chunk bytes]`. H.264 arrives as Annex B — SPS/PPS
9
9
  * inline on every keyframe — so appending the chunks gives a valid `.h264` elementary stream; VP9
10
10
  * has no such framing, so its chunks go into an IVF file (a 32-byte header and a 12-byte header
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitmagic/asset-core",
3
- "version": "0.2.38-dev.3",
3
+ "version": "0.2.38-dev.5",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "author": "Bitmagic Oy",
6
6
  "type": "module",
@@ -50,6 +50,14 @@
50
50
  "./trailer": {
51
51
  "types": "./dist/trailer/index.d.ts",
52
52
  "default": "./dist/trailer/index.js"
53
+ },
54
+ "./engine-specifiers": {
55
+ "types": "./dist/engine-specifiers.d.ts",
56
+ "default": "./dist/engine-specifiers.js"
57
+ },
58
+ "./template-overlay": {
59
+ "types": "./dist/template-overlay.d.ts",
60
+ "default": "./dist/template-overlay.js"
53
61
  }
54
62
  },
55
63
  "files": [