@absolutejs/absolute 0.20.0-beta.110 → 0.20.0-beta.111
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 +7 -0
- package/changelog.json +27 -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-k7gpwfs8.js} +615 -124
- package/dist/mobile/index.js +1467 -1048
- package/dist/mobile/index.js.map +8 -7
- package/dist/src/mobile/ciPromotionAudit.d.ts +103 -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
16789
|
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16790
|
-
import { join as
|
|
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 = 3;
|
|
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([
|
|
@@ -23524,6 +23524,10 @@ var promotionSharedSteps = (platform) => ` - name: Restore exact ${platform
|
|
|
23524
23524
|
path: .absolutejs/mobile/releases/${platform}
|
|
23525
23525
|
run-id: \${{ inputs.source_run_id }}
|
|
23526
23526
|
github-token: \${{ github.token }}
|
|
23527
|
+
- name: Record bounded promotion context
|
|
23528
|
+
shell: bash
|
|
23529
|
+
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: 1, platform: ${JSON.stringify(platform)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform}`)}, sourceRunId }, null, 2) + "\\n");'
|
|
23527
23531
|
- name: Import and verify exact certification
|
|
23528
23532
|
shell: bash
|
|
23529
23533
|
run: |
|
|
@@ -23562,6 +23566,7 @@ var androidPromotionJob = (options) => {
|
|
|
23562
23566
|
env:
|
|
23563
23567
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23564
23568
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23569
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23565
23570
|
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
23566
23571
|
ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
|
|
23567
23572
|
GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
|
|
@@ -23601,6 +23606,7 @@ ${promotionSharedSteps("android")}
|
|
|
23601
23606
|
name: absolute-mobile-android-promotion-\${{ github.run_id }}
|
|
23602
23607
|
path: |
|
|
23603
23608
|
.absolutejs/mobile-ci/imported-certification/
|
|
23609
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
23604
23610
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
23605
23611
|
if-no-files-found: error
|
|
23606
23612
|
retention-days: 90
|
|
@@ -23625,6 +23631,7 @@ var iosPromotionJob = (options) => {
|
|
|
23625
23631
|
env:
|
|
23626
23632
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
23627
23633
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
23634
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
23628
23635
|
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
23629
23636
|
ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
|
|
23630
23637
|
APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
@@ -23666,6 +23673,7 @@ ${promotionSharedSteps("ios")}
|
|
|
23666
23673
|
name: absolute-mobile-ios-promotion-\${{ github.run_id }}
|
|
23667
23674
|
path: |
|
|
23668
23675
|
.absolutejs/mobile-ci/imported-certification/
|
|
23676
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
23669
23677
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
23670
23678
|
if-no-files-found: error
|
|
23671
23679
|
retention-days: 90
|
|
@@ -23785,47 +23793,448 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
|
23785
23793
|
};
|
|
23786
23794
|
};
|
|
23787
23795
|
|
|
23788
|
-
// src/mobile/
|
|
23789
|
-
import { createHash as createHash6, sign, verify } from "crypto";
|
|
23796
|
+
// src/mobile/ciPromotionAudit.ts
|
|
23790
23797
|
import {
|
|
23791
|
-
|
|
23798
|
+
access as access13,
|
|
23792
23799
|
mkdir as mkdir14,
|
|
23793
23800
|
mkdtemp as mkdtemp5,
|
|
23794
|
-
readdir as readdir6,
|
|
23795
23801
|
readFile as readFile19,
|
|
23802
|
+
readdir as readdir6,
|
|
23796
23803
|
rename as rename11,
|
|
23797
23804
|
rm as rm8,
|
|
23798
23805
|
stat as stat7,
|
|
23799
23806
|
writeFile as writeFile15
|
|
23800
23807
|
} from "fs/promises";
|
|
23801
|
-
import { dirname as dirname12, join as join15, relative as relative12, resolve as resolve13 } from "path";
|
|
23808
|
+
import { dirname as dirname12, isAbsolute as isAbsolute8, join as join15, relative as relative12, resolve as resolve13, sep as sep9 } from "path";
|
|
23809
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
|
|
23810
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 1;
|
|
23811
|
+
var MAX_GITHUB_OUTPUT_BYTES = 1048576;
|
|
23812
|
+
var MAX_AUDIT_JSON_BYTES = 1048576;
|
|
23813
|
+
var MAX_SEARCH_FILES = 20000;
|
|
23814
|
+
var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
|
|
23815
|
+
var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
23816
|
+
var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23817
|
+
var requiredString = (value, name) => {
|
|
23818
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
23819
|
+
throw new TypeError(`${name} is invalid.`);
|
|
23820
|
+
return value.trim();
|
|
23821
|
+
};
|
|
23822
|
+
var optionalString = (value, name) => {
|
|
23823
|
+
if (value === null || value === undefined)
|
|
23824
|
+
return null;
|
|
23825
|
+
return requiredString(value, name);
|
|
23826
|
+
};
|
|
23827
|
+
var runId = (value, name) => {
|
|
23828
|
+
if (!RUN_ID_PATTERN.test(value))
|
|
23829
|
+
throw new TypeError(`${name} is invalid.`);
|
|
23830
|
+
return value;
|
|
23831
|
+
};
|
|
23832
|
+
var repositoryArgs = (repository) => {
|
|
23833
|
+
if (repository === undefined)
|
|
23834
|
+
return [];
|
|
23835
|
+
if (!REPOSITORY_PATTERN2.test(repository))
|
|
23836
|
+
throw new TypeError("GitHub repository must be owner/name.");
|
|
23837
|
+
return ["--repo", repository];
|
|
23838
|
+
};
|
|
23839
|
+
var projectPath3 = (projectRoot, requested) => {
|
|
23840
|
+
const root = resolve13(projectRoot);
|
|
23841
|
+
const path = resolve13(root, requested);
|
|
23842
|
+
const portable = relative12(root, path);
|
|
23843
|
+
if (portable === ".." || portable.startsWith(`..${sep9}`) || isAbsolute8(portable))
|
|
23844
|
+
throw new TypeError("Mobile CI audit output must remain inside the project.");
|
|
23845
|
+
return path;
|
|
23846
|
+
};
|
|
23847
|
+
var defaultRunner = async (command, options) => {
|
|
23848
|
+
const child = Bun.spawn(command, {
|
|
23849
|
+
cwd: options.cwd,
|
|
23850
|
+
env: options.env,
|
|
23851
|
+
stderr: "pipe",
|
|
23852
|
+
stdin: "ignore",
|
|
23853
|
+
stdout: "pipe"
|
|
23854
|
+
});
|
|
23855
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
23856
|
+
child.exited,
|
|
23857
|
+
new Response(child.stderr).text(),
|
|
23858
|
+
new Response(child.stdout).text()
|
|
23859
|
+
]);
|
|
23860
|
+
return { exitCode, stderr, stdout };
|
|
23861
|
+
};
|
|
23862
|
+
var requireCommand2 = async (run, command, options, label) => {
|
|
23863
|
+
const result = await run(command, options);
|
|
23864
|
+
if (result.exitCode !== 0)
|
|
23865
|
+
throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
|
|
23866
|
+
if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
|
|
23867
|
+
throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
|
|
23868
|
+
return result;
|
|
23869
|
+
};
|
|
23870
|
+
var parseJob = (value) => {
|
|
23871
|
+
if (!isRecord9(value))
|
|
23872
|
+
throw new TypeError("GitHub run job is invalid.");
|
|
23873
|
+
return {
|
|
23874
|
+
conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
|
|
23875
|
+
name: requiredString(value.name, "GitHub job name"),
|
|
23876
|
+
status: requiredString(value.status, "GitHub job status")
|
|
23877
|
+
};
|
|
23878
|
+
};
|
|
23879
|
+
var inspectAbsoluteMobileCiRun = async (options) => {
|
|
23880
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
23881
|
+
const result = await requireCommand2(options.run ?? defaultRunner, [
|
|
23882
|
+
"gh",
|
|
23883
|
+
"run",
|
|
23884
|
+
"view",
|
|
23885
|
+
requestedRunId,
|
|
23886
|
+
...repositoryArgs(options.repository),
|
|
23887
|
+
"--json",
|
|
23888
|
+
"conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
23889
|
+
], { cwd: options.projectRoot }, "GitHub workflow inspection");
|
|
23890
|
+
return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
|
|
23891
|
+
};
|
|
23892
|
+
var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
23893
|
+
if (!isRecord9(value) || !Array.isArray(value.jobs))
|
|
23894
|
+
throw new TypeError("GitHub workflow run response is invalid.");
|
|
23895
|
+
const { databaseId } = value;
|
|
23896
|
+
if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
|
|
23897
|
+
throw new TypeError("GitHub workflow run identity does not match.");
|
|
23898
|
+
return {
|
|
23899
|
+
conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
|
|
23900
|
+
createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
|
|
23901
|
+
headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
|
|
23902
|
+
headSha: requiredString(value.headSha, "GitHub run headSha"),
|
|
23903
|
+
jobs: value.jobs.map(parseJob),
|
|
23904
|
+
runId: requestedRunId,
|
|
23905
|
+
startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
|
|
23906
|
+
status: requiredString(value.status, "GitHub run status"),
|
|
23907
|
+
updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
|
|
23908
|
+
url: requiredString(value.url, "GitHub run URL"),
|
|
23909
|
+
workflowName: requiredString(value.workflowName, "GitHub workflow name")
|
|
23910
|
+
};
|
|
23911
|
+
};
|
|
23912
|
+
var parseAbsoluteMobilePromotionContext = (value) => {
|
|
23913
|
+
if (!isRecord9(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
23914
|
+
throw new TypeError("Mobile promotion context is invalid.");
|
|
23915
|
+
const { platform } = value;
|
|
23916
|
+
const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
|
|
23917
|
+
if (sourceArtifact !== `absolute-mobile-${platform}`)
|
|
23918
|
+
throw new TypeError("Mobile promotion source artifact is invalid.");
|
|
23919
|
+
return {
|
|
23920
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
|
|
23921
|
+
platform,
|
|
23922
|
+
promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
|
|
23923
|
+
sourceArtifact,
|
|
23924
|
+
sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
|
|
23925
|
+
};
|
|
23926
|
+
};
|
|
23927
|
+
var watchAbsoluteMobileCiRun = async (options) => {
|
|
23928
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
23929
|
+
await requireCommand2(options.run ?? defaultRunner, [
|
|
23930
|
+
"gh",
|
|
23931
|
+
"run",
|
|
23932
|
+
"watch",
|
|
23933
|
+
requestedRunId,
|
|
23934
|
+
...repositoryArgs(options.repository),
|
|
23935
|
+
"--exit-status"
|
|
23936
|
+
], { cwd: options.projectRoot }, "GitHub workflow watch");
|
|
23937
|
+
return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
|
|
23938
|
+
};
|
|
23939
|
+
var readBoundedJson = async (path, label) => {
|
|
23940
|
+
const metadata = await stat7(path).catch(() => null);
|
|
23941
|
+
if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
|
|
23942
|
+
throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
|
|
23943
|
+
const value = JSON.parse(await readFile19(path, "utf8"));
|
|
23944
|
+
return value;
|
|
23945
|
+
};
|
|
23946
|
+
var findFiles = async (root, name) => {
|
|
23947
|
+
const found = [];
|
|
23948
|
+
let visited = 0;
|
|
23949
|
+
const walk = async (directory) => {
|
|
23950
|
+
const entries = await readdir6(directory, { withFileTypes: true });
|
|
23951
|
+
await Promise.all(entries.map(async (entry) => {
|
|
23952
|
+
visited += 1;
|
|
23953
|
+
if (visited > MAX_SEARCH_FILES)
|
|
23954
|
+
throw new TypeError("Downloaded GitHub artifact contains too many files.");
|
|
23955
|
+
const path = join15(directory, entry.name);
|
|
23956
|
+
if (entry.isDirectory()) {
|
|
23957
|
+
await walk(path);
|
|
23958
|
+
return;
|
|
23959
|
+
}
|
|
23960
|
+
if (entry.isFile() && entry.name === name)
|
|
23961
|
+
found.push(path);
|
|
23962
|
+
}));
|
|
23963
|
+
};
|
|
23964
|
+
await walk(root);
|
|
23965
|
+
return found;
|
|
23966
|
+
};
|
|
23967
|
+
var inferPromotionPlatform = (status) => {
|
|
23968
|
+
const matched = status.jobs.flatMap((job) => {
|
|
23969
|
+
if (job.name === "Promote exact certified Android release")
|
|
23970
|
+
return ["android"];
|
|
23971
|
+
if (job.name === "Promote exact certified iOS release")
|
|
23972
|
+
return ["ios"];
|
|
23973
|
+
return [];
|
|
23974
|
+
});
|
|
23975
|
+
if (matched.length !== 1)
|
|
23976
|
+
throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
|
|
23977
|
+
const [platform] = matched;
|
|
23978
|
+
if (!platform)
|
|
23979
|
+
throw new TypeError("Mobile promotion platform is missing.");
|
|
23980
|
+
return platform;
|
|
23981
|
+
};
|
|
23982
|
+
var onlyPath = (paths, label) => {
|
|
23983
|
+
const [path] = paths;
|
|
23984
|
+
if (paths.length !== 1 || !path)
|
|
23985
|
+
throw new TypeError(`${label} must contain exactly one matching file.`);
|
|
23986
|
+
return path;
|
|
23987
|
+
};
|
|
23988
|
+
var canonicalJson = (value) => {
|
|
23989
|
+
if (Array.isArray(value))
|
|
23990
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
23991
|
+
if (isRecord9(value))
|
|
23992
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
23993
|
+
return JSON.stringify(value);
|
|
23994
|
+
};
|
|
23995
|
+
var publicationSummary = (value, platform, release, certification) => {
|
|
23996
|
+
if (!isRecord9(value) || !isRecord9(value.record) || !isRecord9(value.record.metadata))
|
|
23997
|
+
throw new TypeError("Mobile promotion receipt is invalid.");
|
|
23998
|
+
const { metadata } = value.record;
|
|
23999
|
+
if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
|
|
24000
|
+
throw new TypeError("Mobile promotion receipt release identity does not match.");
|
|
24001
|
+
if (!isRecord9(value.certification) || !isRecord9(value.certification.provenance))
|
|
24002
|
+
throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
|
|
24003
|
+
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)
|
|
24004
|
+
throw new TypeError("Mobile promotion certification receipt does not match.");
|
|
24005
|
+
requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
|
|
24006
|
+
requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
|
|
24007
|
+
const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
|
|
24008
|
+
if (Number.isNaN(Date.parse(provenanceTime)))
|
|
24009
|
+
throw new TypeError("Mobile promotion provenance time is invalid.");
|
|
24010
|
+
if (typeof value.reused !== "boolean")
|
|
24011
|
+
throw new TypeError("Mobile promotion reuse status is invalid.");
|
|
24012
|
+
const channel = isRecord9(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
|
|
24013
|
+
if (isRecord9(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
|
|
24014
|
+
throw new TypeError("Mobile promotion channel receipt does not match.");
|
|
24015
|
+
const providerReceipt = platform === "android" ? value.googlePlay : value.appStoreConnect;
|
|
24016
|
+
if (providerReceipt === undefined)
|
|
24017
|
+
return {
|
|
24018
|
+
channel,
|
|
24019
|
+
provider: "registry",
|
|
24020
|
+
reused: value.reused,
|
|
24021
|
+
stage: null
|
|
24022
|
+
};
|
|
24023
|
+
if (!isRecord9(providerReceipt) || !isRecord9(providerReceipt.receipt))
|
|
24024
|
+
throw new TypeError("Mobile store promotion receipt is invalid.");
|
|
24025
|
+
if (typeof providerReceipt.reused !== "boolean")
|
|
24026
|
+
throw new TypeError("Mobile store promotion reuse status is invalid.");
|
|
24027
|
+
const expectedProvider = platform === "android" ? "google-play" : "app-store-connect";
|
|
24028
|
+
if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
|
|
24029
|
+
throw new TypeError("Mobile store promotion receipt does not match.");
|
|
24030
|
+
return {
|
|
24031
|
+
channel,
|
|
24032
|
+
provider: expectedProvider,
|
|
24033
|
+
reused: providerReceipt.reused,
|
|
24034
|
+
stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
|
|
24035
|
+
};
|
|
24036
|
+
};
|
|
24037
|
+
var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
|
|
24038
|
+
|
|
24039
|
+
- Status: **VERIFIED**
|
|
24040
|
+
- Platform: ${audit.platform}
|
|
24041
|
+
- Release: \`${audit.release.releaseId}\`
|
|
24042
|
+
- Artifact SHA-256: \`${audit.release.sha256}\`
|
|
24043
|
+
- Source workflow run: ${audit.github.sourceRunId}
|
|
24044
|
+
- Promotion workflow run: ${audit.github.promotionRunId}
|
|
24045
|
+
- Workflow: ${audit.github.workflowName}
|
|
24046
|
+
- GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
|
|
24047
|
+
- Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
|
|
24048
|
+
- Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
|
|
24049
|
+
- Verified at: ${audit.verifiedAt}
|
|
24050
|
+
|
|
24051
|
+
This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
|
|
24052
|
+
`;
|
|
24053
|
+
var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
24054
|
+
const runner = options.run ?? defaultRunner;
|
|
24055
|
+
const promotionRunId = runId(options.runId, "GitHub promotion run ID");
|
|
24056
|
+
const status = await inspectAbsoluteMobileCiRun({
|
|
24057
|
+
projectRoot: options.projectRoot,
|
|
24058
|
+
...options.repository ? { repository: options.repository } : {},
|
|
24059
|
+
run: runner,
|
|
24060
|
+
runId: promotionRunId
|
|
24061
|
+
});
|
|
24062
|
+
if (status.status !== "completed" || status.conclusion !== "success")
|
|
24063
|
+
throw new TypeError("Mobile promotion run has not completed successfully.");
|
|
24064
|
+
const platform = inferPromotionPlatform(status);
|
|
24065
|
+
const output = projectPath3(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
|
|
24066
|
+
if (await access13(output).then(() => true).catch(() => false))
|
|
24067
|
+
throw new TypeError(`Mobile CI audit output already exists: ${output}`);
|
|
24068
|
+
await mkdir14(dirname12(output), { recursive: true });
|
|
24069
|
+
const staging = await mkdtemp5(join15(dirname12(output), ".promotion-audit-"));
|
|
24070
|
+
try {
|
|
24071
|
+
const evidenceRoot = join15(staging, "promotion-evidence");
|
|
24072
|
+
await mkdir14(evidenceRoot);
|
|
24073
|
+
await requireCommand2(runner, [
|
|
24074
|
+
"gh",
|
|
24075
|
+
"run",
|
|
24076
|
+
"download",
|
|
24077
|
+
promotionRunId,
|
|
24078
|
+
...repositoryArgs(options.repository),
|
|
24079
|
+
"--name",
|
|
24080
|
+
`absolute-mobile-${platform}-promotion-${promotionRunId}`,
|
|
24081
|
+
"--dir",
|
|
24082
|
+
evidenceRoot
|
|
24083
|
+
], { cwd: options.projectRoot }, "GitHub promotion artifact download");
|
|
24084
|
+
const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
|
|
24085
|
+
let context;
|
|
24086
|
+
if (contextPaths.length === 1)
|
|
24087
|
+
context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
|
|
24088
|
+
else if (contextPaths.length > 1)
|
|
24089
|
+
throw new TypeError("Promotion artifact contains duplicate contexts.");
|
|
24090
|
+
if (context && context.promotionRunId !== promotionRunId)
|
|
24091
|
+
throw new TypeError("Promotion context run identity does not match.");
|
|
24092
|
+
if (context && context.platform !== platform)
|
|
24093
|
+
throw new TypeError("Promotion context platform does not match.");
|
|
24094
|
+
const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
|
|
24095
|
+
if (!sourceRunId)
|
|
24096
|
+
throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
|
|
24097
|
+
if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
|
|
24098
|
+
throw new TypeError("Requested source run does not match promotion context.");
|
|
24099
|
+
const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform}`;
|
|
24100
|
+
const sourceRoot = join15(staging, "source-release");
|
|
24101
|
+
await mkdir14(sourceRoot);
|
|
24102
|
+
await requireCommand2(runner, [
|
|
24103
|
+
"gh",
|
|
24104
|
+
"run",
|
|
24105
|
+
"download",
|
|
24106
|
+
sourceRunId,
|
|
24107
|
+
...repositoryArgs(options.repository),
|
|
24108
|
+
"--name",
|
|
24109
|
+
sourceArtifact,
|
|
24110
|
+
"--dir",
|
|
24111
|
+
sourceRoot
|
|
24112
|
+
], { cwd: options.projectRoot }, "GitHub source release download");
|
|
24113
|
+
const releaseMetadata = await findFiles(sourceRoot, "release.json");
|
|
24114
|
+
if (releaseMetadata.length !== 1)
|
|
24115
|
+
throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
|
|
24116
|
+
const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
|
|
24117
|
+
const release = platform === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
|
|
24118
|
+
const certificationPaths = await findFiles(evidenceRoot, "certification.json");
|
|
24119
|
+
const verificationPaths = await findFiles(evidenceRoot, "verification.json");
|
|
24120
|
+
const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
|
|
24121
|
+
if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
|
|
24122
|
+
throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
|
|
24123
|
+
const certificationPath = onlyPath(certificationPaths, "Promotion certification");
|
|
24124
|
+
const verificationPath = onlyPath(verificationPaths, "Promotion verification");
|
|
24125
|
+
const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
|
|
24126
|
+
const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
|
|
24127
|
+
const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
|
|
24128
|
+
const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
|
|
24129
|
+
if (verification.identity.sha !== status.headSha)
|
|
24130
|
+
throw new TypeError("Sigstore workflow commit does not match the promotion run.");
|
|
24131
|
+
if (options.repository && verification.identity.repository !== options.repository)
|
|
24132
|
+
throw new TypeError("Sigstore repository does not match the requested repository.");
|
|
24133
|
+
const bundlePath = `${certificationPath}.sigstore.json`;
|
|
24134
|
+
const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
|
|
24135
|
+
if (canonicalJson(bundle) !== canonicalJson(verification.bundle))
|
|
24136
|
+
throw new TypeError("Sigstore bundle does not match verification envelope.");
|
|
24137
|
+
await requireCommand2(runner, [
|
|
24138
|
+
"bunx",
|
|
24139
|
+
"@absolutejs/attest@0.2.0",
|
|
24140
|
+
"verify-blobs",
|
|
24141
|
+
certificationPath
|
|
24142
|
+
], {
|
|
24143
|
+
cwd: options.projectRoot,
|
|
24144
|
+
env: {
|
|
24145
|
+
...process.env,
|
|
24146
|
+
GITHUB_REF: verification.identity.ref,
|
|
24147
|
+
GITHUB_REPOSITORY: verification.identity.repository,
|
|
24148
|
+
GITHUB_SHA: verification.identity.sha,
|
|
24149
|
+
GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
|
|
24150
|
+
}
|
|
24151
|
+
}, "Sigstore certification verification");
|
|
24152
|
+
const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform, release, certification);
|
|
24153
|
+
const audit = {
|
|
24154
|
+
certification: {
|
|
24155
|
+
certificationId: certification.certificationId,
|
|
24156
|
+
requirement: certification.requirement,
|
|
24157
|
+
strength: certification.strength
|
|
24158
|
+
},
|
|
24159
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
24160
|
+
github: {
|
|
24161
|
+
conclusion: status.conclusion,
|
|
24162
|
+
promotionRunId,
|
|
24163
|
+
repository: verification.identity.repository,
|
|
24164
|
+
sourceRunId,
|
|
24165
|
+
url: status.url,
|
|
24166
|
+
workflowName: status.workflowName
|
|
24167
|
+
},
|
|
24168
|
+
platform,
|
|
24169
|
+
provenance: verification.identity,
|
|
24170
|
+
publication,
|
|
24171
|
+
release: {
|
|
24172
|
+
appId: release.metadata.appId,
|
|
24173
|
+
bytes: release.metadata.bytes,
|
|
24174
|
+
engine: release.metadata.engine,
|
|
24175
|
+
releaseId: release.metadata.releaseId,
|
|
24176
|
+
runtime: release.metadata.runtime,
|
|
24177
|
+
sha256: release.metadata.sha256
|
|
24178
|
+
},
|
|
24179
|
+
status: "verified",
|
|
24180
|
+
verifiedAt: new Date().toISOString()
|
|
24181
|
+
};
|
|
24182
|
+
await Promise.all([
|
|
24183
|
+
writeFile15(join15(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
|
|
24184
|
+
`, { mode: 384 }),
|
|
24185
|
+
writeFile15(join15(staging, "audit.md"), renderAuditMarkdown(audit), {
|
|
24186
|
+
mode: 384
|
|
24187
|
+
})
|
|
24188
|
+
]);
|
|
24189
|
+
await rename11(staging, output);
|
|
24190
|
+
return { audit, directory: output };
|
|
24191
|
+
} catch (error) {
|
|
24192
|
+
await rm8(staging, { force: true, recursive: true });
|
|
24193
|
+
throw error;
|
|
24194
|
+
}
|
|
24195
|
+
};
|
|
24196
|
+
|
|
24197
|
+
// src/mobile/updateSigning.ts
|
|
24198
|
+
import { createHash as createHash6, sign, verify } from "crypto";
|
|
24199
|
+
import {
|
|
24200
|
+
cp,
|
|
24201
|
+
mkdir as mkdir15,
|
|
24202
|
+
mkdtemp as mkdtemp6,
|
|
24203
|
+
readdir as readdir7,
|
|
24204
|
+
readFile as readFile20,
|
|
24205
|
+
rename as rename12,
|
|
24206
|
+
rm as rm9,
|
|
24207
|
+
stat as stat8,
|
|
24208
|
+
writeFile as writeFile16
|
|
24209
|
+
} from "fs/promises";
|
|
24210
|
+
import { dirname as dirname13, join as join16, relative as relative13, resolve as resolve14 } from "path";
|
|
23802
24211
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
23803
24212
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
23804
24213
|
var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
23805
24214
|
var listFiles = async (root, directory = root) => {
|
|
23806
|
-
const entries = await
|
|
24215
|
+
const entries = await readdir7(directory, { withFileTypes: true });
|
|
23807
24216
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
23808
|
-
const path =
|
|
24217
|
+
const path = join16(directory, entry.name);
|
|
23809
24218
|
if (entry.isDirectory())
|
|
23810
24219
|
return listFiles(root, path);
|
|
23811
24220
|
if (!entry.isFile())
|
|
23812
24221
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
23813
|
-
return [
|
|
24222
|
+
return [relative13(root, path).replaceAll("\\", "/")];
|
|
23814
24223
|
}));
|
|
23815
24224
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
23816
24225
|
};
|
|
23817
24226
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
23818
|
-
const source =
|
|
24227
|
+
const source = join16(root, path);
|
|
23819
24228
|
const [metadata, contents] = await Promise.all([
|
|
23820
|
-
|
|
23821
|
-
|
|
24229
|
+
stat8(source),
|
|
24230
|
+
readFile20(source)
|
|
23822
24231
|
]);
|
|
23823
24232
|
return { bytes: metadata.size, path, sha256: sha2564(contents) };
|
|
23824
24233
|
}));
|
|
23825
24234
|
var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
23826
24235
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
23827
|
-
const bundleDirectory =
|
|
23828
|
-
const outputRoot =
|
|
24236
|
+
const bundleDirectory = resolve14(options.bundleDirectory);
|
|
24237
|
+
const outputRoot = resolve14(options.outputDirectory);
|
|
23829
24238
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
23830
24239
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
23831
24240
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -23853,30 +24262,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
23853
24262
|
value: signature.toString("base64")
|
|
23854
24263
|
}
|
|
23855
24264
|
});
|
|
23856
|
-
await
|
|
23857
|
-
const outputDirectory =
|
|
23858
|
-
const staging = await
|
|
24265
|
+
await mkdir15(outputRoot, { recursive: true });
|
|
24266
|
+
const outputDirectory = join16(outputRoot, manifest.releaseId);
|
|
24267
|
+
const staging = await mkdtemp6(join16(outputRoot, ".stage-"));
|
|
23859
24268
|
try {
|
|
23860
|
-
await cp(bundleDirectory,
|
|
24269
|
+
await cp(bundleDirectory, join16(staging, UPDATE_FILES_DIRECTORY), {
|
|
23861
24270
|
force: true,
|
|
23862
24271
|
recursive: true
|
|
23863
24272
|
});
|
|
23864
|
-
await
|
|
24273
|
+
await writeFile16(join16(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
23865
24274
|
`);
|
|
23866
|
-
await
|
|
24275
|
+
await rename12(staging, outputDirectory);
|
|
23867
24276
|
} catch (error) {
|
|
23868
|
-
await
|
|
24277
|
+
await rm9(staging, { force: true, recursive: true });
|
|
23869
24278
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
23870
24279
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
23871
24280
|
throw error;
|
|
23872
24281
|
}
|
|
23873
24282
|
return {
|
|
23874
24283
|
manifest,
|
|
23875
|
-
manifestPath:
|
|
24284
|
+
manifestPath: join16(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
23876
24285
|
outputDirectory
|
|
23877
24286
|
};
|
|
23878
24287
|
};
|
|
23879
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
24288
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join16(resolve14(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
23880
24289
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
23881
24290
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
23882
24291
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -23886,8 +24295,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
23886
24295
|
};
|
|
23887
24296
|
|
|
23888
24297
|
// src/mobile/expoUpdate.ts
|
|
23889
|
-
import { access as
|
|
23890
|
-
import { dirname as
|
|
24298
|
+
import { access as access14, mkdir as mkdir16, readFile as readFile21, writeFile as writeFile17 } from "fs/promises";
|
|
24299
|
+
import { dirname as dirname14, extname as extname3, join as join17 } from "path";
|
|
23891
24300
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
23892
24301
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
23893
24302
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -23953,7 +24362,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
23953
24362
|
};
|
|
23954
24363
|
};
|
|
23955
24364
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
23956
|
-
const metadata = JSON.parse(await
|
|
24365
|
+
const metadata = JSON.parse(await readFile21(join17(options.exportDirectory, "metadata.json"), "utf8"));
|
|
23957
24366
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
23958
24367
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
23959
24368
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -23964,30 +24373,30 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
23964
24373
|
runtimeVersion: options.runtimeVersion
|
|
23965
24374
|
});
|
|
23966
24375
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
23967
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
24376
|
+
await Promise.all(referenced.map(({ path }) => access14(join17(options.exportDirectory, path)).catch(() => {
|
|
23968
24377
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
23969
24378
|
})));
|
|
23970
|
-
const destination =
|
|
23971
|
-
await
|
|
23972
|
-
await
|
|
24379
|
+
const destination = join17(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
24380
|
+
await mkdir16(dirname14(destination), { recursive: true });
|
|
24381
|
+
await writeFile17(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
23973
24382
|
`);
|
|
23974
24383
|
return { descriptor, path: destination };
|
|
23975
24384
|
};
|
|
23976
24385
|
|
|
23977
24386
|
// src/mobile/expoCodeSigning.ts
|
|
23978
24387
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
23979
|
-
import { access as
|
|
23980
|
-
import { dirname as
|
|
23981
|
-
var exists6 = async (path) =>
|
|
24388
|
+
import { access as access15, mkdir as mkdir17, writeFile as writeFile18 } from "fs/promises";
|
|
24389
|
+
import { dirname as dirname15, isAbsolute as isAbsolute9, relative as relative14, resolve as resolve15, sep as sep10 } from "path";
|
|
24390
|
+
var exists6 = async (path) => access15(path).then(() => true).catch(() => false);
|
|
23982
24391
|
var inside4 = (root, path) => {
|
|
23983
|
-
const location =
|
|
23984
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
24392
|
+
const location = relative14(root, path);
|
|
24393
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep10}`) && !isAbsolute9(location);
|
|
23985
24394
|
};
|
|
23986
24395
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
23987
|
-
const root =
|
|
23988
|
-
const certificatePath =
|
|
23989
|
-
const privateKeyPath =
|
|
23990
|
-
const publicKeyPath =
|
|
24396
|
+
const root = resolve15(options.projectRoot);
|
|
24397
|
+
const certificatePath = resolve15(root, options.certificatePath);
|
|
24398
|
+
const privateKeyPath = resolve15(root, options.privateKeyPath);
|
|
24399
|
+
const publicKeyPath = resolve15(root, options.publicKeyPath ?? resolve15(dirname15(privateKeyPath), "public-key.pem"));
|
|
23991
24400
|
if (!inside4(root, certificatePath))
|
|
23992
24401
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
23993
24402
|
if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
|
|
@@ -24017,11 +24426,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24017
24426
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
24018
24427
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
24019
24428
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
24020
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
24429
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir17(dirname15(path), { recursive: true })));
|
|
24021
24430
|
await Promise.all([
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24431
|
+
writeFile18(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
24432
|
+
writeFile18(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
24433
|
+
writeFile18(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
|
|
24025
24434
|
]);
|
|
24026
24435
|
return {
|
|
24027
24436
|
certificatePath,
|
|
@@ -24032,8 +24441,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
24032
24441
|
};
|
|
24033
24442
|
|
|
24034
24443
|
// src/mobile/updatePublisher.ts
|
|
24035
|
-
import { access as
|
|
24036
|
-
import { isAbsolute as
|
|
24444
|
+
import { access as access16 } from "fs/promises";
|
|
24445
|
+
import { isAbsolute as isAbsolute10, relative as relative15, resolve as resolve16, sep as sep11 } from "path";
|
|
24037
24446
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
24038
24447
|
var lifecycleMethod = (publisher, name) => {
|
|
24039
24448
|
const method = publisher[name];
|
|
@@ -24141,17 +24550,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
24141
24550
|
};
|
|
24142
24551
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24143
24552
|
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(`..${
|
|
24553
|
+
var projectPath4 = (projectRoot, requested, label) => {
|
|
24554
|
+
const root = resolve16(projectRoot);
|
|
24555
|
+
const path = resolve16(root, requested);
|
|
24556
|
+
const projectRelative = relative15(root, path);
|
|
24557
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep11}`) || isAbsolute10(projectRelative))
|
|
24149
24558
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
24150
24559
|
return path;
|
|
24151
24560
|
};
|
|
24152
24561
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
24153
|
-
const modulePath =
|
|
24154
|
-
await
|
|
24562
|
+
const modulePath = projectPath4(projectRoot, requestedModulePath, "mobile update registry");
|
|
24563
|
+
await access16(modulePath).catch(() => {
|
|
24155
24564
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
24156
24565
|
});
|
|
24157
24566
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -24173,7 +24582,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
|
|
|
24173
24582
|
return result;
|
|
24174
24583
|
};
|
|
24175
24584
|
var publishAbsoluteMobileUpdate = async (options) => {
|
|
24176
|
-
const releaseDirectory =
|
|
24585
|
+
const releaseDirectory = projectPath4(options.projectRoot, options.releaseDirectory, "mobile update release directory");
|
|
24177
24586
|
const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
|
|
24178
24587
|
const result = await options.publisher.publishUpdate({
|
|
24179
24588
|
manifest,
|
|
@@ -24206,10 +24615,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
|
|
|
24206
24615
|
var NOT_FOUND4 = -1;
|
|
24207
24616
|
var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
|
|
24208
24617
|
var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
|
|
24209
|
-
var
|
|
24618
|
+
var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24210
24619
|
var expoPublicConfig = (config) => {
|
|
24211
24620
|
const result = { ...config };
|
|
24212
|
-
if (!
|
|
24621
|
+
if (!isRecord10(result.updates))
|
|
24213
24622
|
return result;
|
|
24214
24623
|
const {
|
|
24215
24624
|
codeSigningCertificate: _codeSigningCertificate,
|
|
@@ -24249,13 +24658,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
24249
24658
|
];
|
|
24250
24659
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
24251
24660
|
var directProjectPackages = async (projectRoot) => {
|
|
24252
|
-
const manifest = JSON.parse(await
|
|
24253
|
-
if (!
|
|
24661
|
+
const manifest = JSON.parse(await readFile22(join18(projectRoot, "package.json"), "utf8"));
|
|
24662
|
+
if (!isRecord10(manifest))
|
|
24254
24663
|
throw new TypeError("Application package.json must contain an object.");
|
|
24255
24664
|
const names = new Set;
|
|
24256
24665
|
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
24257
24666
|
const dependencies = Reflect.get(manifest, field2);
|
|
24258
|
-
if (
|
|
24667
|
+
if (isRecord10(dependencies))
|
|
24259
24668
|
for (const name of Object.keys(dependencies))
|
|
24260
24669
|
names.add(name);
|
|
24261
24670
|
}
|
|
@@ -24263,8 +24672,8 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
24263
24672
|
};
|
|
24264
24673
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
24265
24674
|
try {
|
|
24266
|
-
const manifest = JSON.parse(await
|
|
24267
|
-
return
|
|
24675
|
+
const manifest = JSON.parse(await readFile22(join18(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
24676
|
+
return isRecord10(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
24268
24677
|
} catch {
|
|
24269
24678
|
return;
|
|
24270
24679
|
}
|
|
@@ -24327,9 +24736,9 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
24327
24736
|
};
|
|
24328
24737
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
24329
24738
|
var expoExecutable = async (project) => {
|
|
24330
|
-
const executable =
|
|
24739
|
+
const executable = join18(project, "node_modules", ".bin", "expo");
|
|
24331
24740
|
try {
|
|
24332
|
-
await
|
|
24741
|
+
await access17(executable);
|
|
24333
24742
|
return executable;
|
|
24334
24743
|
} catch {
|
|
24335
24744
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -24361,14 +24770,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
24361
24770
|
};
|
|
24362
24771
|
var ensureExpoPackages = async (project, args) => {
|
|
24363
24772
|
try {
|
|
24364
|
-
const manifest = JSON.parse(await
|
|
24773
|
+
const manifest = JSON.parse(await readFile22(join18(project, "package.json"), "utf8"));
|
|
24365
24774
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
24366
24775
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
24367
24776
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
24368
24777
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
24369
24778
|
if (typeof expected !== "string")
|
|
24370
24779
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
24371
|
-
const installed = JSON.parse(await
|
|
24780
|
+
const installed = JSON.parse(await readFile22(join18(project, "node_modules", name, "package.json"), "utf8"));
|
|
24372
24781
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
24373
24782
|
if (typeof actual !== "string")
|
|
24374
24783
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -24566,7 +24975,7 @@ var sync = async (args) => {
|
|
|
24566
24975
|
};
|
|
24567
24976
|
var associations = async (args) => {
|
|
24568
24977
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
24569
|
-
const outputDirectory =
|
|
24978
|
+
const outputDirectory = resolve17(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
24570
24979
|
if (args.includes("--verify")) {
|
|
24571
24980
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
24572
24981
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -24618,7 +25027,7 @@ var generateGithubCi = async (args) => {
|
|
|
24618
25027
|
const publicResult = {
|
|
24619
25028
|
changed: result.changed,
|
|
24620
25029
|
format: result.format,
|
|
24621
|
-
path:
|
|
25030
|
+
path: relative16(projectRoot, result.path).replaceAll("\\", "/"),
|
|
24622
25031
|
platforms: result.platforms,
|
|
24623
25032
|
publishing: result.publishing,
|
|
24624
25033
|
requiredSecrets: result.requiredSecrets
|
|
@@ -24649,15 +25058,15 @@ var createGithubCertificationVerification = async (args) => {
|
|
|
24649
25058
|
GITHUB_WORKFLOW_REF: process.env.GITHUB_WORKFLOW_REF
|
|
24650
25059
|
});
|
|
24651
25060
|
const path = await writeAbsoluteMobileCertificationVerification(projectRoot, outputPath, verification);
|
|
24652
|
-
console.log(
|
|
25061
|
+
console.log(relative16(projectRoot, path).replaceAll("\\", "/"));
|
|
24653
25062
|
return verification;
|
|
24654
25063
|
};
|
|
24655
25064
|
var dispatchGithubMobilePromotion = async (args) => {
|
|
24656
25065
|
const [platform2] = args;
|
|
24657
25066
|
if (platform2 !== "android" && platform2 !== "ios")
|
|
24658
25067
|
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 (!
|
|
25068
|
+
const runId2 = valueAfter(args, "--run-id");
|
|
25069
|
+
if (!runId2 || !/^[1-9][0-9]*$/u.test(runId2))
|
|
24661
25070
|
throw new TypeError("mobile ci promote requires a numeric --run-id.");
|
|
24662
25071
|
const requestedCertification = valueAfter(args, "--certification");
|
|
24663
25072
|
if (!requestedCertification || requestedCertification.startsWith("-"))
|
|
@@ -24685,7 +25094,7 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
24685
25094
|
"-f",
|
|
24686
25095
|
`platform=${platform2}`,
|
|
24687
25096
|
"-f",
|
|
24688
|
-
`source_run_id=${
|
|
25097
|
+
`source_run_id=${runId2}`,
|
|
24689
25098
|
"-f",
|
|
24690
25099
|
`certification_base64=${certificationBase64}`,
|
|
24691
25100
|
"-f",
|
|
@@ -24714,7 +25123,85 @@ var dispatchGithubMobilePromotion = async (args) => {
|
|
|
24714
25123
|
provider: "github-actions",
|
|
24715
25124
|
storeTarget: platform2 === "android" ? playTrack !== "registry-only" : testflightGroup !== ""
|
|
24716
25125
|
});
|
|
24717
|
-
console.log(`Dispatched protected ${platform2} promotion for ${certification.release.releaseId} from workflow run ${
|
|
25126
|
+
console.log(`Dispatched protected ${platform2} promotion for ${certification.release.releaseId} from workflow run ${runId2}.`);
|
|
25127
|
+
};
|
|
25128
|
+
var githubMobileRunId = (args) => {
|
|
25129
|
+
const value = valueAfter(args, "--run-id");
|
|
25130
|
+
if (!value || !/^[1-9][0-9]*$/u.test(value))
|
|
25131
|
+
throw new TypeError("mobile ci command requires a numeric --run-id.");
|
|
25132
|
+
return value;
|
|
25133
|
+
};
|
|
25134
|
+
var renderGithubMobileRunStatus = (status) => {
|
|
25135
|
+
console.log(`GitHub run ${status.runId}: ${status.status}${status.conclusion ? ` / ${status.conclusion}` : ""}`);
|
|
25136
|
+
console.log(`Workflow: ${status.workflowName}`);
|
|
25137
|
+
console.log(`Commit: ${status.headSha} (${status.headBranch})`);
|
|
25138
|
+
status.jobs.forEach((job) => console.log(` ${job.name}: ${job.status}${job.conclusion ? ` / ${job.conclusion}` : ""}`));
|
|
25139
|
+
console.log(status.url);
|
|
25140
|
+
};
|
|
25141
|
+
var inspectGithubMobileRun = async (args) => {
|
|
25142
|
+
const projectRoot = process.cwd();
|
|
25143
|
+
const requestedRunId = githubMobileRunId(args);
|
|
25144
|
+
const repository = valueAfter(args, "--repo");
|
|
25145
|
+
if (args.includes("--watch") && !args.includes("--json"))
|
|
25146
|
+
console.log(`Watching GitHub run ${requestedRunId} until completion\u2026`);
|
|
25147
|
+
const status = args.includes("--watch") ? await watchAbsoluteMobileCiRun({
|
|
25148
|
+
projectRoot,
|
|
25149
|
+
...repository ? { repository } : {},
|
|
25150
|
+
runId: requestedRunId
|
|
25151
|
+
}) : await inspectAbsoluteMobileCiRun({
|
|
25152
|
+
projectRoot,
|
|
25153
|
+
...repository ? { repository } : {},
|
|
25154
|
+
runId: requestedRunId
|
|
25155
|
+
});
|
|
25156
|
+
sendTelemetryEvent("mobile:ci-status", {
|
|
25157
|
+
conclusion: status.conclusion ?? "pending",
|
|
25158
|
+
provider: "github-actions",
|
|
25159
|
+
status: status.status,
|
|
25160
|
+
watched: args.includes("--watch")
|
|
25161
|
+
});
|
|
25162
|
+
if (args.includes("--json"))
|
|
25163
|
+
console.log(JSON.stringify(status, null, 2));
|
|
25164
|
+
else
|
|
25165
|
+
renderGithubMobileRunStatus(status);
|
|
25166
|
+
return status;
|
|
25167
|
+
};
|
|
25168
|
+
var renderGithubMobilePromotionAudit = (result, projectRoot) => {
|
|
25169
|
+
console.log(`\u2713 Verified ${result.audit.platform} promotion ${result.audit.github.promotionRunId} for ${result.audit.release.releaseId}.`);
|
|
25170
|
+
console.log(`Audit: ${relative16(projectRoot, result.directory)}`);
|
|
25171
|
+
};
|
|
25172
|
+
var auditGithubMobilePromotion = async (args) => {
|
|
25173
|
+
const projectRoot = process.cwd();
|
|
25174
|
+
const requestedRunId = githubMobileRunId(args);
|
|
25175
|
+
const repository = valueAfter(args, "--repo");
|
|
25176
|
+
const sourceRunId = valueAfter(args, "--source-run-id");
|
|
25177
|
+
const startedAt = performance.now();
|
|
25178
|
+
try {
|
|
25179
|
+
const result = await auditAbsoluteMobileCiPromotion({
|
|
25180
|
+
outputDirectory: valueAfter(args, "--outdir"),
|
|
25181
|
+
projectRoot,
|
|
25182
|
+
...repository ? { repository } : {},
|
|
25183
|
+
runId: requestedRunId,
|
|
25184
|
+
...sourceRunId ? { sourceRunId } : {}
|
|
25185
|
+
});
|
|
25186
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25187
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25188
|
+
platform: result.audit.platform,
|
|
25189
|
+
provider: "github-actions",
|
|
25190
|
+
success: true
|
|
25191
|
+
});
|
|
25192
|
+
if (args.includes("--json"))
|
|
25193
|
+
console.log(JSON.stringify(result.audit, null, 2));
|
|
25194
|
+
else
|
|
25195
|
+
renderGithubMobilePromotionAudit(result, projectRoot);
|
|
25196
|
+
return result;
|
|
25197
|
+
} catch (error) {
|
|
25198
|
+
sendTelemetryEvent("mobile:ci-promotion-audit", {
|
|
25199
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
25200
|
+
provider: "github-actions",
|
|
25201
|
+
success: false
|
|
25202
|
+
});
|
|
25203
|
+
throw error;
|
|
25204
|
+
}
|
|
24718
25205
|
};
|
|
24719
25206
|
var doctorMark = (status) => {
|
|
24720
25207
|
if (status === "pass")
|
|
@@ -24812,9 +25299,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24812
25299
|
temporaryDirectory: undefined
|
|
24813
25300
|
};
|
|
24814
25301
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
24815
|
-
const temporaryParent =
|
|
24816
|
-
await
|
|
24817
|
-
const temporaryDirectory = await
|
|
25302
|
+
const temporaryParent = join18(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
25303
|
+
await mkdir18(temporaryParent, { recursive: true });
|
|
25304
|
+
const temporaryDirectory = await mkdtemp7(join18(temporaryParent, ".stage-"));
|
|
24818
25305
|
try {
|
|
24819
25306
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
24820
25307
|
"export",
|
|
@@ -24826,9 +25313,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24826
25313
|
temporaryDirectory,
|
|
24827
25314
|
"--clear"
|
|
24828
25315
|
], { production: true });
|
|
24829
|
-
const generatedApp = JSON.parse(await
|
|
24830
|
-
const expoConfig =
|
|
24831
|
-
if (!
|
|
25316
|
+
const generatedApp = JSON.parse(await readFile22(join18(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
25317
|
+
const expoConfig = isRecord10(generatedApp) ? generatedApp.expo : undefined;
|
|
25318
|
+
if (!isRecord10(expoConfig))
|
|
24832
25319
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
24833
25320
|
const publicExpoConfig = expoPublicConfig(expoConfig);
|
|
24834
25321
|
const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
|
|
@@ -24844,7 +25331,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
24844
25331
|
temporaryDirectory
|
|
24845
25332
|
};
|
|
24846
25333
|
} catch (error) {
|
|
24847
|
-
await
|
|
25334
|
+
await rm10(temporaryDirectory, { force: true, recursive: true });
|
|
24848
25335
|
throw error;
|
|
24849
25336
|
}
|
|
24850
25337
|
};
|
|
@@ -24869,11 +25356,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
24869
25356
|
let expoExportDirectory;
|
|
24870
25357
|
try {
|
|
24871
25358
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
24872
|
-
const embedded = JSON.parse(await
|
|
24873
|
-
const runtimeFingerprint =
|
|
25359
|
+
const embedded = JSON.parse(await readFile22(join18(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
25360
|
+
const runtimeFingerprint = isRecord10(embedded) ? embedded.nativeRuntime : undefined;
|
|
24874
25361
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
24875
25362
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
24876
|
-
const privateKey = await
|
|
25363
|
+
const privateKey = await readFile22(resolve17(projectRoot, signingKeyPath));
|
|
24877
25364
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
24878
25365
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
24879
25366
|
format: "der",
|
|
@@ -24894,7 +25381,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
24894
25381
|
channel: mobile.updates.channel,
|
|
24895
25382
|
classification,
|
|
24896
25383
|
keyId,
|
|
24897
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
25384
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join18(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
24898
25385
|
privateKey,
|
|
24899
25386
|
runtimeFingerprint
|
|
24900
25387
|
});
|
|
@@ -24906,7 +25393,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
24906
25393
|
return result;
|
|
24907
25394
|
} finally {
|
|
24908
25395
|
if (expoExportDirectory)
|
|
24909
|
-
await
|
|
25396
|
+
await rm10(expoExportDirectory, { force: true, recursive: true });
|
|
24910
25397
|
sendTelemetryEvent("mobile:update-build", {
|
|
24911
25398
|
classification: classification ?? "invalid",
|
|
24912
25399
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -24936,7 +25423,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
24936
25423
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
24937
25424
|
...validityYears === undefined ? {} : { validityYears }
|
|
24938
25425
|
});
|
|
24939
|
-
const certificatePath =
|
|
25426
|
+
const certificatePath = relative16(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
24940
25427
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
24941
25428
|
engine: "expo",
|
|
24942
25429
|
validityYears: validityYears ?? 10
|
|
@@ -24998,7 +25485,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
24998
25485
|
publicKeys: mobile.updates.publicKeys,
|
|
24999
25486
|
storage: requestedStorage
|
|
25000
25487
|
});
|
|
25001
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
25488
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative16(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
25002
25489
|
if (requestedStorage === "local")
|
|
25003
25490
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
25004
25491
|
else
|
|
@@ -25475,7 +25962,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
25475
25962
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
25476
25963
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
25477
25964
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
25478
|
-
console.log(`Metadata: ${
|
|
25965
|
+
console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
|
|
25479
25966
|
return release;
|
|
25480
25967
|
} finally {
|
|
25481
25968
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -25698,7 +26185,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
25698
26185
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
25699
26186
|
if (release.registeredArtifactPath)
|
|
25700
26187
|
console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
|
|
25701
|
-
console.log(`Metadata: ${
|
|
26188
|
+
console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
|
|
25702
26189
|
return release;
|
|
25703
26190
|
} finally {
|
|
25704
26191
|
stopListeningForCancellation();
|
|
@@ -25849,7 +26336,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
25849
26336
|
checks.push({
|
|
25850
26337
|
id: "sync.storage-schema",
|
|
25851
26338
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
25852
|
-
path:
|
|
26339
|
+
path: join18(projectRoot, "package.json"),
|
|
25853
26340
|
platform: "host",
|
|
25854
26341
|
status: "pass"
|
|
25855
26342
|
});
|
|
@@ -25857,7 +26344,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
25857
26344
|
checks.push({
|
|
25858
26345
|
id: "sync.storage-schema",
|
|
25859
26346
|
label: "Offline schema metadata is invalid",
|
|
25860
|
-
path:
|
|
26347
|
+
path: join18(projectRoot, "package.json"),
|
|
25861
26348
|
platform: "host",
|
|
25862
26349
|
remediation: error instanceof Error ? error.message : String(error),
|
|
25863
26350
|
status: "fail"
|
|
@@ -25945,7 +26432,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
25945
26432
|
}
|
|
25946
26433
|
return { https: args.includes("--https"), port };
|
|
25947
26434
|
}
|
|
25948
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
26435
|
+
const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
25949
26436
|
if (instances.length !== 1) {
|
|
25950
26437
|
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
26438
|
}
|
|
@@ -26039,8 +26526,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
26039
26526
|
return waitForBoot();
|
|
26040
26527
|
};
|
|
26041
26528
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
26042
|
-
const root =
|
|
26043
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
26529
|
+
const root = resolve17(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
26530
|
+
if (root !== projectRoot && !root.startsWith(`${resolve17(projectRoot)}/`)) {
|
|
26044
26531
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
26045
26532
|
}
|
|
26046
26533
|
return root;
|
|
@@ -26068,12 +26555,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
26068
26555
|
});
|
|
26069
26556
|
};
|
|
26070
26557
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
26071
|
-
await
|
|
26072
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
26558
|
+
await mkdir18(options.artifactRoot, { recursive: true });
|
|
26559
|
+
const screenshot = options.session ? await options.session.screenshot(join18(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
26073
26560
|
return;
|
|
26074
26561
|
}) : undefined;
|
|
26075
|
-
const diagnosticPath =
|
|
26076
|
-
await
|
|
26562
|
+
const diagnosticPath = join18(options.artifactRoot, "android-failure.json");
|
|
26563
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26077
26564
|
diagnostics: options.session?.diagnostics ?? [],
|
|
26078
26565
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
26079
26566
|
platform: "android",
|
|
@@ -26094,7 +26581,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
|
|
|
26094
26581
|
]);
|
|
26095
26582
|
if (exitCode !== 0)
|
|
26096
26583
|
throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
26097
|
-
await
|
|
26584
|
+
await writeFile19(destination, bytes);
|
|
26098
26585
|
return destination;
|
|
26099
26586
|
};
|
|
26100
26587
|
var printAndroidReleaseAcceptance = (result, json) => {
|
|
@@ -26148,7 +26635,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26148
26635
|
release,
|
|
26149
26636
|
serial
|
|
26150
26637
|
});
|
|
26151
|
-
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial,
|
|
26638
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join18(reportRoot, "android-release.png")) : undefined;
|
|
26152
26639
|
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
26153
26640
|
durationMs: result.durationMs,
|
|
26154
26641
|
engine: result.engine,
|
|
@@ -26195,9 +26682,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
26195
26682
|
platform: "android",
|
|
26196
26683
|
success: false
|
|
26197
26684
|
});
|
|
26198
|
-
await
|
|
26199
|
-
const diagnosticPath =
|
|
26200
|
-
await
|
|
26685
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
26686
|
+
const diagnosticPath = join18(artifactRoot, "android-release-failure.json");
|
|
26687
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26201
26688
|
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
26202
26689
|
platform: "android",
|
|
26203
26690
|
provider: mobile.engine,
|
|
@@ -26291,7 +26778,7 @@ var testAndroid = async (args) => {
|
|
|
26291
26778
|
console.log(JSON.stringify(report, null, 2));
|
|
26292
26779
|
else
|
|
26293
26780
|
printAndroidTestReport(report);
|
|
26294
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
26781
|
+
const screenshot = reportRoot ? await session.screenshot(join18(artifactRoot, "android-emulator.png")) : undefined;
|
|
26295
26782
|
await writeRequestedAndroidReport({
|
|
26296
26783
|
adb: adb2,
|
|
26297
26784
|
args,
|
|
@@ -26360,14 +26847,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
26360
26847
|
const port = Number(explicit);
|
|
26361
26848
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
26362
26849
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
26363
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
26850
|
+
const instance2 = listLiveInstances().find((candidate) => resolve17(candidate.cwd) === resolve17(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
26364
26851
|
return {
|
|
26365
26852
|
https: instance2?.https ?? args.includes("--https"),
|
|
26366
26853
|
instance: instance2,
|
|
26367
26854
|
port
|
|
26368
26855
|
};
|
|
26369
26856
|
}
|
|
26370
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
26857
|
+
const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
26371
26858
|
if (instances.length !== 1)
|
|
26372
26859
|
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
26860
|
const [instance] = instances;
|
|
@@ -26385,7 +26872,7 @@ var waitForIosHmrClient = async (options) => {
|
|
|
26385
26872
|
return;
|
|
26386
26873
|
});
|
|
26387
26874
|
const status = response?.ok ? await response.json().catch(() => null) : null;
|
|
26388
|
-
const targets =
|
|
26875
|
+
const targets = isRecord10(status) && isRecord10(status.connectedTargets) ? status.connectedTargets : undefined;
|
|
26389
26876
|
if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
|
|
26390
26877
|
return;
|
|
26391
26878
|
await Bun.sleep(100);
|
|
@@ -26511,8 +26998,8 @@ var requireCapturedCommand = (command, label) => {
|
|
|
26511
26998
|
return result;
|
|
26512
26999
|
};
|
|
26513
27000
|
var writeIosFailureArtifacts = async (options) => {
|
|
26514
|
-
await
|
|
26515
|
-
const screenshot =
|
|
27001
|
+
await mkdir18(options.artifactRoot, { recursive: true });
|
|
27002
|
+
const screenshot = join18(options.artifactRoot, "ios-failure.png");
|
|
26516
27003
|
const screenshotResult = captureCommand4([
|
|
26517
27004
|
options.xcrun,
|
|
26518
27005
|
"simctl",
|
|
@@ -26521,8 +27008,8 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
26521
27008
|
"screenshot",
|
|
26522
27009
|
screenshot
|
|
26523
27010
|
]);
|
|
26524
|
-
const diagnosticPath =
|
|
26525
|
-
await
|
|
27011
|
+
const diagnosticPath = join18(options.artifactRoot, "ios-failure.json");
|
|
27012
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26526
27013
|
appId: options.appId,
|
|
26527
27014
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
26528
27015
|
platform: "ios",
|
|
@@ -26549,8 +27036,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
26549
27036
|
var absolutejsVersionForReport = async () => {
|
|
26550
27037
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
26551
27038
|
const versions = await Promise.all([
|
|
26552
|
-
|
|
26553
|
-
|
|
27039
|
+
resolve17(import.meta.dir, "..", "..", "package.json"),
|
|
27040
|
+
resolve17(import.meta.dir, "..", "..", "..", "package.json")
|
|
26554
27041
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
26555
27042
|
for (const version of versions) {
|
|
26556
27043
|
if (version === "unknown")
|
|
@@ -26927,9 +27414,9 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
26927
27414
|
remote: remoteProfile !== undefined,
|
|
26928
27415
|
success: false
|
|
26929
27416
|
});
|
|
26930
|
-
await
|
|
26931
|
-
const diagnosticPath =
|
|
26932
|
-
await
|
|
27417
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
27418
|
+
const diagnosticPath = join18(artifactRoot, "ios-release-failure.json");
|
|
27419
|
+
await writeFile19(diagnosticPath, `${JSON.stringify({
|
|
26933
27420
|
error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
|
|
26934
27421
|
platform: "ios",
|
|
26935
27422
|
provider: mobile.engine,
|
|
@@ -27022,8 +27509,8 @@ var testIos = async (args) => {
|
|
|
27022
27509
|
mobile.appId
|
|
27023
27510
|
], "iOS app launch");
|
|
27024
27511
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
27025
|
-
await
|
|
27026
|
-
const screenshot =
|
|
27512
|
+
await mkdir18(artifactRoot, { recursive: true });
|
|
27513
|
+
const screenshot = join18(artifactRoot, "ios-simulator.png");
|
|
27027
27514
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
27028
27515
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
27029
27516
|
const report = {
|
|
@@ -27240,6 +27727,10 @@ var runMobile = async (args) => {
|
|
|
27240
27727
|
if (command === "ci") {
|
|
27241
27728
|
if (args[1] === "promote")
|
|
27242
27729
|
await dispatchGithubMobilePromotion(args.slice(2));
|
|
27730
|
+
else if (args[1] === "status")
|
|
27731
|
+
await inspectGithubMobileRun(args.slice(2));
|
|
27732
|
+
else if (args[1] === "audit")
|
|
27733
|
+
await auditGithubMobilePromotion(args.slice(2));
|
|
27243
27734
|
else if (args[1] === "verification")
|
|
27244
27735
|
await createGithubCertificationVerification(args.slice(2));
|
|
27245
27736
|
else
|
|
@@ -27322,7 +27813,7 @@ var runMobile = async (args) => {
|
|
|
27322
27813
|
await publishIos(args.slice(2));
|
|
27323
27814
|
return;
|
|
27324
27815
|
}
|
|
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]");
|
|
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]");
|
|
27326
27817
|
};
|
|
27327
27818
|
export {
|
|
27328
27819
|
runMobile
|