@auraindustry/aurajs 0.0.1
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/benchmarks/perf-thresholds.json +102 -0
- package/package.json +47 -0
- package/src/.gitkeep +0 -0
- package/src/asset-pack.mjs +316 -0
- package/src/build-contract.mjs +71 -0
- package/src/bundler.mjs +698 -0
- package/src/cli.mjs +764 -0
- package/src/config.mjs +460 -0
- package/src/conformance-runner.mjs +322 -0
- package/src/conformance.mjs +5407 -0
- package/src/headless-test.mjs +4314 -0
- package/src/host-binary.mjs +423 -0
- package/src/perf-benchmark-runner.mjs +233 -0
- package/src/perf-benchmark.mjs +549 -0
- package/src/postinstall.mjs +26 -0
- package/src/scaffold.mjs +74 -0
- package/templates/starter/aura.config.json +28 -0
- package/templates/starter/src/main.js +226 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"description": "AuraJS headless performance regression guardrails (cold-start + warm-cache)",
|
|
4
|
+
"scenePolicy": {
|
|
5
|
+
"requireAllConfiguredScenes": true
|
|
6
|
+
},
|
|
7
|
+
"scenes": {
|
|
8
|
+
"draw2d_primitives": {
|
|
9
|
+
"maxAvgFrameTimeMs": 20,
|
|
10
|
+
"maxJitterMs": 8,
|
|
11
|
+
"minFps": 30,
|
|
12
|
+
"maxP50FrameTimeMs": 20,
|
|
13
|
+
"maxP95FrameTimeMs": 30,
|
|
14
|
+
"maxP99FrameTimeMs": 40,
|
|
15
|
+
"maxStutterBurstCount": 10
|
|
16
|
+
},
|
|
17
|
+
"sprite_transform_swarm": {
|
|
18
|
+
"maxAvgFrameTimeMs": 20,
|
|
19
|
+
"maxJitterMs": 8,
|
|
20
|
+
"minFps": 30,
|
|
21
|
+
"maxP50FrameTimeMs": 20,
|
|
22
|
+
"maxP95FrameTimeMs": 30,
|
|
23
|
+
"maxP99FrameTimeMs": 40,
|
|
24
|
+
"maxStutterBurstCount": 10
|
|
25
|
+
},
|
|
26
|
+
"math_collision": {
|
|
27
|
+
"maxAvgFrameTimeMs": 20,
|
|
28
|
+
"maxJitterMs": 8,
|
|
29
|
+
"minFps": 30,
|
|
30
|
+
"maxP50FrameTimeMs": 20,
|
|
31
|
+
"maxP95FrameTimeMs": 30,
|
|
32
|
+
"maxP99FrameTimeMs": 40,
|
|
33
|
+
"maxStutterBurstCount": 10
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"coldStart": {
|
|
37
|
+
"scenes": {
|
|
38
|
+
"draw2d_primitives": {
|
|
39
|
+
"maxAvgFrameTimeMs": 28,
|
|
40
|
+
"maxJitterMs": 10,
|
|
41
|
+
"minFps": 24,
|
|
42
|
+
"maxP50FrameTimeMs": 28,
|
|
43
|
+
"maxP95FrameTimeMs": 40,
|
|
44
|
+
"maxP99FrameTimeMs": 50,
|
|
45
|
+
"maxStutterBurstCount": 2
|
|
46
|
+
},
|
|
47
|
+
"sprite_transform_swarm": {
|
|
48
|
+
"maxAvgFrameTimeMs": 28,
|
|
49
|
+
"maxJitterMs": 10,
|
|
50
|
+
"minFps": 24,
|
|
51
|
+
"maxP50FrameTimeMs": 28,
|
|
52
|
+
"maxP95FrameTimeMs": 40,
|
|
53
|
+
"maxP99FrameTimeMs": 50,
|
|
54
|
+
"maxStutterBurstCount": 2
|
|
55
|
+
},
|
|
56
|
+
"math_collision": {
|
|
57
|
+
"maxAvgFrameTimeMs": 28,
|
|
58
|
+
"maxJitterMs": 10,
|
|
59
|
+
"minFps": 24,
|
|
60
|
+
"maxP50FrameTimeMs": 28,
|
|
61
|
+
"maxP95FrameTimeMs": 40,
|
|
62
|
+
"maxP99FrameTimeMs": 50,
|
|
63
|
+
"maxStutterBurstCount": 2
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"warmCache": {
|
|
68
|
+
"scenes": {
|
|
69
|
+
"draw2d_primitives": {
|
|
70
|
+
"maxAvgFrameTimeMs": 18,
|
|
71
|
+
"maxJitterMs": 6,
|
|
72
|
+
"minFps": 40,
|
|
73
|
+
"maxP50FrameTimeMs": 18,
|
|
74
|
+
"maxP95FrameTimeMs": 28,
|
|
75
|
+
"maxP99FrameTimeMs": 36,
|
|
76
|
+
"maxStutterBurstCount": 8
|
|
77
|
+
},
|
|
78
|
+
"sprite_transform_swarm": {
|
|
79
|
+
"maxAvgFrameTimeMs": 18,
|
|
80
|
+
"maxJitterMs": 6,
|
|
81
|
+
"minFps": 40,
|
|
82
|
+
"maxP50FrameTimeMs": 18,
|
|
83
|
+
"maxP95FrameTimeMs": 28,
|
|
84
|
+
"maxP99FrameTimeMs": 36,
|
|
85
|
+
"maxStutterBurstCount": 8
|
|
86
|
+
},
|
|
87
|
+
"math_collision": {
|
|
88
|
+
"maxAvgFrameTimeMs": 18,
|
|
89
|
+
"maxJitterMs": 6,
|
|
90
|
+
"minFps": 40,
|
|
91
|
+
"maxP50FrameTimeMs": 18,
|
|
92
|
+
"maxP95FrameTimeMs": 28,
|
|
93
|
+
"maxP99FrameTimeMs": 36,
|
|
94
|
+
"maxStutterBurstCount": 8
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"latency": {
|
|
99
|
+
"maxInputQueryMs": 0.05,
|
|
100
|
+
"maxAudioCallMs": 0.05
|
|
101
|
+
}
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auraindustry/aurajs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Write games in JavaScript, build native binaries.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aura": "./src/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src/",
|
|
14
|
+
"benchmarks/perf-thresholds.json",
|
|
15
|
+
"templates/"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node ./src/postinstall.mjs",
|
|
19
|
+
"conformance": "node ./src/conformance-runner.mjs",
|
|
20
|
+
"test:conformance": "node --test ./test/conformance.test.mjs",
|
|
21
|
+
"test:distribution": "node --test ./test/distribution-contract.test.mjs",
|
|
22
|
+
"benchmark": "node ./src/perf-benchmark-runner.mjs",
|
|
23
|
+
"benchmark:strict": "node ./src/perf-benchmark-runner.mjs --strict",
|
|
24
|
+
"test:benchmark": "node --test ./test/perf-benchmark.test.mjs",
|
|
25
|
+
"test:e2e": "node --test ./test/e2e-flow.test.mjs"
|
|
26
|
+
},
|
|
27
|
+
"optionalDependencies": {
|
|
28
|
+
"@aurajs/darwin-arm64": "github:Aura-Industry/aurajs-darwin-arm64",
|
|
29
|
+
"@aurajs/darwin-x64": "github:Aura-Industry/aurajs-darwin-x64",
|
|
30
|
+
"@aurajs/linux-x64": "github:Aura-Industry/aurajs-linux-x64",
|
|
31
|
+
"@aurajs/win32-x64": "github:Aura-Industry/aurajs-win32-x64"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"game",
|
|
35
|
+
"gamedev",
|
|
36
|
+
"native",
|
|
37
|
+
"binary",
|
|
38
|
+
"2d",
|
|
39
|
+
"sdl2",
|
|
40
|
+
"wgpu",
|
|
41
|
+
"v8"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs';
|
|
12
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
13
|
+
|
|
14
|
+
const SUPPORTED = {
|
|
15
|
+
png: 'image',
|
|
16
|
+
jpg: 'image',
|
|
17
|
+
jpeg: 'image',
|
|
18
|
+
wav: 'audio',
|
|
19
|
+
ogg: 'audio',
|
|
20
|
+
mp3: 'audio',
|
|
21
|
+
ttf: 'font',
|
|
22
|
+
otf: 'font',
|
|
23
|
+
json: 'json',
|
|
24
|
+
txt: 'text',
|
|
25
|
+
csv: 'text',
|
|
26
|
+
xml: 'text',
|
|
27
|
+
toml: 'text',
|
|
28
|
+
yaml: 'text',
|
|
29
|
+
yml: 'text',
|
|
30
|
+
ini: 'text',
|
|
31
|
+
cfg: 'text',
|
|
32
|
+
log: 'text',
|
|
33
|
+
md: 'text',
|
|
34
|
+
bin: 'bytes',
|
|
35
|
+
dat: 'bytes',
|
|
36
|
+
raw: 'bytes',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const APPROVED_FORMATS = [
|
|
40
|
+
'.png', '.jpg', '.jpeg',
|
|
41
|
+
'.wav', '.ogg', '.mp3',
|
|
42
|
+
'.ttf', '.otf',
|
|
43
|
+
'.json',
|
|
44
|
+
'.txt', '.csv', '.xml', '.toml', '.yaml', '.yml', '.ini', '.cfg', '.log', '.md',
|
|
45
|
+
'.bin', '.dat', '.raw',
|
|
46
|
+
].join(', ');
|
|
47
|
+
|
|
48
|
+
export function packageAssets(options = {}) {
|
|
49
|
+
const projectRoot = resolve(options.projectRoot || process.cwd());
|
|
50
|
+
const outRoot = resolve(options.outRoot || join(projectRoot, 'build'));
|
|
51
|
+
const mode = options.mode || 'embed';
|
|
52
|
+
|
|
53
|
+
if (mode !== 'embed' && mode !== 'sibling') {
|
|
54
|
+
throw new Error(`AURA_ASSET_002: unsupported --asset-mode \"${mode}\". Use \"embed\" or \"sibling\".`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const assetsRoot = resolve(projectRoot, 'assets');
|
|
58
|
+
const assets = collectAssets(assetsRoot);
|
|
59
|
+
const indexed = assets.map((absPath) => {
|
|
60
|
+
const rel = toPosix(relative(assetsRoot, absPath));
|
|
61
|
+
const ext = extensionOf(rel);
|
|
62
|
+
const kind = SUPPORTED[ext];
|
|
63
|
+
|
|
64
|
+
if (!kind) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`AURA_ASSET_001: unsupported asset format for \"assets/${rel}\" (.${ext || 'none'}). ` +
|
|
67
|
+
`Approved AS-005 formats: ${APPROVED_FORMATS}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const bytes = readFileSync(absPath);
|
|
72
|
+
return {
|
|
73
|
+
path: rel,
|
|
74
|
+
absolutePath: absPath,
|
|
75
|
+
kind,
|
|
76
|
+
bytes,
|
|
77
|
+
sizeBytes: bytes.length,
|
|
78
|
+
sha1: sha1(bytes),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const canonicalEntries = indexed
|
|
83
|
+
.map((entry) => ({
|
|
84
|
+
path: entry.path,
|
|
85
|
+
kind: entry.kind,
|
|
86
|
+
sizeBytes: entry.sizeBytes,
|
|
87
|
+
sha1: entry.sha1,
|
|
88
|
+
}))
|
|
89
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
90
|
+
const manifestHash = sha1(Buffer.from(JSON.stringify(canonicalEntries)));
|
|
91
|
+
const manifestPath = join(outRoot, 'assets-manifest.json');
|
|
92
|
+
const previousManifest = readJsonIfExists(manifestPath);
|
|
93
|
+
|
|
94
|
+
const manifest = {
|
|
95
|
+
schema: 'aurajs.asset-manifest.v1',
|
|
96
|
+
runtimeResolver: mode === 'embed' ? 'embedded' : 'disk',
|
|
97
|
+
mode,
|
|
98
|
+
manifestHash,
|
|
99
|
+
pack: null,
|
|
100
|
+
entries: [],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const incremental = {
|
|
104
|
+
copied: 0,
|
|
105
|
+
skipped: 0,
|
|
106
|
+
removed: 0,
|
|
107
|
+
reusedPack: false,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (mode === 'embed') {
|
|
111
|
+
const packRel = 'assets/asset-pack.pak';
|
|
112
|
+
const packPath = join(outRoot, packRel);
|
|
113
|
+
const canReusePack = (
|
|
114
|
+
previousManifest
|
|
115
|
+
&& previousManifest.mode === 'embed'
|
|
116
|
+
&& previousManifest.manifestHash === manifestHash
|
|
117
|
+
&& previousManifest.pack
|
|
118
|
+
&& existsSync(packPath)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (canReusePack) {
|
|
122
|
+
manifest.pack = previousManifest.pack;
|
|
123
|
+
manifest.entries = Array.isArray(previousManifest.entries) ? previousManifest.entries : [];
|
|
124
|
+
incremental.reusedPack = true;
|
|
125
|
+
incremental.skipped = indexed.length;
|
|
126
|
+
} else {
|
|
127
|
+
const pack = buildPak(indexed);
|
|
128
|
+
mkdirSync(join(outRoot, 'assets'), { recursive: true });
|
|
129
|
+
writeFileSync(packPath, pack.buffer);
|
|
130
|
+
|
|
131
|
+
manifest.pack = {
|
|
132
|
+
format: 'aurajs-pak-v1',
|
|
133
|
+
file: packRel,
|
|
134
|
+
sha1: sha1(pack.buffer),
|
|
135
|
+
sizeBytes: pack.buffer.length,
|
|
136
|
+
entryCount: indexed.length,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
manifest.entries = pack.entries.map((entry) => ({
|
|
140
|
+
path: entry.path,
|
|
141
|
+
kind: entry.kind,
|
|
142
|
+
sizeBytes: entry.size,
|
|
143
|
+
sha1: entry.sha1,
|
|
144
|
+
offset: entry.offset,
|
|
145
|
+
packSize: entry.size,
|
|
146
|
+
}));
|
|
147
|
+
incremental.copied = indexed.length;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const siblingAssets = join(outRoot, 'assets');
|
|
151
|
+
if (existsSync(siblingAssets)) {
|
|
152
|
+
for (const child of readdirSync(siblingAssets)) {
|
|
153
|
+
if (child === 'asset-pack.pak') continue;
|
|
154
|
+
rmSync(join(siblingAssets, child), { recursive: true, force: true });
|
|
155
|
+
incremental.removed += 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const siblingRoot = join(outRoot, 'assets');
|
|
160
|
+
mkdirSync(siblingRoot, { recursive: true });
|
|
161
|
+
|
|
162
|
+
const previousEntriesByPath = new Map();
|
|
163
|
+
if (previousManifest && Array.isArray(previousManifest.entries)) {
|
|
164
|
+
for (const entry of previousManifest.entries) {
|
|
165
|
+
if (entry && typeof entry.path === 'string' && typeof entry.sha1 === 'string') {
|
|
166
|
+
previousEntriesByPath.set(entry.path, entry.sha1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const expectedPaths = new Set(indexed.map((entry) => entry.path));
|
|
172
|
+
for (const existing of collectAssets(siblingRoot)) {
|
|
173
|
+
const rel = toPosix(relative(siblingRoot, existing));
|
|
174
|
+
if (!expectedPaths.has(rel)) {
|
|
175
|
+
rmSync(existing, { force: true });
|
|
176
|
+
incremental.removed += 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const entry of indexed) {
|
|
181
|
+
const dest = join(siblingRoot, ...entry.path.split('/'));
|
|
182
|
+
mkdirSync(resolve(dest, '..'), { recursive: true });
|
|
183
|
+
const previousSha = previousEntriesByPath.get(entry.path);
|
|
184
|
+
if (previousManifest?.mode === 'sibling' && previousSha === entry.sha1 && existsSync(dest)) {
|
|
185
|
+
incremental.skipped += 1;
|
|
186
|
+
} else {
|
|
187
|
+
copyFileSync(entry.absolutePath, dest);
|
|
188
|
+
incremental.copied += 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
manifest.entries = indexed.map((entry) => ({
|
|
192
|
+
path: entry.path,
|
|
193
|
+
kind: entry.kind,
|
|
194
|
+
sizeBytes: entry.sizeBytes,
|
|
195
|
+
sha1: entry.sha1,
|
|
196
|
+
file: `assets/${entry.path}`,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
writeManifest(manifestPath, manifest);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
mode,
|
|
204
|
+
assetCount: indexed.length,
|
|
205
|
+
manifestHash,
|
|
206
|
+
incremental,
|
|
207
|
+
manifestPath,
|
|
208
|
+
packPath: manifest.pack ? join(outRoot, manifest.pack.file) : null,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function buildPak(entries) {
|
|
213
|
+
const sorted = [...entries].sort((a, b) => a.path.localeCompare(b.path));
|
|
214
|
+
|
|
215
|
+
let tocSize = 4;
|
|
216
|
+
for (const entry of sorted) {
|
|
217
|
+
tocSize += 4 + Buffer.byteLength(entry.path, 'utf8') + 4 + 4;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let cursor = tocSize;
|
|
221
|
+
const rows = sorted.map((entry) => {
|
|
222
|
+
const size = entry.bytes.length;
|
|
223
|
+
const row = {
|
|
224
|
+
path: entry.path,
|
|
225
|
+
kind: entry.kind,
|
|
226
|
+
sha1: entry.sha1,
|
|
227
|
+
offset: cursor,
|
|
228
|
+
size,
|
|
229
|
+
bytes: entry.bytes,
|
|
230
|
+
};
|
|
231
|
+
cursor += size;
|
|
232
|
+
return row;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const totalSize = cursor;
|
|
236
|
+
const out = Buffer.alloc(totalSize);
|
|
237
|
+
let offset = 0;
|
|
238
|
+
|
|
239
|
+
out.writeUInt32LE(rows.length, offset);
|
|
240
|
+
offset += 4;
|
|
241
|
+
|
|
242
|
+
for (const row of rows) {
|
|
243
|
+
const pathBytes = Buffer.from(row.path, 'utf8');
|
|
244
|
+
out.writeUInt32LE(pathBytes.length, offset);
|
|
245
|
+
offset += 4;
|
|
246
|
+
pathBytes.copy(out, offset);
|
|
247
|
+
offset += pathBytes.length;
|
|
248
|
+
out.writeUInt32LE(row.offset, offset);
|
|
249
|
+
offset += 4;
|
|
250
|
+
out.writeUInt32LE(row.size, offset);
|
|
251
|
+
offset += 4;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const row of rows) {
|
|
255
|
+
row.bytes.copy(out, row.offset);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { buffer: out, entries: rows };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function collectAssets(assetsRoot) {
|
|
262
|
+
if (!existsSync(assetsRoot)) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const out = [];
|
|
267
|
+
walkDir(assetsRoot, out);
|
|
268
|
+
out.sort((a, b) => toPosix(a).localeCompare(toPosix(b)));
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function walkDir(dir, out) {
|
|
273
|
+
const items = readdirSync(dir).sort((a, b) => a.localeCompare(b));
|
|
274
|
+
for (const item of items) {
|
|
275
|
+
const full = join(dir, item);
|
|
276
|
+
const stat = statSync(full);
|
|
277
|
+
if (stat.isDirectory()) {
|
|
278
|
+
walkDir(full, out);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
out.push(full);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function extensionOf(path) {
|
|
286
|
+
const idx = path.lastIndexOf('.');
|
|
287
|
+
if (idx < 0 || idx === path.length - 1) return '';
|
|
288
|
+
return path.slice(idx + 1).toLowerCase();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function writeManifest(path, manifest) {
|
|
292
|
+
const normalized = {
|
|
293
|
+
...manifest,
|
|
294
|
+
entries: [...manifest.entries].sort((a, b) => a.path.localeCompare(b.path)),
|
|
295
|
+
};
|
|
296
|
+
writeFileSync(path, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function readJsonIfExists(path) {
|
|
300
|
+
if (!existsSync(path)) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function toPosix(input) {
|
|
311
|
+
return input.split(sep).join('/');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function sha1(value) {
|
|
315
|
+
return createHash('sha1').update(value).digest('hex');
|
|
316
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export const BUILD_MANIFEST_SCHEMA = 'aurajs.build-manifest.v1';
|
|
5
|
+
|
|
6
|
+
export function writeBuildManifest(options = {}) {
|
|
7
|
+
const outRoot = resolve(options.outRoot || process.cwd());
|
|
8
|
+
const bundlePath = resolveRequiredPath(options.bundlePath, 'bundlePath');
|
|
9
|
+
const assetsManifestPath = resolveRequiredPath(options.assetsManifestPath, 'assetsManifestPath');
|
|
10
|
+
const executablePath = options.executablePath
|
|
11
|
+
? resolveRequiredPath(options.executablePath, 'executablePath')
|
|
12
|
+
: null;
|
|
13
|
+
const assetMode = normalizeAssetMode(options.assetMode || 'embed');
|
|
14
|
+
const runtimeResolver = assetMode === 'embed' ? 'embedded' : 'disk';
|
|
15
|
+
const icon = options.icon && typeof options.icon === 'object' ? options.icon : null;
|
|
16
|
+
const identity = options.identity && typeof options.identity === 'object' ? options.identity : null;
|
|
17
|
+
const iconStatus = typeof icon?.status === 'string' ? icon.status : 'not_configured';
|
|
18
|
+
const iconReasonCode = typeof icon?.reasonCode === 'string' ? icon.reasonCode : null;
|
|
19
|
+
|
|
20
|
+
const manifest = {
|
|
21
|
+
schema: BUILD_MANIFEST_SCHEMA,
|
|
22
|
+
entrypoint: toPosix(relative(outRoot, bundlePath)),
|
|
23
|
+
assetsManifest: toPosix(relative(outRoot, assetsManifestPath)),
|
|
24
|
+
executable: executablePath ? toPosix(relative(outRoot, executablePath)) : null,
|
|
25
|
+
assetMode,
|
|
26
|
+
runtimeResolver,
|
|
27
|
+
identity: {
|
|
28
|
+
name: typeof identity?.name === 'string' ? identity.name : null,
|
|
29
|
+
version: typeof identity?.version === 'string' ? identity.version : null,
|
|
30
|
+
windowTitle: typeof identity?.windowTitle === 'string' ? identity.windowTitle : null,
|
|
31
|
+
executableBaseName: typeof identity?.executableBaseName === 'string' ? identity.executableBaseName : null,
|
|
32
|
+
executableFileName: typeof identity?.executableFileName === 'string' ? identity.executableFileName : null,
|
|
33
|
+
},
|
|
34
|
+
icon: {
|
|
35
|
+
configuredPath: typeof icon?.configuredPath === 'string' ? icon.configuredPath : null,
|
|
36
|
+
sourcePath: resolveOptionalRelative(outRoot, icon?.sourcePath),
|
|
37
|
+
outputPath: resolveOptionalRelative(outRoot, icon?.outputPath),
|
|
38
|
+
status: iconStatus,
|
|
39
|
+
reasonCode: iconReasonCode,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const manifestPath = resolve(outRoot, 'build-manifest.json');
|
|
44
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
45
|
+
return manifestPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveRequiredPath(pathLike, fieldName) {
|
|
49
|
+
if (typeof pathLike !== 'string' || pathLike.length === 0) {
|
|
50
|
+
throw new Error(`build contract requires a non-empty "${fieldName}" path.`);
|
|
51
|
+
}
|
|
52
|
+
return resolve(pathLike);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeAssetMode(value) {
|
|
56
|
+
if (value !== 'embed' && value !== 'sibling') {
|
|
57
|
+
throw new Error(`unsupported asset mode "${value}" for build contract.`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toPosix(input) {
|
|
63
|
+
return input.split(sep).join('/');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveOptionalRelative(outRoot, pathLike) {
|
|
67
|
+
if (typeof pathLike !== 'string' || pathLike.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return toPosix(relative(outRoot, resolve(pathLike)));
|
|
71
|
+
}
|