@auraindustry/aurajs 0.1.3 → 0.1.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.
- package/README.md +7 -0
- package/benchmarks/perf-thresholds.json +27 -0
- package/package.json +6 -1
- package/src/ai-guidance.mjs +302 -0
- package/src/authored-project.mjs +498 -2
- package/src/build-contract/capabilities.mjs +87 -1
- package/src/build-contract/constants.mjs +1 -0
- package/src/build-contract.mjs +2 -0
- package/src/bundler.mjs +143 -13
- package/src/cli.mjs +681 -13
- package/src/commands/packs.mjs +741 -0
- package/src/commands/project-authoring.mjs +128 -1
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
- package/src/conformance/cases/core-runtime-cases.mjs +6 -2
- package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +265 -4
- package/src/conformance-mobile.mjs +166 -0
- package/src/conformance.mjs +89 -30
- package/src/evidence-bundle.mjs +242 -0
- package/src/headless-test/runtime-coordinator.mjs +186 -33
- package/src/headless-test.mjs +2 -0
- package/src/helpers/2d/index.mjs +183 -0
- package/src/helpers/index.mjs +26 -0
- package/src/helpers/starter-utils/adventure-objectives.js +102 -0
- package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
- package/src/helpers/starter-utils/animation-2d.js +337 -0
- package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
- package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
- package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
- package/src/helpers/starter-utils/avatar-3d.js +404 -0
- package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
- package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
- package/src/helpers/starter-utils/core.js +150 -0
- package/src/helpers/starter-utils/dialogue-2d.js +351 -0
- package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
- package/src/helpers/starter-utils/index.js +26 -0
- package/src/helpers/starter-utils/inventory-2d.js +268 -0
- package/src/helpers/starter-utils/journal-2d.js +267 -0
- package/src/helpers/starter-utils/platformer-3d.js +132 -0
- package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
- package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
- package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
- package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
- package/src/helpers/starter-utils/triggers.js +662 -0
- package/src/helpers/starter-utils/tween-2d.js +615 -0
- package/src/helpers/starter-utils/wave-director.js +101 -0
- package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
- package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
- package/src/mobile/android/build.mjs +606 -0
- package/src/mobile/android/host-artifact.mjs +280 -0
- package/src/mobile/ios/build.mjs +1323 -0
- package/src/mobile/ios/host-artifact.mjs +819 -0
- package/src/mobile/shared/capabilities.mjs +174 -0
- package/src/packs/catalog.mjs +259 -0
- package/src/perf-benchmark-runner.mjs +17 -12
- package/src/perf-benchmark.mjs +408 -4
- package/src/publish-command.mjs +303 -6
- package/src/replay-runtime.mjs +257 -0
- package/src/scaffold/config.mjs +2 -0
- package/src/scaffold/fs.mjs +8 -1
- package/src/scaffold/project-docs.mjs +43 -1
- package/src/scaffold.mjs +4 -0
- package/src/session-runtime.mjs +4 -3
- package/src/web-conformance.mjs +0 -36
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
- package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
- package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
- package/templates/create/2d-adventure/docs/design/loop.md +4 -3
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
- package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
- package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
- package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
- package/templates/create/3d/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d/src/runtime/capabilities.js +5 -0
- package/templates/create/3d/src/runtime/materials.js +10 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
- package/templates/create/shared/src/runtime/ui-forms.js +552 -0
- package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
- package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
- package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
- package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
- package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
- package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
- package/templates/create/shared/src/starter-utils/index.js +15 -1
- package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
- package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
- package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
- package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
- package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
- package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
- package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
- package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
- package/templates/create-bin/play.js +36 -7
- package/templates/skills/auramaxx/SKILL.md +46 -0
- package/templates/skills/auramaxx/project-requirements.md +68 -0
- package/templates/skills/auramaxx/starter-recipes.md +104 -0
- package/templates/skills/auramaxx/validation-checklist.md +49 -0
- package/templates/skills/aurajs/SKILL.md +0 -96
- package/templates/skills/aurajs/api-contract-3d.md +0 -7
- package/templates/skills/aurajs/api-contract.md +0 -7
package/src/publish-command.mjs
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join, resolve } from 'node:path';
|
|
4
|
+
import { basename, join, resolve } from 'node:path';
|
|
5
5
|
|
|
6
6
|
import { preparePublishPackageSurface } from './external-package-surface.mjs';
|
|
7
7
|
import {
|
|
8
8
|
PACKAGE_INTEGRITY_PUBLISH_FILES,
|
|
9
9
|
writeSignedPackageIntegrityArtifacts,
|
|
10
10
|
} from './package-integrity.mjs';
|
|
11
|
+
import { canPromptInteractively, promptInput, promptSelect } from './terminal-ui.mjs';
|
|
11
12
|
import { resolvePublishEnvExampleSurface } from './publish-env-example.mjs';
|
|
12
13
|
import { validatePublishProject } from './publish-validation.mjs';
|
|
13
14
|
import { formatBytes } from './external-asset-policy.mjs';
|
|
14
15
|
|
|
16
|
+
const SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z-.]+)?$/;
|
|
17
|
+
const SCOPED_PACKAGE_RE = /^@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/;
|
|
18
|
+
const UNSCOPED_PACKAGE_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
19
|
+
|
|
15
20
|
export function isNpmPublishLifecycleInvocation(env = process.env) {
|
|
16
21
|
return env.npm_lifecycle_event === 'publish' && env.npm_command === 'publish';
|
|
17
22
|
}
|
|
@@ -36,6 +41,126 @@ function stripOption(commandArgs, optionName) {
|
|
|
36
41
|
return nextArgs;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
function sanitizePackageSlug(value) {
|
|
45
|
+
const compact = String(value || '')
|
|
46
|
+
.trim()
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/^@[^/]+\//, '')
|
|
49
|
+
.replace(/\s+/g, '-')
|
|
50
|
+
.replace(/[^a-z0-9._-]/g, '-')
|
|
51
|
+
.replace(/-+/g, '-')
|
|
52
|
+
.replace(/^[-._]+|[-._]+$/g, '');
|
|
53
|
+
if (!compact) {
|
|
54
|
+
throw new Error(`Cannot derive package slug from "${value}".`);
|
|
55
|
+
}
|
|
56
|
+
return compact;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildDefaultPackageName(projectRoot) {
|
|
60
|
+
return `@aurajs/${sanitizePackageSlug(basename(projectRoot))}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ensureScopedPackageName(value) {
|
|
64
|
+
const trimmed = String(value || '').trim();
|
|
65
|
+
if (!trimmed) {
|
|
66
|
+
throw new Error('Package name cannot be empty.');
|
|
67
|
+
}
|
|
68
|
+
if (/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/.test(trimmed)) {
|
|
69
|
+
return `@${trimmed}`;
|
|
70
|
+
}
|
|
71
|
+
return trimmed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isValidPackageName(value) {
|
|
75
|
+
const normalized = String(value || '').trim();
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const candidate = ensureScopedPackageName(normalized);
|
|
81
|
+
return SCOPED_PACKAGE_RE.test(candidate) || UNSCOPED_PACKAGE_RE.test(candidate);
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function validatePackageName(value) {
|
|
88
|
+
const normalized = ensureScopedPackageName(value);
|
|
89
|
+
if (!SCOPED_PACKAGE_RE.test(normalized) && !UNSCOPED_PACKAGE_RE.test(normalized)) {
|
|
90
|
+
throw new Error(`Invalid npm package name "${value}".`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeVersion(value) {
|
|
95
|
+
return String(value || '').trim().replace(/^v/i, '');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isValidVersion(value) {
|
|
99
|
+
return SEMVER_RE.test(String(value || '').trim());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validateVersion(value) {
|
|
103
|
+
if (!isValidVersion(value)) {
|
|
104
|
+
throw new Error(`Invalid version "${value}". Expected semver like 1.2.3 or 1.2.3-beta.1.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function bumpVersion(currentVersion, bumpKind) {
|
|
109
|
+
const match = normalizeVersion(currentVersion).match(SEMVER_RE);
|
|
110
|
+
if (!match) {
|
|
111
|
+
throw new Error(`Current version "${currentVersion}" is not valid semver.`);
|
|
112
|
+
}
|
|
113
|
+
const major = Number.parseInt(match[1], 10);
|
|
114
|
+
const minor = Number.parseInt(match[2], 10);
|
|
115
|
+
const patch = Number.parseInt(match[3], 10);
|
|
116
|
+
const prerelease = match[4] || '';
|
|
117
|
+
|
|
118
|
+
if (bumpKind === 'major') return `${major + 1}.0.0${prerelease}`;
|
|
119
|
+
if (bumpKind === 'minor') return `${major}.${minor + 1}.0${prerelease}`;
|
|
120
|
+
return `${major}.${minor}.${patch + 1}${prerelease}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function extractInternalPublishOptions(commandArgs) {
|
|
124
|
+
const sanitizedArgs = [];
|
|
125
|
+
let explicitName = null;
|
|
126
|
+
let explicitVersion = null;
|
|
127
|
+
let yes = false;
|
|
128
|
+
|
|
129
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
130
|
+
const current = commandArgs[index];
|
|
131
|
+
if (current === '--yes' || current === '-y') {
|
|
132
|
+
yes = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (current === '--name') {
|
|
136
|
+
explicitName = commandArgs[index + 1] || '';
|
|
137
|
+
index += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (current.startsWith('--name=')) {
|
|
141
|
+
explicitName = current.slice('--name='.length);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (current === '--version') {
|
|
145
|
+
explicitVersion = commandArgs[index + 1] || '';
|
|
146
|
+
index += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (current.startsWith('--version=')) {
|
|
150
|
+
explicitVersion = current.slice('--version='.length);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
sanitizedArgs.push(current);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
sanitizedArgs,
|
|
158
|
+
explicitName,
|
|
159
|
+
explicitVersion,
|
|
160
|
+
yes,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
39
164
|
export function extractPublishToken(commandArgs, env = process.env) {
|
|
40
165
|
let explicitToken = null;
|
|
41
166
|
|
|
@@ -130,7 +255,7 @@ function createPublishTokenEnv(token, env = process.env) {
|
|
|
130
255
|
};
|
|
131
256
|
}
|
|
132
257
|
|
|
133
|
-
|
|
258
|
+
function readProjectPackageManifest(projectRoot = process.cwd()) {
|
|
134
259
|
const packagePath = resolve(projectRoot, 'package.json');
|
|
135
260
|
if (!existsSync(packagePath)) {
|
|
136
261
|
throw new Error('aura publish requires a package.json in the current project root.');
|
|
@@ -146,9 +271,6 @@ export function readProjectPackage(projectRoot = process.cwd()) {
|
|
|
146
271
|
const packageName = typeof projectPackage?.name === 'string' && projectPackage.name.trim().length > 0
|
|
147
272
|
? projectPackage.name.trim()
|
|
148
273
|
: null;
|
|
149
|
-
if (!packageName) {
|
|
150
|
-
throw new Error('aura publish requires package.json -> name to be set.');
|
|
151
|
-
}
|
|
152
274
|
|
|
153
275
|
return {
|
|
154
276
|
packagePath,
|
|
@@ -157,6 +279,158 @@ export function readProjectPackage(projectRoot = process.cwd()) {
|
|
|
157
279
|
};
|
|
158
280
|
}
|
|
159
281
|
|
|
282
|
+
export function readProjectPackage(projectRoot = process.cwd()) {
|
|
283
|
+
const projectPackage = readProjectPackageManifest(projectRoot);
|
|
284
|
+
if (!projectPackage.packageName) {
|
|
285
|
+
throw new Error('aura publish requires package.json -> name to be set.');
|
|
286
|
+
}
|
|
287
|
+
return projectPackage;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function resolvePublishPackageName(
|
|
291
|
+
commandArgs,
|
|
292
|
+
{
|
|
293
|
+
projectRoot = process.cwd(),
|
|
294
|
+
canPromptInteractivelyImpl = canPromptInteractively,
|
|
295
|
+
promptInputImpl = promptInput,
|
|
296
|
+
} = {},
|
|
297
|
+
) {
|
|
298
|
+
const { packagePath, projectPackage, packageName: currentPackageName } = readProjectPackageManifest(projectRoot);
|
|
299
|
+
const { sanitizedArgs, explicitName, yes } = extractInternalPublishOptions(commandArgs);
|
|
300
|
+
const defaultPackageName = buildDefaultPackageName(projectRoot);
|
|
301
|
+
const currentValidPackageName = currentPackageName && isValidPackageName(currentPackageName)
|
|
302
|
+
? ensureScopedPackageName(currentPackageName)
|
|
303
|
+
: null;
|
|
304
|
+
|
|
305
|
+
let targetPackageName = currentPackageName;
|
|
306
|
+
|
|
307
|
+
if (explicitName !== null) {
|
|
308
|
+
targetPackageName = ensureScopedPackageName(explicitName);
|
|
309
|
+
validatePackageName(targetPackageName);
|
|
310
|
+
} else if (yes) {
|
|
311
|
+
targetPackageName = currentPackageName || defaultPackageName;
|
|
312
|
+
validatePackageName(targetPackageName);
|
|
313
|
+
} else if (canPromptInteractivelyImpl()) {
|
|
314
|
+
const fallbackPackageName = currentValidPackageName || defaultPackageName;
|
|
315
|
+
const fallbackDisplay = fallbackPackageName.startsWith('@')
|
|
316
|
+
? fallbackPackageName.slice(1)
|
|
317
|
+
: fallbackPackageName;
|
|
318
|
+
const input = await promptInputImpl(` Package name (default: ${fallbackDisplay})`);
|
|
319
|
+
targetPackageName = ensureScopedPackageName(input || fallbackPackageName);
|
|
320
|
+
validatePackageName(targetPackageName);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (targetPackageName && projectPackage.name !== targetPackageName) {
|
|
324
|
+
projectPackage.name = targetPackageName;
|
|
325
|
+
writeFileSync(packagePath, `${JSON.stringify(projectPackage, null, 2)}\n`, 'utf8');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
publishArgs: sanitizedArgs,
|
|
330
|
+
packageName: targetPackageName,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function resolvePublishVersion(
|
|
335
|
+
commandArgs,
|
|
336
|
+
{
|
|
337
|
+
projectRoot = process.cwd(),
|
|
338
|
+
canPromptInteractivelyImpl = canPromptInteractively,
|
|
339
|
+
promptSelectImpl = promptSelect,
|
|
340
|
+
promptInputImpl = promptInput,
|
|
341
|
+
} = {},
|
|
342
|
+
) {
|
|
343
|
+
const { packagePath, projectPackage } = readProjectPackage(projectRoot);
|
|
344
|
+
const { sanitizedArgs, explicitVersion, yes } = extractInternalPublishOptions(commandArgs);
|
|
345
|
+
const packageVersion = typeof projectPackage?.version === 'string' && projectPackage.version.trim().length > 0
|
|
346
|
+
? normalizeVersion(projectPackage.version)
|
|
347
|
+
: null;
|
|
348
|
+
const currentVersion = isValidVersion(packageVersion || '') ? packageVersion : null;
|
|
349
|
+
|
|
350
|
+
let targetVersion = packageVersion;
|
|
351
|
+
|
|
352
|
+
if (explicitVersion !== null) {
|
|
353
|
+
const normalized = normalizeVersion(explicitVersion);
|
|
354
|
+
validateVersion(normalized);
|
|
355
|
+
targetVersion = normalized;
|
|
356
|
+
} else if (yes) {
|
|
357
|
+
if (packageVersion) {
|
|
358
|
+
validateVersion(packageVersion);
|
|
359
|
+
targetVersion = packageVersion;
|
|
360
|
+
} else {
|
|
361
|
+
targetVersion = '0.1.0';
|
|
362
|
+
}
|
|
363
|
+
} else if (canPromptInteractivelyImpl()) {
|
|
364
|
+
if (currentVersion) {
|
|
365
|
+
const choice = await promptSelectImpl(
|
|
366
|
+
' Version selection',
|
|
367
|
+
[
|
|
368
|
+
{ value: 'current', label: `Current (${currentVersion})`, aliases: ['c', 'keep'] },
|
|
369
|
+
{ value: 'bump', label: 'Bump version', aliases: ['b'] },
|
|
370
|
+
{ value: 'custom', label: 'Custom version', aliases: ['x'] },
|
|
371
|
+
],
|
|
372
|
+
'current',
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (choice === 'bump') {
|
|
376
|
+
const bumpKind = await promptSelectImpl(
|
|
377
|
+
' Bump type',
|
|
378
|
+
[
|
|
379
|
+
{ value: 'patch', label: `Patch (${bumpVersion(currentVersion, 'patch')})`, aliases: ['p'] },
|
|
380
|
+
{ value: 'minor', label: `Minor (${bumpVersion(currentVersion, 'minor')})`, aliases: ['m'] },
|
|
381
|
+
{ value: 'major', label: `Major (${bumpVersion(currentVersion, 'major')})`, aliases: ['M'] },
|
|
382
|
+
],
|
|
383
|
+
'patch',
|
|
384
|
+
);
|
|
385
|
+
targetVersion = bumpVersion(currentVersion, bumpKind);
|
|
386
|
+
} else if (choice === 'custom') {
|
|
387
|
+
while (true) {
|
|
388
|
+
const input = normalizeVersion(await promptInputImpl(' Custom version (semver)'));
|
|
389
|
+
if (!input) {
|
|
390
|
+
console.error(' Version is required.');
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (!isValidVersion(input)) {
|
|
394
|
+
console.error(' Invalid version format. Use semver like 1.2.3 or 1.2.3-beta.1');
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
targetVersion = input;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
const defaultVersion = packageVersion ? '' : '0.1.0';
|
|
403
|
+
const promptLabel = defaultVersion
|
|
404
|
+
? ` Package version (semver, default: ${defaultVersion})`
|
|
405
|
+
: ' Package version (semver)';
|
|
406
|
+
while (true) {
|
|
407
|
+
const input = normalizeVersion(await promptInputImpl(promptLabel));
|
|
408
|
+
const candidate = input || defaultVersion;
|
|
409
|
+
if (!candidate) {
|
|
410
|
+
console.error(' Version is required.');
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (!isValidVersion(candidate)) {
|
|
414
|
+
console.error(' Invalid version format. Use semver like 1.2.3 or 1.2.3-beta.1');
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
targetVersion = candidate;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (targetVersion && projectPackage.version !== targetVersion) {
|
|
424
|
+
projectPackage.version = targetVersion;
|
|
425
|
+
writeFileSync(packagePath, `${JSON.stringify(projectPackage, null, 2)}\n`, 'utf8');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
publishArgs: sanitizedArgs,
|
|
430
|
+
packageVersion: targetVersion,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
160
434
|
export function buildPublishArgs(commandArgs, { packageName } = {}) {
|
|
161
435
|
const sanitizedArgs = stripOption(commandArgs, '--token');
|
|
162
436
|
const publishArgs = ['publish', ...sanitizedArgs];
|
|
@@ -217,6 +491,9 @@ export async function runPublishCommand(
|
|
|
217
491
|
stdout = process.stdout,
|
|
218
492
|
stderr = process.stderr,
|
|
219
493
|
validateProject = validatePublishProject,
|
|
494
|
+
canPromptInteractivelyImpl = canPromptInteractively,
|
|
495
|
+
promptSelectImpl = promptSelect,
|
|
496
|
+
promptInputImpl = promptInput,
|
|
220
497
|
} = {},
|
|
221
498
|
) {
|
|
222
499
|
if (isNpmPublishLifecycleInvocation(env)) {
|
|
@@ -231,6 +508,19 @@ export async function runPublishCommand(
|
|
|
231
508
|
};
|
|
232
509
|
}
|
|
233
510
|
|
|
511
|
+
await resolvePublishPackageName(commandArgs, {
|
|
512
|
+
projectRoot,
|
|
513
|
+
canPromptInteractivelyImpl,
|
|
514
|
+
promptInputImpl,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const publishVersionResolution = await resolvePublishVersion(commandArgs, {
|
|
518
|
+
projectRoot,
|
|
519
|
+
canPromptInteractivelyImpl,
|
|
520
|
+
promptSelectImpl,
|
|
521
|
+
promptInputImpl,
|
|
522
|
+
});
|
|
523
|
+
|
|
234
524
|
let validation;
|
|
235
525
|
try {
|
|
236
526
|
validation = await validateProject({
|
|
@@ -260,7 +550,10 @@ export async function runPublishCommand(
|
|
|
260
550
|
|
|
261
551
|
const packageName = validation.packageName;
|
|
262
552
|
const { projectPackage } = readProjectPackage(projectRoot);
|
|
263
|
-
const { publishArgs: sanitizedCommandArgs, publishEnv, publishToken } = extractPublishToken(
|
|
553
|
+
const { publishArgs: sanitizedCommandArgs, publishEnv, publishToken } = extractPublishToken(
|
|
554
|
+
publishVersionResolution.publishArgs,
|
|
555
|
+
env,
|
|
556
|
+
);
|
|
264
557
|
const publishArgs = buildPublishArgs(sanitizedCommandArgs, { packageName });
|
|
265
558
|
const publishTokenEnv = createPublishTokenEnv(publishToken, publishEnv);
|
|
266
559
|
const dryRun = hasOption(sanitizedCommandArgs, '--dry-run');
|
|
@@ -284,6 +577,9 @@ export async function runPublishCommand(
|
|
|
284
577
|
});
|
|
285
578
|
stdout?.write(`\n aura publish: ${dryRun ? 'dry-run' : 'npm-first publish'}\n`);
|
|
286
579
|
stdout?.write(` Package: ${packageName}\n`);
|
|
580
|
+
if (publishVersionResolution.packageVersion) {
|
|
581
|
+
stdout?.write(` Version: ${publishVersionResolution.packageVersion}\n`);
|
|
582
|
+
}
|
|
287
583
|
stdout?.write(` Validation report: ${validation.reportPath}\n`);
|
|
288
584
|
if (Number.isFinite(validation?.report?.validation?.assets?.assetBytes)) {
|
|
289
585
|
const assets = validation.report.validation.assets;
|
|
@@ -309,6 +605,7 @@ export async function runPublishCommand(
|
|
|
309
605
|
skipped: false,
|
|
310
606
|
reasonCode: dryRun ? 'publish_dry_run_ok' : 'publish_ok',
|
|
311
607
|
packageName,
|
|
608
|
+
packageVersion: publishVersionResolution.packageVersion || validation.packageVersion || null,
|
|
312
609
|
reportPath: validation.reportPath,
|
|
313
610
|
publishArgs,
|
|
314
611
|
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { executeFrame } from './headless-test.mjs';
|
|
4
|
+
import {
|
|
5
|
+
applySessionState,
|
|
6
|
+
bootSessionRuntime,
|
|
7
|
+
exportSessionState,
|
|
8
|
+
} from './session-runtime.mjs';
|
|
9
|
+
|
|
10
|
+
export const GAME_REPLAY_SCHEMA_VERSION = 'aurajs.game-replay.v1';
|
|
11
|
+
export const GAME_REPLAY_REPORT_SCHEMA_VERSION = 'aurajs.game-replay-report.v1';
|
|
12
|
+
|
|
13
|
+
export class ReplayError extends Error {
|
|
14
|
+
constructor(reasonCode, message, details = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'ReplayError';
|
|
17
|
+
this.reasonCode = reasonCode;
|
|
18
|
+
this.details = details;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseReplayFrames(value, fallback = 1) {
|
|
23
|
+
if (value == null) return fallback;
|
|
24
|
+
const numeric = Number(value);
|
|
25
|
+
if (!Number.isInteger(numeric) || numeric < 1) {
|
|
26
|
+
throw new ReplayError('invalid_replay_frames', 'Replay step frames must be a positive integer.');
|
|
27
|
+
}
|
|
28
|
+
return numeric;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeReplayPayload(payload) {
|
|
32
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
33
|
+
throw new ReplayError('invalid_replay_payload', 'Replay payload must be an object.');
|
|
34
|
+
}
|
|
35
|
+
if (payload.schemaVersion !== GAME_REPLAY_SCHEMA_VERSION) {
|
|
36
|
+
throw new ReplayError(
|
|
37
|
+
'schema_version_mismatch',
|
|
38
|
+
`Unsupported replay schema "${payload.schemaVersion}". Expected "${GAME_REPLAY_SCHEMA_VERSION}".`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(payload.steps) || payload.steps.length === 0) {
|
|
42
|
+
throw new ReplayError('invalid_replay_payload', 'Replay payload requires a non-empty steps array.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
schemaVersion: GAME_REPLAY_SCHEMA_VERSION,
|
|
47
|
+
seed: payload.seed == null ? null : String(payload.seed),
|
|
48
|
+
stopOnDivergence: payload.stopOnDivergence !== false,
|
|
49
|
+
initialState: payload.initialState ?? null,
|
|
50
|
+
steps: payload.steps.map((step, index) => {
|
|
51
|
+
if (!step || typeof step !== 'object' || Array.isArray(step)) {
|
|
52
|
+
throw new ReplayError('invalid_replay_step', `Replay step at index ${index} must be an object.`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
label: typeof step.label === 'string' && step.label.trim().length > 0 ? step.label.trim() : null,
|
|
56
|
+
checkpointId: typeof step.checkpointId === 'string' && step.checkpointId.trim().length > 0 ? step.checkpointId.trim() : null,
|
|
57
|
+
expectFingerprint: typeof step.expectFingerprint === 'string' && step.expectFingerprint.trim().length > 0
|
|
58
|
+
? step.expectFingerprint.trim()
|
|
59
|
+
: null,
|
|
60
|
+
frames: parseReplayFrames(step.frames, 1),
|
|
61
|
+
input: step.input && typeof step.input === 'object' && !Array.isArray(step.input)
|
|
62
|
+
? step.input
|
|
63
|
+
: null,
|
|
64
|
+
};
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hashReplaySeed(seed) {
|
|
70
|
+
const text = String(seed || '');
|
|
71
|
+
let hash = 2166136261;
|
|
72
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
73
|
+
hash ^= text.charCodeAt(index);
|
|
74
|
+
hash = Math.imul(hash, 16777619);
|
|
75
|
+
}
|
|
76
|
+
return hash >>> 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createSeededMath(seed) {
|
|
80
|
+
if (seed == null) return Math;
|
|
81
|
+
let state = hashReplaySeed(seed);
|
|
82
|
+
if (state === 0) state = 0x6d2b79f5;
|
|
83
|
+
const nextRandom = () => {
|
|
84
|
+
state = (state + 0x6d2b79f5) >>> 0;
|
|
85
|
+
let t = state;
|
|
86
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
87
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
88
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
89
|
+
};
|
|
90
|
+
const math = Object.create(Math);
|
|
91
|
+
math.random = nextRandom;
|
|
92
|
+
return math;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function captureStateOrThrow(runtime, seed, capturedAt = null) {
|
|
96
|
+
const result = exportSessionState(runtime, {
|
|
97
|
+
mode: 'headless',
|
|
98
|
+
seed,
|
|
99
|
+
capturedAt,
|
|
100
|
+
});
|
|
101
|
+
if (!result || typeof result !== 'object' || result.ok !== true || !result.payload) {
|
|
102
|
+
throw new ReplayError(
|
|
103
|
+
typeof result?.reasonCode === 'string' ? result.reasonCode : 'replay_state_export_failed',
|
|
104
|
+
result?.detail
|
|
105
|
+
? `Replay state export failed: ${result.detail}`
|
|
106
|
+
: 'Replay state export failed.',
|
|
107
|
+
{ stateResult: result || null },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return result.payload;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildCheckpoint({ step, stepIndex, runtime, payload, matchedExpectation }) {
|
|
114
|
+
return {
|
|
115
|
+
checkpointId: step.checkpointId || null,
|
|
116
|
+
label: step.label || null,
|
|
117
|
+
stepIndex,
|
|
118
|
+
frameIndex: runtime.frameIndex,
|
|
119
|
+
elapsedSeconds: runtime.elapsedSeconds,
|
|
120
|
+
fingerprint: payload.export?.fingerprint || null,
|
|
121
|
+
matchedExpectation,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function runHeadlessReplay(options = {}) {
|
|
126
|
+
const projectRoot = resolve(options.projectRoot || process.cwd());
|
|
127
|
+
const replay = normalizeReplayPayload(options.payload);
|
|
128
|
+
const runtime = await bootSessionRuntime({
|
|
129
|
+
projectRoot,
|
|
130
|
+
file: options.file,
|
|
131
|
+
width: options.width,
|
|
132
|
+
height: options.height,
|
|
133
|
+
math: createSeededMath(replay.seed),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const inputController = runtime.aura?.input?.__headless;
|
|
137
|
+
if (!inputController || typeof inputController.applyFrame !== 'function' || typeof inputController.clearFrameTransitions !== 'function') {
|
|
138
|
+
throw new ReplayError(
|
|
139
|
+
'replay_input_unavailable',
|
|
140
|
+
'Headless replay requires the headless input controller.',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let initialStateApplied = false;
|
|
145
|
+
if (replay.initialState) {
|
|
146
|
+
const applyResult = applySessionState(runtime, replay.initialState);
|
|
147
|
+
if (!applyResult || typeof applyResult !== 'object' || applyResult.ok !== true) {
|
|
148
|
+
throw new ReplayError(
|
|
149
|
+
typeof applyResult?.reasonCode === 'string' ? applyResult.reasonCode : 'replay_initial_state_apply_failed',
|
|
150
|
+
applyResult?.detail
|
|
151
|
+
? `Replay initial state apply failed: ${applyResult.detail}`
|
|
152
|
+
: 'Replay initial state apply failed.',
|
|
153
|
+
{ applyResult: applyResult || null },
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
initialStateApplied = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const checkpoints = [];
|
|
160
|
+
let divergence = null;
|
|
161
|
+
|
|
162
|
+
for (let index = 0; index < replay.steps.length; index += 1) {
|
|
163
|
+
const step = replay.steps[index];
|
|
164
|
+
if (step.input) {
|
|
165
|
+
inputController.applyFrame(step.input);
|
|
166
|
+
} else {
|
|
167
|
+
inputController.clearFrameTransitions();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (let frame = 0; frame < step.frames; frame += 1) {
|
|
171
|
+
await executeFrame(runtime.aura, 1 / 60);
|
|
172
|
+
runtime.frameIndex += 1;
|
|
173
|
+
runtime.elapsedSeconds += 1 / 60;
|
|
174
|
+
if (frame < step.frames - 1) {
|
|
175
|
+
inputController.clearFrameTransitions();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const needsCheckpoint = step.checkpointId || step.expectFingerprint;
|
|
180
|
+
if (!needsCheckpoint) {
|
|
181
|
+
inputController.clearFrameTransitions();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const payload = captureStateOrThrow(runtime, replay.seed, null);
|
|
186
|
+
const actualFingerprint = payload.export?.fingerprint || null;
|
|
187
|
+
const matchedExpectation = step.expectFingerprint == null || step.expectFingerprint === actualFingerprint;
|
|
188
|
+
checkpoints.push(buildCheckpoint({
|
|
189
|
+
step,
|
|
190
|
+
stepIndex: index,
|
|
191
|
+
runtime,
|
|
192
|
+
payload,
|
|
193
|
+
matchedExpectation,
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
if (!matchedExpectation) {
|
|
197
|
+
divergence = {
|
|
198
|
+
stepIndex: index,
|
|
199
|
+
label: step.label || null,
|
|
200
|
+
checkpointId: step.checkpointId || null,
|
|
201
|
+
expectedFingerprint: step.expectFingerprint,
|
|
202
|
+
actualFingerprint,
|
|
203
|
+
frameIndex: runtime.frameIndex,
|
|
204
|
+
elapsedSeconds: runtime.elapsedSeconds,
|
|
205
|
+
};
|
|
206
|
+
if (replay.stopOnDivergence) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
inputController.clearFrameTransitions();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const finalState = captureStateOrThrow(runtime, replay.seed, null);
|
|
215
|
+
const completed = divergence == null;
|
|
216
|
+
const report = {
|
|
217
|
+
schemaVersion: GAME_REPLAY_REPORT_SCHEMA_VERSION,
|
|
218
|
+
ok: completed,
|
|
219
|
+
reasonCode: completed ? 'replay_ok' : 'replay_diverged',
|
|
220
|
+
replaySchemaVersion: GAME_REPLAY_SCHEMA_VERSION,
|
|
221
|
+
mode: 'headless',
|
|
222
|
+
projectRoot,
|
|
223
|
+
entryFile: runtime.entryFile,
|
|
224
|
+
seed: replay.seed,
|
|
225
|
+
initialStateApplied,
|
|
226
|
+
totalSteps: replay.steps.length,
|
|
227
|
+
executedFrames: runtime.frameIndex,
|
|
228
|
+
elapsedSeconds: runtime.elapsedSeconds,
|
|
229
|
+
finalFingerprint: finalState.export?.fingerprint || null,
|
|
230
|
+
checkpoints,
|
|
231
|
+
divergence,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
replay,
|
|
236
|
+
report,
|
|
237
|
+
finalState,
|
|
238
|
+
runtime,
|
|
239
|
+
runtimeSummary: {
|
|
240
|
+
width: runtime.width,
|
|
241
|
+
height: runtime.height,
|
|
242
|
+
framesExecuted: runtime.frameIndex,
|
|
243
|
+
elapsedSeconds: runtime.elapsedSeconds,
|
|
244
|
+
bundleOutFile: runtime.bundle.outFile,
|
|
245
|
+
moduleCount: runtime.bundle.moduleCount,
|
|
246
|
+
assertionsPassed: runtime.testState.passes,
|
|
247
|
+
drawCalls: runtime.testState.drawCalls,
|
|
248
|
+
audioCalls: runtime.testState.audioCalls,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function serializeReplayReport(payload, compact = false) {
|
|
254
|
+
return compact
|
|
255
|
+
? `${JSON.stringify(payload)}\n`
|
|
256
|
+
: `${JSON.stringify(payload, null, 2)}\n`;
|
|
257
|
+
}
|
package/src/scaffold/config.mjs
CHANGED
|
@@ -4,9 +4,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
const SCAFFOLD_DIR = dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
const CLI_SRC_DIR = resolve(SCAFFOLD_DIR, '..');
|
|
6
6
|
const CLI_PACKAGE = JSON.parse(readFileSync(resolve(CLI_SRC_DIR, '../package.json'), 'utf8'));
|
|
7
|
+
export const CLI_PACKAGE_NAME = CLI_PACKAGE.name;
|
|
7
8
|
const CLI_PACKAGE_VERSION = CLI_PACKAGE.version;
|
|
8
9
|
|
|
9
10
|
export const LEGACY_STARTER_TEMPLATE_DIR = resolve(CLI_SRC_DIR, '../templates/starter');
|
|
11
|
+
export const PACKAGED_STARTER_UTILS_DIR = resolve(CLI_SRC_DIR, './helpers/starter-utils');
|
|
10
12
|
|
|
11
13
|
export const CREATE_TEMPLATE_DIRS = {
|
|
12
14
|
'2d-adventure': resolve(CLI_SRC_DIR, '../templates/create/2d-adventure'),
|
package/src/scaffold/fs.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUtf8 } from 'node:buffer';
|
|
1
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
|
|
@@ -33,7 +34,13 @@ export function copyTree(src, dest, replacements) {
|
|
|
33
34
|
continue;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
const sourceBytes = readFileSync(srcPath);
|
|
38
|
+
if (!isUtf8(sourceBytes)) {
|
|
39
|
+
writeFileSync(destPath, sourceBytes);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let content = sourceBytes.toString('utf8');
|
|
37
44
|
for (const [key, value] of Object.entries(replacements)) {
|
|
38
45
|
content = content.replaceAll(`{{${key}}}`, value);
|
|
39
46
|
}
|