@auraindustry/aurajs 0.1.1 → 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/asset-pack.mjs +2 -1
- package/src/authored-project.mjs +498 -2
- package/src/authored-runtime.mjs +14 -0
- package/src/bin-integrity.mjs +33 -26
- 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 +1126 -10
- package/src/conformance-mobile.mjs +166 -0
- package/src/conformance.mjs +89 -30
- package/src/evidence-bundle.mjs +242 -0
- package/src/external-package-surface.mjs +1 -1
- 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/package-integrity.mjs +18 -4
- 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 +434 -17
- package/src/publish-validation.mjs +22 -11
- 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 +101 -41
- package/src/scaffold.mjs +4 -0
- package/src/session-runtime.mjs +4 -3
- package/src/web-conformance.mjs +0 -36
- package/templates/create/2d/src/runtime/app.js +4 -0
- 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/2d-survivor/src/runtime/app.js +4 -0
- package/templates/create/3d/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d/src/runtime/app.js +4 -0
- 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/app.js +4 -0
- 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/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/create/blank/assets/splash/bg.webp +0 -0
- package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
- package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
- package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/create/blank/assets/splash/logoholo.webp +0 -0
- package/templates/create/blank/src/main.js +5 -1
- package/templates/create/blank/src/runtime/splash.js +305 -0
- package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
- package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
- package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/create/shared/assets/splash/bg.webp +0 -0
- package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
- package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
- package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/create/shared/assets/splash/logoholo.webp +0 -0
- package/templates/create/shared/src/runtime/splash.js +305 -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/video-cutscene/src/runtime/app.js +4 -0
- package/templates/create-bin/play.js +148 -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/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/starter/assets/splash/bg.webp +0 -0
- package/templates/starter/assets/splash/boot-loop.wav +0 -0
- package/templates/starter/assets/splash/boot-sting.wav +0 -0
- package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/starter/assets/splash/logoholo.webp +0 -0
- package/templates/starter/src/main.js +4 -0
- package/templates/starter/src/runtime/splash.js +305 -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
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
copyFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
export const ANDROID_BUILD_MANIFEST_SCHEMA = 'aurajs.android-build-manifest.v1';
|
|
15
|
+
export const ANDROID_BUILD_MANIFEST_VERSION = '1.0.0';
|
|
16
|
+
export const ANDROID_BUILD_TARGET = 'android';
|
|
17
|
+
export const ANDROID_BOOTSTRAP_CHOICE = 'aurajs_android_shell_v1';
|
|
18
|
+
export const ANDROID_PACKAGED_ASSETS_ROOT = 'assets/aura';
|
|
19
|
+
export const ANDROID_NATIVE_LIBRARY_NAME = 'libaurajs_host.so';
|
|
20
|
+
export const ANDROID_NATIVE_LIBRARY_BASENAME = 'aurajs_host';
|
|
21
|
+
export const ANDROID_ABI_FLOOR = Object.freeze(['arm64-v8a']);
|
|
22
|
+
export const ANDROID_RELEASE_OUTPUT_RELATIVE_PATH = 'app/build/outputs/bundle/release/app-release.aab';
|
|
23
|
+
export const ANDROID_DEBUG_OUTPUT_RELATIVE_PATH = 'app/build/outputs/apk/debug/app-debug.apk';
|
|
24
|
+
|
|
25
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const TEMPLATE_ROOT = resolve(here, '../../../../platform/android/template');
|
|
27
|
+
const DIST_ROOT = resolve(here, '../../../../platform/dist/android');
|
|
28
|
+
|
|
29
|
+
const TEMPLATE_FILES = Object.freeze([
|
|
30
|
+
'settings.gradle.kts',
|
|
31
|
+
'build.gradle.kts',
|
|
32
|
+
'gradle.properties',
|
|
33
|
+
'gradlew',
|
|
34
|
+
'gradlew.bat',
|
|
35
|
+
'gradle/wrapper/gradle-wrapper.jar',
|
|
36
|
+
'gradle/wrapper/gradle-wrapper.properties',
|
|
37
|
+
'app/build.gradle.kts',
|
|
38
|
+
'app/proguard-rules.pro',
|
|
39
|
+
'app/src/main/AndroidManifest.xml',
|
|
40
|
+
'app/src/main/java/dev/aurajs/runtime/MainActivity.kt',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const TEMPLATE_BINARY_FILES = new Set([
|
|
44
|
+
'gradle/wrapper/gradle-wrapper.jar',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export function detectAndroidToolchain(options = {}) {
|
|
48
|
+
const env = options.env && typeof options.env === 'object' ? options.env : process.env;
|
|
49
|
+
const cwd = resolve(options.cwd || process.cwd());
|
|
50
|
+
const sdkRoot = normalizeOptionalPath(env.ANDROID_SDK_ROOT || env.ANDROID_HOME || null);
|
|
51
|
+
const ndkRoot = normalizeOptionalPath(env.ANDROID_NDK_ROOT || env.ANDROID_NDK_HOME || null);
|
|
52
|
+
const javaHome = normalizeOptionalPath(env.JAVA_HOME || null);
|
|
53
|
+
const localGradlePath = resolve(cwd, 'gradlew');
|
|
54
|
+
const reasonCodes = [];
|
|
55
|
+
|
|
56
|
+
if (!sdkRoot) reasonCodes.push('android_sdk_missing');
|
|
57
|
+
if (!javaHome) reasonCodes.push('android_java_home_missing');
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
ready: reasonCodes.length === 0,
|
|
61
|
+
sdkRoot,
|
|
62
|
+
ndkRoot,
|
|
63
|
+
javaHome,
|
|
64
|
+
gradleWrapperPath: existsSync(localGradlePath) ? localGradlePath : null,
|
|
65
|
+
reasonCodes,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function stageAndroidProject(options = {}) {
|
|
70
|
+
const projectRoot = resolve(options.projectRoot || process.cwd());
|
|
71
|
+
const outRoot = resolve(options.outRoot || join(projectRoot, 'build', 'android'));
|
|
72
|
+
const bundlePath = resolveRequiredPath(options.bundlePath, 'bundlePath');
|
|
73
|
+
const assetsPackPath = resolveRequiredPath(
|
|
74
|
+
options.assetsPackPath || options.assetPackPath,
|
|
75
|
+
'assetsPackPath',
|
|
76
|
+
);
|
|
77
|
+
const sourceBuildManifestPath = options.sourceBuildManifestPath
|
|
78
|
+
? resolve(options.sourceBuildManifestPath)
|
|
79
|
+
: null;
|
|
80
|
+
const runtimeConfigPath = options.runtimeConfigPath
|
|
81
|
+
? resolve(options.runtimeConfigPath)
|
|
82
|
+
: null;
|
|
83
|
+
const nativeHostLibraryContract = resolveNativeHostLibraryContract({
|
|
84
|
+
projectRoot,
|
|
85
|
+
nativeHostLibraryPath: options.nativeHostLibraryPath || null,
|
|
86
|
+
distRoot: options.distRoot || DIST_ROOT,
|
|
87
|
+
});
|
|
88
|
+
const toolchain = detectAndroidToolchain({
|
|
89
|
+
env: options.env,
|
|
90
|
+
cwd: projectRoot,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const identity = normalizeAndroidIdentity(options.identity || {});
|
|
94
|
+
const renderContext = {
|
|
95
|
+
APP_SLUG: identity.slug,
|
|
96
|
+
APP_NAME: identity.name,
|
|
97
|
+
APPLICATION_ID: identity.applicationId,
|
|
98
|
+
VERSION_CODE: String(identity.versionCode),
|
|
99
|
+
VERSION_NAME: identity.versionName,
|
|
100
|
+
NATIVE_LIBRARY_BASENAME: ANDROID_NATIVE_LIBRARY_BASENAME,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
mkdirSync(outRoot, { recursive: true });
|
|
104
|
+
const appRoot = join(outRoot, 'app');
|
|
105
|
+
const stagedAssetsRoot = join(appRoot, 'src', 'main', ANDROID_PACKAGED_ASSETS_ROOT);
|
|
106
|
+
const stagedNativeLibRoot = join(appRoot, 'src', 'main', 'jniLibs', ANDROID_ABI_FLOOR[0]);
|
|
107
|
+
mkdirSync(stagedAssetsRoot, { recursive: true });
|
|
108
|
+
mkdirSync(stagedNativeLibRoot, { recursive: true });
|
|
109
|
+
mkdirSync(join(outRoot, 'release'), { recursive: true });
|
|
110
|
+
mkdirSync(join(outRoot, 'debug'), { recursive: true });
|
|
111
|
+
|
|
112
|
+
const templateOutputs = [];
|
|
113
|
+
for (const relativeTemplatePath of TEMPLATE_FILES) {
|
|
114
|
+
const templatePath = join(TEMPLATE_ROOT, ...relativeTemplatePath.split('/'));
|
|
115
|
+
const outputPath = join(outRoot, ...relativeTemplatePath.split('/'));
|
|
116
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
117
|
+
if (TEMPLATE_BINARY_FILES.has(relativeTemplatePath)) {
|
|
118
|
+
copyFileSync(templatePath, outputPath);
|
|
119
|
+
} else {
|
|
120
|
+
const rendered = renderTemplate(readFileSync(templatePath, 'utf8'), renderContext);
|
|
121
|
+
writeFileSync(outputPath, rendered, 'utf8');
|
|
122
|
+
}
|
|
123
|
+
if (relativeTemplatePath === 'gradlew') {
|
|
124
|
+
chmodSync(outputPath, 0o755);
|
|
125
|
+
}
|
|
126
|
+
templateOutputs.push(relativeTemplatePath.replace(/\\/g, '/'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const stagedBundlePath = join(stagedAssetsRoot, 'bundle.js');
|
|
130
|
+
const stagedAssetPackPath = join(stagedAssetsRoot, 'assets.pak');
|
|
131
|
+
copyFileSync(bundlePath, stagedBundlePath);
|
|
132
|
+
copyFileSync(assetsPackPath, stagedAssetPackPath);
|
|
133
|
+
|
|
134
|
+
const stagedBuildManifestPath = sourceBuildManifestPath
|
|
135
|
+
? join(stagedAssetsRoot, 'build-manifest.json')
|
|
136
|
+
: null;
|
|
137
|
+
if (sourceBuildManifestPath) {
|
|
138
|
+
copyFileSync(sourceBuildManifestPath, stagedBuildManifestPath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const stagedRuntimeConfigPath = runtimeConfigPath
|
|
142
|
+
? join(stagedAssetsRoot, 'runtime-config.json')
|
|
143
|
+
: null;
|
|
144
|
+
if (runtimeConfigPath) {
|
|
145
|
+
copyFileSync(runtimeConfigPath, stagedRuntimeConfigPath);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let stagedNativeLibraryPath = null;
|
|
149
|
+
let nativeLibraryStaged = false;
|
|
150
|
+
let nativeLibrarySizeBytes = null;
|
|
151
|
+
if (nativeHostLibraryContract.exists) {
|
|
152
|
+
stagedNativeLibraryPath = join(stagedNativeLibRoot, ANDROID_NATIVE_LIBRARY_NAME);
|
|
153
|
+
copyFileSync(nativeHostLibraryContract.sourcePath, stagedNativeLibraryPath);
|
|
154
|
+
nativeLibraryStaged = true;
|
|
155
|
+
nativeLibrarySizeBytes = statSync(stagedNativeLibraryPath).size;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const manifest = {
|
|
159
|
+
schema: ANDROID_BUILD_MANIFEST_SCHEMA,
|
|
160
|
+
schemaVersion: ANDROID_BUILD_MANIFEST_VERSION,
|
|
161
|
+
buildTarget: ANDROID_BUILD_TARGET,
|
|
162
|
+
releaseArtifact: `release/${identity.slug}.aab`,
|
|
163
|
+
debugArtifact: options.includeDebugArtifact === false ? null : `debug/${identity.slug}-debug.apk`,
|
|
164
|
+
applicationId: identity.applicationId,
|
|
165
|
+
appName: identity.name,
|
|
166
|
+
versionCode: identity.versionCode,
|
|
167
|
+
versionName: identity.versionName,
|
|
168
|
+
abis: [...ANDROID_ABI_FLOOR],
|
|
169
|
+
nativeLibraries: [ANDROID_NATIVE_LIBRARY_NAME],
|
|
170
|
+
packagedAssetsRoot: ANDROID_PACKAGED_ASSETS_ROOT,
|
|
171
|
+
bootstrapChoice: ANDROID_BOOTSTRAP_CHOICE,
|
|
172
|
+
templateRoot: 'app',
|
|
173
|
+
stagedFiles: {
|
|
174
|
+
bundle: toPosix(relative(outRoot, stagedBundlePath)),
|
|
175
|
+
assetPack: toPosix(relative(outRoot, stagedAssetPackPath)),
|
|
176
|
+
buildManifest: stagedBuildManifestPath ? toPosix(relative(outRoot, stagedBuildManifestPath)) : null,
|
|
177
|
+
runtimeConfig: stagedRuntimeConfigPath ? toPosix(relative(outRoot, stagedRuntimeConfigPath)) : null,
|
|
178
|
+
nativeLibrary: stagedNativeLibraryPath ? toPosix(relative(outRoot, stagedNativeLibraryPath)) : null,
|
|
179
|
+
},
|
|
180
|
+
buildStatus: {
|
|
181
|
+
release: 'not_built',
|
|
182
|
+
debug: options.includeDebugArtifact === false ? 'not_requested' : 'not_built',
|
|
183
|
+
reasonCode: 'android_external_build_not_run',
|
|
184
|
+
},
|
|
185
|
+
toolchain,
|
|
186
|
+
staging: {
|
|
187
|
+
templateFiles: templateOutputs,
|
|
188
|
+
nativeLibraryStaged,
|
|
189
|
+
nativeLibrarySizeBytes,
|
|
190
|
+
nativeLibraryContract: {
|
|
191
|
+
sourceKind: nativeHostLibraryContract.sourceKind,
|
|
192
|
+
reasonCode: nativeHostLibraryContract.reasonCode,
|
|
193
|
+
sourcePath: nativeHostLibraryContract.sourcePath
|
|
194
|
+
? relativeToRootOrAbsolute(projectRoot, nativeHostLibraryContract.sourcePath)
|
|
195
|
+
: null,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const manifestPath = join(outRoot, 'android-build-manifest.json');
|
|
201
|
+
writeCanonicalJsonFile(manifestPath, manifest);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
outRoot,
|
|
205
|
+
appRoot,
|
|
206
|
+
manifestPath,
|
|
207
|
+
stagedAssetsRoot,
|
|
208
|
+
stagedNativeLibRoot,
|
|
209
|
+
toolchain,
|
|
210
|
+
manifest,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function detectAndroidGradleExecutable(options = {}) {
|
|
215
|
+
const env = options.env && typeof options.env === 'object' ? options.env : process.env;
|
|
216
|
+
const spawnSyncImpl = options.spawnSyncImpl || spawnSync;
|
|
217
|
+
const cwd = resolve(options.cwd || process.cwd());
|
|
218
|
+
const explicitPath = normalizeOptionalPath(env.AURA_ANDROID_GRADLE || options.gradleExecutable || null);
|
|
219
|
+
if (explicitPath) {
|
|
220
|
+
return {
|
|
221
|
+
available: true,
|
|
222
|
+
executablePath: explicitPath,
|
|
223
|
+
sourceKind: 'explicit_input',
|
|
224
|
+
reasonCode: 'android_gradle_explicit',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const localGradlePath = normalizeOptionalPath(join(cwd, 'gradlew'));
|
|
229
|
+
if (localGradlePath) {
|
|
230
|
+
return {
|
|
231
|
+
available: true,
|
|
232
|
+
executablePath: localGradlePath,
|
|
233
|
+
sourceKind: 'local_wrapper',
|
|
234
|
+
reasonCode: 'android_gradle_wrapper',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const probe = spawnSyncImpl('gradle', ['--version'], {
|
|
239
|
+
cwd,
|
|
240
|
+
encoding: 'utf8',
|
|
241
|
+
env,
|
|
242
|
+
});
|
|
243
|
+
if (probe && probe.status === 0) {
|
|
244
|
+
return {
|
|
245
|
+
available: true,
|
|
246
|
+
executablePath: 'gradle',
|
|
247
|
+
sourceKind: 'path',
|
|
248
|
+
reasonCode: 'android_gradle_path',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
available: false,
|
|
254
|
+
executablePath: null,
|
|
255
|
+
sourceKind: 'missing',
|
|
256
|
+
reasonCode: 'android_gradle_missing',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function buildAndroidArtifacts(options = {}) {
|
|
261
|
+
const outRoot = resolveRequiredPath(options.outRoot, 'outRoot');
|
|
262
|
+
const manifestPath = resolveRequiredPath(
|
|
263
|
+
options.manifestPath || join(outRoot, 'android-build-manifest.json'),
|
|
264
|
+
'manifestPath',
|
|
265
|
+
);
|
|
266
|
+
const env = options.env && typeof options.env === 'object' ? options.env : process.env;
|
|
267
|
+
const spawnSyncImpl = options.spawnSyncImpl || spawnSync;
|
|
268
|
+
const buildTargets = normalizeAndroidBuildTargets(options.buildTargets);
|
|
269
|
+
const manifest = readAndroidBuildManifest(manifestPath);
|
|
270
|
+
const toolchain = detectAndroidToolchain({
|
|
271
|
+
env,
|
|
272
|
+
cwd: outRoot,
|
|
273
|
+
});
|
|
274
|
+
const gradle = detectAndroidGradleExecutable({
|
|
275
|
+
env,
|
|
276
|
+
cwd: outRoot,
|
|
277
|
+
spawnSyncImpl,
|
|
278
|
+
gradleExecutable: options.gradleExecutable || null,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!toolchain.ready) {
|
|
282
|
+
manifest.buildStatus = {
|
|
283
|
+
...manifest.buildStatus,
|
|
284
|
+
reasonCode: 'android_toolchain_unavailable',
|
|
285
|
+
release: buildTargets.release ? 'not_built' : manifest.buildStatus.release,
|
|
286
|
+
debug: buildTargets.debug ? 'not_built' : manifest.buildStatus.debug,
|
|
287
|
+
};
|
|
288
|
+
writeCanonicalJsonFile(manifestPath, manifest);
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
reasonCode: 'android_toolchain_unavailable',
|
|
292
|
+
manifestPath,
|
|
293
|
+
manifest,
|
|
294
|
+
toolchain,
|
|
295
|
+
gradle,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!gradle.available) {
|
|
300
|
+
manifest.buildStatus = {
|
|
301
|
+
...manifest.buildStatus,
|
|
302
|
+
reasonCode: 'android_gradle_missing',
|
|
303
|
+
release: buildTargets.release ? 'not_built' : manifest.buildStatus.release,
|
|
304
|
+
debug: buildTargets.debug ? 'not_built' : manifest.buildStatus.debug,
|
|
305
|
+
};
|
|
306
|
+
writeCanonicalJsonFile(manifestPath, manifest);
|
|
307
|
+
return {
|
|
308
|
+
ok: false,
|
|
309
|
+
reasonCode: 'android_gradle_missing',
|
|
310
|
+
manifestPath,
|
|
311
|
+
manifest,
|
|
312
|
+
toolchain,
|
|
313
|
+
gradle,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const nativeLibraryReasonCode = manifest.staging?.nativeLibraryContract?.reasonCode || null;
|
|
318
|
+
if (!manifest.staging?.nativeLibraryStaged) {
|
|
319
|
+
manifest.buildStatus = {
|
|
320
|
+
...manifest.buildStatus,
|
|
321
|
+
reasonCode: nativeLibraryReasonCode || 'android_native_host_library_missing',
|
|
322
|
+
release: buildTargets.release ? 'not_built' : manifest.buildStatus.release,
|
|
323
|
+
debug: buildTargets.debug ? 'not_built' : manifest.buildStatus.debug,
|
|
324
|
+
};
|
|
325
|
+
writeCanonicalJsonFile(manifestPath, manifest);
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
reasonCode: nativeLibraryReasonCode || 'android_native_host_library_missing',
|
|
329
|
+
manifestPath,
|
|
330
|
+
manifest,
|
|
331
|
+
toolchain,
|
|
332
|
+
gradle,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const localPropertiesPath = writeAndroidLocalProperties(outRoot, toolchain.sdkRoot);
|
|
337
|
+
const outputs = {
|
|
338
|
+
release: null,
|
|
339
|
+
debug: null,
|
|
340
|
+
};
|
|
341
|
+
const executedTasks = [];
|
|
342
|
+
const baseEnv = {
|
|
343
|
+
...env,
|
|
344
|
+
};
|
|
345
|
+
if (toolchain.sdkRoot) {
|
|
346
|
+
baseEnv.ANDROID_SDK_ROOT = toolchain.sdkRoot;
|
|
347
|
+
}
|
|
348
|
+
if (toolchain.javaHome) {
|
|
349
|
+
baseEnv.JAVA_HOME = toolchain.javaHome;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (buildTargets.release) {
|
|
353
|
+
executeAndroidGradleTask({
|
|
354
|
+
outRoot,
|
|
355
|
+
gradle,
|
|
356
|
+
env: baseEnv,
|
|
357
|
+
spawnSyncImpl,
|
|
358
|
+
taskName: 'bundleRelease',
|
|
359
|
+
});
|
|
360
|
+
const stagedReleasePath = join(outRoot, manifest.releaseArtifact);
|
|
361
|
+
copyBuildOutput(
|
|
362
|
+
join(outRoot, ANDROID_RELEASE_OUTPUT_RELATIVE_PATH),
|
|
363
|
+
stagedReleasePath,
|
|
364
|
+
'release artifact',
|
|
365
|
+
);
|
|
366
|
+
outputs.release = stagedReleasePath;
|
|
367
|
+
executedTasks.push('bundleRelease');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (buildTargets.debug && manifest.debugArtifact) {
|
|
371
|
+
executeAndroidGradleTask({
|
|
372
|
+
outRoot,
|
|
373
|
+
gradle,
|
|
374
|
+
env: baseEnv,
|
|
375
|
+
spawnSyncImpl,
|
|
376
|
+
taskName: 'assembleDebug',
|
|
377
|
+
});
|
|
378
|
+
const stagedDebugPath = join(outRoot, manifest.debugArtifact);
|
|
379
|
+
copyBuildOutput(
|
|
380
|
+
join(outRoot, ANDROID_DEBUG_OUTPUT_RELATIVE_PATH),
|
|
381
|
+
stagedDebugPath,
|
|
382
|
+
'debug artifact',
|
|
383
|
+
);
|
|
384
|
+
outputs.debug = stagedDebugPath;
|
|
385
|
+
executedTasks.push('assembleDebug');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
manifest.toolchain = {
|
|
389
|
+
...toolchain,
|
|
390
|
+
gradleExecutable: gradle.executablePath,
|
|
391
|
+
gradleReasonCode: gradle.reasonCode,
|
|
392
|
+
};
|
|
393
|
+
manifest.buildStatus = {
|
|
394
|
+
release: buildTargets.release ? 'built' : manifest.buildStatus.release,
|
|
395
|
+
debug: buildTargets.debug
|
|
396
|
+
? (manifest.debugArtifact ? 'built' : 'not_requested')
|
|
397
|
+
: manifest.buildStatus.debug,
|
|
398
|
+
reasonCode: 'android_gradle_build_complete',
|
|
399
|
+
executedTasks,
|
|
400
|
+
localPropertiesPath: relativeToRootOrAbsolute(outRoot, localPropertiesPath),
|
|
401
|
+
};
|
|
402
|
+
writeCanonicalJsonFile(manifestPath, manifest);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
ok: true,
|
|
406
|
+
reasonCode: 'android_gradle_build_complete',
|
|
407
|
+
manifestPath,
|
|
408
|
+
manifest,
|
|
409
|
+
toolchain: manifest.toolchain,
|
|
410
|
+
gradle,
|
|
411
|
+
outputs,
|
|
412
|
+
localPropertiesPath,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function normalizeAndroidIdentity(identity) {
|
|
417
|
+
const name = normalizeName(identity.name || 'AuraJS Game');
|
|
418
|
+
const slug = normalizeSlug(identity.slug || name);
|
|
419
|
+
const applicationId = normalizeApplicationId(identity.applicationId || `dev.aurajs.${slug}`);
|
|
420
|
+
const versionCode = normalizeVersionCode(identity.versionCode);
|
|
421
|
+
const versionName = normalizeVersionName(identity.versionName);
|
|
422
|
+
return {
|
|
423
|
+
name,
|
|
424
|
+
slug,
|
|
425
|
+
applicationId,
|
|
426
|
+
versionCode,
|
|
427
|
+
versionName,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function normalizeName(value) {
|
|
432
|
+
const text = String(value || '').trim();
|
|
433
|
+
return text || 'AuraJS Game';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function normalizeSlug(value) {
|
|
437
|
+
return String(value || '')
|
|
438
|
+
.trim()
|
|
439
|
+
.toLowerCase()
|
|
440
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
441
|
+
.replace(/^-+|-+$/g, '')
|
|
442
|
+
|| 'aurajs-game';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function normalizeApplicationId(value) {
|
|
446
|
+
const text = String(value || '').trim();
|
|
447
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(text)) {
|
|
448
|
+
throw new Error(`invalid Android application id "${text}"`);
|
|
449
|
+
}
|
|
450
|
+
return text;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function normalizeVersionCode(value) {
|
|
454
|
+
const parsed = Number.parseInt(String(value ?? '1'), 10);
|
|
455
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
456
|
+
throw new Error('versionCode must be a positive integer');
|
|
457
|
+
}
|
|
458
|
+
return parsed;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function normalizeVersionName(value) {
|
|
462
|
+
const text = String(value ?? '0.1.0').trim();
|
|
463
|
+
return text || '0.1.0';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function renderTemplate(source, values) {
|
|
467
|
+
let output = String(source);
|
|
468
|
+
for (const [key, value] of Object.entries(values)) {
|
|
469
|
+
output = output.replaceAll(`{{${key}}}`, String(value));
|
|
470
|
+
}
|
|
471
|
+
return output;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function resolveRequiredPath(value, label) {
|
|
475
|
+
if (!value) {
|
|
476
|
+
throw new Error(`${label} is required`);
|
|
477
|
+
}
|
|
478
|
+
const resolved = resolve(value);
|
|
479
|
+
if (!existsSync(resolved)) {
|
|
480
|
+
throw new Error(`${label} is missing: ${resolved}`);
|
|
481
|
+
}
|
|
482
|
+
return resolved;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function normalizeOptionalPath(value) {
|
|
486
|
+
if (!value) return null;
|
|
487
|
+
const resolved = resolve(String(value));
|
|
488
|
+
return existsSync(resolved) ? resolved : null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function normalizeAndroidBuildTargets(value) {
|
|
492
|
+
const targets = Array.isArray(value) && value.length > 0 ? value : ['release', 'debug'];
|
|
493
|
+
const set = new Set(targets.map((entry) => String(entry || '').trim().toLowerCase()));
|
|
494
|
+
return {
|
|
495
|
+
release: set.has('release'),
|
|
496
|
+
debug: set.has('debug'),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function resolveNativeHostLibraryContract({ projectRoot, nativeHostLibraryPath, distRoot }) {
|
|
501
|
+
if (nativeHostLibraryPath) {
|
|
502
|
+
const explicitPath = resolve(nativeHostLibraryPath);
|
|
503
|
+
if (existsSync(explicitPath)) {
|
|
504
|
+
return {
|
|
505
|
+
exists: true,
|
|
506
|
+
sourceKind: 'explicit_input',
|
|
507
|
+
reasonCode: 'android_native_host_library_explicit',
|
|
508
|
+
sourcePath: explicitPath,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
exists: false,
|
|
514
|
+
sourceKind: 'explicit_missing',
|
|
515
|
+
reasonCode: 'android_native_host_library_missing',
|
|
516
|
+
sourcePath: explicitPath,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const normalizedDistRoot = resolve(distRoot || DIST_ROOT);
|
|
521
|
+
const distLibraryPath = join(
|
|
522
|
+
normalizedDistRoot,
|
|
523
|
+
ANDROID_ABI_FLOOR[0],
|
|
524
|
+
ANDROID_NATIVE_LIBRARY_NAME,
|
|
525
|
+
);
|
|
526
|
+
if (existsSync(distLibraryPath)) {
|
|
527
|
+
return {
|
|
528
|
+
exists: true,
|
|
529
|
+
sourceKind: 'platform_dist',
|
|
530
|
+
reasonCode: 'android_native_host_library_dist',
|
|
531
|
+
sourcePath: distLibraryPath,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
exists: false,
|
|
537
|
+
sourceKind: 'missing',
|
|
538
|
+
reasonCode: 'android_native_host_library_missing',
|
|
539
|
+
sourcePath: distLibraryPath,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function executeAndroidGradleTask({
|
|
544
|
+
outRoot,
|
|
545
|
+
gradle,
|
|
546
|
+
env,
|
|
547
|
+
spawnSyncImpl,
|
|
548
|
+
taskName,
|
|
549
|
+
}) {
|
|
550
|
+
const result = spawnSyncImpl(gradle.executablePath, ['-p', outRoot, taskName], {
|
|
551
|
+
cwd: outRoot,
|
|
552
|
+
encoding: 'utf8',
|
|
553
|
+
env,
|
|
554
|
+
});
|
|
555
|
+
if (result?.error) {
|
|
556
|
+
throw new Error(`failed to launch Android Gradle task ${taskName}: ${result.error.message}`);
|
|
557
|
+
}
|
|
558
|
+
if (!result || result.status !== 0) {
|
|
559
|
+
const detail = [result?.stdout, result?.stderr].filter(Boolean).join('\n').trim();
|
|
560
|
+
throw new Error(
|
|
561
|
+
detail.length > 0
|
|
562
|
+
? `Android Gradle task ${taskName} failed: ${detail}`
|
|
563
|
+
: `Android Gradle task ${taskName} failed with status ${result?.status ?? 'unknown'}`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function copyBuildOutput(sourcePath, targetPath, label) {
|
|
569
|
+
const resolvedSource = resolveRequiredPath(sourcePath, label);
|
|
570
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
571
|
+
copyFileSync(resolvedSource, targetPath);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function writeAndroidLocalProperties(outRoot, sdkRoot) {
|
|
575
|
+
const localPropertiesPath = join(outRoot, 'local.properties');
|
|
576
|
+
const lines = [];
|
|
577
|
+
if (sdkRoot) {
|
|
578
|
+
lines.push(`sdk.dir=${escapeAndroidLocalPropertiesValue(sdkRoot)}`);
|
|
579
|
+
}
|
|
580
|
+
writeFileSync(localPropertiesPath, `${lines.join('\n')}\n`, 'utf8');
|
|
581
|
+
return localPropertiesPath;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function escapeAndroidLocalPropertiesValue(value) {
|
|
585
|
+
return String(value || '').replace(/\\/g, '\\\\').replace(/:/g, '\\:');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function toPosix(input) {
|
|
589
|
+
return String(input || '').replace(/\\/g, '/');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function relativeToRootOrAbsolute(root, targetPath) {
|
|
593
|
+
const rel = relative(root, targetPath).replace(/\\/g, '/');
|
|
594
|
+
if (!rel || (!rel.startsWith('../') && rel !== '..')) {
|
|
595
|
+
return rel || '.';
|
|
596
|
+
}
|
|
597
|
+
return targetPath;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function writeCanonicalJsonFile(path, value) {
|
|
601
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function readAndroidBuildManifest(path) {
|
|
605
|
+
return JSON.parse(readFileSync(resolveRequiredPath(path, 'manifestPath'), 'utf8'));
|
|
606
|
+
}
|