@lynxship/cli 0.1.20 → 0.1.21
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 +66 -5
- package/dist/android/signing.d.ts +16 -0
- package/dist/android/signing.d.ts.map +1 -0
- package/dist/android/signing.js +131 -0
- package/dist/android/types.d.ts +12 -0
- package/dist/android/types.d.ts.map +1 -0
- package/dist/android/types.js +1 -0
- package/dist/android-build.d.ts +4 -13
- package/dist/android-build.d.ts.map +1 -1
- package/dist/android-build.js +14 -133
- package/dist/android-toolchain/inspection.d.ts +3 -0
- package/dist/android-toolchain/inspection.d.ts.map +1 -0
- package/dist/android-toolchain/inspection.js +122 -0
- package/dist/android-toolchain/probes.d.ts +23 -0
- package/dist/android-toolchain/probes.d.ts.map +1 -0
- package/dist/android-toolchain/probes.js +183 -0
- package/dist/android-toolchain/types.d.ts +20 -0
- package/dist/android-toolchain/types.d.ts.map +1 -0
- package/dist/android-toolchain/types.js +1 -0
- package/dist/android-toolchain.d.ts +4 -20
- package/dist/android-toolchain.d.ts.map +1 -1
- package/dist/android-toolchain.js +4 -300
- package/dist/app-config.d.ts +25 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +106 -0
- package/dist/bundle-build.d.ts.map +1 -1
- package/dist/bundle-build.js +11 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +66 -0
- package/dist/commands/build-execution.d.ts +41 -0
- package/dist/commands/build-execution.d.ts.map +1 -0
- package/dist/commands/build-execution.js +182 -0
- package/dist/commands/configuration.d.ts +19 -0
- package/dist/commands/configuration.d.ts.map +1 -0
- package/dist/commands/configuration.js +174 -0
- package/dist/commands/development.d.ts +22 -0
- package/dist/commands/development.d.ts.map +1 -0
- package/dist/commands/development.js +132 -0
- package/dist/commands/device.d.ts +3 -0
- package/dist/commands/device.d.ts.map +1 -0
- package/dist/commands/device.js +86 -0
- package/dist/commands/doctor.d.ts +18 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +320 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +118 -0
- package/dist/commands/i18n.d.ts +14 -0
- package/dist/commands/i18n.d.ts.map +1 -0
- package/dist/commands/i18n.js +88 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +51 -0
- package/dist/commands/metadata.d.ts +3 -0
- package/dist/commands/metadata.d.ts.map +1 -0
- package/dist/commands/metadata.js +45 -0
- package/dist/commands/ota.d.ts +28 -0
- package/dist/commands/ota.d.ts.map +1 -0
- package/dist/commands/ota.js +203 -0
- package/dist/commands/project.d.ts +14 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +122 -0
- package/dist/commands/remote-build.d.ts +35 -0
- package/dist/commands/remote-build.d.ts.map +1 -0
- package/dist/commands/remote-build.js +73 -0
- package/dist/commands/submit.d.ts +27 -0
- package/dist/commands/submit.d.ts.map +1 -0
- package/dist/commands/submit.js +92 -0
- package/dist/ecosystem.d.ts +9 -0
- package/dist/ecosystem.d.ts.map +1 -0
- package/dist/ecosystem.js +95 -0
- package/dist/frameworks.d.ts.map +1 -1
- package/dist/frameworks.js +5 -1
- package/dist/guidance/catalog.d.ts +3 -0
- package/dist/guidance/catalog.d.ts.map +1 -0
- package/dist/guidance/catalog.js +504 -0
- package/dist/guidance/types.d.ts +10 -0
- package/dist/guidance/types.d.ts.map +1 -0
- package/dist/guidance/types.js +1 -0
- package/dist/guidance.d.ts +2 -9
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +1 -502
- package/dist/i18n/discovery.d.ts +14 -0
- package/dist/i18n/discovery.d.ts.map +1 -0
- package/dist/i18n/discovery.js +106 -0
- package/dist/i18n/plan.d.ts +12 -0
- package/dist/i18n/plan.d.ts.map +1 -0
- package/dist/i18n/plan.js +95 -0
- package/dist/i18n/setup.d.ts +11 -0
- package/dist/i18n/setup.d.ts.map +1 -0
- package/dist/i18n/setup.js +47 -0
- package/dist/i18n/types.d.ts +17 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2069
- package/dist/ios/assets.d.ts +19 -0
- package/dist/ios/assets.d.ts.map +1 -0
- package/dist/ios/assets.js +128 -0
- package/dist/ios/production-build.d.ts +6 -0
- package/dist/ios/production-build.d.ts.map +1 -0
- package/dist/ios/production-build.js +200 -0
- package/dist/ios/project.d.ts +4 -0
- package/dist/ios/project.d.ts.map +1 -0
- package/dist/ios/project.js +43 -0
- package/dist/ios/simulator-build.d.ts +8 -0
- package/dist/ios/simulator-build.d.ts.map +1 -0
- package/dist/ios/simulator-build.js +188 -0
- package/dist/ios/simulator.d.ts +5 -0
- package/dist/ios/simulator.d.ts.map +1 -0
- package/dist/ios/simulator.js +60 -0
- package/dist/ios/types.d.ts +14 -0
- package/dist/ios/types.d.ts.map +1 -0
- package/dist/ios/types.js +1 -0
- package/dist/ios-build.d.ts +4 -37
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +3 -575
- package/dist/ios-host.d.ts.map +1 -1
- package/dist/ios-toolchain/inspection.d.ts +4 -0
- package/dist/ios-toolchain/inspection.d.ts.map +1 -0
- package/dist/ios-toolchain/inspection.js +211 -0
- package/dist/ios-toolchain/probes.d.ts +24 -0
- package/dist/ios-toolchain/probes.d.ts.map +1 -0
- package/dist/ios-toolchain/probes.js +127 -0
- package/dist/ios-toolchain/types.d.ts +17 -0
- package/dist/ios-toolchain/types.d.ts.map +1 -0
- package/dist/ios-toolchain/types.js +1 -0
- package/dist/ios-toolchain.d.ts +3 -18
- package/dist/ios-toolchain.d.ts.map +1 -1
- package/dist/ios-toolchain.js +1 -333
- package/dist/plugins/contracts.d.ts +28 -0
- package/dist/plugins/contracts.d.ts.map +1 -0
- package/dist/plugins/contracts.js +1 -0
- package/dist/plugins/discovery.d.ts +19 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +250 -0
- package/dist/plugins/native-operations.d.ts +28 -0
- package/dist/plugins/native-operations.d.ts.map +1 -0
- package/dist/plugins/native-operations.js +217 -0
- package/dist/plugins/operations.d.ts +9 -0
- package/dist/plugins/operations.d.ts.map +1 -0
- package/dist/plugins/operations.js +76 -0
- package/dist/plugins/validation.d.ts +9 -0
- package/dist/plugins/validation.d.ts.map +1 -0
- package/dist/plugins/validation.js +52 -0
- package/dist/plugins.d.ts +3 -34
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +2 -595
- package/dist/process-runner.d.ts +4 -0
- package/dist/process-runner.d.ts.map +1 -1
- package/dist/process-runner.js +14 -0
- package/dist/remote.d.ts +22 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +141 -2
- package/dist/rspeedy-app-config.d.ts +4 -0
- package/dist/rspeedy-app-config.d.ts.map +1 -0
- package/dist/rspeedy-app-config.js +32 -0
- package/dist/runtime/args.d.ts +3 -0
- package/dist/runtime/args.d.ts.map +1 -0
- package/dist/runtime/args.js +11 -0
- package/dist/runtime/build-commands.d.ts +3 -0
- package/dist/runtime/build-commands.d.ts.map +1 -0
- package/dist/runtime/build-commands.js +282 -0
- package/dist/runtime/context.d.ts +63 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +179 -0
- package/dist/runtime/early-commands.d.ts +4 -0
- package/dist/runtime/early-commands.d.ts.map +1 -0
- package/dist/runtime/early-commands.js +284 -0
- package/dist/runtime/project.d.ts +8 -0
- package/dist/runtime/project.d.ts.map +1 -0
- package/dist/runtime/project.js +38 -0
- package/dist/runtime/state.d.ts +47 -0
- package/dist/runtime/state.d.ts.map +1 -0
- package/dist/runtime/state.js +33 -0
- package/dist/secure-store/backends.d.ts +20 -0
- package/dist/secure-store/backends.d.ts.map +1 -0
- package/dist/secure-store/backends.js +248 -0
- package/dist/secure-store/types.d.ts +27 -0
- package/dist/secure-store/types.d.ts.map +1 -0
- package/dist/secure-store/types.js +1 -0
- package/dist/secure-store.d.ts +2 -26
- package/dist/secure-store.d.ts.map +1 -1
- package/dist/secure-store.js +3 -245
- package/package.json +7 -4
- package/templates/android-host/app/build.gradle +1 -0
- package/templates/android-host/app/src/main/java/template/MainActivity.java +98 -3
- package/templates/ios-host/__IOS_TARGET_NAME__/DemoLynxProvider.swift +26 -16
- package/templates/ios-host/__IOS_TARGET_NAME__/ViewController.swift +107 -19
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Copy generated bundles into the configured iOS native resource directory. */
|
|
2
|
+
export declare function syncIosBuildResources(root: string, target: string): Promise<string[]>;
|
|
3
|
+
/**
|
|
4
|
+
* Rspeedy leaves external resources beside the Lynx bundle. Xcode does not
|
|
5
|
+
* know about those generated files, so copy the complete output into the
|
|
6
|
+
* final app bundle after the native host has been compiled. This also makes
|
|
7
|
+
* older LynxShip-generated iOS hosts behave correctly without editing their
|
|
8
|
+
* Xcode project files.
|
|
9
|
+
*/
|
|
10
|
+
export declare function syncIosRuntimeResources(root: string, appBundle: string): Promise<string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Apply a project-owned 1024x1024 PNG to the generated or existing Xcode
|
|
13
|
+
* AppIcon set. A missing icon is reported as a warning by the caller rather
|
|
14
|
+
* than silently inventing product branding.
|
|
15
|
+
*/
|
|
16
|
+
export declare function prepareIosAppIcon(root: string, configured?: string, options?: {
|
|
17
|
+
allowFallback?: boolean;
|
|
18
|
+
}): Promise<string | undefined>;
|
|
19
|
+
//# sourceMappingURL=assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/ios/assets.ts"],"names":[],"mappings":"AA4CA,gFAAgF;AAChF,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,EAAE,CAAC,CAEnB;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAEnB;AAoED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { copyFile, cp, readdir, readFile, rm, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { assert } from "@lynxship/contracts";
|
|
5
|
+
import { commandExists, runProcess } from "../process-runner.js";
|
|
6
|
+
async function copyIosOutput(source, target) {
|
|
7
|
+
if (!existsSync(source))
|
|
8
|
+
return false;
|
|
9
|
+
await rm(target, { recursive: true, force: true });
|
|
10
|
+
await cp(source, target, { recursive: true, force: true });
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
async function syncBundleOutput(root, target) {
|
|
14
|
+
const distRoot = join(root, "dist");
|
|
15
|
+
const entries = await readdir(distRoot, { withFileTypes: true }).catch(() => []);
|
|
16
|
+
const bundles = entries
|
|
17
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".lynx.bundle"))
|
|
18
|
+
.map((entry) => entry.name);
|
|
19
|
+
assert(bundles.length > 0, "LYNX_BUNDLE_MISSING", `No .lynx.bundle was found in ${distRoot}. Check the Rspeedy output configuration.`);
|
|
20
|
+
const copied = [];
|
|
21
|
+
for (const name of [...bundles, "async", "static"])
|
|
22
|
+
if (await copyIosOutput(join(distRoot, name), join(target, name)))
|
|
23
|
+
copied.push(name);
|
|
24
|
+
return copied;
|
|
25
|
+
}
|
|
26
|
+
/** Copy generated bundles into the configured iOS native resource directory. */
|
|
27
|
+
export async function syncIosBuildResources(root, target) {
|
|
28
|
+
return syncBundleOutput(root, target);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Rspeedy leaves external resources beside the Lynx bundle. Xcode does not
|
|
32
|
+
* know about those generated files, so copy the complete output into the
|
|
33
|
+
* final app bundle after the native host has been compiled. This also makes
|
|
34
|
+
* older LynxShip-generated iOS hosts behave correctly without editing their
|
|
35
|
+
* Xcode project files.
|
|
36
|
+
*/
|
|
37
|
+
export async function syncIosRuntimeResources(root, appBundle) {
|
|
38
|
+
return syncBundleOutput(root, appBundle);
|
|
39
|
+
}
|
|
40
|
+
async function findAppIconSet(directory) {
|
|
41
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const path = join(directory, entry.name);
|
|
44
|
+
if (entry.isDirectory() && entry.name === "AppIcon.appiconset") {
|
|
45
|
+
if (existsSync(join(path, "Contents.json")))
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
if (entry.isDirectory() &&
|
|
49
|
+
!entry.name.endsWith(".xcodeproj") &&
|
|
50
|
+
!entry.name.endsWith(".xcworkspace")) {
|
|
51
|
+
const result = await findAppIconSet(path);
|
|
52
|
+
if (result)
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
function pngDimensions(content) {
|
|
59
|
+
assert(content.length >= 24 &&
|
|
60
|
+
content.subarray(0, 8).toString("hex") === "89504e470d0a1a0a", "IOS_APP_ICON_INVALID", "The iOS app icon must be a valid PNG file.");
|
|
61
|
+
return { width: content.readUInt32BE(16), height: content.readUInt32BE(20) };
|
|
62
|
+
}
|
|
63
|
+
async function findConfiguredIosIcon(root, configured, allowFallback = false) {
|
|
64
|
+
const candidates = [
|
|
65
|
+
configured,
|
|
66
|
+
"icon.png",
|
|
67
|
+
"app-icon.png",
|
|
68
|
+
"assets/icon.png",
|
|
69
|
+
"assets/app-icon.png",
|
|
70
|
+
"src/assets/icon.png",
|
|
71
|
+
"src/assets/app-icon.png",
|
|
72
|
+
"public/icon.png",
|
|
73
|
+
].filter((value) => Boolean(value));
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
const path = resolve(root, candidate);
|
|
76
|
+
if (existsSync(path))
|
|
77
|
+
return { path, fallback: false };
|
|
78
|
+
}
|
|
79
|
+
if (allowFallback) {
|
|
80
|
+
const staticImages = join(root, "dist", "static", "image");
|
|
81
|
+
const entries = await readdir(staticImages, { withFileTypes: true }).catch(() => []);
|
|
82
|
+
const logo = entries.find((entry) => entry.isFile() &&
|
|
83
|
+
entry.name.endsWith(".png") &&
|
|
84
|
+
/(?:lynx|logo|icon)/i.test(entry.name));
|
|
85
|
+
if (logo)
|
|
86
|
+
return { path: join(staticImages, logo.name), fallback: true };
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Apply a project-owned 1024x1024 PNG to the generated or existing Xcode
|
|
92
|
+
* AppIcon set. A missing icon is reported as a warning by the caller rather
|
|
93
|
+
* than silently inventing product branding.
|
|
94
|
+
*/
|
|
95
|
+
export async function prepareIosAppIcon(root, configured, options = {}) {
|
|
96
|
+
const source = await findConfiguredIosIcon(root, configured, options.allowFallback);
|
|
97
|
+
const iconSet = await findAppIconSet(join(root, "ios"));
|
|
98
|
+
if (!source || !iconSet)
|
|
99
|
+
return undefined;
|
|
100
|
+
const dimensions = pngDimensions(await readFile(source.path));
|
|
101
|
+
const filename = "AppIcon.png";
|
|
102
|
+
const destination = join(iconSet, filename);
|
|
103
|
+
if (dimensions.width === 1024 && dimensions.height === 1024) {
|
|
104
|
+
await copyFile(source.path, destination);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
assert(source.fallback &&
|
|
108
|
+
dimensions.width === dimensions.height &&
|
|
109
|
+
dimensions.width >= 512, "IOS_APP_ICON_INVALID", `The iOS app icon must be exactly 1024x1024 PNG; received ${dimensions.width}x${dimensions.height} from ${source.path}.`);
|
|
110
|
+
assert(commandExists("sips"), "IOS_SIPS_REQUIRED", "The fallback Simulator icon needs Apple's sips tool. Install Xcode command-line tools, or provide a 1024x1024 icon.png.");
|
|
111
|
+
await runProcess("sips", ["-z", "1024", "1024", source.path, "--out", destination], {
|
|
112
|
+
cwd: root,
|
|
113
|
+
quiet: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await writeFile(join(iconSet, "Contents.json"), `${JSON.stringify({
|
|
117
|
+
images: [
|
|
118
|
+
{
|
|
119
|
+
filename,
|
|
120
|
+
idiom: "universal",
|
|
121
|
+
platform: "ios",
|
|
122
|
+
size: "1024x1024",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
info: { author: "xcode", version: 1 },
|
|
126
|
+
}, null, 2)}\n`, "utf8");
|
|
127
|
+
return source.path;
|
|
128
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type BuildJob } from "@lynxship/contracts";
|
|
2
|
+
import type { BuildProfile } from "../config.js";
|
|
3
|
+
import type { IosBuildOptions } from "./types.js";
|
|
4
|
+
export declare function hasIosHost(root: string, profile?: BuildProfile): boolean;
|
|
5
|
+
export declare function runRealIosBuild(job: BuildJob, options: IosBuildOptions): Promise<BuildJob>;
|
|
6
|
+
//# sourceMappingURL=production-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-build.d.ts","sourceRoot":"","sources":["../../src/ios/production-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAOjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAUlD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AASD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAqOnB"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { assert, sha256 } from "@lynxship/contracts";
|
|
5
|
+
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
6
|
+
import { loadR2, uploadR2Artifact } from "../r2.js";
|
|
7
|
+
import { nativeArtifactName } from "../artifact-name.js";
|
|
8
|
+
import { commandExists, runProcess } from "../process-runner.js";
|
|
9
|
+
import { buildLynxBundle } from "../bundle-build.js";
|
|
10
|
+
import { loadAppConfig, resolveAppConfigAssetPath } from "../app-config.js";
|
|
11
|
+
import { findArchiveApp } from "./simulator.js";
|
|
12
|
+
import { prepareIosAppIcon, syncIosBuildResources, syncIosRuntimeResources, } from "./assets.js";
|
|
13
|
+
import { runRealIosSimulatorBuild } from "./simulator-build.js";
|
|
14
|
+
import { findProject, installCocoaPods } from "./project.js";
|
|
15
|
+
export function hasIosHost(root, profile) {
|
|
16
|
+
if (process.platform !== "darwin")
|
|
17
|
+
return false;
|
|
18
|
+
const configured = profile?.ios?.workspace ?? profile?.ios?.project;
|
|
19
|
+
if (configured && existsSync(resolve(root, configured)))
|
|
20
|
+
return true;
|
|
21
|
+
return ["ios", "macos"].some((directory) => {
|
|
22
|
+
try {
|
|
23
|
+
return readdirSync(join(root, directory)).some((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function findIpa(directory) {
|
|
31
|
+
const files = await readdir(directory, { withFileTypes: true });
|
|
32
|
+
const ipa = files.find((file) => file.isFile() && file.name.endsWith(".ipa"));
|
|
33
|
+
if (!ipa)
|
|
34
|
+
throw new Error(`xcodebuild exported no IPA in ${directory}`);
|
|
35
|
+
return join(directory, ipa.name);
|
|
36
|
+
}
|
|
37
|
+
export async function runRealIosBuild(job, options) {
|
|
38
|
+
if (options.simulator)
|
|
39
|
+
return runRealIosSimulatorBuild(job, options);
|
|
40
|
+
assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS builds require macOS with Xcode");
|
|
41
|
+
assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
|
|
42
|
+
assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
|
|
43
|
+
const project = findProject(options.root, options.profile);
|
|
44
|
+
assert(existsSync(project), "IOS_PROJECT_REQUIRED", `Configured Xcode project was not found: ${project}`);
|
|
45
|
+
await installCocoaPods(options.root, options.quiet, options.onEvent);
|
|
46
|
+
const resolvedProject = findProject(options.root, options.profile);
|
|
47
|
+
const ios = options.profile.ios ?? {};
|
|
48
|
+
const scheme = ios.scheme;
|
|
49
|
+
assert(scheme, "IOS_SCHEME_REQUIRED", "Configure build.<profile>.ios.scheme in lynxship.json");
|
|
50
|
+
const exportOptions = ios.exportOptionsPlist;
|
|
51
|
+
assert(exportOptions, "IOS_EXPORT_OPTIONS_REQUIRED", "Configure build.<profile>.ios.exportOptionsPlist for a signed IPA export");
|
|
52
|
+
const exportOptionsPath = resolve(options.root, exportOptions);
|
|
53
|
+
assert(existsSync(exportOptionsPath), "IOS_EXPORT_OPTIONS_REQUIRED", `Export options file was not found: ${exportOptionsPath}`);
|
|
54
|
+
const projectFlag = resolvedProject.endsWith(".xcworkspace")
|
|
55
|
+
? "-workspace"
|
|
56
|
+
: "-project";
|
|
57
|
+
const configuration = ios.configuration ?? "Release";
|
|
58
|
+
const archiveDirectory = join(options.root, ".lynxship", "ios", job.id);
|
|
59
|
+
const archivePath = join(archiveDirectory, `${job.id}.xcarchive`);
|
|
60
|
+
const exportPath = join(archiveDirectory, "export");
|
|
61
|
+
const step = (message, progress) => {
|
|
62
|
+
options.onEvent?.(message);
|
|
63
|
+
options.onProgress?.(progress, message);
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
const uploadArtifacts = options.uploadArtifacts ?? true;
|
|
67
|
+
if (uploadArtifacts)
|
|
68
|
+
await loadR2(options.root);
|
|
69
|
+
await mkdir(archiveDirectory, { recursive: true });
|
|
70
|
+
transitionBuild(job, "uploading_source", "iOS source prepared");
|
|
71
|
+
if (options.skipBundleBuild) {
|
|
72
|
+
step("Using shared Lynx bundle", 5);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
step("Building Lynx bundle with Rspeedy…", 5);
|
|
76
|
+
await buildLynxBundle(options.root, {
|
|
77
|
+
quiet: options.quiet,
|
|
78
|
+
onOutput: options.onEvent,
|
|
79
|
+
miso: options.profile.miso,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const appConfig = await loadAppConfig(options.root);
|
|
83
|
+
const copiedBuildResources = await syncIosBuildResources(options.root, resolveAppConfigAssetPath(options.root, appConfig, "ios", "ios/LynxResources/Assets"));
|
|
84
|
+
for (const name of copiedBuildResources)
|
|
85
|
+
options.onEvent?.(`Synced ${name} into the iOS native resources`);
|
|
86
|
+
if (ios.bundleScript) {
|
|
87
|
+
step("Syncing bundle into the iOS host…", 8);
|
|
88
|
+
await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
|
|
89
|
+
cwd: options.root,
|
|
90
|
+
quiet: options.quiet,
|
|
91
|
+
onOutput: options.onEvent,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const icon = await prepareIosAppIcon(options.root, ios.appIcon ?? appConfig?.config.appIcon);
|
|
95
|
+
if (icon)
|
|
96
|
+
step(`Using iOS app icon: ${icon}`, 9);
|
|
97
|
+
else
|
|
98
|
+
options.onEvent?.("No 1024x1024 PNG app icon was found; configure ios.appIcon or add icon.png before distribution.");
|
|
99
|
+
step("Preparing Xcode archive…", 10);
|
|
100
|
+
await runProcess("xcodebuild", [
|
|
101
|
+
projectFlag,
|
|
102
|
+
resolvedProject,
|
|
103
|
+
"-scheme",
|
|
104
|
+
scheme,
|
|
105
|
+
"-configuration",
|
|
106
|
+
configuration,
|
|
107
|
+
"-sdk",
|
|
108
|
+
"iphoneos",
|
|
109
|
+
"-archivePath",
|
|
110
|
+
archivePath,
|
|
111
|
+
"archive",
|
|
112
|
+
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
113
|
+
const archivedApp = await findArchiveApp(archivePath);
|
|
114
|
+
const copiedResources = await syncIosRuntimeResources(options.root, archivedApp);
|
|
115
|
+
for (const name of copiedResources)
|
|
116
|
+
options.onEvent?.(`Packaged ${name} in the iOS archive`);
|
|
117
|
+
transitionBuild(job, "queued", "iOS build queued locally");
|
|
118
|
+
transitionBuild(job, "provisioning", "Xcode signing configuration selected");
|
|
119
|
+
step("Exporting signed IPA…", 55);
|
|
120
|
+
await runProcess("xcodebuild", [
|
|
121
|
+
"-exportArchive",
|
|
122
|
+
"-archivePath",
|
|
123
|
+
archivePath,
|
|
124
|
+
"-exportOptionsPlist",
|
|
125
|
+
exportOptionsPath,
|
|
126
|
+
"-exportPath",
|
|
127
|
+
exportPath,
|
|
128
|
+
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
129
|
+
transitionBuild(job, "installing_dependencies", "iOS archive exported");
|
|
130
|
+
transitionBuild(job, "building", "IPA package created");
|
|
131
|
+
const exportedIpa = await findIpa(exportPath);
|
|
132
|
+
transitionBuild(job, "signing", "Xcode signed the IPA");
|
|
133
|
+
step("Verifying IPA signature…", 75);
|
|
134
|
+
await verifyIpa(options.root, exportedIpa, archiveDirectory);
|
|
135
|
+
transitionBuild(job, "uploading_artifacts", "iOS artifact collected");
|
|
136
|
+
const artifactName = nativeArtifactName("ipa");
|
|
137
|
+
const artifactPath = join(options.root, ".lynxship", "artifacts", artifactName);
|
|
138
|
+
await mkdir(join(options.root, ".lynxship", "artifacts"), {
|
|
139
|
+
recursive: true,
|
|
140
|
+
});
|
|
141
|
+
await copyFile(exportedIpa, artifactPath);
|
|
142
|
+
const content = await readFile(artifactPath);
|
|
143
|
+
const hash = sha256(content);
|
|
144
|
+
job.attempts += 1;
|
|
145
|
+
if (!uploadArtifacts) {
|
|
146
|
+
step("Artifact collected locally; R2 upload skipped", 100);
|
|
147
|
+
job.artifact = {
|
|
148
|
+
name: artifactName,
|
|
149
|
+
hash,
|
|
150
|
+
path: artifactPath,
|
|
151
|
+
size: content.length,
|
|
152
|
+
contentType: "application/octet-stream",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
step("Uploading signed artifact to Cloudflare R2…", 80);
|
|
157
|
+
const uploaded = await uploadR2Artifact(options.root, job.projectId, job.id, artifactPath, "application/octet-stream", undefined, {
|
|
158
|
+
onProgress: (uploadedBytes, totalBytes) => {
|
|
159
|
+
const transfer = totalBytes === 0 ? 1 : uploadedBytes / totalBytes;
|
|
160
|
+
options.onProgress?.(80 + transfer * 19, `Uploading signed artifact to Cloudflare R2… ${Math.round(transfer * 10000) / 100}%`);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
assert(uploaded.hash === hash, "BUILD_ARTIFACT_HASH", "R2 artifact hash mismatch");
|
|
164
|
+
job.artifact = {
|
|
165
|
+
name: artifactName,
|
|
166
|
+
hash,
|
|
167
|
+
path: artifactPath,
|
|
168
|
+
key: uploaded.key,
|
|
169
|
+
size: uploaded.size,
|
|
170
|
+
contentType: uploaded.contentType,
|
|
171
|
+
url: uploaded.url,
|
|
172
|
+
expiresAt: uploaded.expiresAt,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
step(`Artifact ready: ${artifactName}`, 100);
|
|
176
|
+
return transitionBuild(job, "success", "real iOS artifact created");
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!["success", "failed", "canceled", "timed_out"].includes(job.state))
|
|
180
|
+
transitionBuild(job, "failed", error instanceof Error ? error.message : "iOS build failed");
|
|
181
|
+
job.logs.push({
|
|
182
|
+
level: "error",
|
|
183
|
+
message: error instanceof Error ? error.message : "iOS build failed",
|
|
184
|
+
at: new Date().toISOString(),
|
|
185
|
+
});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function verifyIpa(root, ipa, directory) {
|
|
190
|
+
const extracted = join(directory, "verified-payload");
|
|
191
|
+
await mkdir(extracted, { recursive: true });
|
|
192
|
+
await runProcess("unzip", ["-q", ipa, "-d", extracted], {
|
|
193
|
+
cwd: root,
|
|
194
|
+
quiet: true,
|
|
195
|
+
});
|
|
196
|
+
const payload = join(extracted, "Payload");
|
|
197
|
+
const app = (await readdir(payload, { withFileTypes: true })).find((file) => file.isDirectory() && file.name.endsWith(".app"));
|
|
198
|
+
assert(app, "IOS_SIGNATURE_INVALID", "The exported IPA does not contain an app bundle");
|
|
199
|
+
await runProcess("codesign", ["--verify", "--deep", "--strict", join(payload, app.name)], { cwd: root, quiet: true });
|
|
200
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BuildProfile } from "../config.js";
|
|
2
|
+
export declare function findProject(root: string, profile: BuildProfile): string;
|
|
3
|
+
export declare function installCocoaPods(root: string, quiet: boolean | undefined, onEvent: ((message: string) => void) | undefined): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/ios/project.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CAsBvE;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAC/C,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { assert } from "@lynxship/contracts";
|
|
4
|
+
import { commandExists, runProcess } from "../process-runner.js";
|
|
5
|
+
export function findProject(root, profile) {
|
|
6
|
+
const configured = profile.ios?.workspace ?? profile.ios?.project;
|
|
7
|
+
if (configured) {
|
|
8
|
+
const configuredPath = resolve(root, configured);
|
|
9
|
+
if (configuredPath.endsWith(".xcodeproj")) {
|
|
10
|
+
const workspace = configuredPath.replace(/\.xcodeproj$/, ".xcworkspace");
|
|
11
|
+
if (existsSync(workspace))
|
|
12
|
+
return workspace;
|
|
13
|
+
}
|
|
14
|
+
return configuredPath;
|
|
15
|
+
}
|
|
16
|
+
for (const directory of ["ios", "macos"]) {
|
|
17
|
+
try {
|
|
18
|
+
const candidate = readdirSync(join(root, directory)).find((name) => name.endsWith(".xcworkspace") || name.endsWith(".xcodeproj"));
|
|
19
|
+
if (candidate)
|
|
20
|
+
return join(root, directory, candidate);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// The platform host is optional until an iOS/macOS build is requested.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error("No Xcode workspace or project found under ios/ or macos/");
|
|
27
|
+
}
|
|
28
|
+
export async function installCocoaPods(root, quiet, onEvent) {
|
|
29
|
+
const iosDirectory = join(root, "ios");
|
|
30
|
+
const podfile = join(iosDirectory, "Podfile");
|
|
31
|
+
if (!existsSync(podfile))
|
|
32
|
+
return;
|
|
33
|
+
assert(commandExists("pod"), "IOS_COCOAPODS_REQUIRED", "CocoaPods was not found. Install CocoaPods on macOS, then rerun the build.");
|
|
34
|
+
const hasLockfile = existsSync(join(iosDirectory, "Podfile.lock"));
|
|
35
|
+
onEvent?.(hasLockfile
|
|
36
|
+
? "Installing iOS CocoaPods dependencies…"
|
|
37
|
+
: "Updating CocoaPods specs and installing iOS dependencies…");
|
|
38
|
+
await runProcess("pod", hasLockfile ? ["install"] : ["install", "--repo-update"], {
|
|
39
|
+
cwd: iosDirectory,
|
|
40
|
+
quiet,
|
|
41
|
+
onOutput: onEvent,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BuildJob } from "@lynxship/contracts";
|
|
2
|
+
import type { IosBuildOptions } from "./types.js";
|
|
3
|
+
export declare function launchIosSimulatorApp(root: string, device: string, appBundle: string, options?: {
|
|
4
|
+
quiet?: boolean;
|
|
5
|
+
onEvent?: (message: string) => void;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function runRealIosSimulatorBuild(job: BuildJob, options: IosBuildOptions): Promise<BuildJob>;
|
|
8
|
+
//# sourceMappingURL=simulator-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulator-build.d.ts","sourceRoot":"","sources":["../../src/ios/simulator-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAW5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA2ClD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,GACL,OAAO,CAAC,IAAI,CAAC,CAcf;AA+BD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CA8LnB"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { assert } from "@lynxship/contracts";
|
|
5
|
+
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
6
|
+
import { nativeArtifactName } from "../artifact-name.js";
|
|
7
|
+
import { captureProcess, commandExists, runProcess, } from "../process-runner.js";
|
|
8
|
+
import { buildLynxBundle } from "../bundle-build.js";
|
|
9
|
+
import { loadAppConfig, resolveAppConfigAssetPath } from "../app-config.js";
|
|
10
|
+
import { ensureSimulatorBooted, findSimulatorApp, selectSimulatorDevice, } from "./simulator.js";
|
|
11
|
+
import { prepareIosAppIcon, syncIosBuildResources, syncIosRuntimeResources, } from "./assets.js";
|
|
12
|
+
import { findProject, installCocoaPods } from "./project.js";
|
|
13
|
+
async function appBundleIdentifier(root, appBundle) {
|
|
14
|
+
assert(commandExists("plutil"), "IOS_PLUTIL_REQUIRED", "plutil was not found. Install Xcode command-line tools before launching the Simulator app.");
|
|
15
|
+
const result = await captureProcess("plutil", [
|
|
16
|
+
"-extract",
|
|
17
|
+
"CFBundleIdentifier",
|
|
18
|
+
"raw",
|
|
19
|
+
"-o",
|
|
20
|
+
"-",
|
|
21
|
+
join(appBundle, "Info.plist"),
|
|
22
|
+
], { cwd: root });
|
|
23
|
+
const identifier = result.stdout.trim();
|
|
24
|
+
assert(result.code === 0 && identifier, "IOS_BUNDLE_IDENTIFIER_MISSING", `Could not read CFBundleIdentifier from ${join(appBundle, "Info.plist")}.`);
|
|
25
|
+
return identifier;
|
|
26
|
+
}
|
|
27
|
+
export async function launchIosSimulatorApp(root, device, appBundle, options = {}) {
|
|
28
|
+
const identifier = await appBundleIdentifier(root, appBundle);
|
|
29
|
+
options.onEvent?.("Opening iOS Simulator…");
|
|
30
|
+
await runProcess("open", ["-a", "Simulator"], {
|
|
31
|
+
cwd: root,
|
|
32
|
+
quiet: options.quiet,
|
|
33
|
+
onOutput: options.onEvent,
|
|
34
|
+
});
|
|
35
|
+
options.onEvent?.(`Launching ${identifier} in iOS Simulator…`);
|
|
36
|
+
await runProcess("xcrun", ["simctl", "launch", device, identifier], {
|
|
37
|
+
cwd: root,
|
|
38
|
+
quiet: options.quiet,
|
|
39
|
+
onOutput: options.onEvent,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function hashDirectory(directory) {
|
|
43
|
+
const hash = createHash("sha256");
|
|
44
|
+
let size = 0;
|
|
45
|
+
async function visit(current, relative) {
|
|
46
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
47
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const entryRelative = relative ? `${relative}/${entry.name}` : entry.name;
|
|
50
|
+
const entryPath = join(current, entry.name);
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
hash.update(`dir:${entryRelative}\n`);
|
|
53
|
+
await visit(entryPath, entryRelative);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const content = await readFile(entryPath);
|
|
57
|
+
size += content.length;
|
|
58
|
+
hash.update(`file:${entryRelative}:${content.length}\n`);
|
|
59
|
+
hash.update(content.toString("latin1"), "latin1");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await visit(directory, "");
|
|
64
|
+
return { hash: hash.digest("hex"), size };
|
|
65
|
+
}
|
|
66
|
+
export async function runRealIosSimulatorBuild(job, options) {
|
|
67
|
+
assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS Simulator builds require macOS with Xcode");
|
|
68
|
+
assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
|
|
69
|
+
assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
|
|
70
|
+
const project = findProject(options.root, options.profile);
|
|
71
|
+
const ios = options.profile.ios ?? {};
|
|
72
|
+
assert(ios.scheme, "IOS_SCHEME_REQUIRED", "Configure ios.scheme in the selected Simulator build profile");
|
|
73
|
+
const projectFlag = project.endsWith(".xcworkspace")
|
|
74
|
+
? "-workspace"
|
|
75
|
+
: "-project";
|
|
76
|
+
const configuration = ios.configuration ?? "Debug";
|
|
77
|
+
const device = await selectSimulatorDevice(options.root, options.simulatorDevice);
|
|
78
|
+
await installCocoaPods(options.root, options.quiet, options.onEvent);
|
|
79
|
+
const directory = join(options.root, ".lynxship", "ios", job.id, "simulator");
|
|
80
|
+
const derivedData = join(directory, "derived-data");
|
|
81
|
+
const step = (message, progress) => {
|
|
82
|
+
options.onEvent?.(message);
|
|
83
|
+
options.onProgress?.(progress, message);
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
assert(options.uploadArtifacts !== true, "IOS_SIMULATOR_UPLOAD_BLOCKED", "iOS Simulator .app builds stay local. Use --no-upload; simulator artifacts are not store artifacts.");
|
|
87
|
+
await mkdir(directory, { recursive: true });
|
|
88
|
+
transitionBuild(job, "uploading_source", "iOS Simulator source prepared");
|
|
89
|
+
if (options.skipBundleBuild) {
|
|
90
|
+
step("Using shared Lynx bundle", 5);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
step("Building Lynx bundle with Rspeedy…", 5);
|
|
94
|
+
await buildLynxBundle(options.root, {
|
|
95
|
+
quiet: options.quiet,
|
|
96
|
+
onOutput: options.onEvent,
|
|
97
|
+
miso: options.profile.miso,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const appConfig = await loadAppConfig(options.root);
|
|
101
|
+
const copiedBuildResources = await syncIosBuildResources(options.root, resolveAppConfigAssetPath(options.root, appConfig, "ios", "ios/LynxResources/Assets"));
|
|
102
|
+
for (const name of copiedBuildResources)
|
|
103
|
+
options.onEvent?.(`Synced ${name} into the iOS native resources`);
|
|
104
|
+
if (ios.bundleScript) {
|
|
105
|
+
step("Syncing bundle into the iOS host…", 8);
|
|
106
|
+
await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
|
|
107
|
+
cwd: options.root,
|
|
108
|
+
quiet: options.quiet,
|
|
109
|
+
onOutput: options.onEvent,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const icon = await prepareIosAppIcon(options.root, ios.appIcon ?? appConfig?.config.appIcon, {
|
|
113
|
+
allowFallback: true,
|
|
114
|
+
});
|
|
115
|
+
if (icon)
|
|
116
|
+
step(`Using iOS app icon: ${icon}`, 9);
|
|
117
|
+
else
|
|
118
|
+
options.onEvent?.("No 1024x1024 PNG app icon was found; configure ios.appIcon or add icon.png before distribution.");
|
|
119
|
+
transitionBuild(job, "queued", "iOS Simulator build queued locally");
|
|
120
|
+
transitionBuild(job, "provisioning", "iOS Simulator destination selected");
|
|
121
|
+
transitionBuild(job, "installing_dependencies", "iOS Simulator dependencies ready");
|
|
122
|
+
await ensureSimulatorBooted(options.root, device, options.onEvent, options.quiet);
|
|
123
|
+
step("Building iOS Simulator .app…", 15);
|
|
124
|
+
transitionBuild(job, "building", "iOS Simulator build started");
|
|
125
|
+
await runProcess("xcodebuild", [
|
|
126
|
+
projectFlag,
|
|
127
|
+
project,
|
|
128
|
+
"-scheme",
|
|
129
|
+
ios.scheme,
|
|
130
|
+
"-configuration",
|
|
131
|
+
configuration,
|
|
132
|
+
"-sdk",
|
|
133
|
+
"iphonesimulator",
|
|
134
|
+
"-destination",
|
|
135
|
+
`id=${device}`,
|
|
136
|
+
"-derivedDataPath",
|
|
137
|
+
derivedData,
|
|
138
|
+
"CODE_SIGNING_ALLOWED=NO",
|
|
139
|
+
"CODE_SIGNING_REQUIRED=NO",
|
|
140
|
+
"build",
|
|
141
|
+
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
142
|
+
const appPath = await findSimulatorApp(derivedData, configuration, ios.scheme);
|
|
143
|
+
const copiedResources = await syncIosRuntimeResources(options.root, appPath);
|
|
144
|
+
for (const name of copiedResources)
|
|
145
|
+
options.onEvent?.(`Packaged ${name} in the iOS Simulator app`);
|
|
146
|
+
step("Installing .app in iOS Simulator…", 75);
|
|
147
|
+
await runProcess("xcrun", ["simctl", "install", device, appPath], {
|
|
148
|
+
cwd: options.root,
|
|
149
|
+
quiet: options.quiet,
|
|
150
|
+
onOutput: options.onEvent,
|
|
151
|
+
});
|
|
152
|
+
if (options.simulatorAutostart) {
|
|
153
|
+
try {
|
|
154
|
+
await launchIosSimulatorApp(options.root, device, appPath, {
|
|
155
|
+
quiet: options.quiet,
|
|
156
|
+
onEvent: options.onEvent,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
options.onEvent?.(`Simulator app installed but could not be launched automatically: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const artifact = await hashDirectory(appPath);
|
|
164
|
+
const artifactName = nativeArtifactName("app");
|
|
165
|
+
job.attempts += 1;
|
|
166
|
+
job.artifact = {
|
|
167
|
+
name: artifactName,
|
|
168
|
+
hash: artifact.hash,
|
|
169
|
+
path: appPath,
|
|
170
|
+
size: artifact.size,
|
|
171
|
+
contentType: "application/octet-stream",
|
|
172
|
+
};
|
|
173
|
+
transitionBuild(job, "signing", "Simulator app verification completed");
|
|
174
|
+
transitionBuild(job, "uploading_artifacts", "Simulator app collected locally");
|
|
175
|
+
step(`Simulator app ready: ${artifactName}`, 100);
|
|
176
|
+
return transitionBuild(job, "success", "iOS Simulator app created");
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!["success", "failed", "canceled", "timed_out"].includes(job.state))
|
|
180
|
+
transitionBuild(job, "failed", error instanceof Error ? error.message : "iOS Simulator build failed");
|
|
181
|
+
job.logs.push({
|
|
182
|
+
level: "error",
|
|
183
|
+
message: error instanceof Error ? error.message : "iOS Simulator build failed",
|
|
184
|
+
at: new Date().toISOString(),
|
|
185
|
+
});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function selectSimulatorDevice(root: string, requested?: string): Promise<string>;
|
|
2
|
+
export declare function ensureSimulatorBooted(root: string, device: string, onEvent?: (message: string) => void, quiet?: boolean): Promise<void>;
|
|
3
|
+
export declare function findSimulatorApp(derivedData: string, configuration: string, scheme: string): Promise<string>;
|
|
4
|
+
export declare function findArchiveApp(archivePath: string): Promise<string>;
|
|
5
|
+
//# sourceMappingURL=simulator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulator.d.ts","sourceRoot":"","sources":["../../src/ios/simulator.ts"],"names":[],"mappings":"AAgCA,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAajB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACnC,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAczE"}
|