@absolutejs/absolute 0.20.0-beta.38 → 0.20.0-beta.39
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 +11 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +657 -162
- package/dist/mobile/index.js +578 -47
- package/dist/mobile/index.js.map +7 -6
- package/dist/mobile/remoteMacAgentEntry.js +6 -6
- package/dist/src/mobile/androidRelease.d.ts +6 -0
- package/dist/src/mobile/ciWorkflow.d.ts +26 -0
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/mobile/iosRelease.d.ts +1 -0
- package/dist/src/mobile/mobileInspect.d.ts +41 -15
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2589,7 +2589,14 @@ import {
|
|
|
2589
2589
|
writeFile as writeFile3
|
|
2590
2590
|
} from "fs/promises";
|
|
2591
2591
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join9, relative as relative3, resolve as resolve6, sep as sep2 } from "path";
|
|
2592
|
-
var
|
|
2592
|
+
var developmentTeamArgument = (value) => {
|
|
2593
|
+
if (value === undefined)
|
|
2594
|
+
return;
|
|
2595
|
+
const team = value.trim().toUpperCase();
|
|
2596
|
+
if (!/^[A-Z0-9]{10}$/u.test(team))
|
|
2597
|
+
throw new TypeError("iOS development team must contain ten letters or digits.");
|
|
2598
|
+
return `DEVELOPMENT_TEAM=${team}`;
|
|
2599
|
+
}, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest = (value) => {
|
|
2593
2600
|
if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
|
|
2594
2601
|
throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
|
|
2595
2602
|
}
|
|
@@ -2762,8 +2769,10 @@ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array
|
|
|
2762
2769
|
await writeFile3(exportPlist, exportOptions());
|
|
2763
2770
|
const run = options.run ?? defaultRun2;
|
|
2764
2771
|
try {
|
|
2772
|
+
const developmentTeam = developmentTeamArgument(options.developmentTeam);
|
|
2765
2773
|
const versionArguments = [
|
|
2766
2774
|
`MARKETING_VERSION=${marketingVersion}`,
|
|
2775
|
+
...developmentTeam ? [developmentTeam] : [],
|
|
2767
2776
|
...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
|
|
2768
2777
|
];
|
|
2769
2778
|
const archiveExit = await run([
|
|
@@ -18740,6 +18749,20 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18740
18749
|
artifactPath
|
|
18741
18750
|
]);
|
|
18742
18751
|
return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
|
|
18752
|
+
}, signAab = (artifactPath, capture, jarsigner, signing) => {
|
|
18753
|
+
const result = capture([
|
|
18754
|
+
jarsigner,
|
|
18755
|
+
"-keystore",
|
|
18756
|
+
signing.keystorePath,
|
|
18757
|
+
"-storepass:env",
|
|
18758
|
+
signing.storePasswordEnvironment,
|
|
18759
|
+
"-keypass:env",
|
|
18760
|
+
signing.keyPasswordEnvironment,
|
|
18761
|
+
artifactPath,
|
|
18762
|
+
signing.keyAlias
|
|
18763
|
+
]);
|
|
18764
|
+
if (result.exitCode !== 0)
|
|
18765
|
+
throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
|
|
18743
18766
|
}, sha256File2 = async (path) => createHash13("sha256").update(await readFile18(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
|
|
18744
18767
|
const root = resolve40(projectRoot);
|
|
18745
18768
|
const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
|
|
@@ -18830,7 +18853,16 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18830
18853
|
throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
|
|
18831
18854
|
}
|
|
18832
18855
|
const capture = options.capture ?? defaultCapture4;
|
|
18833
|
-
const
|
|
18856
|
+
const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") : options.jarsigner;
|
|
18857
|
+
let signed2 = verifyAabSignature(artifactPath, capture, jarsigner);
|
|
18858
|
+
if (signed2 === false && options.signing) {
|
|
18859
|
+
if (!jarsigner)
|
|
18860
|
+
throw new TypeError("Could not sign the Android App Bundle because jarsigner is unavailable.");
|
|
18861
|
+
signAab(artifactPath, capture, jarsigner, options.signing);
|
|
18862
|
+
signed2 = verifyAabSignature(artifactPath, capture, jarsigner);
|
|
18863
|
+
if (!signed2)
|
|
18864
|
+
throw new TypeError("Android App Bundle signature verification failed after CI signing.");
|
|
18865
|
+
}
|
|
18834
18866
|
if (signed2 === null && !options.allowUnsigned) {
|
|
18835
18867
|
throw new TypeError("Could not verify the Android App Bundle signature because jarsigner is unavailable. Install a JDK, or use --unsigned only for a non-publishable build.");
|
|
18836
18868
|
}
|
|
@@ -19413,9 +19445,9 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
|
19413
19445
|
var init_releasePublisher = () => {};
|
|
19414
19446
|
|
|
19415
19447
|
// src/mobile/mobileInspect.ts
|
|
19416
|
-
import { access as access12, readFile as readFile21
|
|
19448
|
+
import { access as access12, readFile as readFile21 } from "fs/promises";
|
|
19417
19449
|
import { join as join53, relative as relative28, resolve as resolve42 } from "path";
|
|
19418
|
-
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES,
|
|
19450
|
+
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath2 = (projectRoot, path) => {
|
|
19419
19451
|
const value = relative28(resolve42(projectRoot), resolve42(path)).replaceAll("\\", "/");
|
|
19420
19452
|
return value || ".";
|
|
19421
19453
|
}, pathExists8 = async (path) => {
|
|
@@ -19430,103 +19462,6 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19430
19462
|
if (!isObject3(value))
|
|
19431
19463
|
throw new TypeError("JSON root must be an object.");
|
|
19432
19464
|
return value;
|
|
19433
|
-
}, requireString2 = (value, field) => {
|
|
19434
|
-
if (typeof value !== "string" || value.length === 0)
|
|
19435
|
-
throw new TypeError(`${field} must be a non-empty string.`);
|
|
19436
|
-
return value;
|
|
19437
|
-
}, requireStringArray2 = (value, field) => {
|
|
19438
|
-
if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
|
|
19439
|
-
throw new TypeError(`${field} must be a string array.`);
|
|
19440
|
-
return value;
|
|
19441
|
-
}, requireBundleFile2 = async (root, value, field) => {
|
|
19442
|
-
const portable = requireString2(value, field);
|
|
19443
|
-
const path = resolve42(root, portable);
|
|
19444
|
-
const normalizedRoot = resolve42(root);
|
|
19445
|
-
if (path === normalizedRoot || !path.startsWith(`${normalizedRoot}/`))
|
|
19446
|
-
throw new TypeError(`${field} must remain inside the mobile bundle.`);
|
|
19447
|
-
if (!(await stat5(path).catch(() => {
|
|
19448
|
-
return;
|
|
19449
|
-
}))?.isFile())
|
|
19450
|
-
throw new TypeError(`${field} does not exist in the mobile bundle.`);
|
|
19451
|
-
return portable;
|
|
19452
|
-
}, inspectBundle = async (config, projectRoot) => {
|
|
19453
|
-
const manifestPath = join53(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
19454
|
-
const manifest = portablePath2(projectRoot, manifestPath);
|
|
19455
|
-
if (!await pathExists8(manifestPath))
|
|
19456
|
-
return { manifest, status: "missing" };
|
|
19457
|
-
try {
|
|
19458
|
-
const value = await readObject2(manifestPath);
|
|
19459
|
-
if (value.format !== 1)
|
|
19460
|
-
throw new TypeError("format is not supported by this runtime.");
|
|
19461
|
-
if (requireString2(value.appId, "appId") !== config.appId)
|
|
19462
|
-
throw new TypeError("appId does not match the effective mobile config.");
|
|
19463
|
-
if (requireString2(value.productionOrigin, "productionOrigin") !== config.productionOrigin)
|
|
19464
|
-
throw new TypeError("productionOrigin does not match the effective mobile config.");
|
|
19465
|
-
const appBuild = requireString2(value.appBuild, "appBuild");
|
|
19466
|
-
const runtime = requireString2(value.runtime, "runtime");
|
|
19467
|
-
const capabilities = requireStringArray2(value.deviceCapabilities, "deviceCapabilities").sort();
|
|
19468
|
-
if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
|
|
19469
|
-
throw new TypeError("pages and routes must be arrays.");
|
|
19470
|
-
const pageIds = new Set;
|
|
19471
|
-
const frameworks7 = new Set;
|
|
19472
|
-
await Promise.all(value.pages.map(async (candidate) => {
|
|
19473
|
-
if (!isObject3(candidate))
|
|
19474
|
-
throw new TypeError("pages contains an invalid entry.");
|
|
19475
|
-
const pageId = requireString2(candidate.pageId, "page.pageId");
|
|
19476
|
-
if (pageIds.has(pageId))
|
|
19477
|
-
throw new TypeError("page.pageId values must be unique.");
|
|
19478
|
-
pageIds.add(pageId);
|
|
19479
|
-
const framework = requireString2(candidate.framework, "page.framework");
|
|
19480
|
-
if (!MOBILE_FRAMEWORKS2.has(framework))
|
|
19481
|
-
throw new TypeError("page.framework is unsupported.");
|
|
19482
|
-
frameworks7.add(framework);
|
|
19483
|
-
requireString2(candidate.bundleHash, "page.bundleHash");
|
|
19484
|
-
requireString2(candidate.contract, "page.contract");
|
|
19485
|
-
requireString2(candidate.propsSchemaHash, "page.propsSchemaHash");
|
|
19486
|
-
await requireBundleFile2(config.bundleDirectory, candidate.localBundlePath, "page.localBundlePath");
|
|
19487
|
-
if (candidate.localStylePath !== undefined)
|
|
19488
|
-
await requireBundleFile2(config.bundleDirectory, candidate.localStylePath, "page.localStylePath");
|
|
19489
|
-
}));
|
|
19490
|
-
const routes = value.routes.map((candidate) => {
|
|
19491
|
-
if (!isObject3(candidate))
|
|
19492
|
-
throw new TypeError("routes contains an invalid entry.");
|
|
19493
|
-
const { method } = candidate;
|
|
19494
|
-
if (method !== "GET" && method !== "HEAD")
|
|
19495
|
-
throw new TypeError("route.method must be GET or HEAD.");
|
|
19496
|
-
const pageId = requireString2(candidate.pageId, "route.pageId");
|
|
19497
|
-
if (!pageIds.has(pageId))
|
|
19498
|
-
throw new TypeError("route.pageId references a missing page.");
|
|
19499
|
-
return {
|
|
19500
|
-
method,
|
|
19501
|
-
pageId,
|
|
19502
|
-
pattern: requireString2(candidate.pattern, "route.pattern")
|
|
19503
|
-
};
|
|
19504
|
-
});
|
|
19505
|
-
await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) => requireBundleFile2(config.bundleDirectory, file, file)));
|
|
19506
|
-
const entryPath = new URL(config.entry, "https://absolute.invalid").pathname;
|
|
19507
|
-
const entryResolved = resolveAbsoluteMobileRoute(routes, entryPath) !== undefined;
|
|
19508
|
-
if (!entryResolved)
|
|
19509
|
-
throw new TypeError("entry is not owned by an embedded route.");
|
|
19510
|
-
return {
|
|
19511
|
-
appBuild,
|
|
19512
|
-
auth: isObject3(value.auth),
|
|
19513
|
-
capabilities,
|
|
19514
|
-
entryResolved,
|
|
19515
|
-
frameworks: [...frameworks7].sort(),
|
|
19516
|
-
manifest,
|
|
19517
|
-
pageCount: value.pages.length,
|
|
19518
|
-
routeCount: value.routes.length,
|
|
19519
|
-
runtime,
|
|
19520
|
-
status: "valid",
|
|
19521
|
-
sync: isObject3(value.sync)
|
|
19522
|
-
};
|
|
19523
|
-
} catch (error) {
|
|
19524
|
-
return {
|
|
19525
|
-
issue: error instanceof Error ? error.message : "The embedded mobile manifest is invalid.",
|
|
19526
|
-
manifest,
|
|
19527
|
-
status: "invalid"
|
|
19528
|
-
};
|
|
19529
|
-
}
|
|
19530
19465
|
}, addPackageDeclarations = (declarations, value) => {
|
|
19531
19466
|
if (!isObject3(value))
|
|
19532
19467
|
return;
|
|
@@ -19550,7 +19485,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19550
19485
|
};
|
|
19551
19486
|
}));
|
|
19552
19487
|
}, inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) => {
|
|
19553
|
-
const bundle = await
|
|
19488
|
+
const bundle = await inspectAbsoluteMobileBundle(config, projectRoot);
|
|
19554
19489
|
let currentCapabilities = [];
|
|
19555
19490
|
let capabilityIssue;
|
|
19556
19491
|
let plugins = [];
|
|
@@ -19643,7 +19578,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19643
19578
|
var init_mobileInspect = __esm(() => {
|
|
19644
19579
|
init_deviceCapabilities();
|
|
19645
19580
|
init_releaseDoctor();
|
|
19646
|
-
|
|
19581
|
+
init_mobileBundleInspection();
|
|
19647
19582
|
MOBILE_PACKAGE_NAMES = new Set([
|
|
19648
19583
|
"@absolutejs/absolute",
|
|
19649
19584
|
"@absolutejs/auth",
|
|
@@ -19655,15 +19590,484 @@ var init_mobileInspect = __esm(() => {
|
|
|
19655
19590
|
"@absolutejs/sync-capacitor",
|
|
19656
19591
|
"@capacitor-community/sqlite"
|
|
19657
19592
|
]);
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19593
|
+
});
|
|
19594
|
+
|
|
19595
|
+
// src/mobile/ciWorkflow.ts
|
|
19596
|
+
import { existsSync as existsSync42 } from "fs";
|
|
19597
|
+
import { access as access13, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
|
|
19598
|
+
import { dirname as dirname31, extname as extname9, relative as relative29, resolve as resolve43, sep as sep8 } from "path";
|
|
19599
|
+
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTATION = 6, RESERVED_SECRET_NAMES, exists3 = async (path) => {
|
|
19600
|
+
try {
|
|
19601
|
+
await access13(path);
|
|
19602
|
+
return true;
|
|
19603
|
+
} catch {
|
|
19604
|
+
return false;
|
|
19605
|
+
}
|
|
19606
|
+
}, yamlString = (value) => `'${value.replaceAll("'", "''")}'`, projectPath = (projectRoot, value, field, options = {}) => {
|
|
19607
|
+
const root = resolve43(projectRoot);
|
|
19608
|
+
const path = resolve43(root, value);
|
|
19609
|
+
const portable = relative29(root, path).replaceAll("\\", "/");
|
|
19610
|
+
if (portable === ".." || portable.startsWith(`..${sep8}`) || portable.startsWith("../") || portable === "") {
|
|
19611
|
+
throw new TypeError(`${field} must remain inside the project root.`);
|
|
19612
|
+
}
|
|
19613
|
+
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
19614
|
+
throw new TypeError(`${field} contains an unsafe path.`);
|
|
19615
|
+
if (!options.allowMissing && !existsSync42(path))
|
|
19616
|
+
throw new TypeError(`${field} does not exist inside the project.`);
|
|
19617
|
+
return portable;
|
|
19618
|
+
}, workflowOutputPath = (projectRoot, value) => {
|
|
19619
|
+
const root = resolve43(projectRoot);
|
|
19620
|
+
const workflows = resolve43(root, ".github/workflows");
|
|
19621
|
+
const path = resolve43(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
19622
|
+
const portable = relative29(workflows, path);
|
|
19623
|
+
if (portable === ".." || portable.startsWith(`..${sep8}`) || extname9(path) !== ".yml" && extname9(path) !== ".yaml") {
|
|
19624
|
+
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
19625
|
+
}
|
|
19626
|
+
return path;
|
|
19627
|
+
}, normalizeSecretEnvironment = (values = []) => {
|
|
19628
|
+
const names = [...new Set(values)].sort();
|
|
19629
|
+
for (const name of names) {
|
|
19630
|
+
if (!SECRET_NAME_PATTERN.test(name))
|
|
19631
|
+
throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
|
|
19632
|
+
if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
|
|
19633
|
+
throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
|
|
19634
|
+
}
|
|
19635
|
+
}
|
|
19636
|
+
return names;
|
|
19637
|
+
}, customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
|
|
19638
|
+
`), commandEnvironment = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
|
|
19639
|
+
ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
|
|
19640
|
+
ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`, appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
|
|
19641
|
+
args+=(--config "$ABSOLUTE_CONFIG_PATH")
|
|
19642
|
+
fi`, installSteps = ` - name: Check out source
|
|
19643
|
+
uses: actions/checkout@v6
|
|
19644
|
+
- name: Install Bun
|
|
19645
|
+
uses: oven-sh/setup-bun@v2
|
|
19646
|
+
- name: Install exact dependencies
|
|
19647
|
+
run: bun ci`, bundleAuditSteps, releaseAuditSteps = (platform6) => ` - name: Run redacted mobile release audit
|
|
19648
|
+
id: mobile-release-audit
|
|
19649
|
+
continue-on-error: true
|
|
19650
|
+
shell: bash
|
|
19651
|
+
run: |
|
|
19652
|
+
mkdir -p .absolutejs/mobile-ci
|
|
19653
|
+
args=(bunx absolute mobile doctor release ${platform6} --json)
|
|
19654
|
+
${appendConfigArgument}
|
|
19655
|
+
"\${args[@]}" > .absolutejs/mobile-ci/compliance.json
|
|
19656
|
+
- name: Upload mobile compliance report
|
|
19657
|
+
if: always()
|
|
19658
|
+
uses: actions/upload-artifact@v7
|
|
19659
|
+
with:
|
|
19660
|
+
name: absolute-mobile-compliance-\${{ github.job }}
|
|
19661
|
+
path: .absolutejs/mobile-ci/compliance.json
|
|
19662
|
+
if-no-files-found: error
|
|
19663
|
+
retention-days: 30
|
|
19664
|
+
include-hidden-files: true
|
|
19665
|
+
- name: Enforce mobile release audit
|
|
19666
|
+
if: steps.mobile-release-audit.outcome != 'success'
|
|
19667
|
+
run: exit 1`, platformInput = (platforms) => {
|
|
19668
|
+
const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
|
|
19669
|
+
return ` platform:
|
|
19670
|
+
description: Native platform to build
|
|
19671
|
+
required: true
|
|
19672
|
+
type: choice
|
|
19673
|
+
default: ${choices[0]}
|
|
19674
|
+
options:
|
|
19675
|
+
${choices.map((value) => ` - ${value}`).join(`
|
|
19676
|
+
`)}`;
|
|
19677
|
+
}, publishingInputs = (platforms, includePublishing) => {
|
|
19678
|
+
if (!includePublishing)
|
|
19679
|
+
return "";
|
|
19680
|
+
const fields = [
|
|
19681
|
+
` publish:
|
|
19682
|
+
description: Publish through mobile.release.ts after the signed build
|
|
19683
|
+
required: true
|
|
19684
|
+
type: boolean
|
|
19685
|
+
default: false`,
|
|
19686
|
+
` channel:
|
|
19687
|
+
description: Optional AbsoluteJS immutable release channel
|
|
19688
|
+
required: false
|
|
19689
|
+
type: string`
|
|
19690
|
+
];
|
|
19691
|
+
if (platforms.includes("android"))
|
|
19692
|
+
fields.push(` play_track:
|
|
19693
|
+
description: Optional Google Play track
|
|
19694
|
+
required: true
|
|
19695
|
+
type: choice
|
|
19696
|
+
default: registry-only
|
|
19697
|
+
options:
|
|
19698
|
+
- registry-only
|
|
19699
|
+
- internal
|
|
19700
|
+
- alpha
|
|
19701
|
+
- beta
|
|
19702
|
+
- production`);
|
|
19703
|
+
if (platforms.includes("ios")) {
|
|
19704
|
+
fields.push(` testflight_group:
|
|
19705
|
+
description: Optional internal or external TestFlight group
|
|
19706
|
+
required: false
|
|
19707
|
+
type: string`);
|
|
19708
|
+
fields.push(` submit_testflight_review:
|
|
19709
|
+
description: Explicitly submit an external TestFlight build for review
|
|
19710
|
+
required: true
|
|
19711
|
+
type: boolean
|
|
19712
|
+
default: false`);
|
|
19713
|
+
}
|
|
19714
|
+
return `
|
|
19715
|
+
${fields.join(`
|
|
19716
|
+
`)}`;
|
|
19717
|
+
}, jobCondition = (platform6) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform6}')`, androidJob = (options) => {
|
|
19718
|
+
const custom = customSecretEnvironment(options.customSecrets);
|
|
19719
|
+
const publishEnvironment = options.includePublishing ? `
|
|
19720
|
+
ABSOLUTE_PUBLISH: \${{ inputs.publish }}
|
|
19721
|
+
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
19722
|
+
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
19723
|
+
ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
|
|
19724
|
+
GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json` : "";
|
|
19725
|
+
const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
|
|
19726
|
+
args=(bunx absolute mobile publish android "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
|
|
19727
|
+
if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
|
|
19728
|
+
args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
|
|
19729
|
+
fi
|
|
19730
|
+
if [[ "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
|
|
19731
|
+
args+=(--play-track "$ABSOLUTE_PLAY_TRACK")
|
|
19732
|
+
fi
|
|
19733
|
+
else
|
|
19734
|
+
args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")
|
|
19735
|
+
fi` : `args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")`;
|
|
19736
|
+
const googleSetup = options.includePublishing ? `
|
|
19737
|
+
if [[ "$ABSOLUTE_PUBLISH" == "true" && "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
|
|
19738
|
+
if [[ -z "$ABSOLUTE_GOOGLE_CREDENTIALS_BASE64" ]]; then
|
|
19739
|
+
echo "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 is required for Google Play publication." >&2
|
|
19740
|
+
exit 1
|
|
19741
|
+
fi
|
|
19742
|
+
printf '%s' "$ABSOLUTE_GOOGLE_CREDENTIALS_BASE64" | base64 --decode > "$GOOGLE_APPLICATION_CREDENTIALS"
|
|
19743
|
+
chmod 600 "$GOOGLE_APPLICATION_CREDENTIALS"
|
|
19744
|
+
fi` : "";
|
|
19745
|
+
return `
|
|
19746
|
+
android:
|
|
19747
|
+
name: Signed Android release
|
|
19748
|
+
needs: validate
|
|
19749
|
+
if: \${{ ${jobCondition("android")} }}
|
|
19750
|
+
runs-on: ubuntu-latest
|
|
19751
|
+
environment: absolute-mobile-release
|
|
19752
|
+
permissions:
|
|
19753
|
+
contents: read
|
|
19754
|
+
id-token: write
|
|
19755
|
+
attestations: write
|
|
19756
|
+
env:
|
|
19757
|
+
ABSOLUTE_ANDROID_KEYSTORE_BASE64: \${{ secrets.ABSOLUTE_ANDROID_KEYSTORE_BASE64 }}
|
|
19758
|
+
ABSOLUTE_ANDROID_KEYSTORE_PASSWORD: \${{ secrets.ABSOLUTE_ANDROID_KEYSTORE_PASSWORD }}
|
|
19759
|
+
ABSOLUTE_ANDROID_KEY_ALIAS: \${{ secrets.ABSOLUTE_ANDROID_KEY_ALIAS }}
|
|
19760
|
+
ABSOLUTE_ANDROID_KEY_PASSWORD: \${{ secrets.ABSOLUTE_ANDROID_KEY_PASSWORD }}
|
|
19761
|
+
ABSOLUTE_ANDROID_KEYSTORE_PATH: \${{ runner.temp }}/absolute-release.jks${publishEnvironment}${custom ? `
|
|
19762
|
+
${custom}` : ""}
|
|
19763
|
+
${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
|
|
19764
|
+
steps:
|
|
19765
|
+
${installSteps}
|
|
19766
|
+
- name: Provision Android signing
|
|
19767
|
+
shell: bash
|
|
19768
|
+
run: |
|
|
19769
|
+
required=(
|
|
19770
|
+
ABSOLUTE_ANDROID_KEYSTORE_BASE64
|
|
19771
|
+
ABSOLUTE_ANDROID_KEYSTORE_PASSWORD
|
|
19772
|
+
ABSOLUTE_ANDROID_KEY_ALIAS
|
|
19773
|
+
ABSOLUTE_ANDROID_KEY_PASSWORD
|
|
19774
|
+
)
|
|
19775
|
+
for name in "\${required[@]}"; do
|
|
19776
|
+
if [[ -z "\${!name}" ]]; then
|
|
19777
|
+
echo "$name is required in the absolute-mobile-release environment." >&2
|
|
19778
|
+
exit 1
|
|
19779
|
+
fi
|
|
19780
|
+
done
|
|
19781
|
+
printf '%s' "$ABSOLUTE_ANDROID_KEYSTORE_BASE64" | base64 --decode > "\${{ runner.temp }}/absolute-release.jks"
|
|
19782
|
+
chmod 600 "\${{ runner.temp }}/absolute-release.jks"${googleSetup}
|
|
19783
|
+
- name: Build or publish Android
|
|
19784
|
+
shell: bash
|
|
19785
|
+
run: |
|
|
19786
|
+
${publishCommand}
|
|
19787
|
+
${appendConfigArgument}
|
|
19788
|
+
"\${args[@]}"
|
|
19789
|
+
${releaseAuditSteps("android")}
|
|
19790
|
+
- name: Attest Android App Bundle
|
|
19791
|
+
if: inputs.attest
|
|
19792
|
+
uses: actions/attest@v4
|
|
19793
|
+
with:
|
|
19794
|
+
subject-path: .absolutejs/mobile/releases/android/**/app-release.aab
|
|
19795
|
+
- name: Upload Android release
|
|
19796
|
+
uses: actions/upload-artifact@v7
|
|
19797
|
+
with:
|
|
19798
|
+
name: absolute-mobile-android
|
|
19799
|
+
path: .absolutejs/mobile/releases/android/
|
|
19800
|
+
if-no-files-found: error
|
|
19801
|
+
retention-days: 14
|
|
19802
|
+
include-hidden-files: true
|
|
19803
|
+
- name: Remove Android credentials
|
|
19804
|
+
if: always()
|
|
19805
|
+
shell: bash
|
|
19806
|
+
run: |
|
|
19807
|
+
rm -f "\${{ runner.temp }}/absolute-release.jks"
|
|
19808
|
+
rm -f "\${{ runner.temp }}/absolute-google-credentials.json"`;
|
|
19809
|
+
}, iosJob = (options) => {
|
|
19810
|
+
const custom = customSecretEnvironment(options.customSecrets);
|
|
19811
|
+
const publishEnvironment = options.includePublishing ? `
|
|
19812
|
+
ABSOLUTE_PUBLISH: \${{ inputs.publish }}
|
|
19813
|
+
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
19814
|
+
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
19815
|
+
ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
|
|
19816
|
+
APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
19817
|
+
APP_STORE_CONNECT_KEY_ID: \${{ secrets.APP_STORE_CONNECT_KEY_ID }}
|
|
19818
|
+
APP_STORE_CONNECT_PRIVATE_KEY_BASE64: \${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
|
|
19819
|
+
APP_STORE_CONNECT_PRIVATE_KEY_PATH: \${{ runner.temp }}/AuthKey_AbsoluteJS.p8` : "";
|
|
19820
|
+
const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
|
|
19821
|
+
args=(bunx absolute mobile publish ios "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
|
|
19822
|
+
if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
|
|
19823
|
+
args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
|
|
19824
|
+
fi
|
|
19825
|
+
if [[ -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
|
|
19826
|
+
args+=(--testflight-group "$ABSOLUTE_TESTFLIGHT_GROUP")
|
|
19827
|
+
fi
|
|
19828
|
+
if [[ "$ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW" == "true" ]]; then
|
|
19829
|
+
args+=(--testflight-submit-review)
|
|
19830
|
+
fi
|
|
19831
|
+
else
|
|
19832
|
+
args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")
|
|
19833
|
+
fi` : `args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")`;
|
|
19834
|
+
const appStoreSetup = options.includePublishing ? `
|
|
19835
|
+
if [[ "$ABSOLUTE_PUBLISH" == "true" && -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
|
|
19836
|
+
required+=(APP_STORE_CONNECT_ISSUER_ID APP_STORE_CONNECT_KEY_ID APP_STORE_CONNECT_PRIVATE_KEY_BASE64)
|
|
19837
|
+
fi` : "";
|
|
19838
|
+
const appStoreDecode = options.includePublishing ? `
|
|
19839
|
+
if [[ "$ABSOLUTE_PUBLISH" == "true" && -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
|
|
19840
|
+
printf '%s' "$APP_STORE_CONNECT_PRIVATE_KEY_BASE64" | base64 --decode > "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
|
|
19841
|
+
chmod 600 "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
|
|
19842
|
+
fi` : "";
|
|
19843
|
+
return `
|
|
19844
|
+
ios:
|
|
19845
|
+
name: Signed iOS release
|
|
19846
|
+
needs: validate
|
|
19847
|
+
if: \${{ ${jobCondition("ios")} }}
|
|
19848
|
+
runs-on: macos-latest
|
|
19849
|
+
environment: absolute-mobile-release
|
|
19850
|
+
permissions:
|
|
19851
|
+
contents: read
|
|
19852
|
+
id-token: write
|
|
19853
|
+
attestations: write
|
|
19854
|
+
env:
|
|
19855
|
+
ABSOLUTE_IOS_CERTIFICATE_BASE64: \${{ secrets.ABSOLUTE_IOS_CERTIFICATE_BASE64 }}
|
|
19856
|
+
ABSOLUTE_IOS_CERTIFICATE_PASSWORD: \${{ secrets.ABSOLUTE_IOS_CERTIFICATE_PASSWORD }}
|
|
19857
|
+
ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64: \${{ secrets.ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64 }}
|
|
19858
|
+
ABSOLUTE_IOS_KEYCHAIN_PASSWORD: \${{ secrets.ABSOLUTE_IOS_KEYCHAIN_PASSWORD }}${publishEnvironment}${custom ? `
|
|
19859
|
+
${custom}` : ""}
|
|
19860
|
+
ABSOLUTE_IOS_DEVELOPMENT_TEAM: \${{ secrets.ABSOLUTE_IOS_DEVELOPMENT_TEAM }}
|
|
19861
|
+
${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
|
|
19862
|
+
steps:
|
|
19863
|
+
${installSteps}
|
|
19864
|
+
- name: Provision iOS signing
|
|
19865
|
+
shell: bash
|
|
19866
|
+
run: |
|
|
19867
|
+
required=(
|
|
19868
|
+
ABSOLUTE_IOS_CERTIFICATE_BASE64
|
|
19869
|
+
ABSOLUTE_IOS_CERTIFICATE_PASSWORD
|
|
19870
|
+
ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64
|
|
19871
|
+
ABSOLUTE_IOS_KEYCHAIN_PASSWORD
|
|
19872
|
+
ABSOLUTE_IOS_DEVELOPMENT_TEAM
|
|
19873
|
+
)${appStoreSetup}
|
|
19874
|
+
for name in "\${required[@]}"; do
|
|
19875
|
+
if [[ -z "\${!name}" ]]; then
|
|
19876
|
+
echo "$name is required in the absolute-mobile-release environment." >&2
|
|
19877
|
+
exit 1
|
|
19878
|
+
fi
|
|
19879
|
+
done
|
|
19880
|
+
CERTIFICATE_PATH="\${{ runner.temp }}/absolute-signing.p12"
|
|
19881
|
+
PROFILE_PATH="\${{ runner.temp }}/absolute.mobileprovision"
|
|
19882
|
+
KEYCHAIN_PATH="\${{ runner.temp }}/absolute-signing.keychain-db"
|
|
19883
|
+
PROFILE_DESTINATION="$HOME/Library/MobileDevice/Provisioning Profiles/absolute.mobileprovision"
|
|
19884
|
+
printf '%s' "$ABSOLUTE_IOS_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
|
|
19885
|
+
printf '%s' "$ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"
|
|
19886
|
+
security create-keychain -p "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
19887
|
+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
19888
|
+
security unlock-keychain -p "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
19889
|
+
security import "$CERTIFICATE_PATH" -P "$ABSOLUTE_IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
|
19890
|
+
security set-key-partition-list -S apple-tool:,apple: -k "$ABSOLUTE_IOS_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
19891
|
+
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
|
19892
|
+
mkdir -p "$(dirname "$PROFILE_DESTINATION")"
|
|
19893
|
+
cp "$PROFILE_PATH" "$PROFILE_DESTINATION"${appStoreDecode}
|
|
19894
|
+
- name: Build or publish iOS
|
|
19895
|
+
shell: bash
|
|
19896
|
+
run: |
|
|
19897
|
+
${publishCommand}
|
|
19898
|
+
${appendConfigArgument}
|
|
19899
|
+
"\${args[@]}"
|
|
19900
|
+
${releaseAuditSteps("ios")}
|
|
19901
|
+
- name: Attest iOS IPA
|
|
19902
|
+
if: inputs.attest
|
|
19903
|
+
uses: actions/attest@v4
|
|
19904
|
+
with:
|
|
19905
|
+
subject-path: .absolutejs/mobile/releases/ios/**/App.ipa
|
|
19906
|
+
- name: Upload iOS release
|
|
19907
|
+
uses: actions/upload-artifact@v7
|
|
19908
|
+
with:
|
|
19909
|
+
name: absolute-mobile-ios
|
|
19910
|
+
path: .absolutejs/mobile/releases/ios/
|
|
19911
|
+
if-no-files-found: error
|
|
19912
|
+
retention-days: 14
|
|
19913
|
+
include-hidden-files: true
|
|
19914
|
+
- name: Remove iOS credentials
|
|
19915
|
+
if: always()
|
|
19916
|
+
shell: bash
|
|
19917
|
+
run: |
|
|
19918
|
+
security delete-keychain "\${{ runner.temp }}/absolute-signing.keychain-db" 2>/dev/null || true
|
|
19919
|
+
rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/absolute.mobileprovision"
|
|
19920
|
+
rm -f "\${{ runner.temp }}/absolute-signing.p12"
|
|
19921
|
+
rm -f "\${{ runner.temp }}/absolute.mobileprovision"
|
|
19922
|
+
rm -f "\${{ runner.temp }}/AuthKey_AbsoluteJS.p8"`;
|
|
19923
|
+
}, requiredSecrets = (platforms, includePublishing, custom) => [
|
|
19924
|
+
...platforms.includes("android") ? [
|
|
19925
|
+
"ABSOLUTE_ANDROID_KEYSTORE_BASE64",
|
|
19926
|
+
"ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
|
|
19927
|
+
"ABSOLUTE_ANDROID_KEY_ALIAS",
|
|
19928
|
+
"ABSOLUTE_ANDROID_KEY_PASSWORD",
|
|
19929
|
+
...includePublishing ? ["ABSOLUTE_GOOGLE_CREDENTIALS_BASE64"] : []
|
|
19930
|
+
] : [],
|
|
19931
|
+
...platforms.includes("ios") ? [
|
|
19932
|
+
"ABSOLUTE_IOS_CERTIFICATE_BASE64",
|
|
19933
|
+
"ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
|
|
19934
|
+
"ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
|
|
19935
|
+
"ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
|
|
19936
|
+
"ABSOLUTE_IOS_DEVELOPMENT_TEAM",
|
|
19937
|
+
...includePublishing ? [
|
|
19938
|
+
"APP_STORE_CONNECT_ISSUER_ID",
|
|
19939
|
+
"APP_STORE_CONNECT_KEY_ID",
|
|
19940
|
+
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
19941
|
+
] : []
|
|
19942
|
+
] : [],
|
|
19943
|
+
...custom
|
|
19944
|
+
], createAbsoluteMobileGithubWorkflow = (options) => {
|
|
19945
|
+
const platforms = [
|
|
19946
|
+
...options.config.platforms
|
|
19947
|
+
].sort();
|
|
19948
|
+
const includePublishing = options.includePublishing === true;
|
|
19949
|
+
const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
|
|
19950
|
+
const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
|
|
19951
|
+
const configPath2 = options.configPath ? projectPath(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
|
|
19952
|
+
const registryModule = projectPath(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
|
|
19953
|
+
const environment = commandEnvironment({
|
|
19954
|
+
configPath: configPath2,
|
|
19955
|
+
registryModule,
|
|
19956
|
+
serverEntry
|
|
19957
|
+
});
|
|
19958
|
+
let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
|
|
19959
|
+
name: AbsoluteJS Mobile
|
|
19960
|
+
|
|
19961
|
+
on:
|
|
19962
|
+
pull_request:
|
|
19963
|
+
workflow_dispatch:
|
|
19964
|
+
inputs:
|
|
19965
|
+
${platformInput(platforms)}
|
|
19966
|
+
attest:
|
|
19967
|
+
description: Generate GitHub artifact provenance attestations
|
|
19968
|
+
required: true
|
|
19969
|
+
type: boolean
|
|
19970
|
+
default: false${publishingInputs(platforms, includePublishing)}
|
|
19971
|
+
|
|
19972
|
+
concurrency:
|
|
19973
|
+
group: absolute-mobile-\${{ github.repository }}
|
|
19974
|
+
cancel-in-progress: false
|
|
19975
|
+
|
|
19976
|
+
jobs:
|
|
19977
|
+
validate:
|
|
19978
|
+
name: Validate mobile release inputs
|
|
19979
|
+
runs-on: ubuntu-latest
|
|
19980
|
+
permissions:
|
|
19981
|
+
contents: read
|
|
19982
|
+
env:
|
|
19983
|
+
${environment}
|
|
19984
|
+
steps:
|
|
19985
|
+
${installSteps}
|
|
19986
|
+
${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets, includePublishing }) : ""}${platforms.includes("ios") ? iosJob({ customSecrets, includePublishing }) : ""}
|
|
19987
|
+
`;
|
|
19988
|
+
const replacements = new Map([
|
|
19989
|
+
[
|
|
19990
|
+
"ABSOLUTE_CONFIG_PATH: ''",
|
|
19991
|
+
`ABSOLUTE_CONFIG_PATH: ${yamlString(configPath2 ?? "")}`
|
|
19992
|
+
],
|
|
19993
|
+
[
|
|
19994
|
+
"ABSOLUTE_REGISTRY_MODULE: ''",
|
|
19995
|
+
`ABSOLUTE_REGISTRY_MODULE: ${yamlString(registryModule)}`
|
|
19996
|
+
],
|
|
19997
|
+
[
|
|
19998
|
+
"ABSOLUTE_SERVER_ENTRY: ''",
|
|
19999
|
+
`ABSOLUTE_SERVER_ENTRY: ${yamlString(serverEntry)}`
|
|
20000
|
+
]
|
|
20001
|
+
]);
|
|
20002
|
+
for (const [placeholder, replacement] of replacements)
|
|
20003
|
+
workflow = workflow.replaceAll(placeholder, replacement);
|
|
20004
|
+
return {
|
|
20005
|
+
requiredSecrets: requiredSecrets(platforms, includePublishing, customSecrets),
|
|
20006
|
+
workflow
|
|
20007
|
+
};
|
|
20008
|
+
}, writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
20009
|
+
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
20010
|
+
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
20011
|
+
const previous = await exists3(path) ? await readFile22(path, "utf8") : undefined;
|
|
20012
|
+
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
20013
|
+
throw new TypeError(`${relative29(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
20014
|
+
if (previous !== generated.workflow) {
|
|
20015
|
+
await mkdir14(dirname31(path), { recursive: true });
|
|
20016
|
+
await writeFile16(path, generated.workflow);
|
|
20017
|
+
}
|
|
20018
|
+
return {
|
|
20019
|
+
changed: previous !== generated.workflow,
|
|
20020
|
+
format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
|
|
20021
|
+
path,
|
|
20022
|
+
platforms: [...options.config.platforms].sort(),
|
|
20023
|
+
publishing: options.includePublishing === true,
|
|
20024
|
+
requiredSecrets: generated.requiredSecrets
|
|
20025
|
+
};
|
|
20026
|
+
};
|
|
20027
|
+
var init_ciWorkflow = __esm(() => {
|
|
20028
|
+
SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
20029
|
+
RESERVED_SECRET_NAMES = new Set([
|
|
20030
|
+
"ABSOLUTE_ANDROID_KEYSTORE_BASE64",
|
|
20031
|
+
"ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
|
|
20032
|
+
"ABSOLUTE_ANDROID_KEY_ALIAS",
|
|
20033
|
+
"ABSOLUTE_ANDROID_KEY_PASSWORD",
|
|
20034
|
+
"ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
|
|
20035
|
+
"ABSOLUTE_IOS_CERTIFICATE_BASE64",
|
|
20036
|
+
"ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
|
|
20037
|
+
"ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
|
|
20038
|
+
"ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
|
|
20039
|
+
"ABSOLUTE_IOS_DEVELOPMENT_TEAM",
|
|
20040
|
+
"APP_STORE_CONNECT_ISSUER_ID",
|
|
20041
|
+
"APP_STORE_CONNECT_KEY_ID",
|
|
20042
|
+
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
19666
20043
|
]);
|
|
20044
|
+
bundleAuditSteps = ` - name: Prepare production mobile bundle
|
|
20045
|
+
shell: bash
|
|
20046
|
+
run: |
|
|
20047
|
+
args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
|
|
20048
|
+
${appendConfigArgument}
|
|
20049
|
+
"\${args[@]}"
|
|
20050
|
+
- name: Run cryptographic mobile bundle audit
|
|
20051
|
+
id: mobile-bundle-audit
|
|
20052
|
+
continue-on-error: true
|
|
20053
|
+
shell: bash
|
|
20054
|
+
run: |
|
|
20055
|
+
mkdir -p .absolutejs/mobile-ci
|
|
20056
|
+
args=(bunx absolute mobile inspect --json --require-bundle)
|
|
20057
|
+
${appendConfigArgument}
|
|
20058
|
+
"\${args[@]}" > .absolutejs/mobile-ci/inspection.json
|
|
20059
|
+
- name: Upload mobile inspection report
|
|
20060
|
+
if: always()
|
|
20061
|
+
uses: actions/upload-artifact@v7
|
|
20062
|
+
with:
|
|
20063
|
+
name: absolute-mobile-inspection
|
|
20064
|
+
path: .absolutejs/mobile-ci/inspection.json
|
|
20065
|
+
if-no-files-found: error
|
|
20066
|
+
retention-days: 30
|
|
20067
|
+
include-hidden-files: true
|
|
20068
|
+
- name: Enforce mobile bundle audit
|
|
20069
|
+
if: steps.mobile-bundle-audit.outcome != 'success'
|
|
20070
|
+
run: exit 1`;
|
|
19667
20071
|
});
|
|
19668
20072
|
|
|
19669
20073
|
// src/cli/scripts/mobile.ts
|
|
@@ -19671,11 +20075,11 @@ var exports_mobile = {};
|
|
|
19671
20075
|
__export(exports_mobile, {
|
|
19672
20076
|
runMobile: () => runMobile
|
|
19673
20077
|
});
|
|
19674
|
-
import { access as
|
|
19675
|
-
import { join as join54, resolve as
|
|
20078
|
+
import { access as access14, mkdir as mkdir15, readFile as readFile23, writeFile as writeFile17 } from "fs/promises";
|
|
20079
|
+
import { join as join54, relative as relative30, resolve as resolve44 } from "path";
|
|
19676
20080
|
import { createInterface } from "readline/promises";
|
|
19677
20081
|
var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
|
|
19678
|
-
const manifest = JSON.parse(await
|
|
20082
|
+
const manifest = JSON.parse(await readFile23(join54(projectRoot, "package.json"), "utf8"));
|
|
19679
20083
|
if (!isRecord15(manifest))
|
|
19680
20084
|
throw new TypeError("Application package.json must contain an object.");
|
|
19681
20085
|
const names = new Set;
|
|
@@ -19688,7 +20092,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19688
20092
|
return names;
|
|
19689
20093
|
}, resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
19690
20094
|
try {
|
|
19691
|
-
const manifest = JSON.parse(await
|
|
20095
|
+
const manifest = JSON.parse(await readFile23(join54(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
19692
20096
|
return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
19693
20097
|
} catch {
|
|
19694
20098
|
return;
|
|
@@ -19731,7 +20135,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19731
20135
|
}, capacitorExecutable = async (projectRoot) => {
|
|
19732
20136
|
const executable = join54(projectRoot, "node_modules", ".bin", "cap");
|
|
19733
20137
|
try {
|
|
19734
|
-
await
|
|
20138
|
+
await access14(executable);
|
|
19735
20139
|
return executable;
|
|
19736
20140
|
} catch {
|
|
19737
20141
|
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
@@ -19758,11 +20162,19 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19758
20162
|
const report = await inspectAbsoluteMobileProject(mobile, projectRoot, {
|
|
19759
20163
|
absolutejsVersion: await absolutejsVersionForReport()
|
|
19760
20164
|
});
|
|
20165
|
+
const requireBundle = () => {
|
|
20166
|
+
if (!args.includes("--require-bundle"))
|
|
20167
|
+
return;
|
|
20168
|
+
if (report.bundle.status !== "valid" || report.capabilities.issue || report.capabilities.embeddedMatchesCurrent !== true)
|
|
20169
|
+
throw new TypeError("Mobile bundle validation failed. Regenerate the production bundle and resolve capability drift.");
|
|
20170
|
+
};
|
|
19761
20171
|
if (args.includes("--json")) {
|
|
19762
20172
|
console.log(JSON.stringify(report, null, 2));
|
|
20173
|
+
requireBundle();
|
|
19763
20174
|
return report;
|
|
19764
20175
|
}
|
|
19765
20176
|
console.log(renderAbsoluteMobileProjectInspection(report).trimEnd());
|
|
20177
|
+
requireBundle();
|
|
19766
20178
|
return report;
|
|
19767
20179
|
}, remoteProfilePath = () => process.env.ABSOLUTE_REMOTE_MAC_PROFILE_PATH || undefined, pairRemoteMac = async (args) => {
|
|
19768
20180
|
if (args[0] !== "mac" || !args[1] || !args[2])
|
|
@@ -19828,7 +20240,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19828
20240
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
|
|
19829
20241
|
}, associations = async (args) => {
|
|
19830
20242
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19831
|
-
const outputDirectory =
|
|
20243
|
+
const outputDirectory = resolve44(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
19832
20244
|
if (args.includes("--verify")) {
|
|
19833
20245
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
19834
20246
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -19839,6 +20251,59 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19839
20251
|
}
|
|
19840
20252
|
const result = await materializeAbsoluteMobileAssociationFiles(mobile, outputDirectory);
|
|
19841
20253
|
console.log(`Generated ${result.written.length} association files in ${result.root}`);
|
|
20254
|
+
}, mobileCiServerEntry = (args) => {
|
|
20255
|
+
const valueFlags = new Set([
|
|
20256
|
+
"--config",
|
|
20257
|
+
"--output",
|
|
20258
|
+
"--registry",
|
|
20259
|
+
"--secret-env"
|
|
20260
|
+
]);
|
|
20261
|
+
const skipped = new Set;
|
|
20262
|
+
args.forEach((value, index) => {
|
|
20263
|
+
if (!valueFlags.has(value))
|
|
20264
|
+
return;
|
|
20265
|
+
skipped.add(index);
|
|
20266
|
+
skipped.add(index + 1);
|
|
20267
|
+
});
|
|
20268
|
+
return args.find((value, index) => !skipped.has(index) && value !== "github" && !value.startsWith("-")) ?? DEFAULT_SERVER_ENTRY;
|
|
20269
|
+
}, generateGithubCi = async (args) => {
|
|
20270
|
+
if (args[0] !== "github")
|
|
20271
|
+
throw new TypeError("Usage: absolute mobile ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] [--config path]");
|
|
20272
|
+
const configPath2 = valueAfter(args, "--config");
|
|
20273
|
+
const { mobile, projectRoot } = await loadMobile(configPath2);
|
|
20274
|
+
const result = await writeAbsoluteMobileGithubWorkflow({
|
|
20275
|
+
config: mobile,
|
|
20276
|
+
configPath: configPath2,
|
|
20277
|
+
force: args.includes("--force"),
|
|
20278
|
+
includePublishing: args.includes("--publish"),
|
|
20279
|
+
outputPath: valueAfter(args, "--output"),
|
|
20280
|
+
projectRoot,
|
|
20281
|
+
registryModule: valueAfter(args, "--registry"),
|
|
20282
|
+
secretEnvironment: valuesAfter(args, "--secret-env"),
|
|
20283
|
+
serverEntry: mobileCiServerEntry(args)
|
|
20284
|
+
});
|
|
20285
|
+
sendTelemetryEvent("mobile:ci-generated", {
|
|
20286
|
+
platformCount: result.platforms.length,
|
|
20287
|
+
provider: "github-actions",
|
|
20288
|
+
publishing: result.publishing
|
|
20289
|
+
});
|
|
20290
|
+
const publicResult = {
|
|
20291
|
+
changed: result.changed,
|
|
20292
|
+
format: result.format,
|
|
20293
|
+
path: relative30(projectRoot, result.path).replaceAll("\\", "/"),
|
|
20294
|
+
platforms: result.platforms,
|
|
20295
|
+
publishing: result.publishing,
|
|
20296
|
+
requiredSecrets: result.requiredSecrets
|
|
20297
|
+
};
|
|
20298
|
+
if (args.includes("--json")) {
|
|
20299
|
+
console.log(JSON.stringify(publicResult, null, 2));
|
|
20300
|
+
return publicResult;
|
|
20301
|
+
}
|
|
20302
|
+
console.log(`${result.changed ? "Generated" : "Verified"} ${publicResult.path} for ${result.platforms.join(" and ")}.`);
|
|
20303
|
+
console.log("Create a protected GitHub environment named absolute-mobile-release, then add these secrets:");
|
|
20304
|
+
result.requiredSecrets.forEach((name) => console.log(` ${name}`));
|
|
20305
|
+
console.log("Pull requests run the secret-free release audit. Signed builds and optional publishing run only through manual workflow dispatch.");
|
|
20306
|
+
return publicResult;
|
|
19842
20307
|
}, doctorMark = (status2) => {
|
|
19843
20308
|
if (status2 === "pass")
|
|
19844
20309
|
return "\x1B[32m\u2713\x1B[0m";
|
|
@@ -19871,9 +20336,11 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19871
20336
|
}
|
|
19872
20337
|
}, runReleaseDoctor = async (args) => {
|
|
19873
20338
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19874
|
-
const
|
|
20339
|
+
const platform6 = args.find((value) => value === "android" || value === "ios");
|
|
20340
|
+
const effectiveMobile = platform6 ? { ...mobile, platforms: [platform6] } : mobile;
|
|
20341
|
+
const result = await inspectAbsoluteMobileRelease(effectiveMobile, projectRoot);
|
|
19875
20342
|
if (args.includes("--json")) {
|
|
19876
|
-
console.log(JSON.stringify(createAbsoluteMobileComplianceReport(
|
|
20343
|
+
console.log(JSON.stringify(createAbsoluteMobileComplianceReport(effectiveMobile, result), null, 2));
|
|
19877
20344
|
} else {
|
|
19878
20345
|
result.checks.forEach((check2) => {
|
|
19879
20346
|
console.log(`${doctorMark(check2.status)} ${check2.detail}${check2.path ? ` (${check2.path})` : ""}`);
|
|
@@ -20029,6 +20496,27 @@ Mobile release security and compliance checks failed.`);
|
|
|
20029
20496
|
status: check2.status
|
|
20030
20497
|
})));
|
|
20031
20498
|
throw new TypeError("Android release validation failed before Gradle signing.");
|
|
20499
|
+
}, androidCiSigning = () => {
|
|
20500
|
+
const keyAlias = process.env.ABSOLUTE_ANDROID_KEY_ALIAS;
|
|
20501
|
+
const keyPassword = process.env.ABSOLUTE_ANDROID_KEY_PASSWORD;
|
|
20502
|
+
const keystorePath = process.env.ABSOLUTE_ANDROID_KEYSTORE_PATH;
|
|
20503
|
+
const storePassword = process.env.ABSOLUTE_ANDROID_KEYSTORE_PASSWORD;
|
|
20504
|
+
const supplied = [
|
|
20505
|
+
keyAlias,
|
|
20506
|
+
keyPassword,
|
|
20507
|
+
keystorePath,
|
|
20508
|
+
storePassword
|
|
20509
|
+
].filter((value) => value !== undefined && value !== "").length;
|
|
20510
|
+
if (supplied === 0)
|
|
20511
|
+
return;
|
|
20512
|
+
if (!keyAlias || !keyPassword || !keystorePath || !storePassword)
|
|
20513
|
+
throw new TypeError("AbsoluteJS CI signing requires ABSOLUTE_ANDROID_KEYSTORE_PATH, ABSOLUTE_ANDROID_KEYSTORE_PASSWORD, ABSOLUTE_ANDROID_KEY_ALIAS, and ABSOLUTE_ANDROID_KEY_PASSWORD together.");
|
|
20514
|
+
return {
|
|
20515
|
+
keyAlias,
|
|
20516
|
+
keyPasswordEnvironment: "ABSOLUTE_ANDROID_KEY_PASSWORD",
|
|
20517
|
+
keystorePath,
|
|
20518
|
+
storePasswordEnvironment: "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD"
|
|
20519
|
+
};
|
|
20032
20520
|
}, buildAndroid = async (args, prepareVersionCode) => {
|
|
20033
20521
|
const configPath2 = valueAfter(args, "--config");
|
|
20034
20522
|
const { mobile, projectRoot } = await loadMobile(configPath2);
|
|
@@ -20055,6 +20543,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
20055
20543
|
config: mobile,
|
|
20056
20544
|
outputDirectory: valueAfter(args, "--outdir"),
|
|
20057
20545
|
projectRoot,
|
|
20546
|
+
signing: androidCiSigning(),
|
|
20058
20547
|
...prepareVersionCode === undefined ? {} : { prepareVersionCode }
|
|
20059
20548
|
});
|
|
20060
20549
|
success = true;
|
|
@@ -20156,6 +20645,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
20156
20645
|
const release = await buildAbsoluteIosRelease({
|
|
20157
20646
|
allowUnsigned: args.includes("--unsigned"),
|
|
20158
20647
|
config: mobile,
|
|
20648
|
+
developmentTeam: process.env.ABSOLUTE_IOS_DEVELOPMENT_TEAM,
|
|
20159
20649
|
outputDirectory: valueAfter(args, "--outdir"),
|
|
20160
20650
|
...prepareBuildNumber === undefined ? {} : { prepareBuildNumber },
|
|
20161
20651
|
projectRoot
|
|
@@ -20364,7 +20854,7 @@ Emulator setup verification:`);
|
|
|
20364
20854
|
}
|
|
20365
20855
|
return { https: args.includes("--https"), port };
|
|
20366
20856
|
}
|
|
20367
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
20857
|
+
const instances = listLiveInstances().filter((instance2) => resolve44(instance2.cwd) === resolve44(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
20368
20858
|
if (instances.length !== 1) {
|
|
20369
20859
|
throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
|
|
20370
20860
|
}
|
|
@@ -20407,8 +20897,8 @@ Emulator setup verification:`);
|
|
|
20407
20897
|
}
|
|
20408
20898
|
return selected;
|
|
20409
20899
|
}, safeArtifactRoot = (projectRoot, value) => {
|
|
20410
|
-
const root =
|
|
20411
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
20900
|
+
const root = resolve44(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
20901
|
+
if (root !== projectRoot && !root.startsWith(`${resolve44(projectRoot)}/`)) {
|
|
20412
20902
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
20413
20903
|
}
|
|
20414
20904
|
return root;
|
|
@@ -20432,12 +20922,12 @@ Emulator setup verification:`);
|
|
|
20432
20922
|
timeoutMs
|
|
20433
20923
|
});
|
|
20434
20924
|
}, writeAndroidFailureArtifacts = async (options) => {
|
|
20435
|
-
await
|
|
20925
|
+
await mkdir15(options.artifactRoot, { recursive: true });
|
|
20436
20926
|
const screenshot = options.session ? await options.session.screenshot(join54(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
20437
20927
|
return;
|
|
20438
20928
|
}) : undefined;
|
|
20439
20929
|
const diagnosticPath = join54(options.artifactRoot, "android-failure.json");
|
|
20440
|
-
await
|
|
20930
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
20441
20931
|
diagnostics: options.session?.diagnostics ?? [],
|
|
20442
20932
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
20443
20933
|
platform: "android",
|
|
@@ -20568,14 +21058,14 @@ Emulator setup verification:`);
|
|
|
20568
21058
|
const port = Number(explicit);
|
|
20569
21059
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
20570
21060
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
20571
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
21061
|
+
const instance2 = listLiveInstances().find((candidate) => resolve44(candidate.cwd) === resolve44(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
20572
21062
|
return {
|
|
20573
21063
|
https: instance2?.https ?? args.includes("--https"),
|
|
20574
21064
|
instance: instance2,
|
|
20575
21065
|
port
|
|
20576
21066
|
};
|
|
20577
21067
|
}
|
|
20578
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
21068
|
+
const instances = listLiveInstances().filter((instance2) => resolve44(instance2.cwd) === resolve44(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
20579
21069
|
if (instances.length !== 1)
|
|
20580
21070
|
throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
|
|
20581
21071
|
const [instance] = instances;
|
|
@@ -20689,7 +21179,7 @@ Emulator setup verification:`);
|
|
|
20689
21179
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
20690
21180
|
return result;
|
|
20691
21181
|
}, writeIosFailureArtifacts = async (options) => {
|
|
20692
|
-
await
|
|
21182
|
+
await mkdir15(options.artifactRoot, { recursive: true });
|
|
20693
21183
|
const screenshot = join54(options.artifactRoot, "ios-failure.png");
|
|
20694
21184
|
const screenshotResult = captureCommand4([
|
|
20695
21185
|
options.xcrun,
|
|
@@ -20700,7 +21190,7 @@ Emulator setup verification:`);
|
|
|
20700
21190
|
screenshot
|
|
20701
21191
|
]);
|
|
20702
21192
|
const diagnosticPath = join54(options.artifactRoot, "ios-failure.json");
|
|
20703
|
-
await
|
|
21193
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
20704
21194
|
appId: options.appId,
|
|
20705
21195
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
20706
21196
|
platform: "ios",
|
|
@@ -20725,8 +21215,8 @@ Emulator setup verification:`);
|
|
|
20725
21215
|
}, absolutejsVersionForReport = async () => {
|
|
20726
21216
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
20727
21217
|
const versions = await Promise.all([
|
|
20728
|
-
|
|
20729
|
-
|
|
21218
|
+
resolve44(import.meta.dir, "..", "..", "package.json"),
|
|
21219
|
+
resolve44(import.meta.dir, "..", "..", "..", "package.json")
|
|
20730
21220
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
20731
21221
|
for (const version2 of versions) {
|
|
20732
21222
|
if (version2 === "unknown")
|
|
@@ -20944,7 +21434,7 @@ Emulator setup verification:`);
|
|
|
20944
21434
|
mobile.appId
|
|
20945
21435
|
], "iOS app launch");
|
|
20946
21436
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
20947
|
-
await
|
|
21437
|
+
await mkdir15(artifactRoot, { recursive: true });
|
|
20948
21438
|
const screenshot = join54(artifactRoot, "ios-simulator.png");
|
|
20949
21439
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
20950
21440
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
@@ -21054,6 +21544,10 @@ Emulator setup verification:`);
|
|
|
21054
21544
|
await associations(args.slice(1));
|
|
21055
21545
|
return;
|
|
21056
21546
|
}
|
|
21547
|
+
if (command === "ci") {
|
|
21548
|
+
await generateGithubCi(args.slice(1));
|
|
21549
|
+
return;
|
|
21550
|
+
}
|
|
21057
21551
|
if (command === "doctor") {
|
|
21058
21552
|
await doctor(args.slice(1));
|
|
21059
21553
|
return;
|
|
@@ -21086,7 +21580,7 @@ Emulator setup verification:`);
|
|
|
21086
21580
|
await publishIos(args.slice(2));
|
|
21087
21581
|
return;
|
|
21088
21582
|
}
|
|
21089
|
-
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
|
|
21583
|
+
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
|
|
21090
21584
|
};
|
|
21091
21585
|
var init_mobile = __esm(() => {
|
|
21092
21586
|
init_dependencies();
|
|
@@ -21121,6 +21615,7 @@ var init_mobile = __esm(() => {
|
|
|
21121
21615
|
init_syncSchema();
|
|
21122
21616
|
init_deviceCapabilities();
|
|
21123
21617
|
init_mobileInspect();
|
|
21618
|
+
init_ciWorkflow();
|
|
21124
21619
|
CAPACITOR_PACKAGES = [
|
|
21125
21620
|
"@capacitor/core",
|
|
21126
21621
|
"@capacitor/app",
|
|
@@ -21156,11 +21651,11 @@ var exports_typecheck = {};
|
|
|
21156
21651
|
__export(exports_typecheck, {
|
|
21157
21652
|
typecheck: () => typecheck
|
|
21158
21653
|
});
|
|
21159
|
-
import { resolve as
|
|
21160
|
-
import { existsSync as
|
|
21161
|
-
import { mkdir as
|
|
21162
|
-
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) =>
|
|
21163
|
-
if (!
|
|
21654
|
+
import { resolve as resolve45, join as join55 } from "path";
|
|
21655
|
+
import { existsSync as existsSync43, readFileSync as readFileSync40 } from "fs";
|
|
21656
|
+
import { mkdir as mkdir16, writeFile as writeFile18 } from "fs/promises";
|
|
21657
|
+
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve45(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
|
|
21658
|
+
if (!existsSync43(resolveConfigPath(configPath2))) {
|
|
21164
21659
|
const defaultService = {};
|
|
21165
21660
|
return [defaultService];
|
|
21166
21661
|
}
|
|
@@ -21181,8 +21676,8 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
|
|
|
21181
21676
|
const exitCode = await proc.exited;
|
|
21182
21677
|
return { exitCode, name, output: (stdout + stderr).trim() };
|
|
21183
21678
|
}, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
|
|
21184
|
-
const local =
|
|
21185
|
-
return
|
|
21679
|
+
const local = resolve45("node_modules", ".bin", name);
|
|
21680
|
+
return existsSync43(local) ? local : null;
|
|
21186
21681
|
}, ANSI_COLOR_REGEX, ANSI_PURPLE_REGEX, ANSI_CYAN_REGEX, ANSI_TOKEN_END_REGEX, stripAnsi4 = (str) => str.replace(ANSI_COLOR_REGEX, ""), formatSvelteOutput = (output) => {
|
|
21187
21682
|
const cwd = `${process.cwd()}/`;
|
|
21188
21683
|
const summaryMatch = stripAnsi4(output).match(/svelte-check found (\d+) error/);
|
|
@@ -21229,15 +21724,15 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21229
21724
|
return formatted;
|
|
21230
21725
|
}, ABSOLUTE_INTERNAL_EXCLUDES, resolveAbsoluteTypeFile = (fileName) => {
|
|
21231
21726
|
const candidates = [
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21727
|
+
resolve45("node_modules/@absolutejs/absolute/dist/types", fileName),
|
|
21728
|
+
resolve45(import.meta.dir, "../types", fileName),
|
|
21729
|
+
resolve45(import.meta.dir, "../../types", fileName),
|
|
21730
|
+
resolve45(import.meta.dir, "../../../types", fileName)
|
|
21236
21731
|
];
|
|
21237
|
-
return candidates.find((candidate) =>
|
|
21732
|
+
return candidates.find((candidate) => existsSync43(candidate)) ?? candidates[0];
|
|
21238
21733
|
}, ABSOLUTE_TYPECHECK_FILES, readProjectTsconfig = () => {
|
|
21239
21734
|
try {
|
|
21240
|
-
return JSON.parse(readFileSync40(
|
|
21735
|
+
return JSON.parse(readFileSync40(resolve45("tsconfig.json"), "utf-8"));
|
|
21241
21736
|
} catch {
|
|
21242
21737
|
return {};
|
|
21243
21738
|
}
|
|
@@ -21266,19 +21761,19 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21266
21761
|
process.exit(1);
|
|
21267
21762
|
}
|
|
21268
21763
|
const vueTsconfigPath = join55(cacheDir, "tsconfig.vue-check.json");
|
|
21269
|
-
await
|
|
21764
|
+
await writeFile18(vueTsconfigPath, JSON.stringify({
|
|
21270
21765
|
compilerOptions: {
|
|
21271
21766
|
rootDir: ".."
|
|
21272
21767
|
},
|
|
21273
21768
|
exclude: getProjectTypecheckExcludes(),
|
|
21274
|
-
extends:
|
|
21769
|
+
extends: resolve45("tsconfig.json"),
|
|
21275
21770
|
include: getProjectTypecheckIncludes()
|
|
21276
21771
|
}, null, "\t"));
|
|
21277
21772
|
const base = [
|
|
21278
21773
|
vueTscBin,
|
|
21279
21774
|
"--noEmit",
|
|
21280
21775
|
"--project",
|
|
21281
|
-
|
|
21776
|
+
resolve45(vueTsconfigPath),
|
|
21282
21777
|
"--pretty"
|
|
21283
21778
|
];
|
|
21284
21779
|
const cached = await run("vue-tsc", [
|
|
@@ -21297,7 +21792,7 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21297
21792
|
process.exit(1);
|
|
21298
21793
|
}
|
|
21299
21794
|
const angularTsconfigPath = join55(cacheDir, "tsconfig.angular-check.json");
|
|
21300
|
-
await
|
|
21795
|
+
await writeFile18(angularTsconfigPath, JSON.stringify({
|
|
21301
21796
|
angularCompilerOptions: {
|
|
21302
21797
|
strictTemplates: true
|
|
21303
21798
|
},
|
|
@@ -21306,10 +21801,10 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21306
21801
|
rootDir: ".."
|
|
21307
21802
|
},
|
|
21308
21803
|
exclude: ABSOLUTE_INTERNAL_EXCLUDES.map(toGeneratedConfigPath),
|
|
21309
|
-
extends:
|
|
21804
|
+
extends: resolve45("tsconfig.json"),
|
|
21310
21805
|
include: [`../${angularDir}/**/*`]
|
|
21311
21806
|
}, null, "\t"));
|
|
21312
|
-
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(
|
|
21807
|
+
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve45(angularTsconfigPath))}`);
|
|
21313
21808
|
}, buildTscCheck = (cacheDir) => {
|
|
21314
21809
|
const tscBin = findBin("tsc");
|
|
21315
21810
|
if (!tscBin) {
|
|
@@ -21317,18 +21812,18 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21317
21812
|
process.exit(1);
|
|
21318
21813
|
}
|
|
21319
21814
|
const tscConfigPath = join55(cacheDir, "tsconfig.typecheck.json");
|
|
21320
|
-
return
|
|
21815
|
+
return writeFile18(tscConfigPath, JSON.stringify({
|
|
21321
21816
|
compilerOptions: {
|
|
21322
21817
|
rootDir: ".."
|
|
21323
21818
|
},
|
|
21324
21819
|
exclude: getProjectTypecheckExcludes(),
|
|
21325
|
-
extends:
|
|
21820
|
+
extends: resolve45("tsconfig.json"),
|
|
21326
21821
|
include: getProjectTypecheckIncludes()
|
|
21327
21822
|
}, null, "\t")).then(() => run("tsc", [
|
|
21328
21823
|
tscBin,
|
|
21329
21824
|
"--noEmit",
|
|
21330
21825
|
"--project",
|
|
21331
|
-
|
|
21826
|
+
resolve45(tscConfigPath),
|
|
21332
21827
|
"--incremental",
|
|
21333
21828
|
"--tsBuildInfoFile",
|
|
21334
21829
|
join55(cacheDir, "tsc.tsbuildinfo"),
|
|
@@ -21341,15 +21836,15 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21341
21836
|
process.exit(1);
|
|
21342
21837
|
}
|
|
21343
21838
|
const svelteTsconfigPath = join55(cacheDir, "tsconfig.svelte-check.json");
|
|
21344
|
-
await
|
|
21345
|
-
extends:
|
|
21839
|
+
await writeFile18(svelteTsconfigPath, JSON.stringify({
|
|
21840
|
+
extends: resolve45("tsconfig.json"),
|
|
21346
21841
|
files: ABSOLUTE_TYPECHECK_FILES,
|
|
21347
21842
|
include: [`../${svelteDir}/**/*`]
|
|
21348
21843
|
}, null, "\t"));
|
|
21349
21844
|
return run("svelte-check", [
|
|
21350
21845
|
svelteBin,
|
|
21351
21846
|
"--tsconfig",
|
|
21352
|
-
|
|
21847
|
+
resolve45(svelteTsconfigPath),
|
|
21353
21848
|
"--threshold",
|
|
21354
21849
|
"error",
|
|
21355
21850
|
"--compiler-warnings",
|
|
@@ -21370,7 +21865,7 @@ Found ${errorCount} error${suffix}.`;
|
|
|
21370
21865
|
...new Set(targets.map((config) => config.angularDirectory).filter((dir) => typeof dir === "string" && dir.length > 0))
|
|
21371
21866
|
];
|
|
21372
21867
|
const cacheDir = ".absolutejs";
|
|
21373
|
-
await
|
|
21868
|
+
await mkdir16(cacheDir, { recursive: true });
|
|
21374
21869
|
const checks = [];
|
|
21375
21870
|
checks.push(hasVue ? buildVueTscCheck(cacheDir) : buildTscCheck(cacheDir));
|
|
21376
21871
|
for (const svelteDir of hasSvelte ? svelteDirs : []) {
|
|
@@ -21543,11 +22038,11 @@ var DEFAULT_RELAY_PORT = 8787, DEFAULT_REQUEST_TIMEOUT_MS = 30000, headersToObje
|
|
|
21543
22038
|
url: url.pathname + url.search,
|
|
21544
22039
|
...bodyBytes && bodyBytes.length > 0 ? { bodyBase64: Buffer.from(bodyBytes).toString("base64") } : {}
|
|
21545
22040
|
};
|
|
21546
|
-
const responsePromise = new Promise((
|
|
21547
|
-
pending.set(id,
|
|
22041
|
+
const responsePromise = new Promise((resolve46) => {
|
|
22042
|
+
pending.set(id, resolve46);
|
|
21548
22043
|
});
|
|
21549
22044
|
client.send(encodeTunnelMessage(message));
|
|
21550
|
-
const timeout = new Promise((
|
|
22045
|
+
const timeout = new Promise((resolve46) => setTimeout(() => resolve46({ id, message: "timeout", type: "error" }), requestTimeoutMs));
|
|
21551
22046
|
const result = await Promise.race([responsePromise, timeout]);
|
|
21552
22047
|
pending.delete(id);
|
|
21553
22048
|
if (result.type === "error") {
|
|
@@ -25460,7 +25955,7 @@ if (command === "dev") {
|
|
|
25460
25955
|
console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
|
|
25461
25956
|
console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
|
|
25462
25957
|
console.error(" compile [entry] [--outdir dir] [--outfile path] Compile standalone executable");
|
|
25463
|
-
console.error(" mobile <init|sync|inspect|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
|
|
25958
|
+
console.error(" mobile <init|sync|inspect|ci|pair|remotes|doctor|test> Manage Capacitor projects, CI, simulators, physical devices, Remote Macs, guided setup, and deep links");
|
|
25464
25959
|
console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
|
|
25465
25960
|
console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
|
|
25466
25961
|
console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");
|