@absolutejs/absolute 0.20.0-beta.111 → 0.20.0-beta.113
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 +46 -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-k7gpwfs8.js → mobile-v11nwcj9.js} +688 -137
- package/dist/mobile/index.js +516 -203
- package/dist/mobile/index.js.map +7 -6
- package/dist/src/mobile/ciPromotionAudit.d.ts +26 -2
- package/dist/src/mobile/ciPromotionLedger.d.ts +49 -0
- package/dist/src/mobile/ciWorkflow.d.ts +1 -1
- package/dist/src/mobile/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -16283,9 +16283,9 @@ var require_pkcs7 = __commonJS(function(exports, module) {
|
|
|
16283
16283
|
var jan_1_2050 = new Date("2050-01-01T00:00:00Z");
|
|
16284
16284
|
var date = attr.value;
|
|
16285
16285
|
if (typeof date === "string") {
|
|
16286
|
-
var
|
|
16287
|
-
if (!isNaN(
|
|
16288
|
-
date = new Date(
|
|
16286
|
+
var timestamp2 = Date.parse(date);
|
|
16287
|
+
if (!isNaN(timestamp2)) {
|
|
16288
|
+
date = new Date(timestamp2);
|
|
16289
16289
|
} else if (date.length === 13) {
|
|
16290
16290
|
date = asn1.utcTimeToDate(date);
|
|
16291
16291
|
} else {
|
|
@@ -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
|
|
16782
|
+
access as access18,
|
|
16783
|
+
mkdir as mkdir19,
|
|
16784
16784
|
mkdtemp as mkdtemp7,
|
|
16785
|
-
readFile as
|
|
16786
|
-
rm as
|
|
16785
|
+
readFile as readFile23,
|
|
16786
|
+
rm as rm11,
|
|
16787
16787
|
writeFile as writeFile19
|
|
16788
16788
|
} from "fs/promises";
|
|
16789
|
-
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16790
|
-
import { join as
|
|
16789
|
+
import { createHash as createHash7, createPublicKey as createPublicKey2, randomUUID as randomUUID2 } from "crypto";
|
|
16790
|
+
import { join as join19, relative as relative17, resolve as resolve18 } 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
|
|
@@ -23527,7 +23535,7 @@ var promotionSharedSteps = (platform) => ` - name: Restore exact ${platform
|
|
|
23527
23535
|
- name: Record bounded promotion context
|
|
23528
23536
|
shell: bash
|
|
23529
23537
|
run: |
|
|
23530
|
-
bun -e 'const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!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({ format:
|
|
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");'
|
|
23531
23539
|
- name: Import and verify exact certification
|
|
23532
23540
|
shell: bash
|
|
23533
23541
|
run: |
|
|
@@ -23565,6 +23573,7 @@ var androidPromotionJob = (options) => {
|
|
|
23565
23573
|
id-token: write
|
|
23566
23574
|
env:
|
|
23567
23575
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23576
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23568
23577
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23569
23578
|
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23570
23579
|
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
@@ -23630,6 +23639,7 @@ var iosPromotionJob = (options) => {
|
|
|
23630
23639
|
id-token: write
|
|
23631
23640
|
env:
|
|
23632
23641
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23642
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23633
23643
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23634
23644
|
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23635
23645
|
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
@@ -23720,6 +23730,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
|
|
|
23720
23730
|
});
|
|
23721
23731
|
let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
|
|
23722
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) }}
|
|
23723
23734
|
|
|
23724
23735
|
on:
|
|
23725
23736
|
pull_request:
|
|
@@ -23745,6 +23756,7 @@ jobs:
|
|
|
23745
23756
|
env:
|
|
23746
23757
|
${environment}${includePublishing ? `
|
|
23747
23758
|
ABSOLUTE_PROMOTION_PLATFORM: \${{ inputs.platform }}
|
|
23759
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
23748
23760
|
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23749
23761
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}` : ""}
|
|
23750
23762
|
steps:
|
|
@@ -23807,12 +23819,15 @@ import {
|
|
|
23807
23819
|
} from "fs/promises";
|
|
23808
23820
|
import { dirname as dirname12, isAbsolute as isAbsolute8, join as join15, relative as relative12, resolve as resolve13, sep as sep9 } from "path";
|
|
23809
23821
|
var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
|
|
23810
|
-
var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT =
|
|
23822
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 2;
|
|
23811
23823
|
var MAX_GITHUB_OUTPUT_BYTES = 1048576;
|
|
23812
23824
|
var MAX_AUDIT_JSON_BYTES = 1048576;
|
|
23813
23825
|
var MAX_SEARCH_FILES = 20000;
|
|
23814
23826
|
var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
|
|
23815
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;
|
|
23816
23831
|
var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23817
23832
|
var requiredString = (value, name) => {
|
|
23818
23833
|
if (typeof value !== "string" || value.trim() === "")
|
|
@@ -23829,6 +23844,26 @@ var runId = (value, name) => {
|
|
|
23829
23844
|
throw new TypeError(`${name} is invalid.`);
|
|
23830
23845
|
return value;
|
|
23831
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
|
+
];
|
|
23832
23867
|
var repositoryArgs = (repository) => {
|
|
23833
23868
|
if (repository === undefined)
|
|
23834
23869
|
return [];
|
|
@@ -23876,6 +23911,27 @@ var parseJob = (value) => {
|
|
|
23876
23911
|
status: requiredString(value.status, "GitHub job status")
|
|
23877
23912
|
};
|
|
23878
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
|
+
};
|
|
23879
23935
|
var inspectAbsoluteMobileCiRun = async (options) => {
|
|
23880
23936
|
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
23881
23937
|
const result = await requireCommand2(options.run ?? defaultRunner, [
|
|
@@ -23885,7 +23941,7 @@ var inspectAbsoluteMobileCiRun = async (options) => {
|
|
|
23885
23941
|
requestedRunId,
|
|
23886
23942
|
...repositoryArgs(options.repository),
|
|
23887
23943
|
"--json",
|
|
23888
|
-
"conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
23944
|
+
"conclusion,createdAt,databaseId,displayTitle,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
23889
23945
|
], { cwd: options.projectRoot }, "GitHub workflow inspection");
|
|
23890
23946
|
return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
|
|
23891
23947
|
};
|
|
@@ -23898,6 +23954,7 @@ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
|
23898
23954
|
return {
|
|
23899
23955
|
conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
|
|
23900
23956
|
createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
|
|
23957
|
+
displayTitle: requiredString(value.displayTitle, "GitHub run displayTitle"),
|
|
23901
23958
|
headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
|
|
23902
23959
|
headSha: requiredString(value.headSha, "GitHub run headSha"),
|
|
23903
23960
|
jobs: value.jobs.map(parseJob),
|
|
@@ -23910,20 +23967,44 @@ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
|
23910
23967
|
};
|
|
23911
23968
|
};
|
|
23912
23969
|
var parseAbsoluteMobilePromotionContext = (value) => {
|
|
23913
|
-
if (!isRecord9(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
23970
|
+
if (!isRecord9(value) || value.format !== 1 && value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
23914
23971
|
throw new TypeError("Mobile promotion context is invalid.");
|
|
23915
23972
|
const { platform } = value;
|
|
23916
23973
|
const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
|
|
23917
23974
|
if (sourceArtifact !== `absolute-mobile-${platform}`)
|
|
23918
23975
|
throw new TypeError("Mobile promotion source artifact is invalid.");
|
|
23919
23976
|
return {
|
|
23920
|
-
format:
|
|
23977
|
+
dispatchId: value.format === 1 ? null : dispatchId(requiredString(value.dispatchId, "promotion dispatch ID")),
|
|
23978
|
+
format: value.format,
|
|
23921
23979
|
platform,
|
|
23922
23980
|
promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
|
|
23923
23981
|
sourceArtifact,
|
|
23924
23982
|
sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
|
|
23925
23983
|
};
|
|
23926
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
|
+
};
|
|
23927
24008
|
var watchAbsoluteMobileCiRun = async (options) => {
|
|
23928
24009
|
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
23929
24010
|
await requireCommand2(options.run ?? defaultRunner, [
|
|
@@ -24042,6 +24123,7 @@ var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
|
|
|
24042
24123
|
- Artifact SHA-256: \`${audit.release.sha256}\`
|
|
24043
24124
|
- Source workflow run: ${audit.github.sourceRunId}
|
|
24044
24125
|
- Promotion workflow run: ${audit.github.promotionRunId}
|
|
24126
|
+
- Dispatch correlation: ${audit.github.dispatchId ?? "legacy workflow (not recorded)"}
|
|
24045
24127
|
- Workflow: ${audit.github.workflowName}
|
|
24046
24128
|
- GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
|
|
24047
24129
|
- Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
|
|
@@ -24091,6 +24173,8 @@ var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
|
24091
24173
|
throw new TypeError("Promotion context run identity does not match.");
|
|
24092
24174
|
if (context && context.platform !== platform)
|
|
24093
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.");
|
|
24094
24178
|
const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
|
|
24095
24179
|
if (!sourceRunId)
|
|
24096
24180
|
throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
|
|
@@ -24159,6 +24243,7 @@ var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
|
24159
24243
|
format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
24160
24244
|
github: {
|
|
24161
24245
|
conclusion: status.conclusion,
|
|
24246
|
+
dispatchId: context?.dispatchId ?? null,
|
|
24162
24247
|
promotionRunId,
|
|
24163
24248
|
repository: verification.identity.repository,
|
|
24164
24249
|
sourceRunId,
|
|
@@ -24194,47 +24279,266 @@ var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
|
24194
24279
|
}
|
|
24195
24280
|
};
|
|
24196
24281
|
|
|
24282
|
+
// src/mobile/ciPromotionLedger.ts
|
|
24283
|
+
import {
|
|
24284
|
+
access as access14,
|
|
24285
|
+
link,
|
|
24286
|
+
lstat,
|
|
24287
|
+
mkdir as mkdir15,
|
|
24288
|
+
open,
|
|
24289
|
+
readFile as readFile20,
|
|
24290
|
+
readdir as readdir7,
|
|
24291
|
+
rename as rename12,
|
|
24292
|
+
rm as rm9
|
|
24293
|
+
} from "fs/promises";
|
|
24294
|
+
import { dirname as dirname13, isAbsolute as isAbsolute9, join as join16, relative as relative13, resolve as resolve14, sep as sep10 } from "path";
|
|
24295
|
+
import { randomUUID } from "crypto";
|
|
24296
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_OPERATION_FORMAT = 1;
|
|
24297
|
+
var MAX_OPERATION_BYTES = 65536;
|
|
24298
|
+
var MAX_OPERATIONS = 1000;
|
|
24299
|
+
var DISPATCH_ID_PATTERN2 = /^amp_[a-f0-9]{32}$/u;
|
|
24300
|
+
var RUN_ID_PATTERN2 = /^[1-9][0-9]*$/u;
|
|
24301
|
+
var SHA256_PATTERN2 = /^[a-f0-9]{64}$/u;
|
|
24302
|
+
var CERTIFICATION_ID_PATTERN = /^amobile_cert_[a-f0-9]{64}$/u;
|
|
24303
|
+
var RELEASE_ID_PATTERN = /^amobile_(?:android|ios)_[a-f0-9]{64}$/u;
|
|
24304
|
+
var REPOSITORY_PATTERN3 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
24305
|
+
var LAST_ASCII_CONTROL = 31;
|
|
24306
|
+
var DELETE_CHARACTER = 127;
|
|
24307
|
+
var PHASES = [
|
|
24308
|
+
"dispatching",
|
|
24309
|
+
"dispatched",
|
|
24310
|
+
"discovered",
|
|
24311
|
+
"completed",
|
|
24312
|
+
"audited"
|
|
24313
|
+
];
|
|
24314
|
+
var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24315
|
+
var requiredString2 = (value, label, max = 512) => {
|
|
24316
|
+
const hasControlCharacter = typeof value === "string" && Array.from(value).some((character) => {
|
|
24317
|
+
const code = character.codePointAt(0);
|
|
24318
|
+
return code !== undefined && (code <= LAST_ASCII_CONTROL || code === DELETE_CHARACTER);
|
|
24319
|
+
});
|
|
24320
|
+
if (typeof value !== "string" || value.trim() === "" || value !== value.trim() || value.length > max || hasControlCharacter)
|
|
24321
|
+
throw new TypeError(`${label} is invalid.`);
|
|
24322
|
+
return value;
|
|
24323
|
+
};
|
|
24324
|
+
var optionalString2 = (value, label, max = 512) => {
|
|
24325
|
+
if (value === null)
|
|
24326
|
+
return null;
|
|
24327
|
+
return requiredString2(value, label, max);
|
|
24328
|
+
};
|
|
24329
|
+
var boolean = (value, label) => {
|
|
24330
|
+
if (typeof value !== "boolean")
|
|
24331
|
+
throw new TypeError(`${label} is invalid.`);
|
|
24332
|
+
return value;
|
|
24333
|
+
};
|
|
24334
|
+
var timestamp = (value, label) => {
|
|
24335
|
+
const parsed = requiredString2(value, label, 64);
|
|
24336
|
+
if (!Number.isFinite(Date.parse(parsed)))
|
|
24337
|
+
throw new TypeError(`${label} is invalid.`);
|
|
24338
|
+
return parsed;
|
|
24339
|
+
};
|
|
24340
|
+
var projectRelativePath = (value, label) => {
|
|
24341
|
+
const parsed = requiredString2(value, label, 512);
|
|
24342
|
+
if (isAbsolute9(parsed) || parsed === ".." || parsed.startsWith(`..${sep10}`) || parsed.split(/[\\/]/u).includes(".."))
|
|
24343
|
+
throw new TypeError(`${label} must be project-relative.`);
|
|
24344
|
+
return parsed.replaceAll("\\", "/");
|
|
24345
|
+
};
|
|
24346
|
+
var nullableProjectRelativePath = (value, label) => value === null ? null : projectRelativePath(value, label);
|
|
24347
|
+
var dispatchId2 = (value) => {
|
|
24348
|
+
const parsed = requiredString2(value, "Promotion dispatch ID", 36);
|
|
24349
|
+
if (!DISPATCH_ID_PATTERN2.test(parsed))
|
|
24350
|
+
throw new TypeError("Promotion dispatch ID is invalid.");
|
|
24351
|
+
return parsed;
|
|
24352
|
+
};
|
|
24353
|
+
var runId2 = (value, label) => {
|
|
24354
|
+
const parsed = requiredString2(value, label, 32);
|
|
24355
|
+
if (!RUN_ID_PATTERN2.test(parsed))
|
|
24356
|
+
throw new TypeError(`${label} is invalid.`);
|
|
24357
|
+
return parsed;
|
|
24358
|
+
};
|
|
24359
|
+
var nullableRunId = (value, label) => value === null ? null : runId2(value, label);
|
|
24360
|
+
var operationRoot = (projectRoot) => join16(resolve14(projectRoot), ".absolutejs", "mobile-ci", "promotions");
|
|
24361
|
+
var absoluteMobilePromotionOperationPath = (projectRoot, requestedDispatchId) => join16(operationRoot(projectRoot), dispatchId2(requestedDispatchId), "operation.json");
|
|
24362
|
+
var operationPhase = (value) => {
|
|
24363
|
+
const phase = PHASES.find((candidate) => candidate === value);
|
|
24364
|
+
if (!phase)
|
|
24365
|
+
throw new TypeError("Mobile promotion operation phase is invalid.");
|
|
24366
|
+
return phase;
|
|
24367
|
+
};
|
|
24368
|
+
var parseAbsoluteMobilePromotionOperation = (value) => {
|
|
24369
|
+
if (!isRecord10(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_OPERATION_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
24370
|
+
throw new TypeError("Mobile promotion operation is invalid.");
|
|
24371
|
+
const parsed = {
|
|
24372
|
+
auditDirectory: nullableProjectRelativePath(value.auditDirectory, "Promotion audit directory"),
|
|
24373
|
+
auditOutputDirectory: nullableProjectRelativePath(value.auditOutputDirectory, "Promotion requested audit directory"),
|
|
24374
|
+
auditRequested: boolean(value.auditRequested, "Promotion audit request"),
|
|
24375
|
+
certificationId: requiredString2(value.certificationId, "Promotion certification ID", 256),
|
|
24376
|
+
certificationSha256: requiredString2(value.certificationSha256, "Promotion certification digest", 64),
|
|
24377
|
+
channel: optionalString2(value.channel, "Promotion channel", 128),
|
|
24378
|
+
conclusion: optionalString2(value.conclusion, "Promotion conclusion", 64),
|
|
24379
|
+
createdAt: timestamp(value.createdAt, "Promotion creation time"),
|
|
24380
|
+
dispatchId: dispatchId2(value.dispatchId),
|
|
24381
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_OPERATION_FORMAT,
|
|
24382
|
+
githubStatus: optionalString2(value.githubStatus, "Promotion GitHub status", 64),
|
|
24383
|
+
phase: operationPhase(value.phase),
|
|
24384
|
+
platform: value.platform,
|
|
24385
|
+
playTrack: optionalString2(value.playTrack, "Promotion Play track", 128),
|
|
24386
|
+
ref: optionalString2(value.ref, "Promotion ref", 256),
|
|
24387
|
+
releaseId: requiredString2(value.releaseId, "Promotion release ID", 256),
|
|
24388
|
+
repository: optionalString2(value.repository, "Promotion repository", 256),
|
|
24389
|
+
runId: nullableRunId(value.runId, "Promotion run ID"),
|
|
24390
|
+
sourceRunId: runId2(value.sourceRunId, "Promotion source run ID"),
|
|
24391
|
+
testflightGroup: optionalString2(value.testflightGroup, "Promotion TestFlight group", 128),
|
|
24392
|
+
testflightSubmitReview: boolean(value.testflightSubmitReview, "Promotion TestFlight review setting"),
|
|
24393
|
+
updatedAt: timestamp(value.updatedAt, "Promotion update time"),
|
|
24394
|
+
url: optionalString2(value.url, "Promotion run URL", 1024),
|
|
24395
|
+
watchRequested: boolean(value.watchRequested, "Promotion watch request"),
|
|
24396
|
+
workflow: projectRelativePath(value.workflow, "Promotion workflow")
|
|
24397
|
+
};
|
|
24398
|
+
if (!SHA256_PATTERN2.test(parsed.certificationSha256))
|
|
24399
|
+
throw new TypeError("Promotion certification digest is invalid.");
|
|
24400
|
+
if (!CERTIFICATION_ID_PATTERN.test(parsed.certificationId))
|
|
24401
|
+
throw new TypeError("Promotion certification ID is invalid.");
|
|
24402
|
+
if (!RELEASE_ID_PATTERN.test(parsed.releaseId))
|
|
24403
|
+
throw new TypeError("Promotion release ID is invalid.");
|
|
24404
|
+
if (parsed.repository !== null && !REPOSITORY_PATTERN3.test(parsed.repository))
|
|
24405
|
+
throw new TypeError("Promotion repository is invalid.");
|
|
24406
|
+
const discovered = PHASES.indexOf(parsed.phase) >= PHASES.indexOf("discovered");
|
|
24407
|
+
const completed = PHASES.indexOf(parsed.phase) >= PHASES.indexOf("completed");
|
|
24408
|
+
if (discovered && (!parsed.runId || !parsed.url) || !discovered && (parsed.runId || parsed.url) || completed && (parsed.githubStatus !== "completed" || !parsed.conclusion) || !completed && (parsed.githubStatus || parsed.conclusion) || parsed.phase === "audited" && !parsed.auditDirectory || parsed.phase !== "audited" && parsed.auditDirectory)
|
|
24409
|
+
throw new TypeError("Mobile promotion operation phase is incomplete.");
|
|
24410
|
+
if (parsed.url && parsed.runId) {
|
|
24411
|
+
const url = new URL(parsed.url);
|
|
24412
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !url.pathname.endsWith(`/actions/runs/${parsed.runId}`))
|
|
24413
|
+
throw new TypeError("Promotion run URL is invalid.");
|
|
24414
|
+
}
|
|
24415
|
+
if (Date.parse(parsed.updatedAt) < Date.parse(parsed.createdAt))
|
|
24416
|
+
throw new TypeError("Promotion operation time moved backwards.");
|
|
24417
|
+
return parsed;
|
|
24418
|
+
};
|
|
24419
|
+
var serialize = (operation) => `${JSON.stringify(parseAbsoluteMobilePromotionOperation(operation), null, 2)}
|
|
24420
|
+
`;
|
|
24421
|
+
var writeSynced = async (path, source) => {
|
|
24422
|
+
const handle = await open(path, "wx", 384);
|
|
24423
|
+
try {
|
|
24424
|
+
await handle.writeFile(source);
|
|
24425
|
+
await handle.sync();
|
|
24426
|
+
} finally {
|
|
24427
|
+
await handle.close();
|
|
24428
|
+
}
|
|
24429
|
+
};
|
|
24430
|
+
var atomicWrite = async (path, source, exclusive) => {
|
|
24431
|
+
await mkdir15(dirname13(path), { recursive: true });
|
|
24432
|
+
const temporary = join16(dirname13(path), `.operation-${randomUUID()}.tmp`);
|
|
24433
|
+
try {
|
|
24434
|
+
await writeSynced(temporary, source);
|
|
24435
|
+
if (exclusive)
|
|
24436
|
+
await link(temporary, path);
|
|
24437
|
+
else
|
|
24438
|
+
await rename12(temporary, path);
|
|
24439
|
+
} finally {
|
|
24440
|
+
await rm9(temporary, { force: true });
|
|
24441
|
+
}
|
|
24442
|
+
};
|
|
24443
|
+
var advanceAbsoluteMobilePromotionOperation = async (projectRoot, requestedDispatchId, options, now = () => new Date) => {
|
|
24444
|
+
const current = await readAbsoluteMobilePromotionOperation(projectRoot, requestedDispatchId);
|
|
24445
|
+
if (PHASES.indexOf(options.phase) < PHASES.indexOf(current.phase))
|
|
24446
|
+
throw new TypeError("Mobile promotion operation cannot move backwards.");
|
|
24447
|
+
if (current.runId && options.runId && current.runId !== options.runId || current.url && options.url && current.url !== options.url || current.auditDirectory && options.auditDirectory && current.auditDirectory !== options.auditDirectory)
|
|
24448
|
+
throw new TypeError("Mobile promotion operation identity is immutable.");
|
|
24449
|
+
const next = parseAbsoluteMobilePromotionOperation({
|
|
24450
|
+
...current,
|
|
24451
|
+
auditDirectory: options.auditDirectory === undefined ? current.auditDirectory : options.auditDirectory,
|
|
24452
|
+
conclusion: options.conclusion === undefined ? current.conclusion : options.conclusion,
|
|
24453
|
+
githubStatus: options.githubStatus === undefined ? current.githubStatus : options.githubStatus,
|
|
24454
|
+
phase: options.phase,
|
|
24455
|
+
runId: options.runId === undefined ? current.runId : options.runId,
|
|
24456
|
+
updatedAt: now().toISOString(),
|
|
24457
|
+
url: options.url === undefined ? current.url : options.url
|
|
24458
|
+
});
|
|
24459
|
+
await atomicWrite(absoluteMobilePromotionOperationPath(projectRoot, requestedDispatchId), serialize(next), false);
|
|
24460
|
+
return next;
|
|
24461
|
+
};
|
|
24462
|
+
var createAbsoluteMobilePromotionOperation = async (projectRoot, options) => {
|
|
24463
|
+
const now = (options.now ?? (() => new Date))().toISOString();
|
|
24464
|
+
const operation = parseAbsoluteMobilePromotionOperation({
|
|
24465
|
+
...options,
|
|
24466
|
+
auditDirectory: null,
|
|
24467
|
+
conclusion: null,
|
|
24468
|
+
createdAt: now,
|
|
24469
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_OPERATION_FORMAT,
|
|
24470
|
+
githubStatus: null,
|
|
24471
|
+
phase: "dispatching",
|
|
24472
|
+
runId: null,
|
|
24473
|
+
updatedAt: now,
|
|
24474
|
+
url: null
|
|
24475
|
+
});
|
|
24476
|
+
await atomicWrite(absoluteMobilePromotionOperationPath(projectRoot, operation.dispatchId), serialize(operation), true);
|
|
24477
|
+
return operation;
|
|
24478
|
+
};
|
|
24479
|
+
var listAbsoluteMobilePromotionOperations = async (projectRoot) => {
|
|
24480
|
+
const root = operationRoot(projectRoot);
|
|
24481
|
+
if (!await access14(root).then(() => true).catch(() => false))
|
|
24482
|
+
return [];
|
|
24483
|
+
const entries = await readdir7(root, { withFileTypes: true });
|
|
24484
|
+
if (entries.length > MAX_OPERATIONS)
|
|
24485
|
+
throw new TypeError("Mobile promotion operation count exceeds the limit.");
|
|
24486
|
+
const operations = await Promise.all(entries.filter((entry) => entry.isDirectory() && DISPATCH_ID_PATTERN2.test(entry.name)).map((entry) => readAbsoluteMobilePromotionOperation(projectRoot, entry.name)));
|
|
24487
|
+
return operations.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
24488
|
+
};
|
|
24489
|
+
var readAbsoluteMobilePromotionOperation = async (projectRoot, requestedDispatchId) => {
|
|
24490
|
+
const path = absoluteMobilePromotionOperationPath(projectRoot, requestedDispatchId);
|
|
24491
|
+
const metadata = await lstat(path).catch(() => null);
|
|
24492
|
+
if (!metadata?.isFile() || metadata.size > MAX_OPERATION_BYTES)
|
|
24493
|
+
throw new TypeError(`Mobile promotion operation ${requestedDispatchId} was not found or is invalid.`);
|
|
24494
|
+
const operation = parseAbsoluteMobilePromotionOperation(JSON.parse(await readFile20(path, "utf8")));
|
|
24495
|
+
if (operation.dispatchId !== requestedDispatchId)
|
|
24496
|
+
throw new TypeError("Mobile promotion operation identity does not match.");
|
|
24497
|
+
return operation;
|
|
24498
|
+
};
|
|
24499
|
+
var relativeAbsoluteMobilePromotionOperationPath = (projectRoot, dispatchIdValue) => relative13(resolve14(projectRoot), absoluteMobilePromotionOperationPath(projectRoot, dispatchIdValue)).replaceAll("\\", "/");
|
|
24500
|
+
|
|
24197
24501
|
// src/mobile/updateSigning.ts
|
|
24198
24502
|
import { createHash as createHash6, sign, verify } from "crypto";
|
|
24199
24503
|
import {
|
|
24200
24504
|
cp,
|
|
24201
|
-
mkdir as
|
|
24505
|
+
mkdir as mkdir16,
|
|
24202
24506
|
mkdtemp as mkdtemp6,
|
|
24203
|
-
readdir as
|
|
24204
|
-
readFile as
|
|
24205
|
-
rename as
|
|
24206
|
-
rm as
|
|
24507
|
+
readdir as readdir8,
|
|
24508
|
+
readFile as readFile21,
|
|
24509
|
+
rename as rename13,
|
|
24510
|
+
rm as rm10,
|
|
24207
24511
|
stat as stat8,
|
|
24208
24512
|
writeFile as writeFile16
|
|
24209
24513
|
} from "fs/promises";
|
|
24210
|
-
import { dirname as
|
|
24514
|
+
import { dirname as dirname14, join as join17, relative as relative14, resolve as resolve15 } from "path";
|
|
24211
24515
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
24212
24516
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
24213
24517
|
var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
24214
24518
|
var listFiles = async (root, directory = root) => {
|
|
24215
|
-
const entries = await
|
|
24519
|
+
const entries = await readdir8(directory, { withFileTypes: true });
|
|
24216
24520
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
24217
|
-
const path =
|
|
24521
|
+
const path = join17(directory, entry.name);
|
|
24218
24522
|
if (entry.isDirectory())
|
|
24219
24523
|
return listFiles(root, path);
|
|
24220
24524
|
if (!entry.isFile())
|
|
24221
24525
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
24222
|
-
return [
|
|
24526
|
+
return [relative14(root, path).replaceAll("\\", "/")];
|
|
24223
24527
|
}));
|
|
24224
24528
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
24225
24529
|
};
|
|
24226
24530
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
24227
|
-
const source =
|
|
24531
|
+
const source = join17(root, path);
|
|
24228
24532
|
const [metadata, contents] = await Promise.all([
|
|
24229
24533
|
stat8(source),
|
|
24230
|
-
|
|
24534
|
+
readFile21(source)
|
|
24231
24535
|
]);
|
|
24232
24536
|
return { bytes: metadata.size, path, sha256: sha2564(contents) };
|
|
24233
24537
|
}));
|
|
24234
24538
|
var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
24235
24539
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
24236
|
-
const bundleDirectory =
|
|
24237
|
-
const outputRoot =
|
|
24540
|
+
const bundleDirectory = resolve15(options.bundleDirectory);
|
|
24541
|
+
const outputRoot = resolve15(options.outputDirectory);
|
|
24238
24542
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
24239
24543
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
24240
24544
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -24262,30 +24566,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
24262
24566
|
value: signature.toString("base64")
|
|
24263
24567
|
}
|
|
24264
24568
|
});
|
|
24265
|
-
await
|
|
24266
|
-
const outputDirectory =
|
|
24267
|
-
const staging = await mkdtemp6(
|
|
24569
|
+
await mkdir16(outputRoot, { recursive: true });
|
|
24570
|
+
const outputDirectory = join17(outputRoot, manifest.releaseId);
|
|
24571
|
+
const staging = await mkdtemp6(join17(outputRoot, ".stage-"));
|
|
24268
24572
|
try {
|
|
24269
|
-
await cp(bundleDirectory,
|
|
24573
|
+
await cp(bundleDirectory, join17(staging, UPDATE_FILES_DIRECTORY), {
|
|
24270
24574
|
force: true,
|
|
24271
24575
|
recursive: true
|
|
24272
24576
|
});
|
|
24273
|
-
await writeFile16(
|
|
24577
|
+
await writeFile16(join17(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
24274
24578
|
`);
|
|
24275
|
-
await
|
|
24579
|
+
await rename13(staging, outputDirectory);
|
|
24276
24580
|
} catch (error) {
|
|
24277
|
-
await
|
|
24581
|
+
await rm10(staging, { force: true, recursive: true });
|
|
24278
24582
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
24279
24583
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
24280
24584
|
throw error;
|
|
24281
24585
|
}
|
|
24282
24586
|
return {
|
|
24283
24587
|
manifest,
|
|
24284
|
-
manifestPath:
|
|
24588
|
+
manifestPath: join17(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
24285
24589
|
outputDirectory
|
|
24286
24590
|
};
|
|
24287
24591
|
};
|
|
24288
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
24592
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile21(join17(resolve15(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
24289
24593
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
24290
24594
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
24291
24595
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -24295,8 +24599,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
24295
24599
|
};
|
|
24296
24600
|
|
|
24297
24601
|
// src/mobile/expoUpdate.ts
|
|
24298
|
-
import { access as
|
|
24299
|
-
import { dirname as
|
|
24602
|
+
import { access as access15, mkdir as mkdir17, readFile as readFile22, writeFile as writeFile17 } from "fs/promises";
|
|
24603
|
+
import { dirname as dirname15, extname as extname3, join as join18 } from "path";
|
|
24300
24604
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
24301
24605
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
24302
24606
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -24362,7 +24666,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
24362
24666
|
};
|
|
24363
24667
|
};
|
|
24364
24668
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
24365
|
-
const metadata = JSON.parse(await
|
|
24669
|
+
const metadata = JSON.parse(await readFile22(join18(options.exportDirectory, "metadata.json"), "utf8"));
|
|
24366
24670
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
24367
24671
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
24368
24672
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -24373,11 +24677,11 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
24373
24677
|
runtimeVersion: options.runtimeVersion
|
|
24374
24678
|
});
|
|
24375
24679
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
24376
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
24680
|
+
await Promise.all(referenced.map(({ path }) => access15(join18(options.exportDirectory, path)).catch(() => {
|
|
24377
24681
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
24378
24682
|
})));
|
|
24379
|
-
const destination =
|
|
24380
|
-
await
|
|
24683
|
+
const destination = join18(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
24684
|
+
await mkdir17(dirname15(destination), { recursive: true });
|
|
24381
24685
|
await writeFile17(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
24382
24686
|
`);
|
|
24383
24687
|
return { descriptor, path: destination };
|
|
@@ -24385,18 +24689,18 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
24385
24689
|
|
|
24386
24690
|
// src/mobile/expoCodeSigning.ts
|
|
24387
24691
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
24388
|
-
import { access as
|
|
24389
|
-
import { dirname as
|
|
24390
|
-
var exists6 = async (path) =>
|
|
24692
|
+
import { access as access16, mkdir as mkdir18, writeFile as writeFile18 } from "fs/promises";
|
|
24693
|
+
import { dirname as dirname16, isAbsolute as isAbsolute10, relative as relative15, resolve as resolve16, sep as sep11 } from "path";
|
|
24694
|
+
var exists6 = async (path) => access16(path).then(() => true).catch(() => false);
|
|
24391
24695
|
var inside4 = (root, path) => {
|
|
24392
|
-
const location =
|
|
24393
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
24696
|
+
const location = relative15(root, path);
|
|
24697
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep11}`) && !isAbsolute10(location);
|
|
24394
24698
|
};
|
|
24395
24699
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
24396
|
-
const root =
|
|
24397
|
-
const certificatePath =
|
|
24398
|
-
const privateKeyPath =
|
|
24399
|
-
const publicKeyPath =
|
|
24700
|
+
const root = resolve16(options.projectRoot);
|
|
24701
|
+
const certificatePath = resolve16(root, options.certificatePath);
|
|
24702
|
+
const privateKeyPath = resolve16(root, options.privateKeyPath);
|
|
24703
|
+
const publicKeyPath = resolve16(root, options.publicKeyPath ?? resolve16(dirname16(privateKeyPath), "public-key.pem"));
|
|
24400
24704
|
if (!inside4(root, certificatePath))
|
|
24401
24705
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
24402
24706
|
if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
|
|
@@ -24426,7 +24730,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24426
24730
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
24427
24731
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
24428
24732
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
24429
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
24733
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir18(dirname16(path), { recursive: true })));
|
|
24430
24734
|
await Promise.all([
|
|
24431
24735
|
writeFile18(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
24432
24736
|
writeFile18(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
@@ -24441,8 +24745,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24441
24745
|
};
|
|
24442
24746
|
|
|
24443
24747
|
// src/mobile/updatePublisher.ts
|
|
24444
|
-
import { access as
|
|
24445
|
-
import { isAbsolute as
|
|
24748
|
+
import { access as access17 } from "fs/promises";
|
|
24749
|
+
import { isAbsolute as isAbsolute11, relative as relative16, resolve as resolve17, sep as sep12 } from "path";
|
|
24446
24750
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
24447
24751
|
var lifecycleMethod = (publisher, name) => {
|
|
24448
24752
|
const method = publisher[name];
|
|
@@ -24551,16 +24855,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
24551
24855
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24552
24856
|
var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
24553
24857
|
var projectPath4 = (projectRoot, requested, label) => {
|
|
24554
|
-
const root =
|
|
24555
|
-
const path =
|
|
24556
|
-
const projectRelative =
|
|
24557
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
24858
|
+
const root = resolve17(projectRoot);
|
|
24859
|
+
const path = resolve17(root, requested);
|
|
24860
|
+
const projectRelative = relative16(root, path);
|
|
24861
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep12}`) || isAbsolute11(projectRelative))
|
|
24558
24862
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
24559
24863
|
return path;
|
|
24560
24864
|
};
|
|
24561
24865
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
24562
24866
|
const modulePath = projectPath4(projectRoot, requestedModulePath, "mobile update registry");
|
|
24563
|
-
await
|
|
24867
|
+
await access17(modulePath).catch(() => {
|
|
24564
24868
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
24565
24869
|
});
|
|
24566
24870
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -24615,10 +24919,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
|
|
|
24615
24919
|
var NOT_FOUND4 = -1;
|
|
24616
24920
|
var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
|
|
24617
24921
|
var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
|
|
24618
|
-
var
|
|
24922
|
+
var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24619
24923
|
var expoPublicConfig = (config) => {
|
|
24620
24924
|
const result = { ...config };
|
|
24621
|
-
if (!
|
|
24925
|
+
if (!isRecord11(result.updates))
|
|
24622
24926
|
return result;
|
|
24623
24927
|
const {
|
|
24624
24928
|
codeSigningCertificate: _codeSigningCertificate,
|
|
@@ -24658,13 +24962,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
24658
24962
|
];
|
|
24659
24963
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
24660
24964
|
var directProjectPackages = async (projectRoot) => {
|
|
24661
|
-
const manifest = JSON.parse(await
|
|
24662
|
-
if (!
|
|
24965
|
+
const manifest = JSON.parse(await readFile23(join19(projectRoot, "package.json"), "utf8"));
|
|
24966
|
+
if (!isRecord11(manifest))
|
|
24663
24967
|
throw new TypeError("Application package.json must contain an object.");
|
|
24664
24968
|
const names = new Set;
|
|
24665
24969
|
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
24666
24970
|
const dependencies = Reflect.get(manifest, field2);
|
|
24667
|
-
if (
|
|
24971
|
+
if (isRecord11(dependencies))
|
|
24668
24972
|
for (const name of Object.keys(dependencies))
|
|
24669
24973
|
names.add(name);
|
|
24670
24974
|
}
|
|
@@ -24672,8 +24976,8 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
24672
24976
|
};
|
|
24673
24977
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
24674
24978
|
try {
|
|
24675
|
-
const manifest = JSON.parse(await
|
|
24676
|
-
return
|
|
24979
|
+
const manifest = JSON.parse(await readFile23(join19(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
24980
|
+
return isRecord11(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
24677
24981
|
} catch {
|
|
24678
24982
|
return;
|
|
24679
24983
|
}
|
|
@@ -24736,9 +25040,9 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
24736
25040
|
};
|
|
24737
25041
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
24738
25042
|
var expoExecutable = async (project) => {
|
|
24739
|
-
const executable =
|
|
25043
|
+
const executable = join19(project, "node_modules", ".bin", "expo");
|
|
24740
25044
|
try {
|
|
24741
|
-
await
|
|
25045
|
+
await access18(executable);
|
|
24742
25046
|
return executable;
|
|
24743
25047
|
} catch {
|
|
24744
25048
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -24770,14 +25074,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
24770
25074
|
};
|
|
24771
25075
|
var ensureExpoPackages = async (project, args) => {
|
|
24772
25076
|
try {
|
|
24773
|
-
const manifest = JSON.parse(await
|
|
25077
|
+
const manifest = JSON.parse(await readFile23(join19(project, "package.json"), "utf8"));
|
|
24774
25078
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
24775
25079
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
24776
25080
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
24777
25081
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
24778
25082
|
if (typeof expected !== "string")
|
|
24779
25083
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
24780
|
-
const installed = JSON.parse(await
|
|
25084
|
+
const installed = JSON.parse(await readFile23(join19(project, "node_modules", name, "package.json"), "utf8"));
|
|
24781
25085
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
24782
25086
|
if (typeof actual !== "string")
|
|
24783
25087
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -24975,7 +25279,7 @@ var sync = async (args) => {
|
|
|
24975
25279
|
};
|
|
24976
25280
|
var associations = async (args) => {
|
|
24977
25281
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
24978
|
-
const outputDirectory =
|
|
25282
|
+
const outputDirectory = resolve18(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
24979
25283
|
if (args.includes("--verify")) {
|
|
24980
25284
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
24981
25285
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -25027,7 +25331,7 @@ var generateGithubCi = async (args) => {
|
|
|
25027
25331
|
const publicResult = {
|
|
25028
25332
|
changed: result.changed,
|
|
25029
25333
|
format: result.format,
|
|
25030
|
-
path:
|
|
25334
|
+
path: relative17(projectRoot, result.path).replaceAll("\\", "/"),
|
|
25031
25335
|
platforms: result.platforms,
|
|
25032
25336
|
publishing: result.publishing,
|
|
25033
25337
|
requiredSecrets: result.requiredSecrets
|
|
@@ -25058,15 +25362,159 @@ var createGithubCertificationVerification = async (args) => {
|
|
|
25058
25362
|
GITHUB_WORKFLOW_REF: process.env.GITHUB_WORKFLOW_REF
|
|
25059
25363
|
});
|
|
25060
25364
|
const path = await writeAbsoluteMobileCertificationVerification(projectRoot, outputPath, verification);
|
|
25061
|
-
console.log(
|
|
25365
|
+
console.log(relative17(projectRoot, path).replaceAll("\\", "/"));
|
|
25062
25366
|
return verification;
|
|
25063
25367
|
};
|
|
25368
|
+
var auditDispatchedGithubMobilePromotion = async (options) => {
|
|
25369
|
+
const startedAt = performance.now();
|
|
25370
|
+
try {
|
|
25371
|
+
const audited = await auditAbsoluteMobileCiPromotion({
|
|
25372
|
+
outputDirectory: options.outputDirectory,
|
|
25373
|
+
projectRoot: options.projectRoot,
|
|
25374
|
+
...options.repository ? { repository: options.repository } : {},
|
|
25375
|
+
runId: options.runId
|
|
25376
|
+
});
|
|
25377
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25378
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25379
|
+
platform: options.platform,
|
|
25380
|
+
provider: "github-actions",
|
|
25381
|
+
success: true
|
|
25382
|
+
});
|
|
25383
|
+
return audited;
|
|
25384
|
+
} catch (error) {
|
|
25385
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25386
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25387
|
+
platform: options.platform,
|
|
25388
|
+
provider: "github-actions",
|
|
25389
|
+
success: false
|
|
25390
|
+
});
|
|
25391
|
+
throw error;
|
|
25392
|
+
}
|
|
25393
|
+
};
|
|
25394
|
+
var promotionPhaseRank = (phase) => ["dispatching", "dispatched", "discovered", "completed", "audited"].indexOf(phase);
|
|
25395
|
+
var promotionRecoveryCommand = (dispatchId3) => `absolute mobile ci promote --resume ${dispatchId3}`;
|
|
25396
|
+
var promotionRecoveryError = (error, dispatchId3) => {
|
|
25397
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25398
|
+
return new TypeError(`${message}
|
|
25399
|
+
Promotion ${dispatchId3} is recorded. Resume without redispatching: ${promotionRecoveryCommand(dispatchId3)}
|
|
25400
|
+
If immutable audit output already exists, add --outdir with a new project-local directory.`, { cause: error });
|
|
25401
|
+
};
|
|
25402
|
+
var watchAndPersistGithubMobilePromotion = async (options) => {
|
|
25403
|
+
let status;
|
|
25404
|
+
try {
|
|
25405
|
+
status = await watchAbsoluteMobileCiRun({
|
|
25406
|
+
projectRoot: options.projectRoot,
|
|
25407
|
+
...options.operation.repository ? { repository: options.operation.repository } : {},
|
|
25408
|
+
runId: options.runId
|
|
25409
|
+
});
|
|
25410
|
+
} catch (error) {
|
|
25411
|
+
const inspected = await inspectAbsoluteMobileCiRun({
|
|
25412
|
+
projectRoot: options.projectRoot,
|
|
25413
|
+
...options.operation.repository ? { repository: options.operation.repository } : {},
|
|
25414
|
+
runId: options.runId
|
|
25415
|
+
}).catch(() => null);
|
|
25416
|
+
if (inspected?.status === "completed")
|
|
25417
|
+
await advanceAbsoluteMobilePromotionOperation(options.projectRoot, options.operation.dispatchId, {
|
|
25418
|
+
conclusion: inspected.conclusion,
|
|
25419
|
+
githubStatus: inspected.status,
|
|
25420
|
+
phase: "completed"
|
|
25421
|
+
});
|
|
25422
|
+
throw error;
|
|
25423
|
+
}
|
|
25424
|
+
const operation = await advanceAbsoluteMobilePromotionOperation(options.projectRoot, options.operation.dispatchId, {
|
|
25425
|
+
conclusion: status.conclusion,
|
|
25426
|
+
githubStatus: status.status,
|
|
25427
|
+
phase: "completed"
|
|
25428
|
+
});
|
|
25429
|
+
return { operation, status };
|
|
25430
|
+
};
|
|
25431
|
+
var completeGithubMobilePromotion = async (options) => {
|
|
25432
|
+
let { operation } = options;
|
|
25433
|
+
if (!operation.runId || !operation.url) {
|
|
25434
|
+
const promotion = await discoverAbsoluteMobilePromotionRun({
|
|
25435
|
+
dispatchId: operation.dispatchId,
|
|
25436
|
+
projectRoot: options.projectRoot,
|
|
25437
|
+
...operation.repository ? { repository: operation.repository } : {},
|
|
25438
|
+
workflow: operation.workflow
|
|
25439
|
+
});
|
|
25440
|
+
operation = await advanceAbsoluteMobilePromotionOperation(options.projectRoot, operation.dispatchId, {
|
|
25441
|
+
phase: "discovered",
|
|
25442
|
+
runId: promotion.runId,
|
|
25443
|
+
url: promotion.url
|
|
25444
|
+
});
|
|
25445
|
+
}
|
|
25446
|
+
if (!operation.runId || !operation.url)
|
|
25447
|
+
throw new TypeError("Promotion run discovery did not produce an identity.");
|
|
25448
|
+
const promotionRunId = operation.runId;
|
|
25449
|
+
const promotionUrl = operation.url;
|
|
25450
|
+
if (!options.json) {
|
|
25451
|
+
console.log(`Promotion ${operation.dispatchId} is GitHub run ${promotionRunId}.`);
|
|
25452
|
+
console.log(promotionUrl);
|
|
25453
|
+
}
|
|
25454
|
+
let status = null;
|
|
25455
|
+
if (options.watch && promotionPhaseRank(operation.phase) < promotionPhaseRank("completed")) {
|
|
25456
|
+
if (!options.json)
|
|
25457
|
+
console.log(`Watching GitHub run ${promotionRunId} until completion\u2026`);
|
|
25458
|
+
({ operation, status } = await watchAndPersistGithubMobilePromotion({
|
|
25459
|
+
operation,
|
|
25460
|
+
projectRoot: options.projectRoot,
|
|
25461
|
+
runId: promotionRunId
|
|
25462
|
+
}));
|
|
25463
|
+
} else if (options.watch) {
|
|
25464
|
+
status = await inspectAbsoluteMobileCiRun({
|
|
25465
|
+
projectRoot: options.projectRoot,
|
|
25466
|
+
...operation.repository ? { repository: operation.repository } : {},
|
|
25467
|
+
runId: promotionRunId
|
|
25468
|
+
});
|
|
25469
|
+
}
|
|
25470
|
+
if (options.audit && operation.conclusion !== "success")
|
|
25471
|
+
throw new TypeError("Promotion audit requires a successfully completed workflow run.");
|
|
25472
|
+
let audited = null;
|
|
25473
|
+
if (options.audit && operation.phase !== "audited") {
|
|
25474
|
+
audited = await auditDispatchedGithubMobilePromotion({
|
|
25475
|
+
outputDirectory: options.auditOutputDirectory ?? operation.auditOutputDirectory ?? undefined,
|
|
25476
|
+
platform: operation.platform,
|
|
25477
|
+
projectRoot: options.projectRoot,
|
|
25478
|
+
...operation.repository ? { repository: operation.repository } : {},
|
|
25479
|
+
runId: promotionRunId
|
|
25480
|
+
});
|
|
25481
|
+
operation = await advanceAbsoluteMobilePromotionOperation(options.projectRoot, operation.dispatchId, {
|
|
25482
|
+
auditDirectory: relative17(options.projectRoot, audited.directory).replaceAll("\\", "/"),
|
|
25483
|
+
phase: "audited"
|
|
25484
|
+
});
|
|
25485
|
+
}
|
|
25486
|
+
const result = {
|
|
25487
|
+
auditDirectory: operation.auditDirectory,
|
|
25488
|
+
dispatchId: operation.dispatchId,
|
|
25489
|
+
platform: operation.platform,
|
|
25490
|
+
releaseId: operation.releaseId,
|
|
25491
|
+
runId: promotionRunId,
|
|
25492
|
+
sourceRunId: operation.sourceRunId,
|
|
25493
|
+
status: operation.githubStatus ?? operation.phase,
|
|
25494
|
+
url: promotionUrl
|
|
25495
|
+
};
|
|
25496
|
+
if (options.json)
|
|
25497
|
+
console.log(JSON.stringify(result, null, 2));
|
|
25498
|
+
else {
|
|
25499
|
+
if (status)
|
|
25500
|
+
renderGithubMobileRunStatus(status);
|
|
25501
|
+
if (audited)
|
|
25502
|
+
renderGithubMobilePromotionAudit(audited, options.projectRoot);
|
|
25503
|
+
else if (operation.auditDirectory)
|
|
25504
|
+
console.log(`Audit: ${operation.auditDirectory}`);
|
|
25505
|
+
console.log(`Promotion record: ${relativeAbsoluteMobilePromotionOperationPath(options.projectRoot, operation.dispatchId)}`);
|
|
25506
|
+
}
|
|
25507
|
+
return result;
|
|
25508
|
+
};
|
|
25064
25509
|
var dispatchGithubMobilePromotion = async (args) => {
|
|
25510
|
+
const resumeDispatchId = valueAfter(args, "--resume");
|
|
25511
|
+
if (resumeDispatchId)
|
|
25512
|
+
return resumeGithubMobilePromotion(args, resumeDispatchId);
|
|
25065
25513
|
const [platform2] = args;
|
|
25066
25514
|
if (platform2 !== "android" && platform2 !== "ios")
|
|
25067
|
-
throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options]");
|
|
25068
|
-
const
|
|
25069
|
-
if (!
|
|
25515
|
+
throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options] [--watch] [--audit]");
|
|
25516
|
+
const sourceRunId = valueAfter(args, "--run-id");
|
|
25517
|
+
if (!sourceRunId || !/^[1-9][0-9]*$/u.test(sourceRunId))
|
|
25070
25518
|
throw new TypeError("mobile ci promote requires a numeric --run-id.");
|
|
25071
25519
|
const requestedCertification = valueAfter(args, "--certification");
|
|
25072
25520
|
if (!requestedCertification || requestedCertification.startsWith("-"))
|
|
@@ -25085,6 +25533,31 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
25085
25533
|
if (certificationBase64.length > 48000)
|
|
25086
25534
|
throw new TypeError("Mobile certification is too large for protected GitHub dispatch.");
|
|
25087
25535
|
const workflow = valueAfter(args, "--workflow") ?? ".github/workflows/absolute-mobile.yml";
|
|
25536
|
+
const dispatchId3 = `amp_${randomUUID2().replaceAll("-", "")}`;
|
|
25537
|
+
const repository = valueAfter(args, "--repo");
|
|
25538
|
+
const ref = valueAfter(args, "--ref");
|
|
25539
|
+
const auditRequested = args.includes("--audit");
|
|
25540
|
+
const watchRequested = args.includes("--watch") || auditRequested;
|
|
25541
|
+
const requestedAuditOutputDirectory = valueAfter(args, "--outdir");
|
|
25542
|
+
const auditOutputDirectory = requestedAuditOutputDirectory ? relative17(projectRoot, resolve18(projectRoot, requestedAuditOutputDirectory)).replaceAll("\\", "/") : null;
|
|
25543
|
+
await createAbsoluteMobilePromotionOperation(projectRoot, {
|
|
25544
|
+
auditOutputDirectory,
|
|
25545
|
+
auditRequested,
|
|
25546
|
+
certificationId: certification.certificationId,
|
|
25547
|
+
certificationSha256: createHash7("sha256").update(serializeAbsoluteMobileReleaseCertification(certification)).digest("hex"),
|
|
25548
|
+
channel: channel || null,
|
|
25549
|
+
dispatchId: dispatchId3,
|
|
25550
|
+
platform: platform2,
|
|
25551
|
+
playTrack: platform2 === "android" ? playTrack : null,
|
|
25552
|
+
ref: ref ?? null,
|
|
25553
|
+
releaseId: certification.release.releaseId,
|
|
25554
|
+
repository: repository ?? null,
|
|
25555
|
+
sourceRunId,
|
|
25556
|
+
testflightGroup: platform2 === "ios" ? testflightGroup || null : null,
|
|
25557
|
+
testflightSubmitReview: platform2 === "ios" && args.includes("--testflight-submit-review"),
|
|
25558
|
+
watchRequested,
|
|
25559
|
+
workflow
|
|
25560
|
+
});
|
|
25088
25561
|
const command = [
|
|
25089
25562
|
"workflow",
|
|
25090
25563
|
"run",
|
|
@@ -25094,7 +25567,9 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
25094
25567
|
"-f",
|
|
25095
25568
|
`platform=${platform2}`,
|
|
25096
25569
|
"-f",
|
|
25097
|
-
`source_run_id=${
|
|
25570
|
+
`source_run_id=${sourceRunId}`,
|
|
25571
|
+
"-f",
|
|
25572
|
+
`dispatch_id=${dispatchId3}`,
|
|
25098
25573
|
"-f",
|
|
25099
25574
|
`certification_base64=${certificationBase64}`,
|
|
25100
25575
|
"-f",
|
|
@@ -25106,24 +25581,98 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
25106
25581
|
command.push("-f", `testflight_group=${testflightGroup}`);
|
|
25107
25582
|
command.push("-f", `submit_testflight_review=${args.includes("--testflight-submit-review")}`);
|
|
25108
25583
|
}
|
|
25109
|
-
const ref = valueAfter(args, "--ref");
|
|
25110
25584
|
if (ref)
|
|
25111
25585
|
command.push("--ref", ref);
|
|
25112
|
-
|
|
25113
|
-
|
|
25114
|
-
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
|
|
25124
|
-
|
|
25586
|
+
if (repository)
|
|
25587
|
+
command.push("--repo", repository);
|
|
25588
|
+
try {
|
|
25589
|
+
const child = Bun.spawn(["gh", ...command], {
|
|
25590
|
+
cwd: projectRoot,
|
|
25591
|
+
stderr: "pipe",
|
|
25592
|
+
stdin: "inherit",
|
|
25593
|
+
stdout: "pipe"
|
|
25594
|
+
});
|
|
25595
|
+
const [exitCode, stderr] = await Promise.all([
|
|
25596
|
+
child.exited,
|
|
25597
|
+
new Response(child.stderr).text(),
|
|
25598
|
+
new Response(child.stdout).text()
|
|
25599
|
+
]);
|
|
25600
|
+
if (exitCode !== 0)
|
|
25601
|
+
throw new TypeError(`GitHub promotion dispatch exited with status ${exitCode}: ${stderr.trim() || "unknown GitHub CLI error"}`);
|
|
25602
|
+
const operation = await advanceAbsoluteMobilePromotionOperation(projectRoot, dispatchId3, { phase: "dispatched" });
|
|
25603
|
+
sendTelemetryEvent("mobile:ci-promotion-dispatched", {
|
|
25604
|
+
discovery: "correlated",
|
|
25605
|
+
platform: platform2,
|
|
25606
|
+
provider: "github-actions",
|
|
25607
|
+
storeTarget: platform2 === "android" ? playTrack !== "registry-only" : testflightGroup !== ""
|
|
25608
|
+
});
|
|
25609
|
+
return await completeGithubMobilePromotion({
|
|
25610
|
+
audit: operation.auditRequested,
|
|
25611
|
+
json: args.includes("--json"),
|
|
25612
|
+
operation,
|
|
25613
|
+
projectRoot,
|
|
25614
|
+
watch: operation.watchRequested
|
|
25615
|
+
});
|
|
25616
|
+
} catch (error) {
|
|
25617
|
+
throw promotionRecoveryError(error, dispatchId3);
|
|
25618
|
+
}
|
|
25619
|
+
};
|
|
25620
|
+
var resumeGithubMobilePromotion = async (args, dispatchId3) => {
|
|
25621
|
+
const projectRoot = process.cwd();
|
|
25622
|
+
const startedAt = performance.now();
|
|
25623
|
+
let resumedPhase = "unread";
|
|
25624
|
+
try {
|
|
25625
|
+
const operation = await readAbsoluteMobilePromotionOperation(projectRoot, dispatchId3);
|
|
25626
|
+
resumedPhase = operation.phase;
|
|
25627
|
+
const audit = args.includes("--audit") || operation.auditRequested;
|
|
25628
|
+
const watch = args.includes("--watch") || audit || operation.watchRequested;
|
|
25629
|
+
if (!args.includes("--json"))
|
|
25630
|
+
console.log(`Resuming ${operation.platform} promotion ${operation.dispatchId} from ${operation.phase}.`);
|
|
25631
|
+
const result = await completeGithubMobilePromotion({
|
|
25632
|
+
audit,
|
|
25633
|
+
auditOutputDirectory: valueAfter(args, "--outdir"),
|
|
25634
|
+
json: args.includes("--json"),
|
|
25635
|
+
operation,
|
|
25636
|
+
projectRoot,
|
|
25637
|
+
watch
|
|
25638
|
+
});
|
|
25639
|
+
sendTelemetryEvent("mobile:ci-promotion-resumed", {
|
|
25640
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25641
|
+
phase: resumedPhase,
|
|
25642
|
+
provider: "github-actions",
|
|
25643
|
+
success: true
|
|
25644
|
+
});
|
|
25645
|
+
return result;
|
|
25646
|
+
} catch (error) {
|
|
25647
|
+
sendTelemetryEvent("mobile:ci-promotion-resumed", {
|
|
25648
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25649
|
+
phase: resumedPhase,
|
|
25650
|
+
provider: "github-actions",
|
|
25651
|
+
success: false
|
|
25652
|
+
});
|
|
25653
|
+
throw promotionRecoveryError(error, dispatchId3);
|
|
25654
|
+
}
|
|
25655
|
+
};
|
|
25656
|
+
var listGithubMobilePromotions = async (args) => {
|
|
25657
|
+
const projectRoot = process.cwd();
|
|
25658
|
+
const operations = await listAbsoluteMobilePromotionOperations(projectRoot);
|
|
25659
|
+
if (args.includes("--json")) {
|
|
25660
|
+
console.log(JSON.stringify(operations, null, 2));
|
|
25661
|
+
return operations;
|
|
25662
|
+
}
|
|
25663
|
+
if (operations.length === 0) {
|
|
25664
|
+
console.log("No local mobile promotion operations were found.");
|
|
25665
|
+
return operations;
|
|
25666
|
+
}
|
|
25667
|
+
operations.forEach((operation) => {
|
|
25668
|
+
console.log(`${operation.dispatchId} ${operation.platform} ${operation.phase} ${operation.releaseId}`);
|
|
25669
|
+
console.log(` source ${operation.sourceRunId}${operation.runId ? ` \u2192 promotion ${operation.runId}` : ""}`);
|
|
25670
|
+
if (operation.url)
|
|
25671
|
+
console.log(` ${operation.url}`);
|
|
25672
|
+
if (operation.phase !== "audited")
|
|
25673
|
+
console.log(` Resume: ${promotionRecoveryCommand(operation.dispatchId)}`);
|
|
25125
25674
|
});
|
|
25126
|
-
|
|
25675
|
+
return operations;
|
|
25127
25676
|
};
|
|
25128
25677
|
var githubMobileRunId = (args) => {
|
|
25129
25678
|
const value = valueAfter(args, "--run-id");
|
|
@@ -25167,7 +25716,7 @@ var inspectGithubMobileRun = async (args) => {
|
|
|
25167
25716
|
};
|
|
25168
25717
|
var renderGithubMobilePromotionAudit = (result, projectRoot) => {
|
|
25169
25718
|
console.log(`\u2713 Verified ${result.audit.platform} promotion ${result.audit.github.promotionRunId} for ${result.audit.release.releaseId}.`);
|
|
25170
|
-
console.log(`Audit: ${
|
|
25719
|
+
console.log(`Audit: ${relative17(projectRoot, result.directory)}`);
|
|
25171
25720
|
};
|
|
25172
25721
|
var auditGithubMobilePromotion = async (args) => {
|
|
25173
25722
|
const projectRoot = process.cwd();
|
|
@@ -25299,9 +25848,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
25299
25848
|
temporaryDirectory: undefined
|
|
25300
25849
|
};
|
|
25301
25850
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
25302
|
-
const temporaryParent =
|
|
25303
|
-
await
|
|
25304
|
-
const temporaryDirectory = await mkdtemp7(
|
|
25851
|
+
const temporaryParent = join19(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
25852
|
+
await mkdir19(temporaryParent, { recursive: true });
|
|
25853
|
+
const temporaryDirectory = await mkdtemp7(join19(temporaryParent, ".stage-"));
|
|
25305
25854
|
try {
|
|
25306
25855
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
25307
25856
|
"export",
|
|
@@ -25313,9 +25862,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
25313
25862
|
temporaryDirectory,
|
|
25314
25863
|
"--clear"
|
|
25315
25864
|
], { production: true });
|
|
25316
|
-
const generatedApp = JSON.parse(await
|
|
25317
|
-
const expoConfig =
|
|
25318
|
-
if (!
|
|
25865
|
+
const generatedApp = JSON.parse(await readFile23(join19(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
25866
|
+
const expoConfig = isRecord11(generatedApp) ? generatedApp.expo : undefined;
|
|
25867
|
+
if (!isRecord11(expoConfig))
|
|
25319
25868
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
25320
25869
|
const publicExpoConfig = expoPublicConfig(expoConfig);
|
|
25321
25870
|
const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
|
|
@@ -25331,7 +25880,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
25331
25880
|
temporaryDirectory
|
|
25332
25881
|
};
|
|
25333
25882
|
} catch (error) {
|
|
25334
|
-
await
|
|
25883
|
+
await rm11(temporaryDirectory, { force: true, recursive: true });
|
|
25335
25884
|
throw error;
|
|
25336
25885
|
}
|
|
25337
25886
|
};
|
|
@@ -25356,11 +25905,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
25356
25905
|
let expoExportDirectory;
|
|
25357
25906
|
try {
|
|
25358
25907
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
25359
|
-
const embedded = JSON.parse(await
|
|
25360
|
-
const runtimeFingerprint =
|
|
25908
|
+
const embedded = JSON.parse(await readFile23(join19(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
25909
|
+
const runtimeFingerprint = isRecord11(embedded) ? embedded.nativeRuntime : undefined;
|
|
25361
25910
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
25362
25911
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
25363
|
-
const privateKey = await
|
|
25912
|
+
const privateKey = await readFile23(resolve18(projectRoot, signingKeyPath));
|
|
25364
25913
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
25365
25914
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
25366
25915
|
format: "der",
|
|
@@ -25381,7 +25930,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
25381
25930
|
channel: mobile.updates.channel,
|
|
25382
25931
|
classification,
|
|
25383
25932
|
keyId,
|
|
25384
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
25933
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join19(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
25385
25934
|
privateKey,
|
|
25386
25935
|
runtimeFingerprint
|
|
25387
25936
|
});
|
|
@@ -25393,7 +25942,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
25393
25942
|
return result;
|
|
25394
25943
|
} finally {
|
|
25395
25944
|
if (expoExportDirectory)
|
|
25396
|
-
await
|
|
25945
|
+
await rm11(expoExportDirectory, { force: true, recursive: true });
|
|
25397
25946
|
sendTelemetryEvent("mobile:update-build", {
|
|
25398
25947
|
classification: classification ?? "invalid",
|
|
25399
25948
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -25423,7 +25972,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
25423
25972
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
25424
25973
|
...validityYears === undefined ? {} : { validityYears }
|
|
25425
25974
|
});
|
|
25426
|
-
const certificatePath =
|
|
25975
|
+
const certificatePath = relative17(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
25427
25976
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
25428
25977
|
engine: "expo",
|
|
25429
25978
|
validityYears: validityYears ?? 10
|
|
@@ -25485,7 +26034,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
25485
26034
|
publicKeys: mobile.updates.publicKeys,
|
|
25486
26035
|
storage: requestedStorage
|
|
25487
26036
|
});
|
|
25488
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
26037
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative17(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
25489
26038
|
if (requestedStorage === "local")
|
|
25490
26039
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
25491
26040
|
else
|
|
@@ -25962,7 +26511,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
25962
26511
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
25963
26512
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
25964
26513
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
25965
|
-
console.log(`Metadata: ${
|
|
26514
|
+
console.log(`Metadata: ${join19(release.releaseRoot, "release.json")}`);
|
|
25966
26515
|
return release;
|
|
25967
26516
|
} finally {
|
|
25968
26517
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -26185,7 +26734,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
26185
26734
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
26186
26735
|
if (release.registeredArtifactPath)
|
|
26187
26736
|
console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
|
|
26188
|
-
console.log(`Metadata: ${
|
|
26737
|
+
console.log(`Metadata: ${join19(release.releaseRoot, "release.json")}`);
|
|
26189
26738
|
return release;
|
|
26190
26739
|
} finally {
|
|
26191
26740
|
stopListeningForCancellation();
|
|
@@ -26336,7 +26885,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
26336
26885
|
checks.push({
|
|
26337
26886
|
id: "sync.storage-schema",
|
|
26338
26887
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
26339
|
-
path:
|
|
26888
|
+
path: join19(projectRoot, "package.json"),
|
|
26340
26889
|
platform: "host",
|
|
26341
26890
|
status: "pass"
|
|
26342
26891
|
});
|
|
@@ -26344,7 +26893,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
26344
26893
|
checks.push({
|
|
26345
26894
|
id: "sync.storage-schema",
|
|
26346
26895
|
label: "Offline schema metadata is invalid",
|
|
26347
|
-
path:
|
|
26896
|
+
path: join19(projectRoot, "package.json"),
|
|
26348
26897
|
platform: "host",
|
|
26349
26898
|
remediation: error instanceof Error ? error.message : String(error),
|
|
26350
26899
|
status: "fail"
|
|
@@ -26432,7 +26981,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
26432
26981
|
}
|
|
26433
26982
|
return { https: args.includes("--https"), port };
|
|
26434
26983
|
}
|
|
26435
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
26984
|
+
const instances = listLiveInstances().filter((instance2) => resolve18(instance2.cwd) === resolve18(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
26436
26985
|
if (instances.length !== 1) {
|
|
26437
26986
|
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>.");
|
|
26438
26987
|
}
|
|
@@ -26526,8 +27075,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
26526
27075
|
return waitForBoot();
|
|
26527
27076
|
};
|
|
26528
27077
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
26529
|
-
const root =
|
|
26530
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
27078
|
+
const root = resolve18(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
27079
|
+
if (root !== projectRoot && !root.startsWith(`${resolve18(projectRoot)}/`)) {
|
|
26531
27080
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
26532
27081
|
}
|
|
26533
27082
|
return root;
|
|
@@ -26555,11 +27104,11 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
26555
27104
|
});
|
|
26556
27105
|
};
|
|
26557
27106
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
26558
|
-
await
|
|
26559
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
27107
|
+
await mkdir19(options.artifactRoot, { recursive: true });
|
|
27108
|
+
const screenshot = options.session ? await options.session.screenshot(join19(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
26560
27109
|
return;
|
|
26561
27110
|
}) : undefined;
|
|
26562
|
-
const diagnosticPath =
|
|
27111
|
+
const diagnosticPath = join19(options.artifactRoot, "android-failure.json");
|
|
26563
27112
|
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26564
27113
|
diagnostics: options.session?.diagnostics ?? [],
|
|
26565
27114
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -26635,7 +27184,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26635
27184
|
release,
|
|
26636
27185
|
serial
|
|
26637
27186
|
});
|
|
26638
|
-
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial,
|
|
27187
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join19(reportRoot, "android-release.png")) : undefined;
|
|
26639
27188
|
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
26640
27189
|
durationMs: result.durationMs,
|
|
26641
27190
|
engine: result.engine,
|
|
@@ -26682,8 +27231,8 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26682
27231
|
platform: "android",
|
|
26683
27232
|
success: false
|
|
26684
27233
|
});
|
|
26685
|
-
await
|
|
26686
|
-
const diagnosticPath =
|
|
27234
|
+
await mkdir19(artifactRoot, { recursive: true });
|
|
27235
|
+
const diagnosticPath = join19(artifactRoot, "android-release-failure.json");
|
|
26687
27236
|
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26688
27237
|
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
26689
27238
|
platform: "android",
|
|
@@ -26778,7 +27327,7 @@ var testAndroid = async (args) => {
|
|
|
26778
27327
|
console.log(JSON.stringify(report, null, 2));
|
|
26779
27328
|
else
|
|
26780
27329
|
printAndroidTestReport(report);
|
|
26781
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
27330
|
+
const screenshot = reportRoot ? await session.screenshot(join19(artifactRoot, "android-emulator.png")) : undefined;
|
|
26782
27331
|
await writeRequestedAndroidReport({
|
|
26783
27332
|
adb: adb2,
|
|
26784
27333
|
args,
|
|
@@ -26847,14 +27396,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
26847
27396
|
const port = Number(explicit);
|
|
26848
27397
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
26849
27398
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
26850
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
27399
|
+
const instance2 = listLiveInstances().find((candidate) => resolve18(candidate.cwd) === resolve18(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
26851
27400
|
return {
|
|
26852
27401
|
https: instance2?.https ?? args.includes("--https"),
|
|
26853
27402
|
instance: instance2,
|
|
26854
27403
|
port
|
|
26855
27404
|
};
|
|
26856
27405
|
}
|
|
26857
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
27406
|
+
const instances = listLiveInstances().filter((instance2) => resolve18(instance2.cwd) === resolve18(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
26858
27407
|
if (instances.length !== 1)
|
|
26859
27408
|
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>.");
|
|
26860
27409
|
const [instance] = instances;
|
|
@@ -26872,7 +27421,7 @@ var waitForIosHmrClient = async (options) => {
|
|
|
26872
27421
|
return;
|
|
26873
27422
|
});
|
|
26874
27423
|
const status = response?.ok ? await response.json().catch(() => null) : null;
|
|
26875
|
-
const targets =
|
|
27424
|
+
const targets = isRecord11(status) && isRecord11(status.connectedTargets) ? status.connectedTargets : undefined;
|
|
26876
27425
|
if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
|
|
26877
27426
|
return;
|
|
26878
27427
|
await Bun.sleep(100);
|
|
@@ -26998,8 +27547,8 @@ var requireCapturedCommand = (command, label) => {
|
|
|
26998
27547
|
return result;
|
|
26999
27548
|
};
|
|
27000
27549
|
var writeIosFailureArtifacts = async (options) => {
|
|
27001
|
-
await
|
|
27002
|
-
const screenshot =
|
|
27550
|
+
await mkdir19(options.artifactRoot, { recursive: true });
|
|
27551
|
+
const screenshot = join19(options.artifactRoot, "ios-failure.png");
|
|
27003
27552
|
const screenshotResult = captureCommand4([
|
|
27004
27553
|
options.xcrun,
|
|
27005
27554
|
"simctl",
|
|
@@ -27008,7 +27557,7 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
27008
27557
|
"screenshot",
|
|
27009
27558
|
screenshot
|
|
27010
27559
|
]);
|
|
27011
|
-
const diagnosticPath =
|
|
27560
|
+
const diagnosticPath = join19(options.artifactRoot, "ios-failure.json");
|
|
27012
27561
|
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
27013
27562
|
appId: options.appId,
|
|
27014
27563
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -27030,14 +27579,14 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
27030
27579
|
return;
|
|
27031
27580
|
const candidate = args[index + 1];
|
|
27032
27581
|
const explicit = candidate?.startsWith("--") ? undefined : candidate;
|
|
27033
|
-
const
|
|
27034
|
-
return safeArtifactRoot(projectRoot, explicit ?? `.absolutejs/mobile/test-reports/${platform2}-${
|
|
27582
|
+
const timestamp2 = new Date().toISOString().replaceAll(":", "-");
|
|
27583
|
+
return safeArtifactRoot(projectRoot, explicit ?? `.absolutejs/mobile/test-reports/${platform2}-${timestamp2}`);
|
|
27035
27584
|
};
|
|
27036
27585
|
var absolutejsVersionForReport = async () => {
|
|
27037
27586
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
27038
27587
|
const versions = await Promise.all([
|
|
27039
|
-
|
|
27040
|
-
|
|
27588
|
+
resolve18(import.meta.dir, "..", "..", "package.json"),
|
|
27589
|
+
resolve18(import.meta.dir, "..", "..", "..", "package.json")
|
|
27041
27590
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
27042
27591
|
for (const version of versions) {
|
|
27043
27592
|
if (version === "unknown")
|
|
@@ -27414,8 +27963,8 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
27414
27963
|
remote: remoteProfile !== undefined,
|
|
27415
27964
|
success: false
|
|
27416
27965
|
});
|
|
27417
|
-
await
|
|
27418
|
-
const diagnosticPath =
|
|
27966
|
+
await mkdir19(artifactRoot, { recursive: true });
|
|
27967
|
+
const diagnosticPath = join19(artifactRoot, "ios-release-failure.json");
|
|
27419
27968
|
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
27420
27969
|
error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
|
|
27421
27970
|
platform: "ios",
|
|
@@ -27509,8 +28058,8 @@ var testIos = async (args) => {
|
|
|
27509
28058
|
mobile.appId
|
|
27510
28059
|
], "iOS app launch");
|
|
27511
28060
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
27512
|
-
await
|
|
27513
|
-
const screenshot =
|
|
28061
|
+
await mkdir19(artifactRoot, { recursive: true });
|
|
28062
|
+
const screenshot = join19(artifactRoot, "ios-simulator.png");
|
|
27514
28063
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
27515
28064
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
27516
28065
|
const report = {
|
|
@@ -27727,6 +28276,8 @@ var runMobile = async (args) => {
|
|
|
27727
28276
|
if (command === "ci") {
|
|
27728
28277
|
if (args[1] === "promote")
|
|
27729
28278
|
await dispatchGithubMobilePromotion(args.slice(2));
|
|
28279
|
+
else if (args[1] === "promotions")
|
|
28280
|
+
await listGithubMobilePromotions(args.slice(2));
|
|
27730
28281
|
else if (args[1] === "status")
|
|
27731
28282
|
await inspectGithubMobileRun(args.slice(2));
|
|
27732
28283
|
else if (args[1] === "audit")
|
|
@@ -27813,7 +28364,7 @@ var runMobile = async (args) => {
|
|
|
27813
28364
|
await publishIos(args.slice(2));
|
|
27814
28365
|
return;
|
|
27815
28366
|
}
|
|
27816
|
-
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] | 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]");
|
|
28367
|
+
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 promote --resume dispatch-id [--watch] [--audit] [--outdir path] [--json] | ci promotions [--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]");
|
|
27817
28368
|
};
|
|
27818
28369
|
export {
|
|
27819
28370
|
runMobile
|