@absolutejs/absolute 0.20.0-beta.110 → 0.20.0-beta.112
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/CHANGELOG.md +14 -0
- package/changelog.json +48 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/{mobile-12y4gtsq.js → mobile-d5wbqynh.js} +786 -130
- package/dist/mobile/index.js +1554 -1048
- package/dist/mobile/index.js.map +8 -7
- package/dist/src/mobile/ciPromotionAudit.d.ts +127 -0
- package/dist/src/mobile/ciWorkflow.d.ts +1 -1
- package/dist/src/mobile/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -16779,15 +16779,15 @@ var require_main = __commonJS(function(exports) {
|
|
|
16779
16779
|
|
|
16780
16780
|
// src/cli/scripts/mobile.ts
|
|
16781
16781
|
import {
|
|
16782
|
-
access as
|
|
16783
|
-
mkdir as
|
|
16784
|
-
mkdtemp as
|
|
16785
|
-
readFile as
|
|
16786
|
-
rm as
|
|
16787
|
-
writeFile as
|
|
16782
|
+
access as access17,
|
|
16783
|
+
mkdir as mkdir18,
|
|
16784
|
+
mkdtemp as mkdtemp7,
|
|
16785
|
+
readFile as readFile22,
|
|
16786
|
+
rm as rm10,
|
|
16787
|
+
writeFile as writeFile19
|
|
16788
16788
|
} from "fs/promises";
|
|
16789
|
-
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16790
|
-
import { join as
|
|
16789
|
+
import { createPublicKey as createPublicKey2, randomUUID } from "crypto";
|
|
16790
|
+
import { join as join18, relative as relative16, resolve as resolve17 } from "path";
|
|
16791
16791
|
import { createInterface } from "readline/promises";
|
|
16792
16792
|
|
|
16793
16793
|
// src/mobile/nativeDeepLinks.ts
|
|
@@ -22982,7 +22982,7 @@ var renderAbsoluteMobileProjectInspection = (report) => {
|
|
|
22982
22982
|
import { existsSync } from "fs";
|
|
22983
22983
|
import { access as access12, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile14 } from "fs/promises";
|
|
22984
22984
|
import { dirname as dirname11, extname as extname2, relative as relative11, resolve as resolve12, sep as sep8 } from "path";
|
|
22985
|
-
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT =
|
|
22985
|
+
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 4;
|
|
22986
22986
|
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
22987
22987
|
var CI_ENV_INDENTATION = 6;
|
|
22988
22988
|
var RESERVED_SECRET_NAMES = new Set([
|
|
@@ -23141,6 +23141,10 @@ var publishingInputs = (platforms, includePublishing) => {
|
|
|
23141
23141
|
description: Source workflow run ID for promotion without rebuilding
|
|
23142
23142
|
required: false
|
|
23143
23143
|
type: string`,
|
|
23144
|
+
` dispatch_id:
|
|
23145
|
+
description: Unique promotion correlation ID supplied by the AbsoluteJS CLI
|
|
23146
|
+
required: false
|
|
23147
|
+
type: string`,
|
|
23144
23148
|
` certification_base64:
|
|
23145
23149
|
description: AbsoluteJS certification supplied by the promotion CLI
|
|
23146
23150
|
required: false
|
|
@@ -23513,6 +23517,10 @@ var promotionValidationSteps = ` - name: Validate promotion handoff
|
|
|
23513
23517
|
echo "Promotion requires a numeric source workflow run ID." >&2
|
|
23514
23518
|
exit 1
|
|
23515
23519
|
fi
|
|
23520
|
+
if [[ ! "$ABSOLUTE_PROMOTION_DISPATCH_ID" =~ ^amp_[a-f0-9]{32}$ ]]; then
|
|
23521
|
+
echo "Promotion requires a valid AbsoluteJS dispatch correlation ID." >&2
|
|
23522
|
+
exit 1
|
|
23523
|
+
fi
|
|
23516
23524
|
if [[ -z "$ABSOLUTE_CERTIFICATION_BASE64" ]]; then
|
|
23517
23525
|
echo "Promotion requires certification supplied by absolute mobile ci promote." >&2
|
|
23518
23526
|
exit 1
|
|
@@ -23524,6 +23532,10 @@ var promotionSharedSteps = (platform) => ` - name: Restore exact ${platform
|
|
|
23524
23532
|
path: .absolutejs/mobile/releases/${platform}
|
|
23525
23533
|
run-id: \${{ inputs.source_run_id }}
|
|
23526
23534
|
github-token: \${{ github.token }}
|
|
23535
|
+
- name: Record bounded promotion context
|
|
23536
|
+
shell: bash
|
|
23537
|
+
run: |
|
|
23538
|
+
bun -e 'const dispatchId = process.env.ABSOLUTE_PROMOTION_DISPATCH_ID; const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!dispatchId?.match(/^amp_[a-f0-9]{32}$/) || !sourceRunId?.match(/^[1-9][0-9]*$/) || !promotionRunId?.match(/^[1-9][0-9]*$/)) throw new Error("Invalid promotion run identity"); await Bun.write(".absolutejs/mobile-ci/promotion-context.json", JSON.stringify({ dispatchId, format: 2, platform: ${JSON.stringify(platform)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform}`)}, sourceRunId }, null, 2) + "\\n");'
|
|
23527
23539
|
- name: Import and verify exact certification
|
|
23528
23540
|
shell: bash
|
|
23529
23541
|
run: |
|
|
@@ -23561,7 +23573,9 @@ var androidPromotionJob = (options) => {
|
|
|
23561
23573
|
id-token: write
|
|
23562
23574
|
env:
|
|
23563
23575
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23576
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23564
23577
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23578
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23565
23579
|
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
23566
23580
|
ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
|
|
23567
23581
|
GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
|
|
@@ -23601,6 +23615,7 @@ ${promotionSharedSteps("android")}
|
|
|
23601
23615
|
name: absolute-mobile-android-promotion-\${{ github.run_id }}
|
|
23602
23616
|
path: |
|
|
23603
23617
|
.absolutejs/mobile-ci/imported-certification/
|
|
23618
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
23604
23619
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
23605
23620
|
if-no-files-found: error
|
|
23606
23621
|
retention-days: 90
|
|
@@ -23624,7 +23639,9 @@ var iosPromotionJob = (options) => {
|
|
|
23624
23639
|
id-token: write
|
|
23625
23640
|
env:
|
|
23626
23641
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23642
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23627
23643
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23644
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23628
23645
|
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
23629
23646
|
ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
|
|
23630
23647
|
APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
@@ -23666,6 +23683,7 @@ ${promotionSharedSteps("ios")}
|
|
|
23666
23683
|
name: absolute-mobile-ios-promotion-\${{ github.run_id }}
|
|
23667
23684
|
path: |
|
|
23668
23685
|
.absolutejs/mobile-ci/imported-certification/
|
|
23686
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
23669
23687
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
23670
23688
|
if-no-files-found: error
|
|
23671
23689
|
retention-days: 90
|
|
@@ -23712,6 +23730,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
|
|
|
23712
23730
|
});
|
|
23713
23731
|
let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
|
|
23714
23732
|
name: AbsoluteJS Mobile
|
|
23733
|
+
run-name: \${{ inputs.operation == 'promote' && format('AbsoluteJS mobile promotion [{0}]', inputs.dispatch_id) || format('AbsoluteJS mobile {0}', github.event_name) }}
|
|
23715
23734
|
|
|
23716
23735
|
on:
|
|
23717
23736
|
pull_request:
|
|
@@ -23737,6 +23756,7 @@ jobs:
|
|
|
23737
23756
|
env:
|
|
23738
23757
|
${environment}${includePublishing ? `
|
|
23739
23758
|
ABSOLUTE_PROMOTION_PLATFORM: \${{ inputs.platform }}
|
|
23759
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23740
23760
|
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23741
23761
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}` : ""}
|
|
23742
23762
|
steps:
|
|
@@ -23785,47 +23805,521 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
|
23785
23805
|
};
|
|
23786
23806
|
};
|
|
23787
23807
|
|
|
23788
|
-
// src/mobile/
|
|
23789
|
-
import { createHash as createHash6, sign, verify } from "crypto";
|
|
23808
|
+
// src/mobile/ciPromotionAudit.ts
|
|
23790
23809
|
import {
|
|
23791
|
-
|
|
23810
|
+
access as access13,
|
|
23792
23811
|
mkdir as mkdir14,
|
|
23793
23812
|
mkdtemp as mkdtemp5,
|
|
23794
|
-
readdir as readdir6,
|
|
23795
23813
|
readFile as readFile19,
|
|
23814
|
+
readdir as readdir6,
|
|
23796
23815
|
rename as rename11,
|
|
23797
23816
|
rm as rm8,
|
|
23798
23817
|
stat as stat7,
|
|
23799
23818
|
writeFile as writeFile15
|
|
23800
23819
|
} from "fs/promises";
|
|
23801
|
-
import { dirname as dirname12, join as join15, relative as relative12, resolve as resolve13 } from "path";
|
|
23820
|
+
import { dirname as dirname12, isAbsolute as isAbsolute8, join as join15, relative as relative12, resolve as resolve13, sep as sep9 } from "path";
|
|
23821
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
|
|
23822
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 2;
|
|
23823
|
+
var MAX_GITHUB_OUTPUT_BYTES = 1048576;
|
|
23824
|
+
var MAX_AUDIT_JSON_BYTES = 1048576;
|
|
23825
|
+
var MAX_SEARCH_FILES = 20000;
|
|
23826
|
+
var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
|
|
23827
|
+
var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
23828
|
+
var DISPATCH_ID_PATTERN = /^amp_[a-f0-9]{32}$/u;
|
|
23829
|
+
var PROMOTION_DISCOVERY_ATTEMPTS = 60;
|
|
23830
|
+
var PROMOTION_DISCOVERY_INTERVAL_MS = 1000;
|
|
23831
|
+
var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23832
|
+
var requiredString = (value, name) => {
|
|
23833
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
23834
|
+
throw new TypeError(`${name} is invalid.`);
|
|
23835
|
+
return value.trim();
|
|
23836
|
+
};
|
|
23837
|
+
var optionalString = (value, name) => {
|
|
23838
|
+
if (value === null || value === undefined)
|
|
23839
|
+
return null;
|
|
23840
|
+
return requiredString(value, name);
|
|
23841
|
+
};
|
|
23842
|
+
var runId = (value, name) => {
|
|
23843
|
+
if (!RUN_ID_PATTERN.test(value))
|
|
23844
|
+
throw new TypeError(`${name} is invalid.`);
|
|
23845
|
+
return value;
|
|
23846
|
+
};
|
|
23847
|
+
var dispatchId = (value) => {
|
|
23848
|
+
if (!DISPATCH_ID_PATTERN.test(value))
|
|
23849
|
+
throw new TypeError("Mobile promotion dispatch ID is invalid.");
|
|
23850
|
+
return value;
|
|
23851
|
+
};
|
|
23852
|
+
var promotionRunTitle = (value) => `AbsoluteJS mobile promotion [${dispatchId(value)}]`;
|
|
23853
|
+
var promotionDiscoveryCommand = (workflow, repository) => [
|
|
23854
|
+
"gh",
|
|
23855
|
+
"run",
|
|
23856
|
+
"list",
|
|
23857
|
+
"--workflow",
|
|
23858
|
+
workflow,
|
|
23859
|
+
"--event",
|
|
23860
|
+
"workflow_dispatch",
|
|
23861
|
+
"--limit",
|
|
23862
|
+
"50",
|
|
23863
|
+
...repositoryArgs(repository),
|
|
23864
|
+
"--json",
|
|
23865
|
+
"databaseId,displayTitle,event,url"
|
|
23866
|
+
];
|
|
23867
|
+
var repositoryArgs = (repository) => {
|
|
23868
|
+
if (repository === undefined)
|
|
23869
|
+
return [];
|
|
23870
|
+
if (!REPOSITORY_PATTERN2.test(repository))
|
|
23871
|
+
throw new TypeError("GitHub repository must be owner/name.");
|
|
23872
|
+
return ["--repo", repository];
|
|
23873
|
+
};
|
|
23874
|
+
var projectPath3 = (projectRoot, requested) => {
|
|
23875
|
+
const root = resolve13(projectRoot);
|
|
23876
|
+
const path = resolve13(root, requested);
|
|
23877
|
+
const portable = relative12(root, path);
|
|
23878
|
+
if (portable === ".." || portable.startsWith(`..${sep9}`) || isAbsolute8(portable))
|
|
23879
|
+
throw new TypeError("Mobile CI audit output must remain inside the project.");
|
|
23880
|
+
return path;
|
|
23881
|
+
};
|
|
23882
|
+
var defaultRunner = async (command, options) => {
|
|
23883
|
+
const child = Bun.spawn(command, {
|
|
23884
|
+
cwd: options.cwd,
|
|
23885
|
+
env: options.env,
|
|
23886
|
+
stderr: "pipe",
|
|
23887
|
+
stdin: "ignore",
|
|
23888
|
+
stdout: "pipe"
|
|
23889
|
+
});
|
|
23890
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
23891
|
+
child.exited,
|
|
23892
|
+
new Response(child.stderr).text(),
|
|
23893
|
+
new Response(child.stdout).text()
|
|
23894
|
+
]);
|
|
23895
|
+
return { exitCode, stderr, stdout };
|
|
23896
|
+
};
|
|
23897
|
+
var requireCommand2 = async (run, command, options, label) => {
|
|
23898
|
+
const result = await run(command, options);
|
|
23899
|
+
if (result.exitCode !== 0)
|
|
23900
|
+
throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
|
|
23901
|
+
if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
|
|
23902
|
+
throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
|
|
23903
|
+
return result;
|
|
23904
|
+
};
|
|
23905
|
+
var parseJob = (value) => {
|
|
23906
|
+
if (!isRecord9(value))
|
|
23907
|
+
throw new TypeError("GitHub run job is invalid.");
|
|
23908
|
+
return {
|
|
23909
|
+
conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
|
|
23910
|
+
name: requiredString(value.name, "GitHub job name"),
|
|
23911
|
+
status: requiredString(value.status, "GitHub job status")
|
|
23912
|
+
};
|
|
23913
|
+
};
|
|
23914
|
+
var discoverAbsoluteMobilePromotionRun = async (options) => {
|
|
23915
|
+
const requestedDispatchId = dispatchId(options.dispatchId);
|
|
23916
|
+
const attempts = options.attempts ?? PROMOTION_DISCOVERY_ATTEMPTS;
|
|
23917
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 300)
|
|
23918
|
+
throw new TypeError("Mobile promotion discovery attempts are invalid.");
|
|
23919
|
+
const pollIntervalMs = options.pollIntervalMs ?? PROMOTION_DISCOVERY_INTERVAL_MS;
|
|
23920
|
+
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 0 || pollIntervalMs > 1e4)
|
|
23921
|
+
throw new TypeError("Mobile promotion discovery interval is invalid.");
|
|
23922
|
+
const sleep = options.sleep ?? Bun.sleep;
|
|
23923
|
+
const discover = async (attempt) => {
|
|
23924
|
+
const result = await requireCommand2(options.run ?? defaultRunner, promotionDiscoveryCommand(options.workflow, options.repository), { cwd: options.projectRoot }, "GitHub promotion run discovery");
|
|
23925
|
+
const discovered = parseAbsoluteMobilePromotionRunList(JSON.parse(result.stdout), requestedDispatchId);
|
|
23926
|
+
if (discovered)
|
|
23927
|
+
return discovered;
|
|
23928
|
+
if (attempt >= attempts)
|
|
23929
|
+
throw new TypeError(`GitHub did not expose promotion dispatch ${requestedDispatchId} within ${attempts} attempts.`);
|
|
23930
|
+
await sleep(pollIntervalMs);
|
|
23931
|
+
return discover(attempt + 1);
|
|
23932
|
+
};
|
|
23933
|
+
return discover(1);
|
|
23934
|
+
};
|
|
23935
|
+
var inspectAbsoluteMobileCiRun = async (options) => {
|
|
23936
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
23937
|
+
const result = await requireCommand2(options.run ?? defaultRunner, [
|
|
23938
|
+
"gh",
|
|
23939
|
+
"run",
|
|
23940
|
+
"view",
|
|
23941
|
+
requestedRunId,
|
|
23942
|
+
...repositoryArgs(options.repository),
|
|
23943
|
+
"--json",
|
|
23944
|
+
"conclusion,createdAt,databaseId,displayTitle,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
23945
|
+
], { cwd: options.projectRoot }, "GitHub workflow inspection");
|
|
23946
|
+
return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
|
|
23947
|
+
};
|
|
23948
|
+
var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
23949
|
+
if (!isRecord9(value) || !Array.isArray(value.jobs))
|
|
23950
|
+
throw new TypeError("GitHub workflow run response is invalid.");
|
|
23951
|
+
const { databaseId } = value;
|
|
23952
|
+
if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
|
|
23953
|
+
throw new TypeError("GitHub workflow run identity does not match.");
|
|
23954
|
+
return {
|
|
23955
|
+
conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
|
|
23956
|
+
createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
|
|
23957
|
+
displayTitle: requiredString(value.displayTitle, "GitHub run displayTitle"),
|
|
23958
|
+
headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
|
|
23959
|
+
headSha: requiredString(value.headSha, "GitHub run headSha"),
|
|
23960
|
+
jobs: value.jobs.map(parseJob),
|
|
23961
|
+
runId: requestedRunId,
|
|
23962
|
+
startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
|
|
23963
|
+
status: requiredString(value.status, "GitHub run status"),
|
|
23964
|
+
updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
|
|
23965
|
+
url: requiredString(value.url, "GitHub run URL"),
|
|
23966
|
+
workflowName: requiredString(value.workflowName, "GitHub workflow name")
|
|
23967
|
+
};
|
|
23968
|
+
};
|
|
23969
|
+
var parseAbsoluteMobilePromotionContext = (value) => {
|
|
23970
|
+
if (!isRecord9(value) || value.format !== 1 && value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
23971
|
+
throw new TypeError("Mobile promotion context is invalid.");
|
|
23972
|
+
const { platform } = value;
|
|
23973
|
+
const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
|
|
23974
|
+
if (sourceArtifact !== `absolute-mobile-${platform}`)
|
|
23975
|
+
throw new TypeError("Mobile promotion source artifact is invalid.");
|
|
23976
|
+
return {
|
|
23977
|
+
dispatchId: value.format === 1 ? null : dispatchId(requiredString(value.dispatchId, "promotion dispatch ID")),
|
|
23978
|
+
format: value.format,
|
|
23979
|
+
platform,
|
|
23980
|
+
promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
|
|
23981
|
+
sourceArtifact,
|
|
23982
|
+
sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
|
|
23983
|
+
};
|
|
23984
|
+
};
|
|
23985
|
+
var parseAbsoluteMobilePromotionRunList = (value, requestedDispatchId) => {
|
|
23986
|
+
if (!Array.isArray(value))
|
|
23987
|
+
throw new TypeError("GitHub workflow run list response is invalid.");
|
|
23988
|
+
const expectedTitle = promotionRunTitle(requestedDispatchId);
|
|
23989
|
+
const matches = value.filter((candidate) => isRecord9(candidate) && candidate.displayTitle === expectedTitle);
|
|
23990
|
+
if (matches.length === 0)
|
|
23991
|
+
return null;
|
|
23992
|
+
if (matches.length !== 1)
|
|
23993
|
+
throw new TypeError("GitHub returned multiple runs for one promotion dispatch identity.");
|
|
23994
|
+
const [match] = matches;
|
|
23995
|
+
if (!isRecord9(match) || match.event !== "workflow_dispatch")
|
|
23996
|
+
throw new TypeError("GitHub promotion run identity is invalid.");
|
|
23997
|
+
const databaseId = typeof match.databaseId === "number" || typeof match.databaseId === "string" ? String(match.databaseId) : "";
|
|
23998
|
+
const requestedRunId = runId(databaseId, "GitHub promotion run ID");
|
|
23999
|
+
const url = requiredString(match.url, "GitHub promotion run URL");
|
|
24000
|
+
if (!url.endsWith(`/actions/runs/${requestedRunId}`))
|
|
24001
|
+
throw new TypeError("GitHub promotion run URL does not match its ID.");
|
|
24002
|
+
return {
|
|
24003
|
+
dispatchId: requestedDispatchId,
|
|
24004
|
+
runId: requestedRunId,
|
|
24005
|
+
url
|
|
24006
|
+
};
|
|
24007
|
+
};
|
|
24008
|
+
var watchAbsoluteMobileCiRun = async (options) => {
|
|
24009
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
24010
|
+
await requireCommand2(options.run ?? defaultRunner, [
|
|
24011
|
+
"gh",
|
|
24012
|
+
"run",
|
|
24013
|
+
"watch",
|
|
24014
|
+
requestedRunId,
|
|
24015
|
+
...repositoryArgs(options.repository),
|
|
24016
|
+
"--exit-status"
|
|
24017
|
+
], { cwd: options.projectRoot }, "GitHub workflow watch");
|
|
24018
|
+
return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
|
|
24019
|
+
};
|
|
24020
|
+
var readBoundedJson = async (path, label) => {
|
|
24021
|
+
const metadata = await stat7(path).catch(() => null);
|
|
24022
|
+
if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
|
|
24023
|
+
throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
|
|
24024
|
+
const value = JSON.parse(await readFile19(path, "utf8"));
|
|
24025
|
+
return value;
|
|
24026
|
+
};
|
|
24027
|
+
var findFiles = async (root, name) => {
|
|
24028
|
+
const found = [];
|
|
24029
|
+
let visited = 0;
|
|
24030
|
+
const walk = async (directory) => {
|
|
24031
|
+
const entries = await readdir6(directory, { withFileTypes: true });
|
|
24032
|
+
await Promise.all(entries.map(async (entry) => {
|
|
24033
|
+
visited += 1;
|
|
24034
|
+
if (visited > MAX_SEARCH_FILES)
|
|
24035
|
+
throw new TypeError("Downloaded GitHub artifact contains too many files.");
|
|
24036
|
+
const path = join15(directory, entry.name);
|
|
24037
|
+
if (entry.isDirectory()) {
|
|
24038
|
+
await walk(path);
|
|
24039
|
+
return;
|
|
24040
|
+
}
|
|
24041
|
+
if (entry.isFile() && entry.name === name)
|
|
24042
|
+
found.push(path);
|
|
24043
|
+
}));
|
|
24044
|
+
};
|
|
24045
|
+
await walk(root);
|
|
24046
|
+
return found;
|
|
24047
|
+
};
|
|
24048
|
+
var inferPromotionPlatform = (status) => {
|
|
24049
|
+
const matched = status.jobs.flatMap((job) => {
|
|
24050
|
+
if (job.name === "Promote exact certified Android release")
|
|
24051
|
+
return ["android"];
|
|
24052
|
+
if (job.name === "Promote exact certified iOS release")
|
|
24053
|
+
return ["ios"];
|
|
24054
|
+
return [];
|
|
24055
|
+
});
|
|
24056
|
+
if (matched.length !== 1)
|
|
24057
|
+
throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
|
|
24058
|
+
const [platform] = matched;
|
|
24059
|
+
if (!platform)
|
|
24060
|
+
throw new TypeError("Mobile promotion platform is missing.");
|
|
24061
|
+
return platform;
|
|
24062
|
+
};
|
|
24063
|
+
var onlyPath = (paths, label) => {
|
|
24064
|
+
const [path] = paths;
|
|
24065
|
+
if (paths.length !== 1 || !path)
|
|
24066
|
+
throw new TypeError(`${label} must contain exactly one matching file.`);
|
|
24067
|
+
return path;
|
|
24068
|
+
};
|
|
24069
|
+
var canonicalJson = (value) => {
|
|
24070
|
+
if (Array.isArray(value))
|
|
24071
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
24072
|
+
if (isRecord9(value))
|
|
24073
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
24074
|
+
return JSON.stringify(value);
|
|
24075
|
+
};
|
|
24076
|
+
var publicationSummary = (value, platform, release, certification) => {
|
|
24077
|
+
if (!isRecord9(value) || !isRecord9(value.record) || !isRecord9(value.record.metadata))
|
|
24078
|
+
throw new TypeError("Mobile promotion receipt is invalid.");
|
|
24079
|
+
const { metadata } = value.record;
|
|
24080
|
+
if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
|
|
24081
|
+
throw new TypeError("Mobile promotion receipt release identity does not match.");
|
|
24082
|
+
if (!isRecord9(value.certification) || !isRecord9(value.certification.provenance))
|
|
24083
|
+
throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
|
|
24084
|
+
if (value.certification.certificationId !== certification.certificationId || value.certification.releaseId !== release.metadata.releaseId || value.certification.requirement !== certification.requirement || value.certification.strength !== certification.strength || value.certification.provenance.subject !== release.metadata.releaseId)
|
|
24085
|
+
throw new TypeError("Mobile promotion certification receipt does not match.");
|
|
24086
|
+
requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
|
|
24087
|
+
requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
|
|
24088
|
+
const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
|
|
24089
|
+
if (Number.isNaN(Date.parse(provenanceTime)))
|
|
24090
|
+
throw new TypeError("Mobile promotion provenance time is invalid.");
|
|
24091
|
+
if (typeof value.reused !== "boolean")
|
|
24092
|
+
throw new TypeError("Mobile promotion reuse status is invalid.");
|
|
24093
|
+
const channel = isRecord9(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
|
|
24094
|
+
if (isRecord9(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
|
|
24095
|
+
throw new TypeError("Mobile promotion channel receipt does not match.");
|
|
24096
|
+
const providerReceipt = platform === "android" ? value.googlePlay : value.appStoreConnect;
|
|
24097
|
+
if (providerReceipt === undefined)
|
|
24098
|
+
return {
|
|
24099
|
+
channel,
|
|
24100
|
+
provider: "registry",
|
|
24101
|
+
reused: value.reused,
|
|
24102
|
+
stage: null
|
|
24103
|
+
};
|
|
24104
|
+
if (!isRecord9(providerReceipt) || !isRecord9(providerReceipt.receipt))
|
|
24105
|
+
throw new TypeError("Mobile store promotion receipt is invalid.");
|
|
24106
|
+
if (typeof providerReceipt.reused !== "boolean")
|
|
24107
|
+
throw new TypeError("Mobile store promotion reuse status is invalid.");
|
|
24108
|
+
const expectedProvider = platform === "android" ? "google-play" : "app-store-connect";
|
|
24109
|
+
if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
|
|
24110
|
+
throw new TypeError("Mobile store promotion receipt does not match.");
|
|
24111
|
+
return {
|
|
24112
|
+
channel,
|
|
24113
|
+
provider: expectedProvider,
|
|
24114
|
+
reused: providerReceipt.reused,
|
|
24115
|
+
stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
|
|
24116
|
+
};
|
|
24117
|
+
};
|
|
24118
|
+
var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
|
|
24119
|
+
|
|
24120
|
+
- Status: **VERIFIED**
|
|
24121
|
+
- Platform: ${audit.platform}
|
|
24122
|
+
- Release: \`${audit.release.releaseId}\`
|
|
24123
|
+
- Artifact SHA-256: \`${audit.release.sha256}\`
|
|
24124
|
+
- Source workflow run: ${audit.github.sourceRunId}
|
|
24125
|
+
- Promotion workflow run: ${audit.github.promotionRunId}
|
|
24126
|
+
- Dispatch correlation: ${audit.github.dispatchId ?? "legacy workflow (not recorded)"}
|
|
24127
|
+
- Workflow: ${audit.github.workflowName}
|
|
24128
|
+
- GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
|
|
24129
|
+
- Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
|
|
24130
|
+
- Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
|
|
24131
|
+
- Verified at: ${audit.verifiedAt}
|
|
24132
|
+
|
|
24133
|
+
This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
|
|
24134
|
+
`;
|
|
24135
|
+
var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
24136
|
+
const runner = options.run ?? defaultRunner;
|
|
24137
|
+
const promotionRunId = runId(options.runId, "GitHub promotion run ID");
|
|
24138
|
+
const status = await inspectAbsoluteMobileCiRun({
|
|
24139
|
+
projectRoot: options.projectRoot,
|
|
24140
|
+
...options.repository ? { repository: options.repository } : {},
|
|
24141
|
+
run: runner,
|
|
24142
|
+
runId: promotionRunId
|
|
24143
|
+
});
|
|
24144
|
+
if (status.status !== "completed" || status.conclusion !== "success")
|
|
24145
|
+
throw new TypeError("Mobile promotion run has not completed successfully.");
|
|
24146
|
+
const platform = inferPromotionPlatform(status);
|
|
24147
|
+
const output = projectPath3(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
|
|
24148
|
+
if (await access13(output).then(() => true).catch(() => false))
|
|
24149
|
+
throw new TypeError(`Mobile CI audit output already exists: ${output}`);
|
|
24150
|
+
await mkdir14(dirname12(output), { recursive: true });
|
|
24151
|
+
const staging = await mkdtemp5(join15(dirname12(output), ".promotion-audit-"));
|
|
24152
|
+
try {
|
|
24153
|
+
const evidenceRoot = join15(staging, "promotion-evidence");
|
|
24154
|
+
await mkdir14(evidenceRoot);
|
|
24155
|
+
await requireCommand2(runner, [
|
|
24156
|
+
"gh",
|
|
24157
|
+
"run",
|
|
24158
|
+
"download",
|
|
24159
|
+
promotionRunId,
|
|
24160
|
+
...repositoryArgs(options.repository),
|
|
24161
|
+
"--name",
|
|
24162
|
+
`absolute-mobile-${platform}-promotion-${promotionRunId}`,
|
|
24163
|
+
"--dir",
|
|
24164
|
+
evidenceRoot
|
|
24165
|
+
], { cwd: options.projectRoot }, "GitHub promotion artifact download");
|
|
24166
|
+
const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
|
|
24167
|
+
let context;
|
|
24168
|
+
if (contextPaths.length === 1)
|
|
24169
|
+
context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
|
|
24170
|
+
else if (contextPaths.length > 1)
|
|
24171
|
+
throw new TypeError("Promotion artifact contains duplicate contexts.");
|
|
24172
|
+
if (context && context.promotionRunId !== promotionRunId)
|
|
24173
|
+
throw new TypeError("Promotion context run identity does not match.");
|
|
24174
|
+
if (context && context.platform !== platform)
|
|
24175
|
+
throw new TypeError("Promotion context platform does not match.");
|
|
24176
|
+
if (context?.dispatchId && status.displayTitle !== promotionRunTitle(context.dispatchId))
|
|
24177
|
+
throw new TypeError("Promotion dispatch identity does not match the GitHub run.");
|
|
24178
|
+
const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
|
|
24179
|
+
if (!sourceRunId)
|
|
24180
|
+
throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
|
|
24181
|
+
if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
|
|
24182
|
+
throw new TypeError("Requested source run does not match promotion context.");
|
|
24183
|
+
const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform}`;
|
|
24184
|
+
const sourceRoot = join15(staging, "source-release");
|
|
24185
|
+
await mkdir14(sourceRoot);
|
|
24186
|
+
await requireCommand2(runner, [
|
|
24187
|
+
"gh",
|
|
24188
|
+
"run",
|
|
24189
|
+
"download",
|
|
24190
|
+
sourceRunId,
|
|
24191
|
+
...repositoryArgs(options.repository),
|
|
24192
|
+
"--name",
|
|
24193
|
+
sourceArtifact,
|
|
24194
|
+
"--dir",
|
|
24195
|
+
sourceRoot
|
|
24196
|
+
], { cwd: options.projectRoot }, "GitHub source release download");
|
|
24197
|
+
const releaseMetadata = await findFiles(sourceRoot, "release.json");
|
|
24198
|
+
if (releaseMetadata.length !== 1)
|
|
24199
|
+
throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
|
|
24200
|
+
const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
|
|
24201
|
+
const release = platform === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
|
|
24202
|
+
const certificationPaths = await findFiles(evidenceRoot, "certification.json");
|
|
24203
|
+
const verificationPaths = await findFiles(evidenceRoot, "verification.json");
|
|
24204
|
+
const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
|
|
24205
|
+
if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
|
|
24206
|
+
throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
|
|
24207
|
+
const certificationPath = onlyPath(certificationPaths, "Promotion certification");
|
|
24208
|
+
const verificationPath = onlyPath(verificationPaths, "Promotion verification");
|
|
24209
|
+
const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
|
|
24210
|
+
const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
|
|
24211
|
+
const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
|
|
24212
|
+
const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
|
|
24213
|
+
if (verification.identity.sha !== status.headSha)
|
|
24214
|
+
throw new TypeError("Sigstore workflow commit does not match the promotion run.");
|
|
24215
|
+
if (options.repository && verification.identity.repository !== options.repository)
|
|
24216
|
+
throw new TypeError("Sigstore repository does not match the requested repository.");
|
|
24217
|
+
const bundlePath = `${certificationPath}.sigstore.json`;
|
|
24218
|
+
const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
|
|
24219
|
+
if (canonicalJson(bundle) !== canonicalJson(verification.bundle))
|
|
24220
|
+
throw new TypeError("Sigstore bundle does not match verification envelope.");
|
|
24221
|
+
await requireCommand2(runner, [
|
|
24222
|
+
"bunx",
|
|
24223
|
+
"@absolutejs/attest@0.2.0",
|
|
24224
|
+
"verify-blobs",
|
|
24225
|
+
certificationPath
|
|
24226
|
+
], {
|
|
24227
|
+
cwd: options.projectRoot,
|
|
24228
|
+
env: {
|
|
24229
|
+
...process.env,
|
|
24230
|
+
GITHUB_REF: verification.identity.ref,
|
|
24231
|
+
GITHUB_REPOSITORY: verification.identity.repository,
|
|
24232
|
+
GITHUB_SHA: verification.identity.sha,
|
|
24233
|
+
GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
|
|
24234
|
+
}
|
|
24235
|
+
}, "Sigstore certification verification");
|
|
24236
|
+
const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform, release, certification);
|
|
24237
|
+
const audit = {
|
|
24238
|
+
certification: {
|
|
24239
|
+
certificationId: certification.certificationId,
|
|
24240
|
+
requirement: certification.requirement,
|
|
24241
|
+
strength: certification.strength
|
|
24242
|
+
},
|
|
24243
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
24244
|
+
github: {
|
|
24245
|
+
conclusion: status.conclusion,
|
|
24246
|
+
dispatchId: context?.dispatchId ?? null,
|
|
24247
|
+
promotionRunId,
|
|
24248
|
+
repository: verification.identity.repository,
|
|
24249
|
+
sourceRunId,
|
|
24250
|
+
url: status.url,
|
|
24251
|
+
workflowName: status.workflowName
|
|
24252
|
+
},
|
|
24253
|
+
platform,
|
|
24254
|
+
provenance: verification.identity,
|
|
24255
|
+
publication,
|
|
24256
|
+
release: {
|
|
24257
|
+
appId: release.metadata.appId,
|
|
24258
|
+
bytes: release.metadata.bytes,
|
|
24259
|
+
engine: release.metadata.engine,
|
|
24260
|
+
releaseId: release.metadata.releaseId,
|
|
24261
|
+
runtime: release.metadata.runtime,
|
|
24262
|
+
sha256: release.metadata.sha256
|
|
24263
|
+
},
|
|
24264
|
+
status: "verified",
|
|
24265
|
+
verifiedAt: new Date().toISOString()
|
|
24266
|
+
};
|
|
24267
|
+
await Promise.all([
|
|
24268
|
+
writeFile15(join15(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
|
|
24269
|
+
`, { mode: 384 }),
|
|
24270
|
+
writeFile15(join15(staging, "audit.md"), renderAuditMarkdown(audit), {
|
|
24271
|
+
mode: 384
|
|
24272
|
+
})
|
|
24273
|
+
]);
|
|
24274
|
+
await rename11(staging, output);
|
|
24275
|
+
return { audit, directory: output };
|
|
24276
|
+
} catch (error) {
|
|
24277
|
+
await rm8(staging, { force: true, recursive: true });
|
|
24278
|
+
throw error;
|
|
24279
|
+
}
|
|
24280
|
+
};
|
|
24281
|
+
|
|
24282
|
+
// src/mobile/updateSigning.ts
|
|
24283
|
+
import { createHash as createHash6, sign, verify } from "crypto";
|
|
24284
|
+
import {
|
|
24285
|
+
cp,
|
|
24286
|
+
mkdir as mkdir15,
|
|
24287
|
+
mkdtemp as mkdtemp6,
|
|
24288
|
+
readdir as readdir7,
|
|
24289
|
+
readFile as readFile20,
|
|
24290
|
+
rename as rename12,
|
|
24291
|
+
rm as rm9,
|
|
24292
|
+
stat as stat8,
|
|
24293
|
+
writeFile as writeFile16
|
|
24294
|
+
} from "fs/promises";
|
|
24295
|
+
import { dirname as dirname13, join as join16, relative as relative13, resolve as resolve14 } from "path";
|
|
23802
24296
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
23803
24297
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
23804
24298
|
var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
23805
24299
|
var listFiles = async (root, directory = root) => {
|
|
23806
|
-
const entries = await
|
|
24300
|
+
const entries = await readdir7(directory, { withFileTypes: true });
|
|
23807
24301
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
23808
|
-
const path =
|
|
24302
|
+
const path = join16(directory, entry.name);
|
|
23809
24303
|
if (entry.isDirectory())
|
|
23810
24304
|
return listFiles(root, path);
|
|
23811
24305
|
if (!entry.isFile())
|
|
23812
24306
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
23813
|
-
return [
|
|
24307
|
+
return [relative13(root, path).replaceAll("\\", "/")];
|
|
23814
24308
|
}));
|
|
23815
24309
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
23816
24310
|
};
|
|
23817
24311
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
23818
|
-
const source =
|
|
24312
|
+
const source = join16(root, path);
|
|
23819
24313
|
const [metadata, contents] = await Promise.all([
|
|
23820
|
-
|
|
23821
|
-
|
|
24314
|
+
stat8(source),
|
|
24315
|
+
readFile20(source)
|
|
23822
24316
|
]);
|
|
23823
24317
|
return { bytes: metadata.size, path, sha256: sha2564(contents) };
|
|
23824
24318
|
}));
|
|
23825
24319
|
var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
23826
24320
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
23827
|
-
const bundleDirectory =
|
|
23828
|
-
const outputRoot =
|
|
24321
|
+
const bundleDirectory = resolve14(options.bundleDirectory);
|
|
24322
|
+
const outputRoot = resolve14(options.outputDirectory);
|
|
23829
24323
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
23830
24324
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
23831
24325
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -23853,30 +24347,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
23853
24347
|
value: signature.toString("base64")
|
|
23854
24348
|
}
|
|
23855
24349
|
});
|
|
23856
|
-
await
|
|
23857
|
-
const outputDirectory =
|
|
23858
|
-
const staging = await
|
|
24350
|
+
await mkdir15(outputRoot, { recursive: true });
|
|
24351
|
+
const outputDirectory = join16(outputRoot, manifest.releaseId);
|
|
24352
|
+
const staging = await mkdtemp6(join16(outputRoot, ".stage-"));
|
|
23859
24353
|
try {
|
|
23860
|
-
await cp(bundleDirectory,
|
|
24354
|
+
await cp(bundleDirectory, join16(staging, UPDATE_FILES_DIRECTORY), {
|
|
23861
24355
|
force: true,
|
|
23862
24356
|
recursive: true
|
|
23863
24357
|
});
|
|
23864
|
-
await
|
|
24358
|
+
await writeFile16(join16(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
23865
24359
|
`);
|
|
23866
|
-
await
|
|
24360
|
+
await rename12(staging, outputDirectory);
|
|
23867
24361
|
} catch (error) {
|
|
23868
|
-
await
|
|
24362
|
+
await rm9(staging, { force: true, recursive: true });
|
|
23869
24363
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
23870
24364
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
23871
24365
|
throw error;
|
|
23872
24366
|
}
|
|
23873
24367
|
return {
|
|
23874
24368
|
manifest,
|
|
23875
|
-
manifestPath:
|
|
24369
|
+
manifestPath: join16(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
23876
24370
|
outputDirectory
|
|
23877
24371
|
};
|
|
23878
24372
|
};
|
|
23879
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
24373
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join16(resolve14(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
23880
24374
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
23881
24375
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
23882
24376
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -23886,8 +24380,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
23886
24380
|
};
|
|
23887
24381
|
|
|
23888
24382
|
// src/mobile/expoUpdate.ts
|
|
23889
|
-
import { access as
|
|
23890
|
-
import { dirname as
|
|
24383
|
+
import { access as access14, mkdir as mkdir16, readFile as readFile21, writeFile as writeFile17 } from "fs/promises";
|
|
24384
|
+
import { dirname as dirname14, extname as extname3, join as join17 } from "path";
|
|
23891
24385
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
23892
24386
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
23893
24387
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -23953,7 +24447,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
23953
24447
|
};
|
|
23954
24448
|
};
|
|
23955
24449
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
23956
|
-
const metadata = JSON.parse(await
|
|
24450
|
+
const metadata = JSON.parse(await readFile21(join17(options.exportDirectory, "metadata.json"), "utf8"));
|
|
23957
24451
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
23958
24452
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
23959
24453
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -23964,30 +24458,30 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
23964
24458
|
runtimeVersion: options.runtimeVersion
|
|
23965
24459
|
});
|
|
23966
24460
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
23967
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
24461
|
+
await Promise.all(referenced.map(({ path }) => access14(join17(options.exportDirectory, path)).catch(() => {
|
|
23968
24462
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
23969
24463
|
})));
|
|
23970
|
-
const destination =
|
|
23971
|
-
await
|
|
23972
|
-
await
|
|
24464
|
+
const destination = join17(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
24465
|
+
await mkdir16(dirname14(destination), { recursive: true });
|
|
24466
|
+
await writeFile17(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
23973
24467
|
`);
|
|
23974
24468
|
return { descriptor, path: destination };
|
|
23975
24469
|
};
|
|
23976
24470
|
|
|
23977
24471
|
// src/mobile/expoCodeSigning.ts
|
|
23978
24472
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
23979
|
-
import { access as
|
|
23980
|
-
import { dirname as
|
|
23981
|
-
var exists6 = async (path) =>
|
|
24473
|
+
import { access as access15, mkdir as mkdir17, writeFile as writeFile18 } from "fs/promises";
|
|
24474
|
+
import { dirname as dirname15, isAbsolute as isAbsolute9, relative as relative14, resolve as resolve15, sep as sep10 } from "path";
|
|
24475
|
+
var exists6 = async (path) => access15(path).then(() => true).catch(() => false);
|
|
23982
24476
|
var inside4 = (root, path) => {
|
|
23983
|
-
const location =
|
|
23984
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
24477
|
+
const location = relative14(root, path);
|
|
24478
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep10}`) && !isAbsolute9(location);
|
|
23985
24479
|
};
|
|
23986
24480
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
23987
|
-
const root =
|
|
23988
|
-
const certificatePath =
|
|
23989
|
-
const privateKeyPath =
|
|
23990
|
-
const publicKeyPath =
|
|
24481
|
+
const root = resolve15(options.projectRoot);
|
|
24482
|
+
const certificatePath = resolve15(root, options.certificatePath);
|
|
24483
|
+
const privateKeyPath = resolve15(root, options.privateKeyPath);
|
|
24484
|
+
const publicKeyPath = resolve15(root, options.publicKeyPath ?? resolve15(dirname15(privateKeyPath), "public-key.pem"));
|
|
23991
24485
|
if (!inside4(root, certificatePath))
|
|
23992
24486
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
23993
24487
|
if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
|
|
@@ -24017,11 +24511,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24017
24511
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
24018
24512
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
24019
24513
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
24020
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
24514
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir17(dirname15(path), { recursive: true })));
|
|
24021
24515
|
await Promise.all([
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24516
|
+
writeFile18(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
24517
|
+
writeFile18(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
24518
|
+
writeFile18(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
|
|
24025
24519
|
]);
|
|
24026
24520
|
return {
|
|
24027
24521
|
certificatePath,
|
|
@@ -24032,8 +24526,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24032
24526
|
};
|
|
24033
24527
|
|
|
24034
24528
|
// src/mobile/updatePublisher.ts
|
|
24035
|
-
import { access as
|
|
24036
|
-
import { isAbsolute as
|
|
24529
|
+
import { access as access16 } from "fs/promises";
|
|
24530
|
+
import { isAbsolute as isAbsolute10, relative as relative15, resolve as resolve16, sep as sep11 } from "path";
|
|
24037
24531
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
24038
24532
|
var lifecycleMethod = (publisher, name) => {
|
|
24039
24533
|
const method = publisher[name];
|
|
@@ -24141,17 +24635,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
24141
24635
|
};
|
|
24142
24636
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24143
24637
|
var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
24144
|
-
var
|
|
24145
|
-
const root =
|
|
24146
|
-
const path =
|
|
24147
|
-
const projectRelative =
|
|
24148
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
24638
|
+
var projectPath4 = (projectRoot, requested, label) => {
|
|
24639
|
+
const root = resolve16(projectRoot);
|
|
24640
|
+
const path = resolve16(root, requested);
|
|
24641
|
+
const projectRelative = relative15(root, path);
|
|
24642
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep11}`) || isAbsolute10(projectRelative))
|
|
24149
24643
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
24150
24644
|
return path;
|
|
24151
24645
|
};
|
|
24152
24646
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
24153
|
-
const modulePath =
|
|
24154
|
-
await
|
|
24647
|
+
const modulePath = projectPath4(projectRoot, requestedModulePath, "mobile update registry");
|
|
24648
|
+
await access16(modulePath).catch(() => {
|
|
24155
24649
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
24156
24650
|
});
|
|
24157
24651
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -24173,7 +24667,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
|
|
|
24173
24667
|
return result;
|
|
24174
24668
|
};
|
|
24175
24669
|
var publishAbsoluteMobileUpdate = async (options) => {
|
|
24176
|
-
const releaseDirectory =
|
|
24670
|
+
const releaseDirectory = projectPath4(options.projectRoot, options.releaseDirectory, "mobile update release directory");
|
|
24177
24671
|
const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
|
|
24178
24672
|
const result = await options.publisher.publishUpdate({
|
|
24179
24673
|
manifest,
|
|
@@ -24206,10 +24700,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
|
|
|
24206
24700
|
var NOT_FOUND4 = -1;
|
|
24207
24701
|
var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
|
|
24208
24702
|
var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
|
|
24209
|
-
var
|
|
24703
|
+
var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24210
24704
|
var expoPublicConfig = (config) => {
|
|
24211
24705
|
const result = { ...config };
|
|
24212
|
-
if (!
|
|
24706
|
+
if (!isRecord10(result.updates))
|
|
24213
24707
|
return result;
|
|
24214
24708
|
const {
|
|
24215
24709
|
codeSigningCertificate: _codeSigningCertificate,
|
|
@@ -24249,13 +24743,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
24249
24743
|
];
|
|
24250
24744
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
24251
24745
|
var directProjectPackages = async (projectRoot) => {
|
|
24252
|
-
const manifest = JSON.parse(await
|
|
24253
|
-
if (!
|
|
24746
|
+
const manifest = JSON.parse(await readFile22(join18(projectRoot, "package.json"), "utf8"));
|
|
24747
|
+
if (!isRecord10(manifest))
|
|
24254
24748
|
throw new TypeError("Application package.json must contain an object.");
|
|
24255
24749
|
const names = new Set;
|
|
24256
24750
|
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
24257
24751
|
const dependencies = Reflect.get(manifest, field2);
|
|
24258
|
-
if (
|
|
24752
|
+
if (isRecord10(dependencies))
|
|
24259
24753
|
for (const name of Object.keys(dependencies))
|
|
24260
24754
|
names.add(name);
|
|
24261
24755
|
}
|
|
@@ -24263,8 +24757,8 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
24263
24757
|
};
|
|
24264
24758
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
24265
24759
|
try {
|
|
24266
|
-
const manifest = JSON.parse(await
|
|
24267
|
-
return
|
|
24760
|
+
const manifest = JSON.parse(await readFile22(join18(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
24761
|
+
return isRecord10(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
24268
24762
|
} catch {
|
|
24269
24763
|
return;
|
|
24270
24764
|
}
|
|
@@ -24327,9 +24821,9 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
24327
24821
|
};
|
|
24328
24822
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
24329
24823
|
var expoExecutable = async (project) => {
|
|
24330
|
-
const executable =
|
|
24824
|
+
const executable = join18(project, "node_modules", ".bin", "expo");
|
|
24331
24825
|
try {
|
|
24332
|
-
await
|
|
24826
|
+
await access17(executable);
|
|
24333
24827
|
return executable;
|
|
24334
24828
|
} catch {
|
|
24335
24829
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -24361,14 +24855,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
24361
24855
|
};
|
|
24362
24856
|
var ensureExpoPackages = async (project, args) => {
|
|
24363
24857
|
try {
|
|
24364
|
-
const manifest = JSON.parse(await
|
|
24858
|
+
const manifest = JSON.parse(await readFile22(join18(project, "package.json"), "utf8"));
|
|
24365
24859
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
24366
24860
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
24367
24861
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
24368
24862
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
24369
24863
|
if (typeof expected !== "string")
|
|
24370
24864
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
24371
|
-
const installed = JSON.parse(await
|
|
24865
|
+
const installed = JSON.parse(await readFile22(join18(project, "node_modules", name, "package.json"), "utf8"));
|
|
24372
24866
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
24373
24867
|
if (typeof actual !== "string")
|
|
24374
24868
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -24566,7 +25060,7 @@ var sync = async (args) => {
|
|
|
24566
25060
|
};
|
|
24567
25061
|
var associations = async (args) => {
|
|
24568
25062
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
24569
|
-
const outputDirectory =
|
|
25063
|
+
const outputDirectory = resolve17(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
24570
25064
|
if (args.includes("--verify")) {
|
|
24571
25065
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
24572
25066
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -24618,7 +25112,7 @@ var generateGithubCi = async (args) => {
|
|
|
24618
25112
|
const publicResult = {
|
|
24619
25113
|
changed: result.changed,
|
|
24620
25114
|
format: result.format,
|
|
24621
|
-
path:
|
|
25115
|
+
path: relative16(projectRoot, result.path).replaceAll("\\", "/"),
|
|
24622
25116
|
platforms: result.platforms,
|
|
24623
25117
|
publishing: result.publishing,
|
|
24624
25118
|
requiredSecrets: result.requiredSecrets
|
|
@@ -24649,15 +25143,41 @@ var createGithubCertificationVerification = async (args) => {
|
|
|
24649
25143
|
GITHUB_WORKFLOW_REF: process.env.GITHUB_WORKFLOW_REF
|
|
24650
25144
|
});
|
|
24651
25145
|
const path = await writeAbsoluteMobileCertificationVerification(projectRoot, outputPath, verification);
|
|
24652
|
-
console.log(
|
|
25146
|
+
console.log(relative16(projectRoot, path).replaceAll("\\", "/"));
|
|
24653
25147
|
return verification;
|
|
24654
25148
|
};
|
|
25149
|
+
var auditDispatchedGithubMobilePromotion = async (options) => {
|
|
25150
|
+
const startedAt = performance.now();
|
|
25151
|
+
try {
|
|
25152
|
+
const audited = await auditAbsoluteMobileCiPromotion({
|
|
25153
|
+
outputDirectory: valueAfter(options.args, "--outdir"),
|
|
25154
|
+
projectRoot: options.projectRoot,
|
|
25155
|
+
...options.repository ? { repository: options.repository } : {},
|
|
25156
|
+
runId: options.runId
|
|
25157
|
+
});
|
|
25158
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25159
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25160
|
+
platform: options.platform,
|
|
25161
|
+
provider: "github-actions",
|
|
25162
|
+
success: true
|
|
25163
|
+
});
|
|
25164
|
+
return audited;
|
|
25165
|
+
} catch (error) {
|
|
25166
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25167
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25168
|
+
platform: options.platform,
|
|
25169
|
+
provider: "github-actions",
|
|
25170
|
+
success: false
|
|
25171
|
+
});
|
|
25172
|
+
throw error;
|
|
25173
|
+
}
|
|
25174
|
+
};
|
|
24655
25175
|
var dispatchGithubMobilePromotion = async (args) => {
|
|
24656
25176
|
const [platform2] = args;
|
|
24657
25177
|
if (platform2 !== "android" && platform2 !== "ios")
|
|
24658
|
-
throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options]");
|
|
24659
|
-
const
|
|
24660
|
-
if (!
|
|
25178
|
+
throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options] [--watch] [--audit]");
|
|
25179
|
+
const sourceRunId = valueAfter(args, "--run-id");
|
|
25180
|
+
if (!sourceRunId || !/^[1-9][0-9]*$/u.test(sourceRunId))
|
|
24661
25181
|
throw new TypeError("mobile ci promote requires a numeric --run-id.");
|
|
24662
25182
|
const requestedCertification = valueAfter(args, "--certification");
|
|
24663
25183
|
if (!requestedCertification || requestedCertification.startsWith("-"))
|
|
@@ -24676,6 +25196,8 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
24676
25196
|
if (certificationBase64.length > 48000)
|
|
24677
25197
|
throw new TypeError("Mobile certification is too large for protected GitHub dispatch.");
|
|
24678
25198
|
const workflow = valueAfter(args, "--workflow") ?? ".github/workflows/absolute-mobile.yml";
|
|
25199
|
+
const dispatchId2 = `amp_${randomUUID().replaceAll("-", "")}`;
|
|
25200
|
+
const repository = valueAfter(args, "--repo");
|
|
24679
25201
|
const command = [
|
|
24680
25202
|
"workflow",
|
|
24681
25203
|
"run",
|
|
@@ -24685,7 +25207,9 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
24685
25207
|
"-f",
|
|
24686
25208
|
`platform=${platform2}`,
|
|
24687
25209
|
"-f",
|
|
24688
|
-
`source_run_id=${
|
|
25210
|
+
`source_run_id=${sourceRunId}`,
|
|
25211
|
+
"-f",
|
|
25212
|
+
`dispatch_id=${dispatchId2}`,
|
|
24689
25213
|
"-f",
|
|
24690
25214
|
`certification_base64=${certificationBase64}`,
|
|
24691
25215
|
"-f",
|
|
@@ -24700,21 +25224,149 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
24700
25224
|
const ref = valueAfter(args, "--ref");
|
|
24701
25225
|
if (ref)
|
|
24702
25226
|
command.push("--ref", ref);
|
|
25227
|
+
if (repository)
|
|
25228
|
+
command.push("--repo", repository);
|
|
24703
25229
|
const child = Bun.spawn(["gh", ...command], {
|
|
24704
25230
|
cwd: projectRoot,
|
|
24705
|
-
stderr: "
|
|
25231
|
+
stderr: "pipe",
|
|
24706
25232
|
stdin: "inherit",
|
|
24707
|
-
stdout: "
|
|
25233
|
+
stdout: "pipe"
|
|
24708
25234
|
});
|
|
24709
|
-
const exitCode = await
|
|
25235
|
+
const [exitCode, stderr] = await Promise.all([
|
|
25236
|
+
child.exited,
|
|
25237
|
+
new Response(child.stderr).text(),
|
|
25238
|
+
new Response(child.stdout).text()
|
|
25239
|
+
]);
|
|
24710
25240
|
if (exitCode !== 0)
|
|
24711
|
-
throw new TypeError(`GitHub promotion dispatch exited with status ${exitCode}
|
|
25241
|
+
throw new TypeError(`GitHub promotion dispatch exited with status ${exitCode}: ${stderr.trim() || "unknown GitHub CLI error"}`);
|
|
25242
|
+
const promotion = await discoverAbsoluteMobilePromotionRun({
|
|
25243
|
+
dispatchId: dispatchId2,
|
|
25244
|
+
projectRoot,
|
|
25245
|
+
...repository ? { repository } : {},
|
|
25246
|
+
workflow
|
|
25247
|
+
});
|
|
24712
25248
|
sendTelemetryEvent("mobile:ci-promotion-dispatched", {
|
|
25249
|
+
discovery: "correlated",
|
|
24713
25250
|
platform: platform2,
|
|
24714
25251
|
provider: "github-actions",
|
|
24715
25252
|
storeTarget: platform2 === "android" ? playTrack !== "registry-only" : testflightGroup !== ""
|
|
24716
25253
|
});
|
|
24717
|
-
|
|
25254
|
+
if (!args.includes("--json")) {
|
|
25255
|
+
console.log(`Dispatched protected ${platform2} promotion ${promotion.runId} for ${certification.release.releaseId} from workflow run ${sourceRunId}.`);
|
|
25256
|
+
console.log(promotion.url);
|
|
25257
|
+
}
|
|
25258
|
+
const shouldWatch = args.includes("--watch") || args.includes("--audit");
|
|
25259
|
+
if (shouldWatch && !args.includes("--json"))
|
|
25260
|
+
console.log(`Watching GitHub run ${promotion.runId} until completion\u2026`);
|
|
25261
|
+
const status = shouldWatch ? await watchAbsoluteMobileCiRun({
|
|
25262
|
+
projectRoot,
|
|
25263
|
+
...repository ? { repository } : {},
|
|
25264
|
+
runId: promotion.runId
|
|
25265
|
+
}) : null;
|
|
25266
|
+
const audited = args.includes("--audit") ? await auditDispatchedGithubMobilePromotion({
|
|
25267
|
+
args,
|
|
25268
|
+
platform: platform2,
|
|
25269
|
+
projectRoot,
|
|
25270
|
+
...repository ? { repository } : {},
|
|
25271
|
+
runId: promotion.runId
|
|
25272
|
+
}) : null;
|
|
25273
|
+
const result = {
|
|
25274
|
+
auditDirectory: audited ? relative16(projectRoot, audited.directory).replaceAll("\\", "/") : null,
|
|
25275
|
+
dispatchId: dispatchId2,
|
|
25276
|
+
platform: platform2,
|
|
25277
|
+
releaseId: certification.release.releaseId,
|
|
25278
|
+
runId: promotion.runId,
|
|
25279
|
+
sourceRunId,
|
|
25280
|
+
status: status?.status ?? "dispatched",
|
|
25281
|
+
url: promotion.url
|
|
25282
|
+
};
|
|
25283
|
+
if (args.includes("--json"))
|
|
25284
|
+
console.log(JSON.stringify(result, null, 2));
|
|
25285
|
+
if (args.includes("--json"))
|
|
25286
|
+
return result;
|
|
25287
|
+
if (status)
|
|
25288
|
+
renderGithubMobileRunStatus(status);
|
|
25289
|
+
if (audited)
|
|
25290
|
+
renderGithubMobilePromotionAudit(audited, projectRoot);
|
|
25291
|
+
return result;
|
|
25292
|
+
};
|
|
25293
|
+
var githubMobileRunId = (args) => {
|
|
25294
|
+
const value = valueAfter(args, "--run-id");
|
|
25295
|
+
if (!value || !/^[1-9][0-9]*$/u.test(value))
|
|
25296
|
+
throw new TypeError("mobile ci command requires a numeric --run-id.");
|
|
25297
|
+
return value;
|
|
25298
|
+
};
|
|
25299
|
+
var renderGithubMobileRunStatus = (status) => {
|
|
25300
|
+
console.log(`GitHub run ${status.runId}: ${status.status}${status.conclusion ? ` / ${status.conclusion}` : ""}`);
|
|
25301
|
+
console.log(`Workflow: ${status.workflowName}`);
|
|
25302
|
+
console.log(`Commit: ${status.headSha} (${status.headBranch})`);
|
|
25303
|
+
status.jobs.forEach((job) => console.log(` ${job.name}: ${job.status}${job.conclusion ? ` / ${job.conclusion}` : ""}`));
|
|
25304
|
+
console.log(status.url);
|
|
25305
|
+
};
|
|
25306
|
+
var inspectGithubMobileRun = async (args) => {
|
|
25307
|
+
const projectRoot = process.cwd();
|
|
25308
|
+
const requestedRunId = githubMobileRunId(args);
|
|
25309
|
+
const repository = valueAfter(args, "--repo");
|
|
25310
|
+
if (args.includes("--watch") && !args.includes("--json"))
|
|
25311
|
+
console.log(`Watching GitHub run ${requestedRunId} until completion\u2026`);
|
|
25312
|
+
const status = args.includes("--watch") ? await watchAbsoluteMobileCiRun({
|
|
25313
|
+
projectRoot,
|
|
25314
|
+
...repository ? { repository } : {},
|
|
25315
|
+
runId: requestedRunId
|
|
25316
|
+
}) : await inspectAbsoluteMobileCiRun({
|
|
25317
|
+
projectRoot,
|
|
25318
|
+
...repository ? { repository } : {},
|
|
25319
|
+
runId: requestedRunId
|
|
25320
|
+
});
|
|
25321
|
+
sendTelemetryEvent("mobile:ci-status", {
|
|
25322
|
+
conclusion: status.conclusion ?? "pending",
|
|
25323
|
+
provider: "github-actions",
|
|
25324
|
+
status: status.status,
|
|
25325
|
+
watched: args.includes("--watch")
|
|
25326
|
+
});
|
|
25327
|
+
if (args.includes("--json"))
|
|
25328
|
+
console.log(JSON.stringify(status, null, 2));
|
|
25329
|
+
else
|
|
25330
|
+
renderGithubMobileRunStatus(status);
|
|
25331
|
+
return status;
|
|
25332
|
+
};
|
|
25333
|
+
var renderGithubMobilePromotionAudit = (result, projectRoot) => {
|
|
25334
|
+
console.log(`\u2713 Verified ${result.audit.platform} promotion ${result.audit.github.promotionRunId} for ${result.audit.release.releaseId}.`);
|
|
25335
|
+
console.log(`Audit: ${relative16(projectRoot, result.directory)}`);
|
|
25336
|
+
};
|
|
25337
|
+
var auditGithubMobilePromotion = async (args) => {
|
|
25338
|
+
const projectRoot = process.cwd();
|
|
25339
|
+
const requestedRunId = githubMobileRunId(args);
|
|
25340
|
+
const repository = valueAfter(args, "--repo");
|
|
25341
|
+
const sourceRunId = valueAfter(args, "--source-run-id");
|
|
25342
|
+
const startedAt = performance.now();
|
|
25343
|
+
try {
|
|
25344
|
+
const result = await auditAbsoluteMobileCiPromotion({
|
|
25345
|
+
outputDirectory: valueAfter(args, "--outdir"),
|
|
25346
|
+
projectRoot,
|
|
25347
|
+
...repository ? { repository } : {},
|
|
25348
|
+
runId: requestedRunId,
|
|
25349
|
+
...sourceRunId ? { sourceRunId } : {}
|
|
25350
|
+
});
|
|
25351
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25352
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25353
|
+
platform: result.audit.platform,
|
|
25354
|
+
provider: "github-actions",
|
|
25355
|
+
success: true
|
|
25356
|
+
});
|
|
25357
|
+
if (args.includes("--json"))
|
|
25358
|
+
console.log(JSON.stringify(result.audit, null, 2));
|
|
25359
|
+
else
|
|
25360
|
+
renderGithubMobilePromotionAudit(result, projectRoot);
|
|
25361
|
+
return result;
|
|
25362
|
+
} catch (error) {
|
|
25363
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25364
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25365
|
+
provider: "github-actions",
|
|
25366
|
+
success: false
|
|
25367
|
+
});
|
|
25368
|
+
throw error;
|
|
25369
|
+
}
|
|
24718
25370
|
};
|
|
24719
25371
|
var doctorMark = (status) => {
|
|
24720
25372
|
if (status === "pass")
|
|
@@ -24812,9 +25464,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24812
25464
|
temporaryDirectory: undefined
|
|
24813
25465
|
};
|
|
24814
25466
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
24815
|
-
const temporaryParent =
|
|
24816
|
-
await
|
|
24817
|
-
const temporaryDirectory = await
|
|
25467
|
+
const temporaryParent = join18(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
25468
|
+
await mkdir18(temporaryParent, { recursive: true });
|
|
25469
|
+
const temporaryDirectory = await mkdtemp7(join18(temporaryParent, ".stage-"));
|
|
24818
25470
|
try {
|
|
24819
25471
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
24820
25472
|
"export",
|
|
@@ -24826,9 +25478,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24826
25478
|
temporaryDirectory,
|
|
24827
25479
|
"--clear"
|
|
24828
25480
|
], { production: true });
|
|
24829
|
-
const generatedApp = JSON.parse(await
|
|
24830
|
-
const expoConfig =
|
|
24831
|
-
if (!
|
|
25481
|
+
const generatedApp = JSON.parse(await readFile22(join18(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
25482
|
+
const expoConfig = isRecord10(generatedApp) ? generatedApp.expo : undefined;
|
|
25483
|
+
if (!isRecord10(expoConfig))
|
|
24832
25484
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
24833
25485
|
const publicExpoConfig = expoPublicConfig(expoConfig);
|
|
24834
25486
|
const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
|
|
@@ -24844,7 +25496,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24844
25496
|
temporaryDirectory
|
|
24845
25497
|
};
|
|
24846
25498
|
} catch (error) {
|
|
24847
|
-
await
|
|
25499
|
+
await rm10(temporaryDirectory, { force: true, recursive: true });
|
|
24848
25500
|
throw error;
|
|
24849
25501
|
}
|
|
24850
25502
|
};
|
|
@@ -24869,11 +25521,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
24869
25521
|
let expoExportDirectory;
|
|
24870
25522
|
try {
|
|
24871
25523
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
24872
|
-
const embedded = JSON.parse(await
|
|
24873
|
-
const runtimeFingerprint =
|
|
25524
|
+
const embedded = JSON.parse(await readFile22(join18(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
25525
|
+
const runtimeFingerprint = isRecord10(embedded) ? embedded.nativeRuntime : undefined;
|
|
24874
25526
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
24875
25527
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
24876
|
-
const privateKey = await
|
|
25528
|
+
const privateKey = await readFile22(resolve17(projectRoot, signingKeyPath));
|
|
24877
25529
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
24878
25530
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
24879
25531
|
format: "der",
|
|
@@ -24894,7 +25546,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
24894
25546
|
channel: mobile.updates.channel,
|
|
24895
25547
|
classification,
|
|
24896
25548
|
keyId,
|
|
24897
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
25549
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join18(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
24898
25550
|
privateKey,
|
|
24899
25551
|
runtimeFingerprint
|
|
24900
25552
|
});
|
|
@@ -24906,7 +25558,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
24906
25558
|
return result;
|
|
24907
25559
|
} finally {
|
|
24908
25560
|
if (expoExportDirectory)
|
|
24909
|
-
await
|
|
25561
|
+
await rm10(expoExportDirectory, { force: true, recursive: true });
|
|
24910
25562
|
sendTelemetryEvent("mobile:update-build", {
|
|
24911
25563
|
classification: classification ?? "invalid",
|
|
24912
25564
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -24936,7 +25588,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
24936
25588
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
24937
25589
|
...validityYears === undefined ? {} : { validityYears }
|
|
24938
25590
|
});
|
|
24939
|
-
const certificatePath =
|
|
25591
|
+
const certificatePath = relative16(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
24940
25592
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
24941
25593
|
engine: "expo",
|
|
24942
25594
|
validityYears: validityYears ?? 10
|
|
@@ -24998,7 +25650,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
24998
25650
|
publicKeys: mobile.updates.publicKeys,
|
|
24999
25651
|
storage: requestedStorage
|
|
25000
25652
|
});
|
|
25001
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
25653
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative16(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
25002
25654
|
if (requestedStorage === "local")
|
|
25003
25655
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
25004
25656
|
else
|
|
@@ -25475,7 +26127,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
25475
26127
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
25476
26128
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
25477
26129
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
25478
|
-
console.log(`Metadata: ${
|
|
26130
|
+
console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
|
|
25479
26131
|
return release;
|
|
25480
26132
|
} finally {
|
|
25481
26133
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -25698,7 +26350,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
25698
26350
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
25699
26351
|
if (release.registeredArtifactPath)
|
|
25700
26352
|
console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
|
|
25701
|
-
console.log(`Metadata: ${
|
|
26353
|
+
console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
|
|
25702
26354
|
return release;
|
|
25703
26355
|
} finally {
|
|
25704
26356
|
stopListeningForCancellation();
|
|
@@ -25849,7 +26501,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
25849
26501
|
checks.push({
|
|
25850
26502
|
id: "sync.storage-schema",
|
|
25851
26503
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
25852
|
-
path:
|
|
26504
|
+
path: join18(projectRoot, "package.json"),
|
|
25853
26505
|
platform: "host",
|
|
25854
26506
|
status: "pass"
|
|
25855
26507
|
});
|
|
@@ -25857,7 +26509,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
25857
26509
|
checks.push({
|
|
25858
26510
|
id: "sync.storage-schema",
|
|
25859
26511
|
label: "Offline schema metadata is invalid",
|
|
25860
|
-
path:
|
|
26512
|
+
path: join18(projectRoot, "package.json"),
|
|
25861
26513
|
platform: "host",
|
|
25862
26514
|
remediation: error instanceof Error ? error.message : String(error),
|
|
25863
26515
|
status: "fail"
|
|
@@ -25945,7 +26597,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
25945
26597
|
}
|
|
25946
26598
|
return { https: args.includes("--https"), port };
|
|
25947
26599
|
}
|
|
25948
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
26600
|
+
const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
25949
26601
|
if (instances.length !== 1) {
|
|
25950
26602
|
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>.");
|
|
25951
26603
|
}
|
|
@@ -26039,8 +26691,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
26039
26691
|
return waitForBoot();
|
|
26040
26692
|
};
|
|
26041
26693
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
26042
|
-
const root =
|
|
26043
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
26694
|
+
const root = resolve17(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
26695
|
+
if (root !== projectRoot && !root.startsWith(`${resolve17(projectRoot)}/`)) {
|
|
26044
26696
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
26045
26697
|
}
|
|
26046
26698
|
return root;
|
|
@@ -26068,12 +26720,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
26068
26720
|
});
|
|
26069
26721
|
};
|
|
26070
26722
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
26071
|
-
await
|
|
26072
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
26723
|
+
await mkdir18(options.artifactRoot, { recursive: true });
|
|
26724
|
+
const screenshot = options.session ? await options.session.screenshot(join18(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
26073
26725
|
return;
|
|
26074
26726
|
}) : undefined;
|
|
26075
|
-
const diagnosticPath =
|
|
26076
|
-
await
|
|
26727
|
+
const diagnosticPath = join18(options.artifactRoot, "android-failure.json");
|
|
26728
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26077
26729
|
diagnostics: options.session?.diagnostics ?? [],
|
|
26078
26730
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
26079
26731
|
platform: "android",
|
|
@@ -26094,7 +26746,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
|
|
|
26094
26746
|
]);
|
|
26095
26747
|
if (exitCode !== 0)
|
|
26096
26748
|
throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
26097
|
-
await
|
|
26749
|
+
await writeFile19(destination, bytes);
|
|
26098
26750
|
return destination;
|
|
26099
26751
|
};
|
|
26100
26752
|
var printAndroidReleaseAcceptance = (result, json) => {
|
|
@@ -26148,7 +26800,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26148
26800
|
release,
|
|
26149
26801
|
serial
|
|
26150
26802
|
});
|
|
26151
|
-
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial,
|
|
26803
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join18(reportRoot, "android-release.png")) : undefined;
|
|
26152
26804
|
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
26153
26805
|
durationMs: result.durationMs,
|
|
26154
26806
|
engine: result.engine,
|
|
@@ -26195,9 +26847,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26195
26847
|
platform: "android",
|
|
26196
26848
|
success: false
|
|
26197
26849
|
});
|
|
26198
|
-
await
|
|
26199
|
-
const diagnosticPath =
|
|
26200
|
-
await
|
|
26850
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
26851
|
+
const diagnosticPath = join18(artifactRoot, "android-release-failure.json");
|
|
26852
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26201
26853
|
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
26202
26854
|
platform: "android",
|
|
26203
26855
|
provider: mobile.engine,
|
|
@@ -26291,7 +26943,7 @@ var testAndroid = async (args) => {
|
|
|
26291
26943
|
console.log(JSON.stringify(report, null, 2));
|
|
26292
26944
|
else
|
|
26293
26945
|
printAndroidTestReport(report);
|
|
26294
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
26946
|
+
const screenshot = reportRoot ? await session.screenshot(join18(artifactRoot, "android-emulator.png")) : undefined;
|
|
26295
26947
|
await writeRequestedAndroidReport({
|
|
26296
26948
|
adb: adb2,
|
|
26297
26949
|
args,
|
|
@@ -26360,14 +27012,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
26360
27012
|
const port = Number(explicit);
|
|
26361
27013
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
26362
27014
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
26363
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
27015
|
+
const instance2 = listLiveInstances().find((candidate) => resolve17(candidate.cwd) === resolve17(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
26364
27016
|
return {
|
|
26365
27017
|
https: instance2?.https ?? args.includes("--https"),
|
|
26366
27018
|
instance: instance2,
|
|
26367
27019
|
port
|
|
26368
27020
|
};
|
|
26369
27021
|
}
|
|
26370
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
27022
|
+
const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
26371
27023
|
if (instances.length !== 1)
|
|
26372
27024
|
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>.");
|
|
26373
27025
|
const [instance] = instances;
|
|
@@ -26385,7 +27037,7 @@ var waitForIosHmrClient = async (options) => {
|
|
|
26385
27037
|
return;
|
|
26386
27038
|
});
|
|
26387
27039
|
const status = response?.ok ? await response.json().catch(() => null) : null;
|
|
26388
|
-
const targets =
|
|
27040
|
+
const targets = isRecord10(status) && isRecord10(status.connectedTargets) ? status.connectedTargets : undefined;
|
|
26389
27041
|
if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
|
|
26390
27042
|
return;
|
|
26391
27043
|
await Bun.sleep(100);
|
|
@@ -26511,8 +27163,8 @@ var requireCapturedCommand = (command, label) => {
|
|
|
26511
27163
|
return result;
|
|
26512
27164
|
};
|
|
26513
27165
|
var writeIosFailureArtifacts = async (options) => {
|
|
26514
|
-
await
|
|
26515
|
-
const screenshot =
|
|
27166
|
+
await mkdir18(options.artifactRoot, { recursive: true });
|
|
27167
|
+
const screenshot = join18(options.artifactRoot, "ios-failure.png");
|
|
26516
27168
|
const screenshotResult = captureCommand4([
|
|
26517
27169
|
options.xcrun,
|
|
26518
27170
|
"simctl",
|
|
@@ -26521,8 +27173,8 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
26521
27173
|
"screenshot",
|
|
26522
27174
|
screenshot
|
|
26523
27175
|
]);
|
|
26524
|
-
const diagnosticPath =
|
|
26525
|
-
await
|
|
27176
|
+
const diagnosticPath = join18(options.artifactRoot, "ios-failure.json");
|
|
27177
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26526
27178
|
appId: options.appId,
|
|
26527
27179
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
26528
27180
|
platform: "ios",
|
|
@@ -26549,8 +27201,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
26549
27201
|
var absolutejsVersionForReport = async () => {
|
|
26550
27202
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
26551
27203
|
const versions = await Promise.all([
|
|
26552
|
-
|
|
26553
|
-
|
|
27204
|
+
resolve17(import.meta.dir, "..", "..", "package.json"),
|
|
27205
|
+
resolve17(import.meta.dir, "..", "..", "..", "package.json")
|
|
26554
27206
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
26555
27207
|
for (const version of versions) {
|
|
26556
27208
|
if (version === "unknown")
|
|
@@ -26927,9 +27579,9 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
26927
27579
|
remote: remoteProfile !== undefined,
|
|
26928
27580
|
success: false
|
|
26929
27581
|
});
|
|
26930
|
-
await
|
|
26931
|
-
const diagnosticPath =
|
|
26932
|
-
await
|
|
27582
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
27583
|
+
const diagnosticPath = join18(artifactRoot, "ios-release-failure.json");
|
|
27584
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26933
27585
|
error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
|
|
26934
27586
|
platform: "ios",
|
|
26935
27587
|
provider: mobile.engine,
|
|
@@ -27022,8 +27674,8 @@ var testIos = async (args) => {
|
|
|
27022
27674
|
mobile.appId
|
|
27023
27675
|
], "iOS app launch");
|
|
27024
27676
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
27025
|
-
await
|
|
27026
|
-
const screenshot =
|
|
27677
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
27678
|
+
const screenshot = join18(artifactRoot, "ios-simulator.png");
|
|
27027
27679
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
27028
27680
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
27029
27681
|
const report = {
|
|
@@ -27240,6 +27892,10 @@ var runMobile = async (args) => {
|
|
|
27240
27892
|
if (command === "ci") {
|
|
27241
27893
|
if (args[1] === "promote")
|
|
27242
27894
|
await dispatchGithubMobilePromotion(args.slice(2));
|
|
27895
|
+
else if (args[1] === "status")
|
|
27896
|
+
await inspectGithubMobileRun(args.slice(2));
|
|
27897
|
+
else if (args[1] === "audit")
|
|
27898
|
+
await auditGithubMobilePromotion(args.slice(2));
|
|
27243
27899
|
else if (args[1] === "verification")
|
|
27244
27900
|
await createGithubCertificationVerification(args.slice(2));
|
|
27245
27901
|
else
|
|
@@ -27322,7 +27978,7 @@ var runMobile = async (args) => {
|
|
|
27322
27978
|
await publishIos(args.slice(2));
|
|
27323
27979
|
return;
|
|
27324
27980
|
}
|
|
27325
|
-
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | ci promote <android|ios> --run-id id --certification path [--channel name] [--play-track track|--testflight-group group] [--ref branch] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--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] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
|
|
27981
|
+
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | ci promote <android|ios> --run-id id --certification path [--channel name] [--play-track track|--testflight-group group] [--ref branch] [--repo owner/name] [--watch] [--audit] [--outdir path] [--json] | ci status --run-id id [--watch] [--repo owner/name] [--json] | ci audit --run-id id [--source-run-id id] [--repo owner/name] [--outdir path] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--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] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
|
|
27326
27982
|
};
|
|
27327
27983
|
export {
|
|
27328
27984
|
runMobile
|