@absolutejs/absolute 0.20.0-beta.110 → 0.20.0-beta.112
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/changelog.json +48 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/{mobile-12y4gtsq.js → mobile-d5wbqynh.js} +786 -130
- package/dist/mobile/index.js +1554 -1048
- package/dist/mobile/index.js.map +8 -7
- package/dist/src/mobile/ciPromotionAudit.d.ts +127 -0
- package/dist/src/mobile/ciWorkflow.d.ts +1 -1
- package/dist/src/mobile/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/mobile/index.js
CHANGED
|
@@ -19401,7 +19401,7 @@ var init_compatibilityDispatcher = __esm(() => {
|
|
|
19401
19401
|
// src/cli/scripts/telemetry.ts
|
|
19402
19402
|
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
19403
19403
|
import { homedir as homedir4 } from "os";
|
|
19404
|
-
import { join as
|
|
19404
|
+
import { join as join25 } from "path";
|
|
19405
19405
|
var configDir, configPath, getTelemetryConfig = () => {
|
|
19406
19406
|
try {
|
|
19407
19407
|
if (!existsSync5(configPath))
|
|
@@ -19414,14 +19414,14 @@ var configDir, configPath, getTelemetryConfig = () => {
|
|
|
19414
19414
|
}
|
|
19415
19415
|
};
|
|
19416
19416
|
var init_telemetry = __esm(() => {
|
|
19417
|
-
configDir =
|
|
19418
|
-
configPath =
|
|
19417
|
+
configDir = join25(homedir4(), ".absolutejs");
|
|
19418
|
+
configPath = join25(configDir, "telemetry.json");
|
|
19419
19419
|
});
|
|
19420
19420
|
|
|
19421
19421
|
// src/cli/telemetryEvent.ts
|
|
19422
19422
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
19423
19423
|
import { arch, platform as platform3 } from "os";
|
|
19424
|
-
import { dirname as
|
|
19424
|
+
import { dirname as dirname18, join as join26, parse } from "path";
|
|
19425
19425
|
var checkCandidate = (candidate) => {
|
|
19426
19426
|
if (!existsSync6(candidate)) {
|
|
19427
19427
|
return null;
|
|
@@ -19441,12 +19441,12 @@ var checkCandidate = (candidate) => {
|
|
|
19441
19441
|
}, findPackageVersion = () => {
|
|
19442
19442
|
let { dir } = import.meta;
|
|
19443
19443
|
while (dir !== parse(dir).root) {
|
|
19444
|
-
const candidate =
|
|
19444
|
+
const candidate = join26(dir, "package.json");
|
|
19445
19445
|
const version = checkCandidate(candidate);
|
|
19446
19446
|
if (version) {
|
|
19447
19447
|
return version;
|
|
19448
19448
|
}
|
|
19449
|
-
dir =
|
|
19449
|
+
dir = dirname18(dir);
|
|
19450
19450
|
}
|
|
19451
19451
|
return "unknown";
|
|
19452
19452
|
}, sendTelemetryEvent = (event, payload) => {
|
|
@@ -19487,7 +19487,7 @@ __export(exports_mobilePreview, {
|
|
|
19487
19487
|
createAbsoluteMobilePreviewPlugin: () => createAbsoluteMobilePreviewPlugin
|
|
19488
19488
|
});
|
|
19489
19489
|
import { Elysia as Elysia3 } from "elysia";
|
|
19490
|
-
var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """),
|
|
19490
|
+
var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """), isRecord14 = (value) => typeof value === "object" && value !== null, normalizeEntry2 = (entry) => {
|
|
19491
19491
|
const parsed = new URL(entry ?? "/", "https://absolute.invalid");
|
|
19492
19492
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
19493
19493
|
}, absoluteMobilePreviewDocument = (mobile) => {
|
|
@@ -19535,7 +19535,7 @@ var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (
|
|
|
19535
19535
|
"X-Robots-Tag": "noindex, nofollow"
|
|
19536
19536
|
}
|
|
19537
19537
|
})).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
|
|
19538
|
-
const value =
|
|
19538
|
+
const value = isRecord14(body) ? body : undefined;
|
|
19539
19539
|
const durationMs = value?.durationMs;
|
|
19540
19540
|
const platform4 = value?.platform;
|
|
19541
19541
|
if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform4 !== "android" && platform4 !== "ios") {
|
|
@@ -20092,7 +20092,7 @@ import {
|
|
|
20092
20092
|
verify as verify2,
|
|
20093
20093
|
X509Certificate as X509Certificate3
|
|
20094
20094
|
} from "crypto";
|
|
20095
|
-
import { readFile as
|
|
20095
|
+
import { readFile as readFile30, stat as stat9 } from "fs/promises";
|
|
20096
20096
|
import path from "path";
|
|
20097
20097
|
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", HEALTH_TOKEN_VERSION = 1, HEALTH_KINDS, FAILURE_HEALTH_KINDS, MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
|
|
20098
20098
|
if (typeof value !== "string" || value.length === 0)
|
|
@@ -21125,7 +21125,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
21125
21125
|
verifyManifestSignature(manifest, options.publicKeys);
|
|
21126
21126
|
await assertNotMarked(manifest.appId, manifest.releaseId);
|
|
21127
21127
|
const localRoot = path.resolve(input.releaseDirectory);
|
|
21128
|
-
const localManifest = parseMobileUpdateManifest(JSON.parse(await
|
|
21128
|
+
const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile30(path.join(localRoot, "update.json"), "utf8")));
|
|
21129
21129
|
if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
|
|
21130
21130
|
throw new MobileUpdateRegistryError("Local mobile update manifest changed");
|
|
21131
21131
|
const existing = await readManifest(manifest.appId, manifest.releaseId);
|
|
@@ -21143,7 +21143,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
21143
21143
|
if (!existing) {
|
|
21144
21144
|
for (const file of manifest.files) {
|
|
21145
21145
|
const local = path.join(localRoot, "files", file.path);
|
|
21146
|
-
const metadata = await
|
|
21146
|
+
const metadata = await stat9(local).catch(() => null);
|
|
21147
21147
|
if (!metadata?.isFile() || metadata.size !== file.bytes)
|
|
21148
21148
|
throw new MobileUpdateRegistryError(`Mobile update file ${file.path} size changed`);
|
|
21149
21149
|
if (await fileDigest(Bun.file(local)) !== file.sha256)
|
|
@@ -21411,8 +21411,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
21411
21411
|
});
|
|
21412
21412
|
}
|
|
21413
21413
|
const pathname = new URL(request.url).pathname.replace(/^\/+/, "");
|
|
21414
|
-
const
|
|
21415
|
-
if (request.method === "POST" &&
|
|
21414
|
+
const relative23 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
|
|
21415
|
+
if (request.method === "POST" && relative23 === "health") {
|
|
21416
21416
|
if (!options.registry.recordUpdateHealth)
|
|
21417
21417
|
return new Response(null, { status: 404 });
|
|
21418
21418
|
const appId = request.headers.get("x-absolute-mobile-app");
|
|
@@ -21455,7 +21455,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
21455
21455
|
}
|
|
21456
21456
|
if (request.method !== "GET")
|
|
21457
21457
|
return new Response(null, { status: 405 });
|
|
21458
|
-
if (
|
|
21458
|
+
if (relative23 === "update.json") {
|
|
21459
21459
|
const expoProtocolVersion = request.headers.get("expo-protocol-version");
|
|
21460
21460
|
const expoProtocol = expoProtocolVersion !== null;
|
|
21461
21461
|
if (expoProtocol && expoProtocolVersion !== "1")
|
|
@@ -21573,7 +21573,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
21573
21573
|
}
|
|
21574
21574
|
});
|
|
21575
21575
|
}
|
|
21576
|
-
const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(
|
|
21576
|
+
const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative23);
|
|
21577
21577
|
if (!match?.[1] || !match[2])
|
|
21578
21578
|
return new Response(null, { status: 404 });
|
|
21579
21579
|
const file = await options.registry.readUpdateFile({
|
|
@@ -21666,15 +21666,15 @@ import {
|
|
|
21666
21666
|
createPublicKey as createPublicKey3,
|
|
21667
21667
|
X509Certificate as X509Certificate4
|
|
21668
21668
|
} from "crypto";
|
|
21669
|
-
import { access as
|
|
21670
|
-
import { dirname as
|
|
21669
|
+
import { access as access22, mkdir as mkdir24 } from "fs/promises";
|
|
21670
|
+
import { dirname as dirname24, isAbsolute as isAbsolute14, relative as relative23, resolve as resolve25, sep as sep16 } from "path";
|
|
21671
21671
|
import { pathToFileURL as pathToFileURL5 } from "url";
|
|
21672
21672
|
import { Elysia as Elysia4 } from "elysia";
|
|
21673
|
-
var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value),
|
|
21674
|
-
const root =
|
|
21675
|
-
const path2 =
|
|
21676
|
-
const projectRelative =
|
|
21677
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
21673
|
+
var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectPath4 = (projectRoot, requested) => {
|
|
21674
|
+
const root = resolve25(projectRoot);
|
|
21675
|
+
const path2 = resolve25(root, requested);
|
|
21676
|
+
const projectRelative = relative23(root, path2);
|
|
21677
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep16}`) || isAbsolute14(projectRelative))
|
|
21678
21678
|
throw new TypeError("mobile.updates.server.registry must remain inside the project.");
|
|
21679
21679
|
return path2;
|
|
21680
21680
|
}, isRegistry4 = (value) => object7(value) && [
|
|
@@ -21696,8 +21696,8 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
21696
21696
|
};
|
|
21697
21697
|
return metadata;
|
|
21698
21698
|
}, loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePath = DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE) => {
|
|
21699
|
-
const modulePath =
|
|
21700
|
-
await
|
|
21699
|
+
const modulePath = projectPath4(projectRoot, requestedModulePath);
|
|
21700
|
+
await access22(modulePath).catch(() => {
|
|
21701
21701
|
throw new TypeError(`Mobile update server registry does not exist: ${modulePath}. Run \`absolute mobile update provision\`.`);
|
|
21702
21702
|
});
|
|
21703
21703
|
const loaded = await import(pathToFileURL5(modulePath).href);
|
|
@@ -21884,15 +21884,15 @@ export default createMobileUpdateRegistry({
|
|
|
21884
21884
|
`, `export default createMobileUpdateRegistry({
|
|
21885
21885
|
${generated}`);
|
|
21886
21886
|
}, writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
21887
|
-
const path2 =
|
|
21887
|
+
const path2 = projectPath4(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
|
|
21888
21888
|
if (!options.force) {
|
|
21889
|
-
await
|
|
21889
|
+
await access22(path2).then(() => {
|
|
21890
21890
|
throw new TypeError(`Mobile update registry already exists: ${path2}. Pass --force to replace it.`);
|
|
21891
21891
|
}, () => {
|
|
21892
21892
|
return;
|
|
21893
21893
|
});
|
|
21894
21894
|
}
|
|
21895
|
-
await
|
|
21895
|
+
await mkdir24(dirname24(path2), { recursive: true });
|
|
21896
21896
|
await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
|
|
21897
21897
|
...options.health ? { health: options.health } : {},
|
|
21898
21898
|
...options.rollout ? { rollout: options.rollout } : {},
|
|
@@ -33029,140 +33029,1130 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
33029
33029
|
// src/mobile/index.ts
|
|
33030
33030
|
init_config();
|
|
33031
33031
|
|
|
33032
|
-
// src/mobile/
|
|
33033
|
-
import {
|
|
33034
|
-
|
|
33035
|
-
|
|
33036
|
-
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33040
|
-
|
|
33041
|
-
|
|
33042
|
-
|
|
33043
|
-
|
|
33044
|
-
|
|
33045
|
-
|
|
33046
|
-
|
|
33047
|
-
|
|
33048
|
-
|
|
33049
|
-
|
|
33050
|
-
|
|
33051
|
-
|
|
33052
|
-
|
|
33053
|
-
]
|
|
33032
|
+
// src/mobile/ciPromotionAudit.ts
|
|
33033
|
+
import {
|
|
33034
|
+
access as access18,
|
|
33035
|
+
mkdir as mkdir18,
|
|
33036
|
+
mkdtemp as mkdtemp11,
|
|
33037
|
+
readFile as readFile22,
|
|
33038
|
+
readdir as readdir7,
|
|
33039
|
+
rename as rename14,
|
|
33040
|
+
rm as rm14,
|
|
33041
|
+
stat as stat7,
|
|
33042
|
+
writeFile as writeFile18
|
|
33043
|
+
} from "fs/promises";
|
|
33044
|
+
import { dirname as dirname16, isAbsolute as isAbsolute11, join as join24, relative as relative17, resolve as resolve19, sep as sep12 } from "path";
|
|
33045
|
+
|
|
33046
|
+
// src/mobile/certificationVerification.ts
|
|
33047
|
+
import { access as access16, mkdir as mkdir16, readFile as readFile20, stat as stat6, writeFile as writeFile16 } from "fs/promises";
|
|
33048
|
+
import { dirname as dirname15, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve17, sep as sep10 } from "path";
|
|
33049
|
+
var ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT = 1;
|
|
33050
|
+
var MAX_VERIFICATION_BYTES = 1048576;
|
|
33051
|
+
var SHA_PATTERN = /^[a-f0-9]{40}$/u;
|
|
33052
|
+
var REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
33053
|
+
var REF_PATTERN = /^refs\/(?:heads|tags)\/[^@\s]+$/u;
|
|
33054
|
+
var WORKFLOW_PATTERN = /^\.github\/workflows\/[^/@]+\.ya?ml$/u;
|
|
33055
|
+
var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33056
|
+
var required = (value, name) => {
|
|
33057
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
33058
|
+
throw new TypeError(`${name} is required.`);
|
|
33059
|
+
return value.trim();
|
|
33060
|
+
};
|
|
33061
|
+
var requireMatch = (value, pattern, name) => {
|
|
33062
|
+
if (!pattern.test(value))
|
|
33063
|
+
throw new TypeError(`${name} is invalid.`);
|
|
33064
|
+
return value;
|
|
33065
|
+
};
|
|
33066
|
+
var createAbsoluteMobileCertificationVerification = (bundle, environment) => {
|
|
33067
|
+
const repository = required(environment.GITHUB_REPOSITORY, "GITHUB_REPOSITORY");
|
|
33068
|
+
const workflowReference = required(environment.GITHUB_WORKFLOW_REF, "GITHUB_WORKFLOW_REF");
|
|
33069
|
+
const prefix = `${repository}/`;
|
|
33070
|
+
const separator = workflowReference.lastIndexOf("@");
|
|
33071
|
+
if (!workflowReference.startsWith(prefix) || separator <= prefix.length)
|
|
33072
|
+
throw new TypeError("GITHUB_WORKFLOW_REF is invalid.");
|
|
33073
|
+
return parseAbsoluteMobileCertificationVerification({
|
|
33074
|
+
bundle,
|
|
33075
|
+
format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
33076
|
+
identity: {
|
|
33077
|
+
issuer: "https://token.actions.githubusercontent.com",
|
|
33078
|
+
ref: environment.GITHUB_REF,
|
|
33079
|
+
repository,
|
|
33080
|
+
sha: environment.GITHUB_SHA,
|
|
33081
|
+
workflowPath: workflowReference.slice(prefix.length, separator)
|
|
33082
|
+
},
|
|
33083
|
+
kind: "sigstore-bundle"
|
|
33084
|
+
});
|
|
33085
|
+
};
|
|
33086
|
+
var parseAbsoluteMobileCertificationVerification = (value) => {
|
|
33087
|
+
if (!isRecord11(value) || value.format !== ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT || value.kind !== "sigstore-bundle" || !isRecord11(value.bundle) || !isRecord11(value.identity))
|
|
33088
|
+
throw new TypeError("Mobile certification verification contract is invalid.");
|
|
33089
|
+
const issuer = required(value.identity.issuer, "verification issuer");
|
|
33090
|
+
if (issuer !== "https://token.actions.githubusercontent.com")
|
|
33091
|
+
throw new TypeError("verification issuer is invalid.");
|
|
33092
|
+
const verification = {
|
|
33093
|
+
bundle: value.bundle,
|
|
33094
|
+
format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
33095
|
+
identity: {
|
|
33096
|
+
issuer,
|
|
33097
|
+
ref: requireMatch(required(value.identity.ref, "verification ref"), REF_PATTERN, "verification ref"),
|
|
33098
|
+
repository: requireMatch(required(value.identity.repository, "verification repository"), REPOSITORY_PATTERN, "verification repository"),
|
|
33099
|
+
sha: requireMatch(required(value.identity.sha, "verification sha"), SHA_PATTERN, "verification sha"),
|
|
33100
|
+
workflowPath: requireMatch(required(value.identity.workflowPath, "verification workflowPath"), WORKFLOW_PATTERN, "verification workflowPath")
|
|
33101
|
+
},
|
|
33102
|
+
kind: "sigstore-bundle"
|
|
33103
|
+
};
|
|
33104
|
+
const bytes = new TextEncoder().encode(JSON.stringify(verification));
|
|
33105
|
+
if (bytes.byteLength > MAX_VERIFICATION_BYTES)
|
|
33106
|
+
throw new TypeError("Mobile certification verification exceeds the 1 MiB limit.");
|
|
33107
|
+
return verification;
|
|
33108
|
+
};
|
|
33109
|
+
var projectFile = (projectRoot, requested) => {
|
|
33110
|
+
const root = resolve17(projectRoot);
|
|
33111
|
+
const path = resolve17(root, requested);
|
|
33112
|
+
const portable = relative15(root, path);
|
|
33113
|
+
if (portable === ".." || portable.startsWith(`..${sep10}`) || isAbsolute9(portable))
|
|
33114
|
+
throw new TypeError("Mobile certification verification path must remain inside the project.");
|
|
33115
|
+
return path;
|
|
33116
|
+
};
|
|
33117
|
+
var readAbsoluteMobileCertificationVerification = async (projectRoot, requested) => {
|
|
33118
|
+
const path = projectFile(projectRoot, requested);
|
|
33119
|
+
await access16(path).catch(() => {
|
|
33120
|
+
throw new TypeError(`Mobile certification verification does not exist: ${path}`);
|
|
33121
|
+
});
|
|
33122
|
+
const metadata = await stat6(path);
|
|
33123
|
+
if (!metadata.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
|
|
33124
|
+
throw new TypeError("Mobile certification verification file is invalid.");
|
|
33125
|
+
return parseAbsoluteMobileCertificationVerification(JSON.parse(await readFile20(path, "utf8")));
|
|
33126
|
+
};
|
|
33127
|
+
var readAbsoluteSigstoreBundle = async (projectRoot, requested) => {
|
|
33128
|
+
const path = projectFile(projectRoot, requested);
|
|
33129
|
+
const metadata = await stat6(path).catch(() => null);
|
|
33130
|
+
if (!metadata?.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
|
|
33131
|
+
throw new TypeError("Sigstore bundle file is invalid.");
|
|
33132
|
+
const value = JSON.parse(await readFile20(path, "utf8"));
|
|
33133
|
+
if (!isRecord11(value))
|
|
33134
|
+
throw new TypeError("Sigstore bundle is invalid.");
|
|
33135
|
+
return value;
|
|
33136
|
+
};
|
|
33137
|
+
var writeAbsoluteMobileCertificationVerification = async (projectRoot, requested, verification) => {
|
|
33138
|
+
const path = projectFile(projectRoot, requested);
|
|
33139
|
+
const parsed = parseAbsoluteMobileCertificationVerification(verification);
|
|
33140
|
+
await mkdir16(dirname15(path), { recursive: true });
|
|
33141
|
+
await writeFile16(path, `${JSON.stringify(parsed, null, 2)}
|
|
33142
|
+
`, {
|
|
33143
|
+
mode: 384
|
|
33144
|
+
});
|
|
33145
|
+
return path;
|
|
33146
|
+
};
|
|
33147
|
+
|
|
33148
|
+
// src/mobile/releaseCertification.ts
|
|
33149
|
+
import { createHash as createHash16 } from "crypto";
|
|
33150
|
+
import {
|
|
33151
|
+
access as access17,
|
|
33152
|
+
mkdir as mkdir17,
|
|
33153
|
+
mkdtemp as mkdtemp10,
|
|
33154
|
+
readFile as readFile21,
|
|
33155
|
+
rename as rename13,
|
|
33156
|
+
rm as rm13,
|
|
33157
|
+
writeFile as writeFile17
|
|
33158
|
+
} from "fs/promises";
|
|
33159
|
+
import { basename as basename8, isAbsolute as isAbsolute10, join as join23, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
|
|
33160
|
+
var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
|
|
33161
|
+
var CERTIFICATION_PREFIX = "amobile_cert_";
|
|
33162
|
+
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
33163
|
+
var STRENGTH_RANK = {
|
|
33164
|
+
device: 1,
|
|
33165
|
+
installed: 0,
|
|
33166
|
+
simulator: 0,
|
|
33167
|
+
store: 2
|
|
33168
|
+
};
|
|
33169
|
+
var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
33170
|
+
var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
33171
|
+
var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
|
|
33172
|
+
var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
|
|
33173
|
+
var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33174
|
+
var inside4 = (root, candidate) => {
|
|
33175
|
+
const path = relative16(resolve18(root), resolve18(candidate));
|
|
33176
|
+
return path === "" || !isAbsolute10(path) && path !== ".." && !path.startsWith(`..${sep11}`);
|
|
33177
|
+
};
|
|
33054
33178
|
var exists8 = async (path) => {
|
|
33055
33179
|
try {
|
|
33056
|
-
await
|
|
33180
|
+
await access17(path);
|
|
33057
33181
|
return true;
|
|
33058
33182
|
} catch {
|
|
33059
33183
|
return false;
|
|
33060
33184
|
}
|
|
33061
33185
|
};
|
|
33062
|
-
var
|
|
33063
|
-
var
|
|
33064
|
-
|
|
33065
|
-
|
|
33066
|
-
|
|
33067
|
-
if (portable === ".." || portable.startsWith(`..${sep10}`) || portable.startsWith("../") || portable === "") {
|
|
33068
|
-
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
33069
|
-
}
|
|
33070
|
-
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
33071
|
-
throw new TypeError(`${field2} contains an unsafe path.`);
|
|
33072
|
-
if (!options.allowMissing && !existsSync4(path))
|
|
33073
|
-
throw new TypeError(`${field2} does not exist inside the project.`);
|
|
33074
|
-
return portable;
|
|
33186
|
+
var sha2564 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
|
|
33187
|
+
var requireString2 = (value, name) => {
|
|
33188
|
+
if (typeof value !== "string" || !value)
|
|
33189
|
+
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
33190
|
+
return value;
|
|
33075
33191
|
};
|
|
33076
|
-
var
|
|
33077
|
-
|
|
33078
|
-
|
|
33079
|
-
|
|
33080
|
-
const portable = relative15(workflows, path);
|
|
33081
|
-
if (portable === ".." || portable.startsWith(`..${sep10}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
|
|
33082
|
-
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
33083
|
-
}
|
|
33084
|
-
return path;
|
|
33192
|
+
var requirePositiveInteger = (value, name) => {
|
|
33193
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1)
|
|
33194
|
+
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
33195
|
+
return Number(value);
|
|
33085
33196
|
};
|
|
33086
|
-
var
|
|
33087
|
-
const
|
|
33088
|
-
|
|
33089
|
-
|
|
33090
|
-
|
|
33091
|
-
|
|
33092
|
-
|
|
33093
|
-
|
|
33094
|
-
|
|
33095
|
-
return
|
|
33197
|
+
var resolveEvidencePath = async (projectRoot, requested) => {
|
|
33198
|
+
const selected = resolve18(projectRoot, requested);
|
|
33199
|
+
if (!inside4(projectRoot, selected))
|
|
33200
|
+
throw new TypeError("Mobile certification evidence must remain inside the project.");
|
|
33201
|
+
if (basename8(selected) === "report.json")
|
|
33202
|
+
return selected;
|
|
33203
|
+
const report = join23(selected, "report.json");
|
|
33204
|
+
if (!await exists8(report))
|
|
33205
|
+
throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
|
|
33206
|
+
return report;
|
|
33096
33207
|
};
|
|
33097
|
-
var
|
|
33098
|
-
|
|
33099
|
-
|
|
33100
|
-
|
|
33101
|
-
|
|
33102
|
-
|
|
33103
|
-
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
|
|
33109
|
-
|
|
33110
|
-
|
|
33111
|
-
|
|
33112
|
-
|
|
33113
|
-
|
|
33114
|
-
|
|
33115
|
-
|
|
33116
|
-
|
|
33117
|
-
|
|
33118
|
-
|
|
33119
|
-
|
|
33120
|
-
|
|
33121
|
-
|
|
33122
|
-
|
|
33123
|
-
|
|
33124
|
-
|
|
33125
|
-
|
|
33126
|
-
|
|
33127
|
-
|
|
33128
|
-
|
|
33129
|
-
|
|
33130
|
-
|
|
33131
|
-
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
|
|
33136
|
-
|
|
33137
|
-
|
|
33138
|
-
|
|
33139
|
-
|
|
33140
|
-
|
|
33141
|
-
|
|
33142
|
-
|
|
33143
|
-
|
|
33144
|
-
|
|
33145
|
-
|
|
33146
|
-
|
|
33147
|
-
|
|
33148
|
-
|
|
33149
|
-
|
|
33150
|
-
|
|
33151
|
-
|
|
33152
|
-
|
|
33153
|
-
|
|
33154
|
-
|
|
33155
|
-
|
|
33156
|
-
|
|
33157
|
-
|
|
33158
|
-
|
|
33159
|
-
|
|
33160
|
-
|
|
33161
|
-
|
|
33162
|
-
|
|
33163
|
-
|
|
33164
|
-
|
|
33165
|
-
|
|
33208
|
+
var releaseIdentity = (release) => {
|
|
33209
|
+
const { metadata } = release;
|
|
33210
|
+
if (!metadata.signed)
|
|
33211
|
+
throw new TypeError("Mobile release certification requires a signed native release.");
|
|
33212
|
+
const common = {
|
|
33213
|
+
appBuild: metadata.appBuild,
|
|
33214
|
+
appId: metadata.appId,
|
|
33215
|
+
artifactBytes: metadata.bytes,
|
|
33216
|
+
artifactSha256: metadata.sha256,
|
|
33217
|
+
engine: metadata.engine,
|
|
33218
|
+
platform: metadata.platform,
|
|
33219
|
+
releaseId: metadata.releaseId,
|
|
33220
|
+
runtime: metadata.runtime,
|
|
33221
|
+
signed: true
|
|
33222
|
+
};
|
|
33223
|
+
if (metadata.platform === "android")
|
|
33224
|
+
return {
|
|
33225
|
+
...common,
|
|
33226
|
+
...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
|
|
33227
|
+
};
|
|
33228
|
+
return {
|
|
33229
|
+
...common,
|
|
33230
|
+
...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
|
|
33231
|
+
marketingVersion: metadata.marketingVersion
|
|
33232
|
+
};
|
|
33233
|
+
};
|
|
33234
|
+
var requireCheck = (report, id, accepted = ["PASS"]) => {
|
|
33235
|
+
const checks = report.automatedChecks;
|
|
33236
|
+
if (!Array.isArray(checks))
|
|
33237
|
+
throw new TypeError("Mobile certification evidence has invalid automated checks.");
|
|
33238
|
+
const check = checks.find((candidate) => isRecord12(candidate) && candidate.id === id);
|
|
33239
|
+
if (!isRecord12(check) || !accepted.includes(String(check.result)))
|
|
33240
|
+
throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
|
|
33241
|
+
};
|
|
33242
|
+
var validateCommonReport = (report, release) => {
|
|
33243
|
+
if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord12(report.metadata) || report.metadata.provider !== release.engine || !isRecord12(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
|
|
33244
|
+
throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
|
|
33245
|
+
requireCheck(report, "AUTO-DEV-01");
|
|
33246
|
+
return {
|
|
33247
|
+
generatedAt: requireString2(report.generatedAt, "evidence generatedAt"),
|
|
33248
|
+
run: report.run
|
|
33249
|
+
};
|
|
33250
|
+
};
|
|
33251
|
+
var validateReleaseFields = (value, release) => {
|
|
33252
|
+
if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
|
|
33253
|
+
throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
|
|
33254
|
+
};
|
|
33255
|
+
var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
33256
|
+
if (!isRecord12(run.release))
|
|
33257
|
+
throw new TypeError("Android certification evidence has no installed release result.");
|
|
33258
|
+
validateReleaseFields(run.release, release);
|
|
33259
|
+
if (run.release.embeddedOffline !== true)
|
|
33260
|
+
throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
|
|
33261
|
+
requireCheck(report, "AUTO-RELEASE-01");
|
|
33262
|
+
requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
|
|
33263
|
+
return {
|
|
33264
|
+
generatedAt,
|
|
33265
|
+
networkUnavailable: "proven",
|
|
33266
|
+
remote: false,
|
|
33267
|
+
reportSha256,
|
|
33268
|
+
strength: "installed"
|
|
33269
|
+
};
|
|
33270
|
+
};
|
|
33271
|
+
var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
33272
|
+
if (!isRecord12(run.iosRelease))
|
|
33273
|
+
throw new TypeError("iOS certification evidence has no installed release result.");
|
|
33274
|
+
const ios = run.iosRelease;
|
|
33275
|
+
validateReleaseFields(ios, release);
|
|
33276
|
+
if (ios.embeddedLocal !== true)
|
|
33277
|
+
throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
|
|
33278
|
+
requireCheck(report, "AUTO-IOS-RELEASE-01");
|
|
33279
|
+
let strength;
|
|
33280
|
+
if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
|
|
33281
|
+
strength = "simulator";
|
|
33282
|
+
else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
33283
|
+
strength = "device";
|
|
33284
|
+
else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
33285
|
+
strength = "store";
|
|
33286
|
+
else
|
|
33287
|
+
throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
|
|
33288
|
+
if (strength === "simulator")
|
|
33289
|
+
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
|
|
33290
|
+
else
|
|
33291
|
+
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
|
|
33292
|
+
return {
|
|
33293
|
+
artifactExactness: ios.artifactExactness,
|
|
33294
|
+
distribution: ios.distribution,
|
|
33295
|
+
generatedAt,
|
|
33296
|
+
networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
|
|
33297
|
+
remote: ios.remote === true,
|
|
33298
|
+
reportSha256,
|
|
33299
|
+
strength
|
|
33300
|
+
};
|
|
33301
|
+
};
|
|
33302
|
+
var validateEvidence = async (projectRoot, requested, release) => {
|
|
33303
|
+
const path = await resolveEvidencePath(projectRoot, requested);
|
|
33304
|
+
const bytes = await readFile21(path);
|
|
33305
|
+
let value;
|
|
33306
|
+
try {
|
|
33307
|
+
value = JSON.parse(bytes.toString("utf8"));
|
|
33308
|
+
} catch {
|
|
33309
|
+
throw new TypeError("Mobile certification evidence report is not valid JSON.");
|
|
33310
|
+
}
|
|
33311
|
+
if (!isRecord12(value))
|
|
33312
|
+
throw new TypeError("Mobile certification evidence report must contain an object.");
|
|
33313
|
+
const { generatedAt, run } = validateCommonReport(value, release);
|
|
33314
|
+
const digest = sha2564(bytes);
|
|
33315
|
+
return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
|
|
33316
|
+
};
|
|
33317
|
+
var defaultRequirement = (platform3) => platform3 === "android" ? "installed" : "simulator";
|
|
33318
|
+
var validateRequirement = (platform3, requirement) => {
|
|
33319
|
+
if (platform3 === "android" && requirement !== "installed")
|
|
33320
|
+
throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
|
|
33321
|
+
if (platform3 === "ios" && requirement === "installed")
|
|
33322
|
+
throw new TypeError("iOS certification requires simulator, device, or store evidence.");
|
|
33323
|
+
};
|
|
33324
|
+
var satisfiesRequirement = (strength, requirement) => {
|
|
33325
|
+
if (requirement === "installed")
|
|
33326
|
+
return strength === "installed";
|
|
33327
|
+
if (strength === "installed")
|
|
33328
|
+
return false;
|
|
33329
|
+
return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
|
|
33330
|
+
};
|
|
33331
|
+
var resolveAbsoluteMobileCertificationRequirement = (config, target) => {
|
|
33332
|
+
const channelRequirement = target.channel ? config.releaseCertification.channels[target.channel]?.[target.platform] : undefined;
|
|
33333
|
+
const playRequirement = target.googlePlayTrack ? config.releaseCertification.googlePlayTracks[target.googlePlayTrack] : undefined;
|
|
33334
|
+
const requirements = [channelRequirement, playRequirement].filter((value) => value !== undefined);
|
|
33335
|
+
if (requirements.length === 0)
|
|
33336
|
+
return;
|
|
33337
|
+
return requirements.reduce((strongest, candidate) => STRENGTH_RANK[candidate] > STRENGTH_RANK[strongest] ? candidate : strongest);
|
|
33338
|
+
};
|
|
33339
|
+
var validateCertifiedEvidence = (platform3, evidence) => {
|
|
33340
|
+
if (platform3 === "android") {
|
|
33341
|
+
if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
|
|
33342
|
+
throw new TypeError("Android release certification contains invalid evidence strength.");
|
|
33343
|
+
return;
|
|
33344
|
+
}
|
|
33345
|
+
const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
|
|
33346
|
+
const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
|
|
33347
|
+
const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
|
|
33348
|
+
if (!simulator && !device && !store)
|
|
33349
|
+
throw new TypeError("iOS release certification contains invalid evidence strength.");
|
|
33350
|
+
};
|
|
33351
|
+
var canonicalJsonValue = (value) => {
|
|
33352
|
+
if (Array.isArray(value))
|
|
33353
|
+
return value.map(canonicalJsonValue);
|
|
33354
|
+
if (!isRecord12(value))
|
|
33355
|
+
return value;
|
|
33356
|
+
const sorted = {};
|
|
33357
|
+
for (const key of Object.keys(value).sort())
|
|
33358
|
+
sorted[key] = canonicalJsonValue(value[key]);
|
|
33359
|
+
return sorted;
|
|
33360
|
+
};
|
|
33361
|
+
var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
|
|
33362
|
+
var createAbsoluteMobileReleaseCertification = async (options) => {
|
|
33363
|
+
if (options.evidencePaths.length === 0)
|
|
33364
|
+
throw new TypeError("Mobile release certification requires at least one --evidence report.");
|
|
33365
|
+
const release = releaseIdentity(options.release);
|
|
33366
|
+
const requirement = options.requirement ?? defaultRequirement(release.platform);
|
|
33367
|
+
validateRequirement(release.platform, requirement);
|
|
33368
|
+
const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
|
|
33369
|
+
evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
|
|
33370
|
+
const [firstEvidence] = evidence;
|
|
33371
|
+
if (!firstEvidence)
|
|
33372
|
+
throw new TypeError("Mobile release certification requires valid evidence.");
|
|
33373
|
+
const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
|
|
33374
|
+
if (!satisfiesRequirement(strength, requirement))
|
|
33375
|
+
throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
|
|
33376
|
+
const body = {
|
|
33377
|
+
evidence,
|
|
33378
|
+
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
33379
|
+
generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
|
|
33380
|
+
release,
|
|
33381
|
+
requirement,
|
|
33382
|
+
status: "certified",
|
|
33383
|
+
strength
|
|
33384
|
+
};
|
|
33385
|
+
return {
|
|
33386
|
+
certificationId: `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`,
|
|
33387
|
+
...body
|
|
33388
|
+
};
|
|
33389
|
+
};
|
|
33390
|
+
var parseCertification = (value) => {
|
|
33391
|
+
if (!isRecord12(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord12(value.release) || !Array.isArray(value.evidence))
|
|
33392
|
+
throw new TypeError("Mobile release certification contract is invalid.");
|
|
33393
|
+
const { release } = value;
|
|
33394
|
+
const { platform: platform3 } = release;
|
|
33395
|
+
const { engine } = release;
|
|
33396
|
+
if (platform3 !== "android" && platform3 !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
|
|
33397
|
+
throw new TypeError("Mobile release certification identity is invalid.");
|
|
33398
|
+
const { requirement } = value;
|
|
33399
|
+
const { strength } = value;
|
|
33400
|
+
if (!isRequirement(requirement) || !isStrength(strength))
|
|
33401
|
+
throw new TypeError("Mobile release certification policy is invalid.");
|
|
33402
|
+
validateRequirement(platform3, requirement);
|
|
33403
|
+
if (!satisfiesRequirement(strength, requirement))
|
|
33404
|
+
throw new TypeError("Mobile release certification does not satisfy its declared policy.");
|
|
33405
|
+
const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
|
|
33406
|
+
const artifactSha256 = requireString2(release.artifactSha256, "release artifactSha256");
|
|
33407
|
+
if (!SHA256.test(artifactSha256))
|
|
33408
|
+
throw new TypeError("Mobile release certification artifact digest is invalid.");
|
|
33409
|
+
const parsedRelease = {
|
|
33410
|
+
appBuild: requireString2(release.appBuild, "release appBuild"),
|
|
33411
|
+
appId: requireString2(release.appId, "release appId"),
|
|
33412
|
+
artifactBytes,
|
|
33413
|
+
artifactSha256,
|
|
33414
|
+
engine,
|
|
33415
|
+
platform: platform3,
|
|
33416
|
+
releaseId: requireString2(release.releaseId, "release releaseId"),
|
|
33417
|
+
runtime: requireString2(release.runtime, "release runtime"),
|
|
33418
|
+
signed: true,
|
|
33419
|
+
...release.buildNumber === undefined ? {} : {
|
|
33420
|
+
buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
|
|
33421
|
+
},
|
|
33422
|
+
...release.marketingVersion === undefined ? {} : {
|
|
33423
|
+
marketingVersion: requireString2(release.marketingVersion, "release marketingVersion")
|
|
33424
|
+
},
|
|
33425
|
+
...release.versionCode === undefined ? {} : {
|
|
33426
|
+
versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
|
|
33427
|
+
}
|
|
33428
|
+
};
|
|
33429
|
+
const evidence = value.evidence.map((candidate) => {
|
|
33430
|
+
if (!isRecord12(candidate) || !SHA256.test(String(candidate.reportSha256)) || !isStrength(candidate.strength) || candidate.networkUnavailable !== "not-proven" && candidate.networkUnavailable !== "proven" || typeof candidate.remote !== "boolean" || typeof candidate.generatedAt !== "string" || candidate.artifactExactness !== undefined && !isArtifactExactness(candidate.artifactExactness) || candidate.distribution !== undefined && !isDistribution(candidate.distribution))
|
|
33431
|
+
throw new TypeError("Mobile release certification evidence is invalid.");
|
|
33432
|
+
const parsedEvidence = {
|
|
33433
|
+
generatedAt: candidate.generatedAt,
|
|
33434
|
+
networkUnavailable: candidate.networkUnavailable,
|
|
33435
|
+
remote: candidate.remote,
|
|
33436
|
+
reportSha256: String(candidate.reportSha256),
|
|
33437
|
+
strength: candidate.strength,
|
|
33438
|
+
...candidate.artifactExactness === undefined ? {} : {
|
|
33439
|
+
artifactExactness: candidate.artifactExactness
|
|
33440
|
+
},
|
|
33441
|
+
...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
|
|
33442
|
+
};
|
|
33443
|
+
validateCertifiedEvidence(platform3, parsedEvidence);
|
|
33444
|
+
return parsedEvidence;
|
|
33445
|
+
});
|
|
33446
|
+
const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
|
|
33447
|
+
if (evidence.length === 0 || strongestEvidence !== strength)
|
|
33448
|
+
throw new TypeError("Mobile release certification evidence does not support its declared strength.");
|
|
33449
|
+
const parsed = {
|
|
33450
|
+
certificationId: requireString2(value.certificationId, "certificationId"),
|
|
33451
|
+
evidence,
|
|
33452
|
+
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
33453
|
+
generatedAt: requireString2(value.generatedAt, "generatedAt"),
|
|
33454
|
+
release: parsedRelease,
|
|
33455
|
+
requirement,
|
|
33456
|
+
status: "certified",
|
|
33457
|
+
strength
|
|
33458
|
+
};
|
|
33459
|
+
const { certificationId, ...body } = parsed;
|
|
33460
|
+
if (certificationId !== `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`)
|
|
33461
|
+
throw new TypeError("Mobile release certification content digest is invalid.");
|
|
33462
|
+
return parsed;
|
|
33463
|
+
};
|
|
33464
|
+
var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
|
|
33465
|
+
const selected = resolve18(projectRoot, requested);
|
|
33466
|
+
if (!inside4(projectRoot, selected))
|
|
33467
|
+
throw new TypeError("Mobile release certification must remain inside the project.");
|
|
33468
|
+
const path = basename8(selected) === "certification.json" ? selected : join23(selected, "certification.json");
|
|
33469
|
+
const value = JSON.parse(await readFile21(path, "utf8"));
|
|
33470
|
+
return { certification: parseCertification(value), path };
|
|
33471
|
+
};
|
|
33472
|
+
var renderAbsoluteMobileReleaseCertification = (certification) => {
|
|
33473
|
+
const { release } = certification;
|
|
33474
|
+
const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
|
|
33475
|
+
const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
|
|
33476
|
+
`);
|
|
33477
|
+
return `# AbsoluteJS mobile release certification
|
|
33478
|
+
|
|
33479
|
+
- Status: CERTIFIED
|
|
33480
|
+
- Certification: ${certification.certificationId}
|
|
33481
|
+
- Generated: ${certification.generatedAt}
|
|
33482
|
+
- Platform: ${release.platform}
|
|
33483
|
+
- Engine: ${release.engine}
|
|
33484
|
+
- Requirement: ${certification.requirement}
|
|
33485
|
+
- Evidence strength: ${certification.strength}
|
|
33486
|
+
- Release: ${release.releaseId}
|
|
33487
|
+
- Artifact SHA-256: ${release.artifactSha256}
|
|
33488
|
+
- Runtime fingerprint: ${release.runtime}
|
|
33489
|
+
- Embedded app build: ${release.appBuild}
|
|
33490
|
+
- Version/build: ${version}
|
|
33491
|
+
|
|
33492
|
+
This certification is local and contains no signing credentials, device identifiers, SSH destinations, report paths, application data, or page contents. Re-run verification against the immutable release before using it as a CI or promotion gate.
|
|
33493
|
+
|
|
33494
|
+
| Strength | Network unavailable | Execution | Report SHA-256 |
|
|
33495
|
+
| --- | --- | --- | --- |
|
|
33496
|
+
${evidence}
|
|
33497
|
+
`;
|
|
33498
|
+
};
|
|
33499
|
+
var serializeAbsoluteMobileReleaseCertification = (certification) => `${JSON.stringify(certification, null, 2)}
|
|
33500
|
+
`;
|
|
33501
|
+
var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
|
|
33502
|
+
const parsed = parseCertification(certification);
|
|
33503
|
+
const expected = releaseIdentity(release);
|
|
33504
|
+
if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
|
|
33505
|
+
throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
|
|
33506
|
+
if (requirement) {
|
|
33507
|
+
validateRequirement(expected.platform, requirement);
|
|
33508
|
+
if (!satisfiesRequirement(parsed.strength, requirement))
|
|
33509
|
+
throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
|
|
33510
|
+
}
|
|
33511
|
+
return parsed;
|
|
33512
|
+
};
|
|
33513
|
+
var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
|
|
33514
|
+
const root = resolve18(projectRoot);
|
|
33515
|
+
const parent = resolve18(root, outputDirectory ?? join23(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
|
|
33516
|
+
if (!inside4(root, parent))
|
|
33517
|
+
throw new TypeError("Mobile certification output must remain inside the project.");
|
|
33518
|
+
await mkdir17(parent, { recursive: true });
|
|
33519
|
+
const destination = join23(parent, certification.certificationId);
|
|
33520
|
+
const paths = {
|
|
33521
|
+
directory: destination,
|
|
33522
|
+
jsonPath: join23(destination, "certification.json"),
|
|
33523
|
+
markdownPath: join23(destination, "certification.md")
|
|
33524
|
+
};
|
|
33525
|
+
const json = serializeAbsoluteMobileReleaseCertification(certification);
|
|
33526
|
+
const markdown = renderAbsoluteMobileReleaseCertification(certification);
|
|
33527
|
+
const verifyExisting = async () => {
|
|
33528
|
+
const [existingJson, existingMarkdown] = await Promise.all([
|
|
33529
|
+
readFile21(paths.jsonPath, "utf8"),
|
|
33530
|
+
readFile21(paths.markdownPath, "utf8")
|
|
33531
|
+
]);
|
|
33532
|
+
if (existingJson !== json || existingMarkdown !== markdown)
|
|
33533
|
+
throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
|
|
33534
|
+
};
|
|
33535
|
+
if (await exists8(destination)) {
|
|
33536
|
+
await verifyExisting();
|
|
33537
|
+
return paths;
|
|
33538
|
+
}
|
|
33539
|
+
const staging = await mkdtemp10(join23(parent, ".certification-"));
|
|
33540
|
+
try {
|
|
33541
|
+
await Promise.all([
|
|
33542
|
+
writeFile17(join23(staging, "certification.json"), json, {
|
|
33543
|
+
flag: "wx"
|
|
33544
|
+
}),
|
|
33545
|
+
writeFile17(join23(staging, "certification.md"), markdown, {
|
|
33546
|
+
flag: "wx"
|
|
33547
|
+
})
|
|
33548
|
+
]);
|
|
33549
|
+
await rename13(staging, destination).catch(async (error) => {
|
|
33550
|
+
if (await exists8(destination))
|
|
33551
|
+
return;
|
|
33552
|
+
throw error;
|
|
33553
|
+
});
|
|
33554
|
+
} finally {
|
|
33555
|
+
await rm13(staging, { force: true, recursive: true });
|
|
33556
|
+
}
|
|
33557
|
+
await verifyExisting();
|
|
33558
|
+
return paths;
|
|
33559
|
+
};
|
|
33560
|
+
|
|
33561
|
+
// src/mobile/ciPromotionAudit.ts
|
|
33562
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
|
|
33563
|
+
var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 2;
|
|
33564
|
+
var MAX_GITHUB_OUTPUT_BYTES = 1048576;
|
|
33565
|
+
var MAX_AUDIT_JSON_BYTES = 1048576;
|
|
33566
|
+
var MAX_SEARCH_FILES = 20000;
|
|
33567
|
+
var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
|
|
33568
|
+
var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
33569
|
+
var DISPATCH_ID_PATTERN = /^amp_[a-f0-9]{32}$/u;
|
|
33570
|
+
var PROMOTION_DISCOVERY_ATTEMPTS = 60;
|
|
33571
|
+
var PROMOTION_DISCOVERY_INTERVAL_MS = 1000;
|
|
33572
|
+
var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33573
|
+
var requiredString = (value, name) => {
|
|
33574
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
33575
|
+
throw new TypeError(`${name} is invalid.`);
|
|
33576
|
+
return value.trim();
|
|
33577
|
+
};
|
|
33578
|
+
var optionalString = (value, name) => {
|
|
33579
|
+
if (value === null || value === undefined)
|
|
33580
|
+
return null;
|
|
33581
|
+
return requiredString(value, name);
|
|
33582
|
+
};
|
|
33583
|
+
var runId = (value, name) => {
|
|
33584
|
+
if (!RUN_ID_PATTERN.test(value))
|
|
33585
|
+
throw new TypeError(`${name} is invalid.`);
|
|
33586
|
+
return value;
|
|
33587
|
+
};
|
|
33588
|
+
var dispatchId = (value) => {
|
|
33589
|
+
if (!DISPATCH_ID_PATTERN.test(value))
|
|
33590
|
+
throw new TypeError("Mobile promotion dispatch ID is invalid.");
|
|
33591
|
+
return value;
|
|
33592
|
+
};
|
|
33593
|
+
var promotionRunTitle = (value) => `AbsoluteJS mobile promotion [${dispatchId(value)}]`;
|
|
33594
|
+
var promotionDiscoveryCommand = (workflow, repository) => [
|
|
33595
|
+
"gh",
|
|
33596
|
+
"run",
|
|
33597
|
+
"list",
|
|
33598
|
+
"--workflow",
|
|
33599
|
+
workflow,
|
|
33600
|
+
"--event",
|
|
33601
|
+
"workflow_dispatch",
|
|
33602
|
+
"--limit",
|
|
33603
|
+
"50",
|
|
33604
|
+
...repositoryArgs(repository),
|
|
33605
|
+
"--json",
|
|
33606
|
+
"databaseId,displayTitle,event,url"
|
|
33607
|
+
];
|
|
33608
|
+
var repositoryArgs = (repository) => {
|
|
33609
|
+
if (repository === undefined)
|
|
33610
|
+
return [];
|
|
33611
|
+
if (!REPOSITORY_PATTERN2.test(repository))
|
|
33612
|
+
throw new TypeError("GitHub repository must be owner/name.");
|
|
33613
|
+
return ["--repo", repository];
|
|
33614
|
+
};
|
|
33615
|
+
var projectPath = (projectRoot, requested) => {
|
|
33616
|
+
const root = resolve19(projectRoot);
|
|
33617
|
+
const path = resolve19(root, requested);
|
|
33618
|
+
const portable = relative17(root, path);
|
|
33619
|
+
if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute11(portable))
|
|
33620
|
+
throw new TypeError("Mobile CI audit output must remain inside the project.");
|
|
33621
|
+
return path;
|
|
33622
|
+
};
|
|
33623
|
+
var defaultRunner = async (command, options) => {
|
|
33624
|
+
const child = Bun.spawn(command, {
|
|
33625
|
+
cwd: options.cwd,
|
|
33626
|
+
env: options.env,
|
|
33627
|
+
stderr: "pipe",
|
|
33628
|
+
stdin: "ignore",
|
|
33629
|
+
stdout: "pipe"
|
|
33630
|
+
});
|
|
33631
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
33632
|
+
child.exited,
|
|
33633
|
+
new Response(child.stderr).text(),
|
|
33634
|
+
new Response(child.stdout).text()
|
|
33635
|
+
]);
|
|
33636
|
+
return { exitCode, stderr, stdout };
|
|
33637
|
+
};
|
|
33638
|
+
var requireCommand = async (run, command, options, label) => {
|
|
33639
|
+
const result = await run(command, options);
|
|
33640
|
+
if (result.exitCode !== 0)
|
|
33641
|
+
throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
|
|
33642
|
+
if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
|
|
33643
|
+
throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
|
|
33644
|
+
return result;
|
|
33645
|
+
};
|
|
33646
|
+
var parseJob = (value) => {
|
|
33647
|
+
if (!isRecord13(value))
|
|
33648
|
+
throw new TypeError("GitHub run job is invalid.");
|
|
33649
|
+
return {
|
|
33650
|
+
conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
|
|
33651
|
+
name: requiredString(value.name, "GitHub job name"),
|
|
33652
|
+
status: requiredString(value.status, "GitHub job status")
|
|
33653
|
+
};
|
|
33654
|
+
};
|
|
33655
|
+
var discoverAbsoluteMobilePromotionRun = async (options) => {
|
|
33656
|
+
const requestedDispatchId = dispatchId(options.dispatchId);
|
|
33657
|
+
const attempts = options.attempts ?? PROMOTION_DISCOVERY_ATTEMPTS;
|
|
33658
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 300)
|
|
33659
|
+
throw new TypeError("Mobile promotion discovery attempts are invalid.");
|
|
33660
|
+
const pollIntervalMs = options.pollIntervalMs ?? PROMOTION_DISCOVERY_INTERVAL_MS;
|
|
33661
|
+
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 0 || pollIntervalMs > 1e4)
|
|
33662
|
+
throw new TypeError("Mobile promotion discovery interval is invalid.");
|
|
33663
|
+
const sleep = options.sleep ?? Bun.sleep;
|
|
33664
|
+
const discover = async (attempt) => {
|
|
33665
|
+
const result = await requireCommand(options.run ?? defaultRunner, promotionDiscoveryCommand(options.workflow, options.repository), { cwd: options.projectRoot }, "GitHub promotion run discovery");
|
|
33666
|
+
const discovered = parseAbsoluteMobilePromotionRunList(JSON.parse(result.stdout), requestedDispatchId);
|
|
33667
|
+
if (discovered)
|
|
33668
|
+
return discovered;
|
|
33669
|
+
if (attempt >= attempts)
|
|
33670
|
+
throw new TypeError(`GitHub did not expose promotion dispatch ${requestedDispatchId} within ${attempts} attempts.`);
|
|
33671
|
+
await sleep(pollIntervalMs);
|
|
33672
|
+
return discover(attempt + 1);
|
|
33673
|
+
};
|
|
33674
|
+
return discover(1);
|
|
33675
|
+
};
|
|
33676
|
+
var inspectAbsoluteMobileCiRun = async (options) => {
|
|
33677
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
33678
|
+
const result = await requireCommand(options.run ?? defaultRunner, [
|
|
33679
|
+
"gh",
|
|
33680
|
+
"run",
|
|
33681
|
+
"view",
|
|
33682
|
+
requestedRunId,
|
|
33683
|
+
...repositoryArgs(options.repository),
|
|
33684
|
+
"--json",
|
|
33685
|
+
"conclusion,createdAt,databaseId,displayTitle,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
33686
|
+
], { cwd: options.projectRoot }, "GitHub workflow inspection");
|
|
33687
|
+
return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
|
|
33688
|
+
};
|
|
33689
|
+
var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
33690
|
+
if (!isRecord13(value) || !Array.isArray(value.jobs))
|
|
33691
|
+
throw new TypeError("GitHub workflow run response is invalid.");
|
|
33692
|
+
const { databaseId } = value;
|
|
33693
|
+
if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
|
|
33694
|
+
throw new TypeError("GitHub workflow run identity does not match.");
|
|
33695
|
+
return {
|
|
33696
|
+
conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
|
|
33697
|
+
createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
|
|
33698
|
+
displayTitle: requiredString(value.displayTitle, "GitHub run displayTitle"),
|
|
33699
|
+
headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
|
|
33700
|
+
headSha: requiredString(value.headSha, "GitHub run headSha"),
|
|
33701
|
+
jobs: value.jobs.map(parseJob),
|
|
33702
|
+
runId: requestedRunId,
|
|
33703
|
+
startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
|
|
33704
|
+
status: requiredString(value.status, "GitHub run status"),
|
|
33705
|
+
updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
|
|
33706
|
+
url: requiredString(value.url, "GitHub run URL"),
|
|
33707
|
+
workflowName: requiredString(value.workflowName, "GitHub workflow name")
|
|
33708
|
+
};
|
|
33709
|
+
};
|
|
33710
|
+
var parseAbsoluteMobilePromotionContext = (value) => {
|
|
33711
|
+
if (!isRecord13(value) || value.format !== 1 && value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
33712
|
+
throw new TypeError("Mobile promotion context is invalid.");
|
|
33713
|
+
const { platform: platform3 } = value;
|
|
33714
|
+
const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
|
|
33715
|
+
if (sourceArtifact !== `absolute-mobile-${platform3}`)
|
|
33716
|
+
throw new TypeError("Mobile promotion source artifact is invalid.");
|
|
33717
|
+
return {
|
|
33718
|
+
dispatchId: value.format === 1 ? null : dispatchId(requiredString(value.dispatchId, "promotion dispatch ID")),
|
|
33719
|
+
format: value.format,
|
|
33720
|
+
platform: platform3,
|
|
33721
|
+
promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
|
|
33722
|
+
sourceArtifact,
|
|
33723
|
+
sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
|
|
33724
|
+
};
|
|
33725
|
+
};
|
|
33726
|
+
var parseAbsoluteMobilePromotionRunList = (value, requestedDispatchId) => {
|
|
33727
|
+
if (!Array.isArray(value))
|
|
33728
|
+
throw new TypeError("GitHub workflow run list response is invalid.");
|
|
33729
|
+
const expectedTitle = promotionRunTitle(requestedDispatchId);
|
|
33730
|
+
const matches2 = value.filter((candidate) => isRecord13(candidate) && candidate.displayTitle === expectedTitle);
|
|
33731
|
+
if (matches2.length === 0)
|
|
33732
|
+
return null;
|
|
33733
|
+
if (matches2.length !== 1)
|
|
33734
|
+
throw new TypeError("GitHub returned multiple runs for one promotion dispatch identity.");
|
|
33735
|
+
const [match] = matches2;
|
|
33736
|
+
if (!isRecord13(match) || match.event !== "workflow_dispatch")
|
|
33737
|
+
throw new TypeError("GitHub promotion run identity is invalid.");
|
|
33738
|
+
const databaseId = typeof match.databaseId === "number" || typeof match.databaseId === "string" ? String(match.databaseId) : "";
|
|
33739
|
+
const requestedRunId = runId(databaseId, "GitHub promotion run ID");
|
|
33740
|
+
const url = requiredString(match.url, "GitHub promotion run URL");
|
|
33741
|
+
if (!url.endsWith(`/actions/runs/${requestedRunId}`))
|
|
33742
|
+
throw new TypeError("GitHub promotion run URL does not match its ID.");
|
|
33743
|
+
return {
|
|
33744
|
+
dispatchId: requestedDispatchId,
|
|
33745
|
+
runId: requestedRunId,
|
|
33746
|
+
url
|
|
33747
|
+
};
|
|
33748
|
+
};
|
|
33749
|
+
var watchAbsoluteMobileCiRun = async (options) => {
|
|
33750
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
33751
|
+
await requireCommand(options.run ?? defaultRunner, [
|
|
33752
|
+
"gh",
|
|
33753
|
+
"run",
|
|
33754
|
+
"watch",
|
|
33755
|
+
requestedRunId,
|
|
33756
|
+
...repositoryArgs(options.repository),
|
|
33757
|
+
"--exit-status"
|
|
33758
|
+
], { cwd: options.projectRoot }, "GitHub workflow watch");
|
|
33759
|
+
return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
|
|
33760
|
+
};
|
|
33761
|
+
var readBoundedJson = async (path, label) => {
|
|
33762
|
+
const metadata = await stat7(path).catch(() => null);
|
|
33763
|
+
if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
|
|
33764
|
+
throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
|
|
33765
|
+
const value = JSON.parse(await readFile22(path, "utf8"));
|
|
33766
|
+
return value;
|
|
33767
|
+
};
|
|
33768
|
+
var findFiles = async (root, name) => {
|
|
33769
|
+
const found = [];
|
|
33770
|
+
let visited = 0;
|
|
33771
|
+
const walk = async (directory) => {
|
|
33772
|
+
const entries = await readdir7(directory, { withFileTypes: true });
|
|
33773
|
+
await Promise.all(entries.map(async (entry) => {
|
|
33774
|
+
visited += 1;
|
|
33775
|
+
if (visited > MAX_SEARCH_FILES)
|
|
33776
|
+
throw new TypeError("Downloaded GitHub artifact contains too many files.");
|
|
33777
|
+
const path = join24(directory, entry.name);
|
|
33778
|
+
if (entry.isDirectory()) {
|
|
33779
|
+
await walk(path);
|
|
33780
|
+
return;
|
|
33781
|
+
}
|
|
33782
|
+
if (entry.isFile() && entry.name === name)
|
|
33783
|
+
found.push(path);
|
|
33784
|
+
}));
|
|
33785
|
+
};
|
|
33786
|
+
await walk(root);
|
|
33787
|
+
return found;
|
|
33788
|
+
};
|
|
33789
|
+
var inferPromotionPlatform = (status) => {
|
|
33790
|
+
const matched = status.jobs.flatMap((job) => {
|
|
33791
|
+
if (job.name === "Promote exact certified Android release")
|
|
33792
|
+
return ["android"];
|
|
33793
|
+
if (job.name === "Promote exact certified iOS release")
|
|
33794
|
+
return ["ios"];
|
|
33795
|
+
return [];
|
|
33796
|
+
});
|
|
33797
|
+
if (matched.length !== 1)
|
|
33798
|
+
throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
|
|
33799
|
+
const [platform3] = matched;
|
|
33800
|
+
if (!platform3)
|
|
33801
|
+
throw new TypeError("Mobile promotion platform is missing.");
|
|
33802
|
+
return platform3;
|
|
33803
|
+
};
|
|
33804
|
+
var onlyPath = (paths, label) => {
|
|
33805
|
+
const [path] = paths;
|
|
33806
|
+
if (paths.length !== 1 || !path)
|
|
33807
|
+
throw new TypeError(`${label} must contain exactly one matching file.`);
|
|
33808
|
+
return path;
|
|
33809
|
+
};
|
|
33810
|
+
var canonicalJson2 = (value) => {
|
|
33811
|
+
if (Array.isArray(value))
|
|
33812
|
+
return `[${value.map((entry) => canonicalJson2(entry)).join(",")}]`;
|
|
33813
|
+
if (isRecord13(value))
|
|
33814
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
|
|
33815
|
+
return JSON.stringify(value);
|
|
33816
|
+
};
|
|
33817
|
+
var publicationSummary = (value, platform3, release, certification) => {
|
|
33818
|
+
if (!isRecord13(value) || !isRecord13(value.record) || !isRecord13(value.record.metadata))
|
|
33819
|
+
throw new TypeError("Mobile promotion receipt is invalid.");
|
|
33820
|
+
const { metadata } = value.record;
|
|
33821
|
+
if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
|
|
33822
|
+
throw new TypeError("Mobile promotion receipt release identity does not match.");
|
|
33823
|
+
if (!isRecord13(value.certification) || !isRecord13(value.certification.provenance))
|
|
33824
|
+
throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
|
|
33825
|
+
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)
|
|
33826
|
+
throw new TypeError("Mobile promotion certification receipt does not match.");
|
|
33827
|
+
requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
|
|
33828
|
+
requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
|
|
33829
|
+
const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
|
|
33830
|
+
if (Number.isNaN(Date.parse(provenanceTime)))
|
|
33831
|
+
throw new TypeError("Mobile promotion provenance time is invalid.");
|
|
33832
|
+
if (typeof value.reused !== "boolean")
|
|
33833
|
+
throw new TypeError("Mobile promotion reuse status is invalid.");
|
|
33834
|
+
const channel = isRecord13(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
|
|
33835
|
+
if (isRecord13(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
|
|
33836
|
+
throw new TypeError("Mobile promotion channel receipt does not match.");
|
|
33837
|
+
const providerReceipt = platform3 === "android" ? value.googlePlay : value.appStoreConnect;
|
|
33838
|
+
if (providerReceipt === undefined)
|
|
33839
|
+
return {
|
|
33840
|
+
channel,
|
|
33841
|
+
provider: "registry",
|
|
33842
|
+
reused: value.reused,
|
|
33843
|
+
stage: null
|
|
33844
|
+
};
|
|
33845
|
+
if (!isRecord13(providerReceipt) || !isRecord13(providerReceipt.receipt))
|
|
33846
|
+
throw new TypeError("Mobile store promotion receipt is invalid.");
|
|
33847
|
+
if (typeof providerReceipt.reused !== "boolean")
|
|
33848
|
+
throw new TypeError("Mobile store promotion reuse status is invalid.");
|
|
33849
|
+
const expectedProvider = platform3 === "android" ? "google-play" : "app-store-connect";
|
|
33850
|
+
if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
|
|
33851
|
+
throw new TypeError("Mobile store promotion receipt does not match.");
|
|
33852
|
+
return {
|
|
33853
|
+
channel,
|
|
33854
|
+
provider: expectedProvider,
|
|
33855
|
+
reused: providerReceipt.reused,
|
|
33856
|
+
stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
|
|
33857
|
+
};
|
|
33858
|
+
};
|
|
33859
|
+
var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
|
|
33860
|
+
|
|
33861
|
+
- Status: **VERIFIED**
|
|
33862
|
+
- Platform: ${audit.platform}
|
|
33863
|
+
- Release: \`${audit.release.releaseId}\`
|
|
33864
|
+
- Artifact SHA-256: \`${audit.release.sha256}\`
|
|
33865
|
+
- Source workflow run: ${audit.github.sourceRunId}
|
|
33866
|
+
- Promotion workflow run: ${audit.github.promotionRunId}
|
|
33867
|
+
- Dispatch correlation: ${audit.github.dispatchId ?? "legacy workflow (not recorded)"}
|
|
33868
|
+
- Workflow: ${audit.github.workflowName}
|
|
33869
|
+
- GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
|
|
33870
|
+
- Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
|
|
33871
|
+
- Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
|
|
33872
|
+
- Verified at: ${audit.verifiedAt}
|
|
33873
|
+
|
|
33874
|
+
This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
|
|
33875
|
+
`;
|
|
33876
|
+
var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
33877
|
+
const runner = options.run ?? defaultRunner;
|
|
33878
|
+
const promotionRunId = runId(options.runId, "GitHub promotion run ID");
|
|
33879
|
+
const status = await inspectAbsoluteMobileCiRun({
|
|
33880
|
+
projectRoot: options.projectRoot,
|
|
33881
|
+
...options.repository ? { repository: options.repository } : {},
|
|
33882
|
+
run: runner,
|
|
33883
|
+
runId: promotionRunId
|
|
33884
|
+
});
|
|
33885
|
+
if (status.status !== "completed" || status.conclusion !== "success")
|
|
33886
|
+
throw new TypeError("Mobile promotion run has not completed successfully.");
|
|
33887
|
+
const platform3 = inferPromotionPlatform(status);
|
|
33888
|
+
const output = projectPath(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
|
|
33889
|
+
if (await access18(output).then(() => true).catch(() => false))
|
|
33890
|
+
throw new TypeError(`Mobile CI audit output already exists: ${output}`);
|
|
33891
|
+
await mkdir18(dirname16(output), { recursive: true });
|
|
33892
|
+
const staging = await mkdtemp11(join24(dirname16(output), ".promotion-audit-"));
|
|
33893
|
+
try {
|
|
33894
|
+
const evidenceRoot = join24(staging, "promotion-evidence");
|
|
33895
|
+
await mkdir18(evidenceRoot);
|
|
33896
|
+
await requireCommand(runner, [
|
|
33897
|
+
"gh",
|
|
33898
|
+
"run",
|
|
33899
|
+
"download",
|
|
33900
|
+
promotionRunId,
|
|
33901
|
+
...repositoryArgs(options.repository),
|
|
33902
|
+
"--name",
|
|
33903
|
+
`absolute-mobile-${platform3}-promotion-${promotionRunId}`,
|
|
33904
|
+
"--dir",
|
|
33905
|
+
evidenceRoot
|
|
33906
|
+
], { cwd: options.projectRoot }, "GitHub promotion artifact download");
|
|
33907
|
+
const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
|
|
33908
|
+
let context;
|
|
33909
|
+
if (contextPaths.length === 1)
|
|
33910
|
+
context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
|
|
33911
|
+
else if (contextPaths.length > 1)
|
|
33912
|
+
throw new TypeError("Promotion artifact contains duplicate contexts.");
|
|
33913
|
+
if (context && context.promotionRunId !== promotionRunId)
|
|
33914
|
+
throw new TypeError("Promotion context run identity does not match.");
|
|
33915
|
+
if (context && context.platform !== platform3)
|
|
33916
|
+
throw new TypeError("Promotion context platform does not match.");
|
|
33917
|
+
if (context?.dispatchId && status.displayTitle !== promotionRunTitle(context.dispatchId))
|
|
33918
|
+
throw new TypeError("Promotion dispatch identity does not match the GitHub run.");
|
|
33919
|
+
const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
|
|
33920
|
+
if (!sourceRunId)
|
|
33921
|
+
throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
|
|
33922
|
+
if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
|
|
33923
|
+
throw new TypeError("Requested source run does not match promotion context.");
|
|
33924
|
+
const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform3}`;
|
|
33925
|
+
const sourceRoot = join24(staging, "source-release");
|
|
33926
|
+
await mkdir18(sourceRoot);
|
|
33927
|
+
await requireCommand(runner, [
|
|
33928
|
+
"gh",
|
|
33929
|
+
"run",
|
|
33930
|
+
"download",
|
|
33931
|
+
sourceRunId,
|
|
33932
|
+
...repositoryArgs(options.repository),
|
|
33933
|
+
"--name",
|
|
33934
|
+
sourceArtifact,
|
|
33935
|
+
"--dir",
|
|
33936
|
+
sourceRoot
|
|
33937
|
+
], { cwd: options.projectRoot }, "GitHub source release download");
|
|
33938
|
+
const releaseMetadata = await findFiles(sourceRoot, "release.json");
|
|
33939
|
+
if (releaseMetadata.length !== 1)
|
|
33940
|
+
throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
|
|
33941
|
+
const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
|
|
33942
|
+
const release = platform3 === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
|
|
33943
|
+
const certificationPaths = await findFiles(evidenceRoot, "certification.json");
|
|
33944
|
+
const verificationPaths = await findFiles(evidenceRoot, "verification.json");
|
|
33945
|
+
const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
|
|
33946
|
+
if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
|
|
33947
|
+
throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
|
|
33948
|
+
const certificationPath = onlyPath(certificationPaths, "Promotion certification");
|
|
33949
|
+
const verificationPath = onlyPath(verificationPaths, "Promotion verification");
|
|
33950
|
+
const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
|
|
33951
|
+
const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
|
|
33952
|
+
const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
|
|
33953
|
+
const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
|
|
33954
|
+
if (verification.identity.sha !== status.headSha)
|
|
33955
|
+
throw new TypeError("Sigstore workflow commit does not match the promotion run.");
|
|
33956
|
+
if (options.repository && verification.identity.repository !== options.repository)
|
|
33957
|
+
throw new TypeError("Sigstore repository does not match the requested repository.");
|
|
33958
|
+
const bundlePath = `${certificationPath}.sigstore.json`;
|
|
33959
|
+
const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
|
|
33960
|
+
if (canonicalJson2(bundle) !== canonicalJson2(verification.bundle))
|
|
33961
|
+
throw new TypeError("Sigstore bundle does not match verification envelope.");
|
|
33962
|
+
await requireCommand(runner, [
|
|
33963
|
+
"bunx",
|
|
33964
|
+
"@absolutejs/attest@0.2.0",
|
|
33965
|
+
"verify-blobs",
|
|
33966
|
+
certificationPath
|
|
33967
|
+
], {
|
|
33968
|
+
cwd: options.projectRoot,
|
|
33969
|
+
env: {
|
|
33970
|
+
...process.env,
|
|
33971
|
+
GITHUB_REF: verification.identity.ref,
|
|
33972
|
+
GITHUB_REPOSITORY: verification.identity.repository,
|
|
33973
|
+
GITHUB_SHA: verification.identity.sha,
|
|
33974
|
+
GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
|
|
33975
|
+
}
|
|
33976
|
+
}, "Sigstore certification verification");
|
|
33977
|
+
const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform3, release, certification);
|
|
33978
|
+
const audit = {
|
|
33979
|
+
certification: {
|
|
33980
|
+
certificationId: certification.certificationId,
|
|
33981
|
+
requirement: certification.requirement,
|
|
33982
|
+
strength: certification.strength
|
|
33983
|
+
},
|
|
33984
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
33985
|
+
github: {
|
|
33986
|
+
conclusion: status.conclusion,
|
|
33987
|
+
dispatchId: context?.dispatchId ?? null,
|
|
33988
|
+
promotionRunId,
|
|
33989
|
+
repository: verification.identity.repository,
|
|
33990
|
+
sourceRunId,
|
|
33991
|
+
url: status.url,
|
|
33992
|
+
workflowName: status.workflowName
|
|
33993
|
+
},
|
|
33994
|
+
platform: platform3,
|
|
33995
|
+
provenance: verification.identity,
|
|
33996
|
+
publication,
|
|
33997
|
+
release: {
|
|
33998
|
+
appId: release.metadata.appId,
|
|
33999
|
+
bytes: release.metadata.bytes,
|
|
34000
|
+
engine: release.metadata.engine,
|
|
34001
|
+
releaseId: release.metadata.releaseId,
|
|
34002
|
+
runtime: release.metadata.runtime,
|
|
34003
|
+
sha256: release.metadata.sha256
|
|
34004
|
+
},
|
|
34005
|
+
status: "verified",
|
|
34006
|
+
verifiedAt: new Date().toISOString()
|
|
34007
|
+
};
|
|
34008
|
+
await Promise.all([
|
|
34009
|
+
writeFile18(join24(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
|
|
34010
|
+
`, { mode: 384 }),
|
|
34011
|
+
writeFile18(join24(staging, "audit.md"), renderAuditMarkdown(audit), {
|
|
34012
|
+
mode: 384
|
|
34013
|
+
})
|
|
34014
|
+
]);
|
|
34015
|
+
await rename14(staging, output);
|
|
34016
|
+
return { audit, directory: output };
|
|
34017
|
+
} catch (error) {
|
|
34018
|
+
await rm14(staging, { force: true, recursive: true });
|
|
34019
|
+
throw error;
|
|
34020
|
+
}
|
|
34021
|
+
};
|
|
34022
|
+
// src/mobile/ciWorkflow.ts
|
|
34023
|
+
import { existsSync as existsSync4 } from "fs";
|
|
34024
|
+
import { access as access19, mkdir as mkdir19, readFile as readFile23, writeFile as writeFile19 } from "fs/promises";
|
|
34025
|
+
import { dirname as dirname17, extname as extname5, relative as relative18, resolve as resolve20, sep as sep13 } from "path";
|
|
34026
|
+
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 4;
|
|
34027
|
+
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
34028
|
+
var CI_ENV_INDENTATION = 6;
|
|
34029
|
+
var RESERVED_SECRET_NAMES = new Set([
|
|
34030
|
+
"ABSOLUTE_ANDROID_KEYSTORE_BASE64",
|
|
34031
|
+
"ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
|
|
34032
|
+
"ABSOLUTE_ANDROID_KEY_ALIAS",
|
|
34033
|
+
"ABSOLUTE_ANDROID_KEY_PASSWORD",
|
|
34034
|
+
"ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
|
|
34035
|
+
"ABSOLUTE_IOS_CERTIFICATE_BASE64",
|
|
34036
|
+
"ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
|
|
34037
|
+
"ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
|
|
34038
|
+
"ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
|
|
34039
|
+
"ABSOLUTE_IOS_DEVELOPMENT_TEAM",
|
|
34040
|
+
"APP_STORE_CONNECT_ISSUER_ID",
|
|
34041
|
+
"APP_STORE_CONNECT_KEY_ID",
|
|
34042
|
+
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
34043
|
+
]);
|
|
34044
|
+
var exists9 = async (path) => {
|
|
34045
|
+
try {
|
|
34046
|
+
await access19(path);
|
|
34047
|
+
return true;
|
|
34048
|
+
} catch {
|
|
34049
|
+
return false;
|
|
34050
|
+
}
|
|
34051
|
+
};
|
|
34052
|
+
var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
34053
|
+
var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
34054
|
+
const root = resolve20(projectRoot);
|
|
34055
|
+
const path = resolve20(root, value);
|
|
34056
|
+
const portable = relative18(root, path).replaceAll("\\", "/");
|
|
34057
|
+
if (portable === ".." || portable.startsWith(`..${sep13}`) || portable.startsWith("../") || portable === "") {
|
|
34058
|
+
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
34059
|
+
}
|
|
34060
|
+
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
34061
|
+
throw new TypeError(`${field2} contains an unsafe path.`);
|
|
34062
|
+
if (!options.allowMissing && !existsSync4(path))
|
|
34063
|
+
throw new TypeError(`${field2} does not exist inside the project.`);
|
|
34064
|
+
return portable;
|
|
34065
|
+
};
|
|
34066
|
+
var workflowOutputPath = (projectRoot, value) => {
|
|
34067
|
+
const root = resolve20(projectRoot);
|
|
34068
|
+
const workflows = resolve20(root, ".github/workflows");
|
|
34069
|
+
const path = resolve20(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
34070
|
+
const portable = relative18(workflows, path);
|
|
34071
|
+
if (portable === ".." || portable.startsWith(`..${sep13}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
|
|
34072
|
+
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
34073
|
+
}
|
|
34074
|
+
return path;
|
|
34075
|
+
};
|
|
34076
|
+
var normalizeSecretEnvironment = (values = []) => {
|
|
34077
|
+
const names = [...new Set(values)].sort();
|
|
34078
|
+
for (const name of names) {
|
|
34079
|
+
if (!SECRET_NAME_PATTERN.test(name))
|
|
34080
|
+
throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
|
|
34081
|
+
if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
|
|
34082
|
+
throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
|
|
34083
|
+
}
|
|
34084
|
+
}
|
|
34085
|
+
return names;
|
|
34086
|
+
};
|
|
34087
|
+
var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
|
|
34088
|
+
`);
|
|
34089
|
+
var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
|
|
34090
|
+
ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
|
|
34091
|
+
ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
|
|
34092
|
+
var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
|
|
34093
|
+
args+=(--config "$ABSOLUTE_CONFIG_PATH")
|
|
34094
|
+
fi`;
|
|
34095
|
+
var installSteps = ` - name: Check out source
|
|
34096
|
+
uses: actions/checkout@v6
|
|
34097
|
+
- name: Install Bun
|
|
34098
|
+
uses: oven-sh/setup-bun@v2
|
|
34099
|
+
- name: Install exact dependencies
|
|
34100
|
+
run: bun ci`;
|
|
34101
|
+
var bundleAuditSteps = ` - name: Prepare production mobile bundle
|
|
34102
|
+
shell: bash
|
|
34103
|
+
run: |
|
|
34104
|
+
args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
|
|
34105
|
+
${appendConfigArgument}
|
|
34106
|
+
"\${args[@]}"
|
|
34107
|
+
- name: Run cryptographic mobile bundle audit
|
|
34108
|
+
id: mobile-bundle-audit
|
|
34109
|
+
continue-on-error: true
|
|
34110
|
+
shell: bash
|
|
34111
|
+
run: |
|
|
34112
|
+
mkdir -p .absolutejs/mobile-ci
|
|
34113
|
+
args=(bunx absolute mobile inspect --json --require-bundle)
|
|
34114
|
+
${appendConfigArgument}
|
|
34115
|
+
"\${args[@]}" > .absolutejs/mobile-ci/inspection.json
|
|
34116
|
+
- name: Upload mobile inspection report
|
|
34117
|
+
if: always()
|
|
34118
|
+
uses: actions/upload-artifact@v7
|
|
34119
|
+
with:
|
|
34120
|
+
name: absolute-mobile-inspection
|
|
34121
|
+
path: .absolutejs/mobile-ci/inspection.json
|
|
34122
|
+
if-no-files-found: error
|
|
34123
|
+
retention-days: 30
|
|
34124
|
+
include-hidden-files: true
|
|
34125
|
+
- name: Enforce mobile bundle audit
|
|
34126
|
+
if: steps.mobile-bundle-audit.outcome != 'success'
|
|
34127
|
+
run: exit 1`;
|
|
34128
|
+
var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
|
|
34129
|
+
id: mobile-release-audit
|
|
34130
|
+
continue-on-error: true
|
|
34131
|
+
shell: bash
|
|
34132
|
+
run: |
|
|
34133
|
+
mkdir -p .absolutejs/mobile-ci
|
|
34134
|
+
args=(bunx absolute mobile doctor release ${platform3} --json)
|
|
34135
|
+
${appendConfigArgument}
|
|
34136
|
+
"\${args[@]}" > .absolutejs/mobile-ci/compliance.json
|
|
34137
|
+
- name: Upload mobile compliance report
|
|
34138
|
+
if: always()
|
|
34139
|
+
uses: actions/upload-artifact@v7
|
|
34140
|
+
with:
|
|
34141
|
+
name: absolute-mobile-compliance-\${{ github.job }}
|
|
34142
|
+
path: .absolutejs/mobile-ci/compliance.json
|
|
34143
|
+
if-no-files-found: error
|
|
34144
|
+
retention-days: 30
|
|
34145
|
+
include-hidden-files: true
|
|
34146
|
+
- name: Enforce mobile release audit
|
|
34147
|
+
if: steps.mobile-release-audit.outcome != 'success'
|
|
34148
|
+
run: exit 1`;
|
|
34149
|
+
var platformInput = (platforms) => {
|
|
34150
|
+
const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
|
|
34151
|
+
return ` platform:
|
|
34152
|
+
description: Native platform to build
|
|
34153
|
+
required: true
|
|
34154
|
+
type: choice
|
|
34155
|
+
default: ${choices[0]}
|
|
33166
34156
|
options:
|
|
33167
34157
|
${choices.map((value) => ` - ${value}`).join(`
|
|
33168
34158
|
`)}`;
|
|
@@ -33192,6 +34182,10 @@ var publishingInputs = (platforms, includePublishing) => {
|
|
|
33192
34182
|
description: Source workflow run ID for promotion without rebuilding
|
|
33193
34183
|
required: false
|
|
33194
34184
|
type: string`,
|
|
34185
|
+
` dispatch_id:
|
|
34186
|
+
description: Unique promotion correlation ID supplied by the AbsoluteJS CLI
|
|
34187
|
+
required: false
|
|
34188
|
+
type: string`,
|
|
33195
34189
|
` certification_base64:
|
|
33196
34190
|
description: AbsoluteJS certification supplied by the promotion CLI
|
|
33197
34191
|
required: false
|
|
@@ -33564,6 +34558,10 @@ var promotionValidationSteps = ` - name: Validate promotion handoff
|
|
|
33564
34558
|
echo "Promotion requires a numeric source workflow run ID." >&2
|
|
33565
34559
|
exit 1
|
|
33566
34560
|
fi
|
|
34561
|
+
if [[ ! "$ABSOLUTE_PROMOTION_DISPATCH_ID" =~ ^amp_[a-f0-9]{32}$ ]]; then
|
|
34562
|
+
echo "Promotion requires a valid AbsoluteJS dispatch correlation ID." >&2
|
|
34563
|
+
exit 1
|
|
34564
|
+
fi
|
|
33567
34565
|
if [[ -z "$ABSOLUTE_CERTIFICATION_BASE64" ]]; then
|
|
33568
34566
|
echo "Promotion requires certification supplied by absolute mobile ci promote." >&2
|
|
33569
34567
|
exit 1
|
|
@@ -33575,6 +34573,10 @@ var promotionSharedSteps = (platform3) => ` - name: Restore exact ${platfor
|
|
|
33575
34573
|
path: .absolutejs/mobile/releases/${platform3}
|
|
33576
34574
|
run-id: \${{ inputs.source_run_id }}
|
|
33577
34575
|
github-token: \${{ github.token }}
|
|
34576
|
+
- name: Record bounded promotion context
|
|
34577
|
+
shell: bash
|
|
34578
|
+
run: |
|
|
34579
|
+
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(platform3)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform3}`)}, sourceRunId }, null, 2) + "\\n");'
|
|
33578
34580
|
- name: Import and verify exact certification
|
|
33579
34581
|
shell: bash
|
|
33580
34582
|
run: |
|
|
@@ -33612,7 +34614,9 @@ var androidPromotionJob = (options) => {
|
|
|
33612
34614
|
id-token: write
|
|
33613
34615
|
env:
|
|
33614
34616
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
34617
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
33615
34618
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
34619
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
33616
34620
|
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
33617
34621
|
ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
|
|
33618
34622
|
GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
|
|
@@ -33652,6 +34656,7 @@ ${promotionSharedSteps("android")}
|
|
|
33652
34656
|
name: absolute-mobile-android-promotion-\${{ github.run_id }}
|
|
33653
34657
|
path: |
|
|
33654
34658
|
.absolutejs/mobile-ci/imported-certification/
|
|
34659
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
33655
34660
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
33656
34661
|
if-no-files-found: error
|
|
33657
34662
|
retention-days: 90
|
|
@@ -33675,7 +34680,9 @@ var iosPromotionJob = (options) => {
|
|
|
33675
34680
|
id-token: write
|
|
33676
34681
|
env:
|
|
33677
34682
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
34683
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
33678
34684
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
34685
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
33679
34686
|
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
33680
34687
|
ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
|
|
33681
34688
|
APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
@@ -33717,6 +34724,7 @@ ${promotionSharedSteps("ios")}
|
|
|
33717
34724
|
name: absolute-mobile-ios-promotion-\${{ github.run_id }}
|
|
33718
34725
|
path: |
|
|
33719
34726
|
.absolutejs/mobile-ci/imported-certification/
|
|
34727
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
33720
34728
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
33721
34729
|
if-no-files-found: error
|
|
33722
34730
|
retention-days: 90
|
|
@@ -33753,9 +34761,9 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
|
|
|
33753
34761
|
].sort();
|
|
33754
34762
|
const includePublishing = options.includePublishing === true;
|
|
33755
34763
|
const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
|
|
33756
|
-
const serverEntry =
|
|
33757
|
-
const configPath = options.configPath ?
|
|
33758
|
-
const registryModule =
|
|
34764
|
+
const serverEntry = projectPath2(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
|
|
34765
|
+
const configPath = options.configPath ? projectPath2(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
|
|
34766
|
+
const registryModule = projectPath2(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
|
|
33759
34767
|
const environment = commandEnvironment2({
|
|
33760
34768
|
configPath,
|
|
33761
34769
|
registryModule,
|
|
@@ -33763,6 +34771,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
|
|
|
33763
34771
|
});
|
|
33764
34772
|
let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
|
|
33765
34773
|
name: AbsoluteJS Mobile
|
|
34774
|
+
run-name: \${{ inputs.operation == 'promote' && format('AbsoluteJS mobile promotion [{0}]', inputs.dispatch_id) || format('AbsoluteJS mobile {0}', github.event_name) }}
|
|
33766
34775
|
|
|
33767
34776
|
on:
|
|
33768
34777
|
pull_request:
|
|
@@ -33788,6 +34797,7 @@ jobs:
|
|
|
33788
34797
|
env:
|
|
33789
34798
|
${environment}${includePublishing ? `
|
|
33790
34799
|
ABSOLUTE_PROMOTION_PLATFORM: \${{ inputs.platform }}
|
|
34800
|
+
ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
|
|
33791
34801
|
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
33792
34802
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}` : ""}
|
|
33793
34803
|
steps:
|
|
@@ -33819,12 +34829,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
33819
34829
|
var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
33820
34830
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
33821
34831
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
33822
|
-
const previous = await
|
|
34832
|
+
const previous = await exists9(path) ? await readFile23(path, "utf8") : undefined;
|
|
33823
34833
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
33824
|
-
throw new TypeError(`${
|
|
34834
|
+
throw new TypeError(`${relative18(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
33825
34835
|
if (previous !== generated.workflow) {
|
|
33826
|
-
await
|
|
33827
|
-
await
|
|
34836
|
+
await mkdir19(dirname17(path), { recursive: true });
|
|
34837
|
+
await writeFile19(path, generated.workflow);
|
|
33828
34838
|
}
|
|
33829
34839
|
return {
|
|
33830
34840
|
changed: previous !== generated.workflow,
|
|
@@ -33842,20 +34852,20 @@ init_materializedBundle();
|
|
|
33842
34852
|
init_mobilePreview();
|
|
33843
34853
|
|
|
33844
34854
|
// src/mobile/nativeDeepLinks.ts
|
|
33845
|
-
import { readFile as
|
|
33846
|
-
import { join as
|
|
34855
|
+
import { readFile as readFile24, rename as rename15, writeFile as writeFile20 } from "fs/promises";
|
|
34856
|
+
import { join as join27 } from "path";
|
|
33847
34857
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->";
|
|
33848
34858
|
var END_MARKER = "<!-- absolutejs:deep-links:end -->";
|
|
33849
34859
|
var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
|
|
33850
34860
|
var NOT_FOUND = -1;
|
|
33851
34861
|
var escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
33852
34862
|
var writeChangedFile = async (path, source) => {
|
|
33853
|
-
const current = await
|
|
34863
|
+
const current = await readFile24(path, "utf8");
|
|
33854
34864
|
if (current === source)
|
|
33855
34865
|
return false;
|
|
33856
34866
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
33857
|
-
await
|
|
33858
|
-
await
|
|
34867
|
+
await writeFile20(temporary, source, { flag: "wx" });
|
|
34868
|
+
await rename15(temporary, path);
|
|
33859
34869
|
return true;
|
|
33860
34870
|
};
|
|
33861
34871
|
var replaceManagedRegion = (source, region, insertAt) => {
|
|
@@ -33900,8 +34910,8 @@ ${hosts}
|
|
|
33900
34910
|
`;
|
|
33901
34911
|
};
|
|
33902
34912
|
var configureAndroid = async (config) => {
|
|
33903
|
-
const path =
|
|
33904
|
-
const source = await
|
|
34913
|
+
const path = join27(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
34914
|
+
const source = await readFile24(path, "utf8");
|
|
33905
34915
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
33906
34916
|
if (mainActivity === NOT_FOUND) {
|
|
33907
34917
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -33926,8 +34936,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
|
|
|
33926
34936
|
${END_MARKER}
|
|
33927
34937
|
`;
|
|
33928
34938
|
var configureIosInfo = async (config) => {
|
|
33929
|
-
const path =
|
|
33930
|
-
const source = await
|
|
34939
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
34940
|
+
const source = await readFile24(path, "utf8");
|
|
33931
34941
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
33932
34942
|
${END_MARKER}
|
|
33933
34943
|
`;
|
|
@@ -33950,10 +34960,10 @@ ${domains}
|
|
|
33950
34960
|
`;
|
|
33951
34961
|
};
|
|
33952
34962
|
var configureIosEntitlements = async (config) => {
|
|
33953
|
-
const path =
|
|
34963
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
33954
34964
|
let current = "";
|
|
33955
34965
|
try {
|
|
33956
|
-
current = await
|
|
34966
|
+
current = await readFile24(path, "utf8");
|
|
33957
34967
|
} catch (error) {
|
|
33958
34968
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
33959
34969
|
throw error;
|
|
@@ -33963,13 +34973,13 @@ var configureIosEntitlements = async (config) => {
|
|
|
33963
34973
|
if (current === source)
|
|
33964
34974
|
return false;
|
|
33965
34975
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
33966
|
-
await
|
|
33967
|
-
await
|
|
34976
|
+
await writeFile20(temporary, source, { flag: "wx" });
|
|
34977
|
+
await rename15(temporary, path);
|
|
33968
34978
|
return true;
|
|
33969
34979
|
};
|
|
33970
34980
|
var configureIosProject = async (config) => {
|
|
33971
|
-
const path =
|
|
33972
|
-
const source = await
|
|
34981
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
34982
|
+
const source = await readFile24(path, "utf8");
|
|
33973
34983
|
const declarations = [
|
|
33974
34984
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
33975
34985
|
].map((match) => match[1]);
|
|
@@ -34008,8 +35018,8 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
|
|
|
34008
35018
|
};
|
|
34009
35019
|
// src/mobile/nativeDeviceCapabilities.ts
|
|
34010
35020
|
init_deviceCapabilities();
|
|
34011
|
-
import { readFile as
|
|
34012
|
-
import { join as
|
|
35021
|
+
import { readFile as readFile25, rename as rename16, writeFile as writeFile21 } from "fs/promises";
|
|
35022
|
+
import { join as join28 } from "path";
|
|
34013
35023
|
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
|
|
34014
35024
|
var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
|
|
34015
35025
|
var NOT_FOUND2 = -1;
|
|
@@ -34019,12 +35029,12 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
|
|
|
34019
35029
|
var PUSH_END_MARKER = "absolutejs:push-notifications:end";
|
|
34020
35030
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
34021
35031
|
var writeChangedFile2 = async (path, source) => {
|
|
34022
|
-
const current = await
|
|
35032
|
+
const current = await readFile25(path, "utf8");
|
|
34023
35033
|
if (current === source)
|
|
34024
35034
|
return false;
|
|
34025
35035
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34026
|
-
await
|
|
34027
|
-
await
|
|
35036
|
+
await writeFile21(temporary, source, { flag: "wx" });
|
|
35037
|
+
await rename16(temporary, path);
|
|
34028
35038
|
return true;
|
|
34029
35039
|
};
|
|
34030
35040
|
var writeOptionalChangedFile = async (path, source) => {
|
|
@@ -34032,14 +35042,14 @@ var writeOptionalChangedFile = async (path, source) => {
|
|
|
34032
35042
|
if (current === source)
|
|
34033
35043
|
return false;
|
|
34034
35044
|
if (current === null) {
|
|
34035
|
-
await
|
|
35045
|
+
await writeFile21(path, source, { flag: "wx" });
|
|
34036
35046
|
return true;
|
|
34037
35047
|
}
|
|
34038
35048
|
return writeChangedFile2(path, source);
|
|
34039
35049
|
};
|
|
34040
35050
|
var optionalSource = async (path) => {
|
|
34041
35051
|
try {
|
|
34042
|
-
return await
|
|
35052
|
+
return await readFile25(path, "utf8");
|
|
34043
35053
|
} catch (error) {
|
|
34044
35054
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34045
35055
|
return null;
|
|
@@ -34153,15 +35163,15 @@ var writeIosPrivacyManifest = async (path, current, source) => {
|
|
|
34153
35163
|
return false;
|
|
34154
35164
|
if (current !== null)
|
|
34155
35165
|
return writeChangedFile2(path, source);
|
|
34156
|
-
await
|
|
35166
|
+
await writeFile21(path, source, { flag: "wx" });
|
|
34157
35167
|
return true;
|
|
34158
35168
|
};
|
|
34159
35169
|
var configureIosPrivacyProject = async (config, requirements) => {
|
|
34160
35170
|
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
34161
35171
|
return false;
|
|
34162
|
-
const
|
|
34163
|
-
const project = await
|
|
34164
|
-
return writeChangedFile2(
|
|
35172
|
+
const projectPath3 = join28(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
35173
|
+
const project = await readFile25(projectPath3, "utf8");
|
|
35174
|
+
return writeChangedFile2(projectPath3, addIosPrivacyProjectReference(project));
|
|
34165
35175
|
};
|
|
34166
35176
|
var addIosPrivacyProjectReference = (source) => {
|
|
34167
35177
|
const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
|
|
@@ -34212,8 +35222,8 @@ ${next.slice(index)}`;
|
|
|
34212
35222
|
return next;
|
|
34213
35223
|
};
|
|
34214
35224
|
var configureIos2 = async (config, plan) => {
|
|
34215
|
-
const path =
|
|
34216
|
-
const source = await
|
|
35225
|
+
const path = join28(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
35226
|
+
const source = await readFile25(path, "utf8");
|
|
34217
35227
|
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
34218
35228
|
const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
|
|
34219
35229
|
const ownedStart = source.indexOf(START_MARKER2);
|
|
@@ -34233,7 +35243,7 @@ ${content}
|
|
|
34233
35243
|
${END_MARKER2}
|
|
34234
35244
|
` : "";
|
|
34235
35245
|
const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
|
|
34236
|
-
const privacyPath =
|
|
35246
|
+
const privacyPath = join28(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
|
|
34237
35247
|
const privacyCurrent = await optionalSource(privacyPath);
|
|
34238
35248
|
const privacySource = privacyManifestSource(privacyCurrent, requirements);
|
|
34239
35249
|
const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
|
|
@@ -34244,7 +35254,7 @@ ${content}
|
|
|
34244
35254
|
return infoChanged || privacyChanged || projectChanged || pushChanged;
|
|
34245
35255
|
};
|
|
34246
35256
|
var configureIosPushNotifications = async (config, enabled) => {
|
|
34247
|
-
const entitlementsPath =
|
|
35257
|
+
const entitlementsPath = join28(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
34248
35258
|
const entitlements = await optionalSource(entitlementsPath);
|
|
34249
35259
|
if (entitlements === null && !enabled)
|
|
34250
35260
|
return false;
|
|
@@ -34256,7 +35266,7 @@ var configureIosPushNotifications = async (config, enabled) => {
|
|
|
34256
35266
|
<!-- ${PUSH_END_MARKER} -->
|
|
34257
35267
|
` : "";
|
|
34258
35268
|
const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
|
|
34259
|
-
const delegatePath =
|
|
35269
|
+
const delegatePath = join28(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
34260
35270
|
const delegate = await optionalSource(delegatePath);
|
|
34261
35271
|
if (delegate === null && !enabled)
|
|
34262
35272
|
return false;
|
|
@@ -34305,8 +35315,8 @@ var replacePushRegion = (source, region, insertion) => {
|
|
|
34305
35315
|
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
34306
35316
|
};
|
|
34307
35317
|
var configureAndroid2 = async (config, plan) => {
|
|
34308
|
-
const path =
|
|
34309
|
-
const source = await
|
|
35318
|
+
const path = join28(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
35319
|
+
const source = await readFile25(path, "utf8");
|
|
34310
35320
|
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
34311
35321
|
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
34312
35322
|
`);
|
|
@@ -34341,7 +35351,7 @@ ${content}
|
|
|
34341
35351
|
throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
|
|
34342
35352
|
const [manifestChanged, firebaseChanged] = await Promise.all([
|
|
34343
35353
|
writeChangedFile2(path, nextManifest),
|
|
34344
|
-
writeOptionalChangedFile(
|
|
35354
|
+
writeOptionalChangedFile(join28(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
|
|
34345
35355
|
]);
|
|
34346
35356
|
return manifestChanged || firebaseChanged;
|
|
34347
35357
|
};
|
|
@@ -34360,14 +35370,14 @@ init_nativeAuth();
|
|
|
34360
35370
|
|
|
34361
35371
|
// src/mobile/nativeUpdates.ts
|
|
34362
35372
|
import {
|
|
34363
|
-
mkdir as
|
|
34364
|
-
readFile as
|
|
34365
|
-
readdir as
|
|
34366
|
-
rename as
|
|
34367
|
-
rm as
|
|
34368
|
-
writeFile as
|
|
35373
|
+
mkdir as mkdir20,
|
|
35374
|
+
readFile as readFile26,
|
|
35375
|
+
readdir as readdir8,
|
|
35376
|
+
rename as rename17,
|
|
35377
|
+
rm as rm15,
|
|
35378
|
+
writeFile as writeFile22
|
|
34369
35379
|
} from "fs/promises";
|
|
34370
|
-
import { dirname as
|
|
35380
|
+
import { dirname as dirname19, join as join29 } from "path";
|
|
34371
35381
|
var START = "// absolutejs:mobile-updates:start";
|
|
34372
35382
|
var END = "// absolutejs:mobile-updates:end";
|
|
34373
35383
|
var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
|
|
@@ -34375,32 +35385,32 @@ var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
|
|
|
34375
35385
|
var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
|
|
34376
35386
|
var optionalSource2 = async (path) => {
|
|
34377
35387
|
try {
|
|
34378
|
-
return await
|
|
35388
|
+
return await readFile26(path, "utf8");
|
|
34379
35389
|
} catch (error) {
|
|
34380
35390
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34381
35391
|
return null;
|
|
34382
35392
|
throw error;
|
|
34383
35393
|
}
|
|
34384
35394
|
};
|
|
34385
|
-
var
|
|
35395
|
+
var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34386
35396
|
var writeChanged = async (path, source) => {
|
|
34387
35397
|
const current = await optionalSource2(path);
|
|
34388
35398
|
if (current === source)
|
|
34389
35399
|
return false;
|
|
34390
|
-
await
|
|
35400
|
+
await mkdir20(dirname19(path), { recursive: true });
|
|
34391
35401
|
if (current === null) {
|
|
34392
|
-
await
|
|
35402
|
+
await writeFile22(path, source, { flag: "wx" });
|
|
34393
35403
|
return true;
|
|
34394
35404
|
}
|
|
34395
35405
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34396
|
-
await
|
|
34397
|
-
await
|
|
35406
|
+
await writeFile22(temporary, source, { flag: "wx" });
|
|
35407
|
+
await rename17(temporary, path);
|
|
34398
35408
|
return true;
|
|
34399
35409
|
};
|
|
34400
35410
|
var removeOwnedFile = async (path) => {
|
|
34401
35411
|
if (await optionalSource2(path) === null)
|
|
34402
35412
|
return false;
|
|
34403
|
-
await
|
|
35413
|
+
await rm15(path);
|
|
34404
35414
|
return true;
|
|
34405
35415
|
};
|
|
34406
35416
|
var replaceMarkedRegion = (source, start, end, region, insertion, error) => {
|
|
@@ -34537,9 +35547,9 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
|
|
|
34537
35547
|
${PLUGIN_END}
|
|
34538
35548
|
`;
|
|
34539
35549
|
var configureIos3 = async (config, enabled) => {
|
|
34540
|
-
const path =
|
|
34541
|
-
const capacitorConfigPath =
|
|
34542
|
-
let source = await
|
|
35550
|
+
const path = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
35551
|
+
const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
35552
|
+
let source = await readFile26(path, "utf8");
|
|
34543
35553
|
const launch2 = source.indexOf("didFinishLaunchingWithOptions");
|
|
34544
35554
|
const brace = launch2 < 0 ? -1 : source.indexOf("{", launch2);
|
|
34545
35555
|
const launchRegion = enabled ? ` ${START}
|
|
@@ -34550,11 +35560,11 @@ var configureIos3 = async (config, enabled) => {
|
|
|
34550
35560
|
`, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
|
|
34551
35561
|
const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
|
|
34552
35562
|
source = replaceMarkedRegion(source, PLUGIN_START, PLUGIN_END, plugin, source.length, "Could not find a safe iOS location for the mobile update watchdog.");
|
|
34553
|
-
const capacitorConfigSource2 = await
|
|
35563
|
+
const capacitorConfigSource2 = await readFile26(capacitorConfigPath, "utf8");
|
|
34554
35564
|
let capacitorConfig;
|
|
34555
35565
|
try {
|
|
34556
35566
|
const value = JSON.parse(capacitorConfigSource2);
|
|
34557
|
-
if (!
|
|
35567
|
+
if (!isRecord15(value))
|
|
34558
35568
|
throw new TypeError;
|
|
34559
35569
|
capacitorConfig = value;
|
|
34560
35570
|
} catch {
|
|
@@ -34723,19 +35733,19 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
|
|
|
34723
35733
|
var androidActivityInRoot = async (root) => {
|
|
34724
35734
|
let entries;
|
|
34725
35735
|
try {
|
|
34726
|
-
entries = await
|
|
35736
|
+
entries = await readdir8(root, { recursive: true });
|
|
34727
35737
|
} catch (error) {
|
|
34728
35738
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34729
35739
|
return;
|
|
34730
35740
|
throw error;
|
|
34731
35741
|
}
|
|
34732
35742
|
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
34733
|
-
return candidate ?
|
|
35743
|
+
return candidate ? join29(root, candidate) : undefined;
|
|
34734
35744
|
};
|
|
34735
35745
|
var androidActivity = async (config) => {
|
|
34736
35746
|
const roots = [
|
|
34737
|
-
|
|
34738
|
-
|
|
35747
|
+
join29(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
35748
|
+
join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
34739
35749
|
];
|
|
34740
35750
|
const candidates = await Promise.all(roots.map(androidActivityInRoot));
|
|
34741
35751
|
const candidate = candidates.find((value) => value !== undefined);
|
|
@@ -34834,12 +35844,12 @@ var injectAndroidActivity = (source, enabled) => {
|
|
|
34834
35844
|
};
|
|
34835
35845
|
var configureAndroid3 = async (config, enabled) => {
|
|
34836
35846
|
const activityPath = await androidActivity(config);
|
|
34837
|
-
const activity = await
|
|
35847
|
+
const activity = await readFile26(activityPath, "utf8");
|
|
34838
35848
|
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
34839
35849
|
if (!packageName)
|
|
34840
35850
|
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
34841
35851
|
const packagePath = packageName.replaceAll(".", "/");
|
|
34842
|
-
const pluginPath =
|
|
35852
|
+
const pluginPath = join29(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
|
|
34843
35853
|
const changed = await Promise.all([
|
|
34844
35854
|
writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
|
|
34845
35855
|
enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
|
|
@@ -34859,14 +35869,14 @@ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) =>
|
|
|
34859
35869
|
};
|
|
34860
35870
|
// src/mobile/nativeObservability.ts
|
|
34861
35871
|
import {
|
|
34862
|
-
mkdir as
|
|
34863
|
-
readFile as
|
|
34864
|
-
readdir as
|
|
34865
|
-
rename as
|
|
34866
|
-
rm as
|
|
34867
|
-
writeFile as
|
|
35872
|
+
mkdir as mkdir21,
|
|
35873
|
+
readFile as readFile27,
|
|
35874
|
+
readdir as readdir9,
|
|
35875
|
+
rename as rename18,
|
|
35876
|
+
rm as rm16,
|
|
35877
|
+
writeFile as writeFile23
|
|
34868
35878
|
} from "fs/promises";
|
|
34869
|
-
import { dirname as
|
|
35879
|
+
import { dirname as dirname20, join as join30 } from "path";
|
|
34870
35880
|
var START2 = "// absolutejs:native-observability:start";
|
|
34871
35881
|
var END2 = "// absolutejs:native-observability:end";
|
|
34872
35882
|
var PLUGIN_START2 = "// absolutejs:native-observability-plugin:start";
|
|
@@ -34874,7 +35884,7 @@ var PLUGIN_END2 = "// absolutejs:native-observability-plugin:end";
|
|
|
34874
35884
|
var ANDROID_PLUGIN2 = "AbsoluteMobileObservabilityPlugin.java";
|
|
34875
35885
|
var optionalSource3 = async (path) => {
|
|
34876
35886
|
try {
|
|
34877
|
-
return await
|
|
35887
|
+
return await readFile27(path, "utf8");
|
|
34878
35888
|
} catch (error) {
|
|
34879
35889
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34880
35890
|
return null;
|
|
@@ -34885,23 +35895,23 @@ var writeChanged2 = async (path, source) => {
|
|
|
34885
35895
|
const current = await optionalSource3(path);
|
|
34886
35896
|
if (current === source)
|
|
34887
35897
|
return false;
|
|
34888
|
-
await
|
|
35898
|
+
await mkdir21(dirname20(path), { recursive: true });
|
|
34889
35899
|
if (current === null) {
|
|
34890
|
-
await
|
|
35900
|
+
await writeFile23(path, source, { flag: "wx" });
|
|
34891
35901
|
return true;
|
|
34892
35902
|
}
|
|
34893
35903
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34894
|
-
await
|
|
34895
|
-
await
|
|
35904
|
+
await writeFile23(temporary, source, { flag: "wx" });
|
|
35905
|
+
await rename18(temporary, path);
|
|
34896
35906
|
return true;
|
|
34897
35907
|
};
|
|
34898
35908
|
var removeOwnedFile2 = async (path) => {
|
|
34899
35909
|
if (await optionalSource3(path) === null)
|
|
34900
35910
|
return false;
|
|
34901
|
-
await
|
|
35911
|
+
await rm16(path);
|
|
34902
35912
|
return true;
|
|
34903
35913
|
};
|
|
34904
|
-
var
|
|
35914
|
+
var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34905
35915
|
var replaceMarkedRegion2 = (source, start, end, region, insertion, error) => {
|
|
34906
35916
|
const existingStart = source.indexOf(start);
|
|
34907
35917
|
const existingEnd = source.indexOf(end);
|
|
@@ -35034,9 +36044,9 @@ public final class AbsoluteMobileObservabilityPlugin: CAPPlugin, CAPBridgedPlugi
|
|
|
35034
36044
|
${PLUGIN_END2}
|
|
35035
36045
|
`;
|
|
35036
36046
|
var configureIos4 = async (config, enabled) => {
|
|
35037
|
-
const path =
|
|
35038
|
-
const capacitorConfigPath =
|
|
35039
|
-
let source = await
|
|
36047
|
+
const path = join30(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
36048
|
+
const capacitorConfigPath = join30(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
36049
|
+
let source = await readFile27(path, "utf8");
|
|
35040
36050
|
if (enabled && !source.includes("import MetricKit")) {
|
|
35041
36051
|
const capacitorImport = source.lastIndexOf("import Capacitor");
|
|
35042
36052
|
if (capacitorImport < 0)
|
|
@@ -35050,11 +36060,11 @@ ${source.slice(newline + 1)}`;
|
|
|
35050
36060
|
source = source.replace(`import MetricKit
|
|
35051
36061
|
`, "");
|
|
35052
36062
|
source = replaceMarkedRegion2(source, PLUGIN_START2, PLUGIN_END2, enabled ? iosPluginRegion2 : "", source.length, "Could not find a safe iOS location for native observability.");
|
|
35053
|
-
const capacitorConfigSource2 = await
|
|
36063
|
+
const capacitorConfigSource2 = await readFile27(capacitorConfigPath, "utf8");
|
|
35054
36064
|
let capacitorConfig;
|
|
35055
36065
|
try {
|
|
35056
36066
|
const value = JSON.parse(capacitorConfigSource2);
|
|
35057
|
-
if (!
|
|
36067
|
+
if (!isRecord16(value))
|
|
35058
36068
|
throw new TypeError;
|
|
35059
36069
|
capacitorConfig = value;
|
|
35060
36070
|
} catch {
|
|
@@ -35288,19 +36298,19 @@ public final class AbsoluteMobileObservabilityPlugin extends Plugin {
|
|
|
35288
36298
|
var androidActivityInRoot2 = async (root) => {
|
|
35289
36299
|
let entries;
|
|
35290
36300
|
try {
|
|
35291
|
-
entries = await
|
|
36301
|
+
entries = await readdir9(root, { recursive: true });
|
|
35292
36302
|
} catch (error) {
|
|
35293
36303
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
35294
36304
|
return;
|
|
35295
36305
|
throw error;
|
|
35296
36306
|
}
|
|
35297
36307
|
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
35298
|
-
return candidate ?
|
|
36308
|
+
return candidate ? join30(root, candidate) : undefined;
|
|
35299
36309
|
};
|
|
35300
36310
|
var androidActivity2 = async (config) => {
|
|
35301
36311
|
const roots = [
|
|
35302
|
-
|
|
35303
|
-
|
|
36312
|
+
join30(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
36313
|
+
join30(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
35304
36314
|
];
|
|
35305
36315
|
const candidates = await Promise.all(roots.map(androidActivityInRoot2));
|
|
35306
36316
|
const candidate = candidates.find((value) => value !== undefined);
|
|
@@ -35349,148 +36359,14 @@ var injectAndroidActivity2 = (source, enabled) => {
|
|
|
35349
36359
|
return `${source.slice(0, from)}${source.slice(through)}`;
|
|
35350
36360
|
return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
35351
36361
|
}
|
|
35352
|
-
if (!enabled)
|
|
35353
|
-
return source;
|
|
35354
|
-
const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
|
|
35355
|
-
if (onCreate >= 0) {
|
|
35356
|
-
const brace = source.indexOf("{", onCreate);
|
|
35357
|
-
const insert = source.indexOf(`
|
|
35358
|
-
`, brace) + 1;
|
|
35359
|
-
return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
|
|
35360
|
-
}
|
|
35361
|
-
const close = source.lastIndexOf("}");
|
|
35362
|
-
if (close < 0)
|
|
35363
|
-
throw new TypeError("Could not find the Android MainActivity class body.");
|
|
35364
|
-
const separator = source[close - 1] === `
|
|
35365
|
-
` ? "" : `
|
|
35366
|
-
`;
|
|
35367
|
-
return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
|
|
35368
|
-
};
|
|
35369
|
-
var configureAndroid4 = async (config, enabled) => {
|
|
35370
|
-
const activityPath = await androidActivity2(config);
|
|
35371
|
-
const activity = await readFile24(activityPath, "utf8");
|
|
35372
|
-
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
35373
|
-
if (!packageName)
|
|
35374
|
-
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
35375
|
-
const pluginPath = join28(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
|
|
35376
|
-
const changed = await Promise.all([
|
|
35377
|
-
writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
|
|
35378
|
-
enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
|
|
35379
|
-
]);
|
|
35380
|
-
return changed.some(Boolean);
|
|
35381
|
-
};
|
|
35382
|
-
var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
|
|
35383
|
-
if (config.engine !== "capacitor")
|
|
35384
|
-
return { changed: false };
|
|
35385
|
-
const enabled = config.observability !== undefined;
|
|
35386
|
-
const changed = [];
|
|
35387
|
-
if (platforms.includes("ios"))
|
|
35388
|
-
changed.push(await configureIos4(config, enabled));
|
|
35389
|
-
if (platforms.includes("android"))
|
|
35390
|
-
changed.push(await configureAndroid4(config, enabled));
|
|
35391
|
-
return { changed: changed.some(Boolean) };
|
|
35392
|
-
};
|
|
35393
|
-
// src/mobile/nativeReleaseReadiness.ts
|
|
35394
|
-
import { mkdir as mkdir19, readFile as readFile25, readdir as readdir9, rename as rename17, writeFile as writeFile21 } from "fs/promises";
|
|
35395
|
-
import { dirname as dirname19, join as join29 } from "path";
|
|
35396
|
-
var START3 = "// absolutejs:release-readiness:start";
|
|
35397
|
-
var END3 = "// absolutejs:release-readiness:end";
|
|
35398
|
-
var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
|
|
35399
|
-
var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
|
|
35400
|
-
var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
|
|
35401
|
-
var optionalSource4 = async (path) => {
|
|
35402
|
-
try {
|
|
35403
|
-
return await readFile25(path, "utf8");
|
|
35404
|
-
} catch (error) {
|
|
35405
|
-
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
35406
|
-
return null;
|
|
35407
|
-
throw error;
|
|
35408
|
-
}
|
|
35409
|
-
};
|
|
35410
|
-
var writeChanged3 = async (path, source) => {
|
|
35411
|
-
const current = await optionalSource4(path);
|
|
35412
|
-
if (current === source)
|
|
35413
|
-
return false;
|
|
35414
|
-
await mkdir19(dirname19(path), { recursive: true });
|
|
35415
|
-
if (current === null) {
|
|
35416
|
-
await writeFile21(path, source, { flag: "wx" });
|
|
35417
|
-
return true;
|
|
35418
|
-
}
|
|
35419
|
-
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
35420
|
-
await writeFile21(temporary, source, { flag: "wx" });
|
|
35421
|
-
await rename17(temporary, path);
|
|
35422
|
-
return true;
|
|
35423
|
-
};
|
|
35424
|
-
var activityInRoot = async (root) => {
|
|
35425
|
-
let entries;
|
|
35426
|
-
try {
|
|
35427
|
-
entries = await readdir9(root, { recursive: true });
|
|
35428
|
-
} catch (error) {
|
|
35429
|
-
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
35430
|
-
return;
|
|
35431
|
-
throw error;
|
|
35432
|
-
}
|
|
35433
|
-
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
35434
|
-
return candidate ? join29(root, candidate) : undefined;
|
|
35435
|
-
};
|
|
35436
|
-
var activityPath = async (config) => {
|
|
35437
|
-
const roots = [
|
|
35438
|
-
join29(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
35439
|
-
join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
35440
|
-
];
|
|
35441
|
-
const candidates = await Promise.all(roots.map(activityInRoot));
|
|
35442
|
-
const candidate = candidates.find((value) => value !== undefined);
|
|
35443
|
-
if (candidate)
|
|
35444
|
-
return candidate;
|
|
35445
|
-
throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
|
|
35446
|
-
};
|
|
35447
|
-
var registrationRegion = (kotlin, generatedMethod) => {
|
|
35448
|
-
if (generatedMethod)
|
|
35449
|
-
return kotlin ? ` ${START3}
|
|
35450
|
-
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
35451
|
-
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
35452
|
-
super.onCreate(savedInstanceState)
|
|
35453
|
-
}
|
|
35454
|
-
${END3}
|
|
35455
|
-
` : ` ${START3}
|
|
35456
|
-
@Override
|
|
35457
|
-
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
35458
|
-
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
35459
|
-
super.onCreate(savedInstanceState);
|
|
35460
|
-
}
|
|
35461
|
-
${END3}
|
|
35462
|
-
`;
|
|
35463
|
-
return kotlin ? ` ${START3}
|
|
35464
|
-
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
35465
|
-
${END3}
|
|
35466
|
-
` : ` ${START3}
|
|
35467
|
-
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
35468
|
-
${END3}
|
|
35469
|
-
`;
|
|
35470
|
-
};
|
|
35471
|
-
var injectRegistration = (source) => {
|
|
35472
|
-
const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
|
|
35473
|
-
const existingStart = source.indexOf(START3);
|
|
35474
|
-
const existingEnd = source.indexOf(END3);
|
|
35475
|
-
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
35476
|
-
throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
|
|
35477
|
-
if (existingStart >= 0) {
|
|
35478
|
-
const from = source.lastIndexOf(`
|
|
35479
|
-
`, existingStart) + 1;
|
|
35480
|
-
const newline = source.indexOf(`
|
|
35481
|
-
`, existingEnd + END3.length);
|
|
35482
|
-
const through = newline < 0 ? source.length : newline + 1;
|
|
35483
|
-
const generatedMethod = source.slice(from, through).includes("onCreate(");
|
|
35484
|
-
return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
35485
|
-
}
|
|
36362
|
+
if (!enabled)
|
|
36363
|
+
return source;
|
|
35486
36364
|
const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
|
|
35487
36365
|
if (onCreate >= 0) {
|
|
35488
36366
|
const brace = source.indexOf("{", onCreate);
|
|
35489
36367
|
const insert = source.indexOf(`
|
|
35490
36368
|
`, brace) + 1;
|
|
35491
|
-
|
|
35492
|
-
throw new TypeError("Could not inject Android release readiness into MainActivity.");
|
|
35493
|
-
return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
|
|
36369
|
+
return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
|
|
35494
36370
|
}
|
|
35495
36371
|
const close = source.lastIndexOf("}");
|
|
35496
36372
|
if (close < 0)
|
|
@@ -35498,633 +36374,254 @@ var injectRegistration = (source) => {
|
|
|
35498
36374
|
const separator = source[close - 1] === `
|
|
35499
36375
|
` ? "" : `
|
|
35500
36376
|
`;
|
|
35501
|
-
return `${source.slice(0, close)}${separator}${
|
|
35502
|
-
};
|
|
35503
|
-
var pluginSource = (packageName) => `package ${packageName};
|
|
35504
|
-
|
|
35505
|
-
import android.util.Log;
|
|
35506
|
-
import com.getcapacitor.JSObject;
|
|
35507
|
-
import com.getcapacitor.Plugin;
|
|
35508
|
-
import com.getcapacitor.PluginCall;
|
|
35509
|
-
import com.getcapacitor.PluginMethod;
|
|
35510
|
-
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
35511
|
-
|
|
35512
|
-
@CapacitorPlugin(name = "AbsoluteReleaseReadiness")
|
|
35513
|
-
public final class AbsoluteReleaseReadinessPlugin extends Plugin {
|
|
35514
|
-
@PluginMethod
|
|
35515
|
-
public void ready(PluginCall call) {
|
|
35516
|
-
Log.i("AbsoluteJS", "Capacitor embedded web content ready");
|
|
35517
|
-
call.resolve(new JSObject().put("ready", true));
|
|
35518
|
-
}
|
|
35519
|
-
}
|
|
35520
|
-
`;
|
|
35521
|
-
var iosPluginRegion3 = `${IOS_PLUGIN_START}
|
|
35522
|
-
@objc(AbsoluteReleaseReadinessPlugin)
|
|
35523
|
-
public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
35524
|
-
public let identifier = "AbsoluteReleaseReadinessPlugin"
|
|
35525
|
-
public let jsName = "AbsoluteReleaseReadiness"
|
|
35526
|
-
public let pluginMethods: [CAPPluginMethod] = [
|
|
35527
|
-
CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
|
|
35528
|
-
]
|
|
35529
|
-
|
|
35530
|
-
@objc public func ready(_ call: CAPPluginCall) {
|
|
35531
|
-
let info = Bundle.main.infoDictionary ?? [:]
|
|
35532
|
-
let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
|
|
35533
|
-
let build = info["CFBundleVersion"] as? String ?? "unknown"
|
|
35534
|
-
NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
|
|
35535
|
-
call.resolve(["ready": true])
|
|
35536
|
-
}
|
|
35537
|
-
}
|
|
35538
|
-
${IOS_PLUGIN_END}
|
|
35539
|
-
`;
|
|
35540
|
-
var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
|
|
35541
|
-
const from = appDelegate.lastIndexOf(`
|
|
35542
|
-
`, existingStart) + 1;
|
|
35543
|
-
const newline = appDelegate.indexOf(`
|
|
35544
|
-
`, existingEnd + IOS_PLUGIN_END.length);
|
|
35545
|
-
const through = newline < 0 ? appDelegate.length : newline + 1;
|
|
35546
|
-
return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
|
|
35547
|
-
};
|
|
35548
|
-
var configureIos5 = async (config) => {
|
|
35549
|
-
const appDelegatePath = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
35550
|
-
const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
35551
|
-
const appDelegate = await readFile25(appDelegatePath, "utf8");
|
|
35552
|
-
const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
|
|
35553
|
-
const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
|
|
35554
|
-
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
35555
|
-
throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
|
|
35556
|
-
const source = existingStart < 0 ? `${appDelegate.trimEnd()}
|
|
35557
|
-
|
|
35558
|
-
${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
|
|
35559
|
-
let capacitorConfig;
|
|
35560
|
-
try {
|
|
35561
|
-
capacitorConfig = JSON.parse(await readFile25(capacitorConfigPath, "utf8"));
|
|
35562
|
-
} catch {
|
|
35563
|
-
throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
|
|
35564
|
-
}
|
|
35565
|
-
if (typeof capacitorConfig !== "object" || capacitorConfig === null)
|
|
35566
|
-
throw new TypeError("iOS capacitor.config.json must contain an object.");
|
|
35567
|
-
const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
|
|
35568
|
-
if (!Array.isArray(packageClassList))
|
|
35569
|
-
throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
|
|
35570
|
-
const classes = packageClassList.filter((value) => typeof value === "string");
|
|
35571
|
-
const nextConfig = `${JSON.stringify({
|
|
35572
|
-
...capacitorConfig,
|
|
35573
|
-
packageClassList: [
|
|
35574
|
-
...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
|
|
35575
|
-
]
|
|
35576
|
-
}, null, "\t")}
|
|
35577
|
-
`;
|
|
35578
|
-
const changed = await Promise.all([
|
|
35579
|
-
writeChanged3(appDelegatePath, source),
|
|
35580
|
-
writeChanged3(capacitorConfigPath, nextConfig)
|
|
35581
|
-
]);
|
|
35582
|
-
return changed.some(Boolean);
|
|
36377
|
+
return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
|
|
35583
36378
|
};
|
|
35584
|
-
var
|
|
35585
|
-
const
|
|
35586
|
-
const activity = await
|
|
36379
|
+
var configureAndroid4 = async (config, enabled) => {
|
|
36380
|
+
const activityPath = await androidActivity2(config);
|
|
36381
|
+
const activity = await readFile27(activityPath, "utf8");
|
|
35587
36382
|
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
35588
|
-
if (!packageName)
|
|
35589
|
-
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
35590
|
-
const pluginPath =
|
|
35591
|
-
const changed = await Promise.all([
|
|
35592
|
-
|
|
35593
|
-
|
|
35594
|
-
]);
|
|
35595
|
-
return changed.some(Boolean);
|
|
35596
|
-
};
|
|
35597
|
-
var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
|
|
35598
|
-
if (config.engine !== "capacitor")
|
|
35599
|
-
return { changed: false };
|
|
35600
|
-
const changed = await Promise.all([
|
|
35601
|
-
platforms.includes("android") ? configureAndroid5(config) : false,
|
|
35602
|
-
platforms.includes("ios") ? configureIos5(config) : false
|
|
35603
|
-
]);
|
|
35604
|
-
return { changed: changed.some(Boolean) };
|
|
35605
|
-
};
|
|
35606
|
-
|
|
35607
|
-
// src/mobile/index.ts
|
|
35608
|
-
init_pageProtocol();
|
|
35609
|
-
init_producerContext();
|
|
35610
|
-
init_releaseArtifact();
|
|
35611
|
-
|
|
35612
|
-
// src/mobile/releaseCertification.ts
|
|
35613
|
-
import { createHash as createHash16 } from "crypto";
|
|
35614
|
-
import {
|
|
35615
|
-
access as access17,
|
|
35616
|
-
mkdir as mkdir20,
|
|
35617
|
-
mkdtemp as mkdtemp10,
|
|
35618
|
-
readFile as readFile26,
|
|
35619
|
-
rename as rename18,
|
|
35620
|
-
rm as rm15,
|
|
35621
|
-
writeFile as writeFile22
|
|
35622
|
-
} from "fs/promises";
|
|
35623
|
-
import { basename as basename8, isAbsolute as isAbsolute9, join as join30, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
|
|
35624
|
-
var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
|
|
35625
|
-
var CERTIFICATION_PREFIX = "amobile_cert_";
|
|
35626
|
-
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
35627
|
-
var STRENGTH_RANK = {
|
|
35628
|
-
device: 1,
|
|
35629
|
-
installed: 0,
|
|
35630
|
-
simulator: 0,
|
|
35631
|
-
store: 2
|
|
35632
|
-
};
|
|
35633
|
-
var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
35634
|
-
var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
35635
|
-
var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
|
|
35636
|
-
var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
|
|
35637
|
-
var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35638
|
-
var inside4 = (root, candidate) => {
|
|
35639
|
-
const path = relative16(resolve18(root), resolve18(candidate));
|
|
35640
|
-
return path === "" || !isAbsolute9(path) && path !== ".." && !path.startsWith(`..${sep11}`);
|
|
35641
|
-
};
|
|
35642
|
-
var exists9 = async (path) => {
|
|
35643
|
-
try {
|
|
35644
|
-
await access17(path);
|
|
35645
|
-
return true;
|
|
35646
|
-
} catch {
|
|
35647
|
-
return false;
|
|
35648
|
-
}
|
|
35649
|
-
};
|
|
35650
|
-
var sha2564 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
|
|
35651
|
-
var requireString2 = (value, name) => {
|
|
35652
|
-
if (typeof value !== "string" || !value)
|
|
35653
|
-
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
35654
|
-
return value;
|
|
35655
|
-
};
|
|
35656
|
-
var requirePositiveInteger = (value, name) => {
|
|
35657
|
-
if (!Number.isSafeInteger(value) || Number(value) < 1)
|
|
35658
|
-
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
35659
|
-
return Number(value);
|
|
35660
|
-
};
|
|
35661
|
-
var resolveEvidencePath = async (projectRoot, requested) => {
|
|
35662
|
-
const selected = resolve18(projectRoot, requested);
|
|
35663
|
-
if (!inside4(projectRoot, selected))
|
|
35664
|
-
throw new TypeError("Mobile certification evidence must remain inside the project.");
|
|
35665
|
-
if (basename8(selected) === "report.json")
|
|
35666
|
-
return selected;
|
|
35667
|
-
const report = join30(selected, "report.json");
|
|
35668
|
-
if (!await exists9(report))
|
|
35669
|
-
throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
|
|
35670
|
-
return report;
|
|
35671
|
-
};
|
|
35672
|
-
var releaseIdentity = (release) => {
|
|
35673
|
-
const { metadata } = release;
|
|
35674
|
-
if (!metadata.signed)
|
|
35675
|
-
throw new TypeError("Mobile release certification requires a signed native release.");
|
|
35676
|
-
const common = {
|
|
35677
|
-
appBuild: metadata.appBuild,
|
|
35678
|
-
appId: metadata.appId,
|
|
35679
|
-
artifactBytes: metadata.bytes,
|
|
35680
|
-
artifactSha256: metadata.sha256,
|
|
35681
|
-
engine: metadata.engine,
|
|
35682
|
-
platform: metadata.platform,
|
|
35683
|
-
releaseId: metadata.releaseId,
|
|
35684
|
-
runtime: metadata.runtime,
|
|
35685
|
-
signed: true
|
|
35686
|
-
};
|
|
35687
|
-
if (metadata.platform === "android")
|
|
35688
|
-
return {
|
|
35689
|
-
...common,
|
|
35690
|
-
...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
|
|
35691
|
-
};
|
|
35692
|
-
return {
|
|
35693
|
-
...common,
|
|
35694
|
-
...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
|
|
35695
|
-
marketingVersion: metadata.marketingVersion
|
|
35696
|
-
};
|
|
35697
|
-
};
|
|
35698
|
-
var requireCheck = (report, id, accepted = ["PASS"]) => {
|
|
35699
|
-
const checks = report.automatedChecks;
|
|
35700
|
-
if (!Array.isArray(checks))
|
|
35701
|
-
throw new TypeError("Mobile certification evidence has invalid automated checks.");
|
|
35702
|
-
const check = checks.find((candidate) => isRecord14(candidate) && candidate.id === id);
|
|
35703
|
-
if (!isRecord14(check) || !accepted.includes(String(check.result)))
|
|
35704
|
-
throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
|
|
35705
|
-
};
|
|
35706
|
-
var validateCommonReport = (report, release) => {
|
|
35707
|
-
if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord14(report.metadata) || report.metadata.provider !== release.engine || !isRecord14(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
|
|
35708
|
-
throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
|
|
35709
|
-
requireCheck(report, "AUTO-DEV-01");
|
|
35710
|
-
return {
|
|
35711
|
-
generatedAt: requireString2(report.generatedAt, "evidence generatedAt"),
|
|
35712
|
-
run: report.run
|
|
35713
|
-
};
|
|
35714
|
-
};
|
|
35715
|
-
var validateReleaseFields = (value, release) => {
|
|
35716
|
-
if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
|
|
35717
|
-
throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
|
|
35718
|
-
};
|
|
35719
|
-
var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
35720
|
-
if (!isRecord14(run.release))
|
|
35721
|
-
throw new TypeError("Android certification evidence has no installed release result.");
|
|
35722
|
-
validateReleaseFields(run.release, release);
|
|
35723
|
-
if (run.release.embeddedOffline !== true)
|
|
35724
|
-
throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
|
|
35725
|
-
requireCheck(report, "AUTO-RELEASE-01");
|
|
35726
|
-
requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
|
|
35727
|
-
return {
|
|
35728
|
-
generatedAt,
|
|
35729
|
-
networkUnavailable: "proven",
|
|
35730
|
-
remote: false,
|
|
35731
|
-
reportSha256,
|
|
35732
|
-
strength: "installed"
|
|
35733
|
-
};
|
|
35734
|
-
};
|
|
35735
|
-
var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
35736
|
-
if (!isRecord14(run.iosRelease))
|
|
35737
|
-
throw new TypeError("iOS certification evidence has no installed release result.");
|
|
35738
|
-
const ios = run.iosRelease;
|
|
35739
|
-
validateReleaseFields(ios, release);
|
|
35740
|
-
if (ios.embeddedLocal !== true)
|
|
35741
|
-
throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
|
|
35742
|
-
requireCheck(report, "AUTO-IOS-RELEASE-01");
|
|
35743
|
-
let strength;
|
|
35744
|
-
if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
|
|
35745
|
-
strength = "simulator";
|
|
35746
|
-
else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
35747
|
-
strength = "device";
|
|
35748
|
-
else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
35749
|
-
strength = "store";
|
|
35750
|
-
else
|
|
35751
|
-
throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
|
|
35752
|
-
if (strength === "simulator")
|
|
35753
|
-
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
|
|
35754
|
-
else
|
|
35755
|
-
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
|
|
35756
|
-
return {
|
|
35757
|
-
artifactExactness: ios.artifactExactness,
|
|
35758
|
-
distribution: ios.distribution,
|
|
35759
|
-
generatedAt,
|
|
35760
|
-
networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
|
|
35761
|
-
remote: ios.remote === true,
|
|
35762
|
-
reportSha256,
|
|
35763
|
-
strength
|
|
35764
|
-
};
|
|
36383
|
+
if (!packageName)
|
|
36384
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
36385
|
+
const pluginPath = join30(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
|
|
36386
|
+
const changed = await Promise.all([
|
|
36387
|
+
writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
|
|
36388
|
+
enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
|
|
36389
|
+
]);
|
|
36390
|
+
return changed.some(Boolean);
|
|
35765
36391
|
};
|
|
35766
|
-
var
|
|
35767
|
-
|
|
35768
|
-
|
|
35769
|
-
|
|
36392
|
+
var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
|
|
36393
|
+
if (config.engine !== "capacitor")
|
|
36394
|
+
return { changed: false };
|
|
36395
|
+
const enabled = config.observability !== undefined;
|
|
36396
|
+
const changed = [];
|
|
36397
|
+
if (platforms.includes("ios"))
|
|
36398
|
+
changed.push(await configureIos4(config, enabled));
|
|
36399
|
+
if (platforms.includes("android"))
|
|
36400
|
+
changed.push(await configureAndroid4(config, enabled));
|
|
36401
|
+
return { changed: changed.some(Boolean) };
|
|
36402
|
+
};
|
|
36403
|
+
// src/mobile/nativeReleaseReadiness.ts
|
|
36404
|
+
import { mkdir as mkdir22, readFile as readFile28, readdir as readdir10, rename as rename19, writeFile as writeFile24 } from "fs/promises";
|
|
36405
|
+
import { dirname as dirname21, join as join31 } from "path";
|
|
36406
|
+
var START3 = "// absolutejs:release-readiness:start";
|
|
36407
|
+
var END3 = "// absolutejs:release-readiness:end";
|
|
36408
|
+
var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
|
|
36409
|
+
var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
|
|
36410
|
+
var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
|
|
36411
|
+
var optionalSource4 = async (path) => {
|
|
35770
36412
|
try {
|
|
35771
|
-
|
|
35772
|
-
} catch {
|
|
35773
|
-
|
|
36413
|
+
return await readFile28(path, "utf8");
|
|
36414
|
+
} catch (error) {
|
|
36415
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
36416
|
+
return null;
|
|
36417
|
+
throw error;
|
|
35774
36418
|
}
|
|
35775
|
-
if (!isRecord14(value))
|
|
35776
|
-
throw new TypeError("Mobile certification evidence report must contain an object.");
|
|
35777
|
-
const { generatedAt, run } = validateCommonReport(value, release);
|
|
35778
|
-
const digest = sha2564(bytes);
|
|
35779
|
-
return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
|
|
35780
|
-
};
|
|
35781
|
-
var defaultRequirement = (platform4) => platform4 === "android" ? "installed" : "simulator";
|
|
35782
|
-
var validateRequirement = (platform4, requirement) => {
|
|
35783
|
-
if (platform4 === "android" && requirement !== "installed")
|
|
35784
|
-
throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
|
|
35785
|
-
if (platform4 === "ios" && requirement === "installed")
|
|
35786
|
-
throw new TypeError("iOS certification requires simulator, device, or store evidence.");
|
|
35787
36419
|
};
|
|
35788
|
-
var
|
|
35789
|
-
|
|
35790
|
-
|
|
35791
|
-
if (strength === "installed")
|
|
36420
|
+
var writeChanged3 = async (path, source) => {
|
|
36421
|
+
const current = await optionalSource4(path);
|
|
36422
|
+
if (current === source)
|
|
35792
36423
|
return false;
|
|
35793
|
-
|
|
35794
|
-
|
|
35795
|
-
|
|
35796
|
-
|
|
35797
|
-
const playRequirement = target.googlePlayTrack ? config.releaseCertification.googlePlayTracks[target.googlePlayTrack] : undefined;
|
|
35798
|
-
const requirements = [channelRequirement, playRequirement].filter((value) => value !== undefined);
|
|
35799
|
-
if (requirements.length === 0)
|
|
35800
|
-
return;
|
|
35801
|
-
return requirements.reduce((strongest, candidate) => STRENGTH_RANK[candidate] > STRENGTH_RANK[strongest] ? candidate : strongest);
|
|
35802
|
-
};
|
|
35803
|
-
var validateCertifiedEvidence = (platform4, evidence) => {
|
|
35804
|
-
if (platform4 === "android") {
|
|
35805
|
-
if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
|
|
35806
|
-
throw new TypeError("Android release certification contains invalid evidence strength.");
|
|
35807
|
-
return;
|
|
36424
|
+
await mkdir22(dirname21(path), { recursive: true });
|
|
36425
|
+
if (current === null) {
|
|
36426
|
+
await writeFile24(path, source, { flag: "wx" });
|
|
36427
|
+
return true;
|
|
35808
36428
|
}
|
|
35809
|
-
const
|
|
35810
|
-
|
|
35811
|
-
|
|
35812
|
-
|
|
35813
|
-
throw new TypeError("iOS release certification contains invalid evidence strength.");
|
|
36429
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
36430
|
+
await writeFile24(temporary, source, { flag: "wx" });
|
|
36431
|
+
await rename19(temporary, path);
|
|
36432
|
+
return true;
|
|
35814
36433
|
};
|
|
35815
|
-
var
|
|
35816
|
-
|
|
35817
|
-
|
|
35818
|
-
|
|
35819
|
-
|
|
35820
|
-
|
|
35821
|
-
|
|
35822
|
-
|
|
35823
|
-
|
|
36434
|
+
var activityInRoot = async (root) => {
|
|
36435
|
+
let entries;
|
|
36436
|
+
try {
|
|
36437
|
+
entries = await readdir10(root, { recursive: true });
|
|
36438
|
+
} catch (error) {
|
|
36439
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
36440
|
+
return;
|
|
36441
|
+
throw error;
|
|
36442
|
+
}
|
|
36443
|
+
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
36444
|
+
return candidate ? join31(root, candidate) : undefined;
|
|
35824
36445
|
};
|
|
35825
|
-
var
|
|
35826
|
-
|
|
35827
|
-
|
|
35828
|
-
|
|
35829
|
-
|
|
35830
|
-
const
|
|
35831
|
-
|
|
35832
|
-
|
|
35833
|
-
|
|
35834
|
-
|
|
35835
|
-
if (!firstEvidence)
|
|
35836
|
-
throw new TypeError("Mobile release certification requires valid evidence.");
|
|
35837
|
-
const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
|
|
35838
|
-
if (!satisfiesRequirement(strength, requirement))
|
|
35839
|
-
throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
|
|
35840
|
-
const body = {
|
|
35841
|
-
evidence,
|
|
35842
|
-
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
35843
|
-
generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
|
|
35844
|
-
release,
|
|
35845
|
-
requirement,
|
|
35846
|
-
status: "certified",
|
|
35847
|
-
strength
|
|
35848
|
-
};
|
|
35849
|
-
return {
|
|
35850
|
-
certificationId: `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`,
|
|
35851
|
-
...body
|
|
35852
|
-
};
|
|
36446
|
+
var activityPath = async (config) => {
|
|
36447
|
+
const roots = [
|
|
36448
|
+
join31(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
36449
|
+
join31(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
36450
|
+
];
|
|
36451
|
+
const candidates = await Promise.all(roots.map(activityInRoot));
|
|
36452
|
+
const candidate = candidates.find((value) => value !== undefined);
|
|
36453
|
+
if (candidate)
|
|
36454
|
+
return candidate;
|
|
36455
|
+
throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
|
|
35853
36456
|
};
|
|
35854
|
-
var
|
|
35855
|
-
if (
|
|
35856
|
-
|
|
35857
|
-
|
|
35858
|
-
|
|
35859
|
-
|
|
35860
|
-
if (platform4 !== "android" && platform4 !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
|
|
35861
|
-
throw new TypeError("Mobile release certification identity is invalid.");
|
|
35862
|
-
const { requirement } = value;
|
|
35863
|
-
const { strength } = value;
|
|
35864
|
-
if (!isRequirement(requirement) || !isStrength(strength))
|
|
35865
|
-
throw new TypeError("Mobile release certification policy is invalid.");
|
|
35866
|
-
validateRequirement(platform4, requirement);
|
|
35867
|
-
if (!satisfiesRequirement(strength, requirement))
|
|
35868
|
-
throw new TypeError("Mobile release certification does not satisfy its declared policy.");
|
|
35869
|
-
const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
|
|
35870
|
-
const artifactSha256 = requireString2(release.artifactSha256, "release artifactSha256");
|
|
35871
|
-
if (!SHA256.test(artifactSha256))
|
|
35872
|
-
throw new TypeError("Mobile release certification artifact digest is invalid.");
|
|
35873
|
-
const parsedRelease = {
|
|
35874
|
-
appBuild: requireString2(release.appBuild, "release appBuild"),
|
|
35875
|
-
appId: requireString2(release.appId, "release appId"),
|
|
35876
|
-
artifactBytes,
|
|
35877
|
-
artifactSha256,
|
|
35878
|
-
engine,
|
|
35879
|
-
platform: platform4,
|
|
35880
|
-
releaseId: requireString2(release.releaseId, "release releaseId"),
|
|
35881
|
-
runtime: requireString2(release.runtime, "release runtime"),
|
|
35882
|
-
signed: true,
|
|
35883
|
-
...release.buildNumber === undefined ? {} : {
|
|
35884
|
-
buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
|
|
35885
|
-
},
|
|
35886
|
-
...release.marketingVersion === undefined ? {} : {
|
|
35887
|
-
marketingVersion: requireString2(release.marketingVersion, "release marketingVersion")
|
|
35888
|
-
},
|
|
35889
|
-
...release.versionCode === undefined ? {} : {
|
|
35890
|
-
versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
|
|
36457
|
+
var registrationRegion = (kotlin, generatedMethod) => {
|
|
36458
|
+
if (generatedMethod)
|
|
36459
|
+
return kotlin ? ` ${START3}
|
|
36460
|
+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
36461
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
36462
|
+
super.onCreate(savedInstanceState)
|
|
35891
36463
|
}
|
|
35892
|
-
|
|
35893
|
-
|
|
35894
|
-
|
|
35895
|
-
|
|
35896
|
-
|
|
35897
|
-
|
|
35898
|
-
|
|
35899
|
-
|
|
35900
|
-
|
|
35901
|
-
|
|
35902
|
-
|
|
35903
|
-
|
|
35904
|
-
|
|
35905
|
-
|
|
35906
|
-
|
|
35907
|
-
|
|
35908
|
-
return parsedEvidence;
|
|
35909
|
-
});
|
|
35910
|
-
const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
|
|
35911
|
-
if (evidence.length === 0 || strongestEvidence !== strength)
|
|
35912
|
-
throw new TypeError("Mobile release certification evidence does not support its declared strength.");
|
|
35913
|
-
const parsed = {
|
|
35914
|
-
certificationId: requireString2(value.certificationId, "certificationId"),
|
|
35915
|
-
evidence,
|
|
35916
|
-
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
35917
|
-
generatedAt: requireString2(value.generatedAt, "generatedAt"),
|
|
35918
|
-
release: parsedRelease,
|
|
35919
|
-
requirement,
|
|
35920
|
-
status: "certified",
|
|
35921
|
-
strength
|
|
35922
|
-
};
|
|
35923
|
-
const { certificationId, ...body } = parsed;
|
|
35924
|
-
if (certificationId !== `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`)
|
|
35925
|
-
throw new TypeError("Mobile release certification content digest is invalid.");
|
|
35926
|
-
return parsed;
|
|
35927
|
-
};
|
|
35928
|
-
var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
|
|
35929
|
-
const selected = resolve18(projectRoot, requested);
|
|
35930
|
-
if (!inside4(projectRoot, selected))
|
|
35931
|
-
throw new TypeError("Mobile release certification must remain inside the project.");
|
|
35932
|
-
const path = basename8(selected) === "certification.json" ? selected : join30(selected, "certification.json");
|
|
35933
|
-
const value = JSON.parse(await readFile26(path, "utf8"));
|
|
35934
|
-
return { certification: parseCertification(value), path };
|
|
36464
|
+
${END3}
|
|
36465
|
+
` : ` ${START3}
|
|
36466
|
+
@Override
|
|
36467
|
+
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
36468
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
36469
|
+
super.onCreate(savedInstanceState);
|
|
36470
|
+
}
|
|
36471
|
+
${END3}
|
|
36472
|
+
`;
|
|
36473
|
+
return kotlin ? ` ${START3}
|
|
36474
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
36475
|
+
${END3}
|
|
36476
|
+
` : ` ${START3}
|
|
36477
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
36478
|
+
${END3}
|
|
36479
|
+
`;
|
|
35935
36480
|
};
|
|
35936
|
-
var
|
|
35937
|
-
const
|
|
35938
|
-
const
|
|
35939
|
-
const
|
|
35940
|
-
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
35952
|
-
|
|
35953
|
-
|
|
35954
|
-
|
|
36481
|
+
var injectRegistration = (source) => {
|
|
36482
|
+
const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
|
|
36483
|
+
const existingStart = source.indexOf(START3);
|
|
36484
|
+
const existingEnd = source.indexOf(END3);
|
|
36485
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
36486
|
+
throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
|
|
36487
|
+
if (existingStart >= 0) {
|
|
36488
|
+
const from = source.lastIndexOf(`
|
|
36489
|
+
`, existingStart) + 1;
|
|
36490
|
+
const newline = source.indexOf(`
|
|
36491
|
+
`, existingEnd + END3.length);
|
|
36492
|
+
const through = newline < 0 ? source.length : newline + 1;
|
|
36493
|
+
const generatedMethod = source.slice(from, through).includes("onCreate(");
|
|
36494
|
+
return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
36495
|
+
}
|
|
36496
|
+
const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
|
|
36497
|
+
if (onCreate >= 0) {
|
|
36498
|
+
const brace = source.indexOf("{", onCreate);
|
|
36499
|
+
const insert = source.indexOf(`
|
|
36500
|
+
`, brace) + 1;
|
|
36501
|
+
if (insert <= 0)
|
|
36502
|
+
throw new TypeError("Could not inject Android release readiness into MainActivity.");
|
|
36503
|
+
return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
|
|
36504
|
+
}
|
|
36505
|
+
const close = source.lastIndexOf("}");
|
|
36506
|
+
if (close < 0)
|
|
36507
|
+
throw new TypeError("Could not find the Android MainActivity class body.");
|
|
36508
|
+
const separator = source[close - 1] === `
|
|
36509
|
+
` ? "" : `
|
|
36510
|
+
`;
|
|
36511
|
+
return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
|
|
36512
|
+
};
|
|
36513
|
+
var pluginSource = (packageName) => `package ${packageName};
|
|
35955
36514
|
|
|
35956
|
-
|
|
36515
|
+
import android.util.Log;
|
|
36516
|
+
import com.getcapacitor.JSObject;
|
|
36517
|
+
import com.getcapacitor.Plugin;
|
|
36518
|
+
import com.getcapacitor.PluginCall;
|
|
36519
|
+
import com.getcapacitor.PluginMethod;
|
|
36520
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
35957
36521
|
|
|
35958
|
-
|
|
35959
|
-
|
|
35960
|
-
|
|
36522
|
+
@CapacitorPlugin(name = "AbsoluteReleaseReadiness")
|
|
36523
|
+
public final class AbsoluteReleaseReadinessPlugin extends Plugin {
|
|
36524
|
+
@PluginMethod
|
|
36525
|
+
public void ready(PluginCall call) {
|
|
36526
|
+
Log.i("AbsoluteJS", "Capacitor embedded web content ready");
|
|
36527
|
+
call.resolve(new JSObject().put("ready", true));
|
|
36528
|
+
}
|
|
36529
|
+
}
|
|
35961
36530
|
`;
|
|
35962
|
-
}
|
|
35963
|
-
|
|
36531
|
+
var iosPluginRegion3 = `${IOS_PLUGIN_START}
|
|
36532
|
+
@objc(AbsoluteReleaseReadinessPlugin)
|
|
36533
|
+
public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
36534
|
+
public let identifier = "AbsoluteReleaseReadinessPlugin"
|
|
36535
|
+
public let jsName = "AbsoluteReleaseReadiness"
|
|
36536
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
36537
|
+
CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
|
|
36538
|
+
]
|
|
36539
|
+
|
|
36540
|
+
@objc public func ready(_ call: CAPPluginCall) {
|
|
36541
|
+
let info = Bundle.main.infoDictionary ?? [:]
|
|
36542
|
+
let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
|
|
36543
|
+
let build = info["CFBundleVersion"] as? String ?? "unknown"
|
|
36544
|
+
NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
|
|
36545
|
+
call.resolve(["ready": true])
|
|
36546
|
+
}
|
|
36547
|
+
}
|
|
36548
|
+
${IOS_PLUGIN_END}
|
|
35964
36549
|
`;
|
|
35965
|
-
var
|
|
35966
|
-
const
|
|
35967
|
-
|
|
35968
|
-
|
|
35969
|
-
|
|
35970
|
-
|
|
35971
|
-
|
|
35972
|
-
if (!satisfiesRequirement(parsed.strength, requirement))
|
|
35973
|
-
throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
|
|
35974
|
-
}
|
|
35975
|
-
return parsed;
|
|
36550
|
+
var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
|
|
36551
|
+
const from = appDelegate.lastIndexOf(`
|
|
36552
|
+
`, existingStart) + 1;
|
|
36553
|
+
const newline = appDelegate.indexOf(`
|
|
36554
|
+
`, existingEnd + IOS_PLUGIN_END.length);
|
|
36555
|
+
const through = newline < 0 ? appDelegate.length : newline + 1;
|
|
36556
|
+
return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
|
|
35976
36557
|
};
|
|
35977
|
-
var
|
|
35978
|
-
const
|
|
35979
|
-
const
|
|
35980
|
-
|
|
35981
|
-
|
|
35982
|
-
|
|
35983
|
-
|
|
35984
|
-
|
|
35985
|
-
|
|
35986
|
-
|
|
35987
|
-
|
|
35988
|
-
|
|
35989
|
-
const json = serializeAbsoluteMobileReleaseCertification(certification);
|
|
35990
|
-
const markdown = renderAbsoluteMobileReleaseCertification(certification);
|
|
35991
|
-
const verifyExisting = async () => {
|
|
35992
|
-
const [existingJson, existingMarkdown] = await Promise.all([
|
|
35993
|
-
readFile26(paths.jsonPath, "utf8"),
|
|
35994
|
-
readFile26(paths.markdownPath, "utf8")
|
|
35995
|
-
]);
|
|
35996
|
-
if (existingJson !== json || existingMarkdown !== markdown)
|
|
35997
|
-
throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
|
|
35998
|
-
};
|
|
35999
|
-
if (await exists9(destination)) {
|
|
36000
|
-
await verifyExisting();
|
|
36001
|
-
return paths;
|
|
36002
|
-
}
|
|
36003
|
-
const staging = await mkdtemp10(join30(parent, ".certification-"));
|
|
36558
|
+
var configureIos5 = async (config) => {
|
|
36559
|
+
const appDelegatePath = join31(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
36560
|
+
const capacitorConfigPath = join31(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
36561
|
+
const appDelegate = await readFile28(appDelegatePath, "utf8");
|
|
36562
|
+
const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
|
|
36563
|
+
const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
|
|
36564
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
36565
|
+
throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
|
|
36566
|
+
const source = existingStart < 0 ? `${appDelegate.trimEnd()}
|
|
36567
|
+
|
|
36568
|
+
${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
|
|
36569
|
+
let capacitorConfig;
|
|
36004
36570
|
try {
|
|
36005
|
-
|
|
36006
|
-
|
|
36007
|
-
|
|
36008
|
-
}),
|
|
36009
|
-
writeFile22(join30(staging, "certification.md"), markdown, {
|
|
36010
|
-
flag: "wx"
|
|
36011
|
-
})
|
|
36012
|
-
]);
|
|
36013
|
-
await rename18(staging, destination).catch(async (error) => {
|
|
36014
|
-
if (await exists9(destination))
|
|
36015
|
-
return;
|
|
36016
|
-
throw error;
|
|
36017
|
-
});
|
|
36018
|
-
} finally {
|
|
36019
|
-
await rm15(staging, { force: true, recursive: true });
|
|
36571
|
+
capacitorConfig = JSON.parse(await readFile28(capacitorConfigPath, "utf8"));
|
|
36572
|
+
} catch {
|
|
36573
|
+
throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
|
|
36020
36574
|
}
|
|
36021
|
-
|
|
36022
|
-
|
|
36023
|
-
|
|
36024
|
-
|
|
36025
|
-
|
|
36026
|
-
|
|
36027
|
-
|
|
36028
|
-
|
|
36029
|
-
|
|
36030
|
-
|
|
36031
|
-
|
|
36032
|
-
|
|
36033
|
-
|
|
36034
|
-
|
|
36035
|
-
|
|
36036
|
-
|
|
36037
|
-
|
|
36038
|
-
|
|
36039
|
-
var requireMatch = (value, pattern, name) => {
|
|
36040
|
-
if (!pattern.test(value))
|
|
36041
|
-
throw new TypeError(`${name} is invalid.`);
|
|
36042
|
-
return value;
|
|
36043
|
-
};
|
|
36044
|
-
var createAbsoluteMobileCertificationVerification = (bundle, environment) => {
|
|
36045
|
-
const repository = required(environment.GITHUB_REPOSITORY, "GITHUB_REPOSITORY");
|
|
36046
|
-
const workflowReference = required(environment.GITHUB_WORKFLOW_REF, "GITHUB_WORKFLOW_REF");
|
|
36047
|
-
const prefix = `${repository}/`;
|
|
36048
|
-
const separator = workflowReference.lastIndexOf("@");
|
|
36049
|
-
if (!workflowReference.startsWith(prefix) || separator <= prefix.length)
|
|
36050
|
-
throw new TypeError("GITHUB_WORKFLOW_REF is invalid.");
|
|
36051
|
-
return parseAbsoluteMobileCertificationVerification({
|
|
36052
|
-
bundle,
|
|
36053
|
-
format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
36054
|
-
identity: {
|
|
36055
|
-
issuer: "https://token.actions.githubusercontent.com",
|
|
36056
|
-
ref: environment.GITHUB_REF,
|
|
36057
|
-
repository,
|
|
36058
|
-
sha: environment.GITHUB_SHA,
|
|
36059
|
-
workflowPath: workflowReference.slice(prefix.length, separator)
|
|
36060
|
-
},
|
|
36061
|
-
kind: "sigstore-bundle"
|
|
36062
|
-
});
|
|
36063
|
-
};
|
|
36064
|
-
var parseAbsoluteMobileCertificationVerification = (value) => {
|
|
36065
|
-
if (!isRecord15(value) || value.format !== ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT || value.kind !== "sigstore-bundle" || !isRecord15(value.bundle) || !isRecord15(value.identity))
|
|
36066
|
-
throw new TypeError("Mobile certification verification contract is invalid.");
|
|
36067
|
-
const issuer = required(value.identity.issuer, "verification issuer");
|
|
36068
|
-
if (issuer !== "https://token.actions.githubusercontent.com")
|
|
36069
|
-
throw new TypeError("verification issuer is invalid.");
|
|
36070
|
-
const verification = {
|
|
36071
|
-
bundle: value.bundle,
|
|
36072
|
-
format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
36073
|
-
identity: {
|
|
36074
|
-
issuer,
|
|
36075
|
-
ref: requireMatch(required(value.identity.ref, "verification ref"), REF_PATTERN, "verification ref"),
|
|
36076
|
-
repository: requireMatch(required(value.identity.repository, "verification repository"), REPOSITORY_PATTERN, "verification repository"),
|
|
36077
|
-
sha: requireMatch(required(value.identity.sha, "verification sha"), SHA_PATTERN, "verification sha"),
|
|
36078
|
-
workflowPath: requireMatch(required(value.identity.workflowPath, "verification workflowPath"), WORKFLOW_PATTERN, "verification workflowPath")
|
|
36079
|
-
},
|
|
36080
|
-
kind: "sigstore-bundle"
|
|
36081
|
-
};
|
|
36082
|
-
const bytes = new TextEncoder().encode(JSON.stringify(verification));
|
|
36083
|
-
if (bytes.byteLength > MAX_VERIFICATION_BYTES)
|
|
36084
|
-
throw new TypeError("Mobile certification verification exceeds the 1 MiB limit.");
|
|
36085
|
-
return verification;
|
|
36086
|
-
};
|
|
36087
|
-
var projectFile = (projectRoot, requested) => {
|
|
36088
|
-
const root = resolve19(projectRoot);
|
|
36089
|
-
const path = resolve19(root, requested);
|
|
36090
|
-
const portable = relative17(root, path);
|
|
36091
|
-
if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute10(portable))
|
|
36092
|
-
throw new TypeError("Mobile certification verification path must remain inside the project.");
|
|
36093
|
-
return path;
|
|
36094
|
-
};
|
|
36095
|
-
var readAbsoluteMobileCertificationVerification = async (projectRoot, requested) => {
|
|
36096
|
-
const path = projectFile(projectRoot, requested);
|
|
36097
|
-
await access18(path).catch(() => {
|
|
36098
|
-
throw new TypeError(`Mobile certification verification does not exist: ${path}`);
|
|
36099
|
-
});
|
|
36100
|
-
const metadata = await stat6(path);
|
|
36101
|
-
if (!metadata.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
|
|
36102
|
-
throw new TypeError("Mobile certification verification file is invalid.");
|
|
36103
|
-
return parseAbsoluteMobileCertificationVerification(JSON.parse(await readFile27(path, "utf8")));
|
|
36575
|
+
if (typeof capacitorConfig !== "object" || capacitorConfig === null)
|
|
36576
|
+
throw new TypeError("iOS capacitor.config.json must contain an object.");
|
|
36577
|
+
const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
|
|
36578
|
+
if (!Array.isArray(packageClassList))
|
|
36579
|
+
throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
|
|
36580
|
+
const classes = packageClassList.filter((value) => typeof value === "string");
|
|
36581
|
+
const nextConfig = `${JSON.stringify({
|
|
36582
|
+
...capacitorConfig,
|
|
36583
|
+
packageClassList: [
|
|
36584
|
+
...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
|
|
36585
|
+
]
|
|
36586
|
+
}, null, "\t")}
|
|
36587
|
+
`;
|
|
36588
|
+
const changed = await Promise.all([
|
|
36589
|
+
writeChanged3(appDelegatePath, source),
|
|
36590
|
+
writeChanged3(capacitorConfigPath, nextConfig)
|
|
36591
|
+
]);
|
|
36592
|
+
return changed.some(Boolean);
|
|
36104
36593
|
};
|
|
36105
|
-
var
|
|
36106
|
-
const
|
|
36107
|
-
const
|
|
36108
|
-
|
|
36109
|
-
|
|
36110
|
-
|
|
36111
|
-
|
|
36112
|
-
|
|
36113
|
-
|
|
36594
|
+
var configureAndroid5 = async (config) => {
|
|
36595
|
+
const mainActivityPath = await activityPath(config);
|
|
36596
|
+
const activity = await readFile28(mainActivityPath, "utf8");
|
|
36597
|
+
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
36598
|
+
if (!packageName)
|
|
36599
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
36600
|
+
const pluginPath = join31(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
|
|
36601
|
+
const changed = await Promise.all([
|
|
36602
|
+
writeChanged3(mainActivityPath, injectRegistration(activity)),
|
|
36603
|
+
writeChanged3(pluginPath, pluginSource(packageName))
|
|
36604
|
+
]);
|
|
36605
|
+
return changed.some(Boolean);
|
|
36114
36606
|
};
|
|
36115
|
-
var
|
|
36116
|
-
|
|
36117
|
-
|
|
36118
|
-
|
|
36119
|
-
|
|
36120
|
-
|
|
36121
|
-
|
|
36122
|
-
}
|
|
36123
|
-
return path;
|
|
36607
|
+
var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
|
|
36608
|
+
if (config.engine !== "capacitor")
|
|
36609
|
+
return { changed: false };
|
|
36610
|
+
const changed = await Promise.all([
|
|
36611
|
+
platforms.includes("android") ? configureAndroid5(config) : false,
|
|
36612
|
+
platforms.includes("ios") ? configureIos5(config) : false
|
|
36613
|
+
]);
|
|
36614
|
+
return { changed: changed.some(Boolean) };
|
|
36124
36615
|
};
|
|
36616
|
+
|
|
36617
|
+
// src/mobile/index.ts
|
|
36618
|
+
init_pageProtocol();
|
|
36619
|
+
init_producerContext();
|
|
36620
|
+
init_releaseArtifact();
|
|
36621
|
+
|
|
36125
36622
|
// src/mobile/releasePublisher.ts
|
|
36126
|
-
import { access as
|
|
36127
|
-
import { isAbsolute as
|
|
36623
|
+
import { access as access20 } from "fs/promises";
|
|
36624
|
+
import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve21, sep as sep14 } from "path";
|
|
36128
36625
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
36129
36626
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
36130
36627
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -36158,24 +36655,24 @@ var requireCertificationReceipt = (publication, certification, requirement, rele
|
|
|
36158
36655
|
if (verification && (!receipt.provenance || receipt.provenance.subject !== releaseId))
|
|
36159
36656
|
throw new TypeError("Native release registry did not retain trusted certification provenance.");
|
|
36160
36657
|
};
|
|
36161
|
-
var
|
|
36162
|
-
var isPublisher = (value) =>
|
|
36658
|
+
var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36659
|
+
var isPublisher = (value) => isRecord17(value) && typeof value.publish === "function";
|
|
36163
36660
|
var publisherModulePath = (projectRoot, requested) => {
|
|
36164
|
-
const root =
|
|
36165
|
-
const path =
|
|
36166
|
-
const projectRelative =
|
|
36167
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
36661
|
+
const root = resolve21(projectRoot);
|
|
36662
|
+
const path = resolve21(root, requested);
|
|
36663
|
+
const projectRelative = relative19(root, path);
|
|
36664
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative)) {
|
|
36168
36665
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
36169
36666
|
}
|
|
36170
36667
|
return path;
|
|
36171
36668
|
};
|
|
36172
36669
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
36173
36670
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
36174
|
-
await
|
|
36671
|
+
await access20(modulePath).catch(() => {
|
|
36175
36672
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
36176
36673
|
});
|
|
36177
36674
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
36178
|
-
const publisher =
|
|
36675
|
+
const publisher = isRecord17(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
36179
36676
|
if (!isPublisher(publisher)) {
|
|
36180
36677
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
36181
36678
|
}
|
|
@@ -36257,7 +36754,7 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
36257
36754
|
init_pageProtocol();
|
|
36258
36755
|
init_releaseArtifact();
|
|
36259
36756
|
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
36260
|
-
import { dirname as
|
|
36757
|
+
import { dirname as dirname22, extname as extname6, relative as relative20, resolve as resolve22 } from "path";
|
|
36261
36758
|
import ts2 from "typescript";
|
|
36262
36759
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
36263
36760
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
@@ -36308,7 +36805,7 @@ var PAGE_HANDLERS = new Map([
|
|
|
36308
36805
|
]
|
|
36309
36806
|
]);
|
|
36310
36807
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
36311
|
-
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(
|
|
36808
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname22(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
|
|
36312
36809
|
var createProgram = (entry, projectRoot) => {
|
|
36313
36810
|
const configPath2 = findTsconfig(entry, projectRoot);
|
|
36314
36811
|
if (!configPath2) {
|
|
@@ -36320,7 +36817,7 @@ var createProgram = (entry, projectRoot) => {
|
|
|
36320
36817
|
target: ts2.ScriptTarget.ESNext
|
|
36321
36818
|
});
|
|
36322
36819
|
}
|
|
36323
|
-
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys,
|
|
36820
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname22(configPath2));
|
|
36324
36821
|
if (!parsed.fileNames.includes(entry))
|
|
36325
36822
|
parsed.fileNames.push(entry);
|
|
36326
36823
|
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
@@ -36426,7 +36923,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
|
|
|
36426
36923
|
const declaration = symbol?.declarations?.[0];
|
|
36427
36924
|
const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
|
|
36428
36925
|
const exportedName = symbol?.name ?? expression.getText(sourceFile);
|
|
36429
|
-
const source = posixPath(
|
|
36926
|
+
const source = posixPath(relative20(projectRoot, file));
|
|
36430
36927
|
return `${source}#${exportedName}`;
|
|
36431
36928
|
};
|
|
36432
36929
|
var resolveAlias = (symbol, checker) => {
|
|
@@ -36665,7 +37162,7 @@ var analyzeProgram = (program, projectRoot) => {
|
|
|
36665
37162
|
const checker = program.getTypeChecker();
|
|
36666
37163
|
const analyzed = new Map;
|
|
36667
37164
|
for (const sourceFile of program.getSourceFiles()) {
|
|
36668
|
-
const resolvedFile =
|
|
37165
|
+
const resolvedFile = resolve22(sourceFile.fileName);
|
|
36669
37166
|
if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
|
|
36670
37167
|
continue;
|
|
36671
37168
|
const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
|
|
@@ -36758,14 +37255,14 @@ var transformFile = (source, fileName, analysis) => {
|
|
|
36758
37255
|
};
|
|
36759
37256
|
var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
|
|
36760
37257
|
var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
36761
|
-
const projectRoot =
|
|
36762
|
-
const entry =
|
|
37258
|
+
const projectRoot = resolve22(options.projectRoot ?? process.cwd());
|
|
37259
|
+
const entry = resolve22(options.entry);
|
|
36763
37260
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
36764
37261
|
return {
|
|
36765
37262
|
name: "absolute-mobile-route-metadata",
|
|
36766
37263
|
setup(build) {
|
|
36767
37264
|
build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
|
|
36768
|
-
const analysis = analyzed.get(
|
|
37265
|
+
const analysis = analyzed.get(resolve22(path));
|
|
36769
37266
|
if (!analysis)
|
|
36770
37267
|
return;
|
|
36771
37268
|
const source = await Bun.file(path).text();
|
|
@@ -36778,11 +37275,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
|
36778
37275
|
};
|
|
36779
37276
|
};
|
|
36780
37277
|
var inspectAbsoluteMobileRouteMetadata = (options) => {
|
|
36781
|
-
const projectRoot =
|
|
36782
|
-
const entry =
|
|
37278
|
+
const projectRoot = resolve22(options.projectRoot ?? process.cwd());
|
|
37279
|
+
const entry = resolve22(options.entry);
|
|
36783
37280
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
36784
37281
|
return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
|
|
36785
|
-
file: posixPath(
|
|
37282
|
+
file: posixPath(relative20(projectRoot, file)),
|
|
36786
37283
|
metadata
|
|
36787
37284
|
})));
|
|
36788
37285
|
};
|
|
@@ -37116,7 +37613,7 @@ var createCapacitor = (win) => {
|
|
|
37116
37613
|
callbackId
|
|
37117
37614
|
}, callback);
|
|
37118
37615
|
};
|
|
37119
|
-
const p = new Promise((
|
|
37616
|
+
const p = new Promise((resolve23) => call.then(() => resolve23({ remove })));
|
|
37120
37617
|
p.remove = async () => {
|
|
37121
37618
|
console.warn(`Using addListener() without 'await' is deprecated.`);
|
|
37122
37619
|
await remove();
|
|
@@ -37321,11 +37818,11 @@ class CapacitorCookiesPluginWeb extends WebPlugin {
|
|
|
37321
37818
|
var CapacitorCookies = registerPlugin("CapacitorCookies", {
|
|
37322
37819
|
web: () => new CapacitorCookiesPluginWeb
|
|
37323
37820
|
});
|
|
37324
|
-
var readBlobAsBase64 = async (blob) => new Promise((
|
|
37821
|
+
var readBlobAsBase64 = async (blob) => new Promise((resolve23, reject) => {
|
|
37325
37822
|
const reader = new FileReader;
|
|
37326
37823
|
reader.onload = () => {
|
|
37327
37824
|
const base64String = reader.result;
|
|
37328
|
-
|
|
37825
|
+
resolve23(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
|
|
37329
37826
|
};
|
|
37330
37827
|
reader.onerror = (error) => reject(error);
|
|
37331
37828
|
reader.readAsDataURL(blob);
|
|
@@ -37501,7 +37998,7 @@ var REPORT_KINDS = new Set([
|
|
|
37501
37998
|
"signal"
|
|
37502
37999
|
]);
|
|
37503
38000
|
var nativeObservability = registerPlugin("AbsoluteMobileObservability");
|
|
37504
|
-
var
|
|
38001
|
+
var isRecord18 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37505
38002
|
var redactText = (value) => value.slice(0, MAX_STRING_LENGTH).replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/giu, "$1 [REDACTED]").replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\s]*/giu, "$1[REDACTED]").replace(/\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\s*[:=]\s*)[^,\s;]+/giu, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]");
|
|
37506
38003
|
var redactValue = (value, depth = 0) => {
|
|
37507
38004
|
if (depth >= MAX_REDACTION_DEPTH)
|
|
@@ -37512,7 +38009,7 @@ var redactValue = (value, depth = 0) => {
|
|
|
37512
38009
|
return value;
|
|
37513
38010
|
if (Array.isArray(value))
|
|
37514
38011
|
return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
|
|
37515
|
-
if (!
|
|
38012
|
+
if (!isRecord18(value))
|
|
37516
38013
|
return String(value).slice(0, MAX_STRING_LENGTH);
|
|
37517
38014
|
return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
|
|
37518
38015
|
key,
|
|
@@ -37520,7 +38017,7 @@ var redactValue = (value, depth = 0) => {
|
|
|
37520
38017
|
]));
|
|
37521
38018
|
};
|
|
37522
38019
|
var parseReport = (value) => {
|
|
37523
|
-
if (!
|
|
38020
|
+
if (!isRecord18(value) || !isRecord18(value.details))
|
|
37524
38021
|
return;
|
|
37525
38022
|
if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
|
|
37526
38023
|
return;
|
|
@@ -37531,7 +38028,7 @@ var parseReport = (value) => {
|
|
|
37531
38028
|
if (value.platform !== "android" && value.platform !== "ios")
|
|
37532
38029
|
return;
|
|
37533
38030
|
const details = redactValue(value.details);
|
|
37534
|
-
if (!
|
|
38031
|
+
if (!isRecord18(details))
|
|
37535
38032
|
return;
|
|
37536
38033
|
if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
|
|
37537
38034
|
return;
|
|
@@ -41985,8 +42482,8 @@ var createBeacon = (options) => {
|
|
|
41985
42482
|
if (await fontAvailable(element))
|
|
41986
42483
|
return;
|
|
41987
42484
|
if (confirmationMs > 0) {
|
|
41988
|
-
await new Promise((
|
|
41989
|
-
const timer2 = window.setTimeout(
|
|
42485
|
+
await new Promise((resolve23) => {
|
|
42486
|
+
const timer2 = window.setTimeout(resolve23, confirmationMs);
|
|
41990
42487
|
cleanups.push(() => window.clearTimeout(timer2));
|
|
41991
42488
|
});
|
|
41992
42489
|
if (document.visibilityState === "hidden")
|
|
@@ -42974,51 +43471,51 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
42974
43471
|
};
|
|
42975
43472
|
};
|
|
42976
43473
|
// src/mobile/updatePublisher.ts
|
|
42977
|
-
import { access as
|
|
42978
|
-
import { isAbsolute as
|
|
43474
|
+
import { access as access21 } from "fs/promises";
|
|
43475
|
+
import { isAbsolute as isAbsolute13, relative as relative22, resolve as resolve24, sep as sep15 } from "path";
|
|
42979
43476
|
import { pathToFileURL as pathToFileURL4 } from "url";
|
|
42980
43477
|
|
|
42981
43478
|
// src/mobile/updateSigning.ts
|
|
42982
43479
|
import { createHash as createHash17, sign, verify } from "crypto";
|
|
42983
43480
|
import {
|
|
42984
43481
|
cp as cp2,
|
|
42985
|
-
mkdir as
|
|
42986
|
-
mkdtemp as
|
|
42987
|
-
readdir as
|
|
42988
|
-
readFile as
|
|
42989
|
-
rename as
|
|
42990
|
-
rm as
|
|
42991
|
-
stat as
|
|
42992
|
-
writeFile as
|
|
43482
|
+
mkdir as mkdir23,
|
|
43483
|
+
mkdtemp as mkdtemp12,
|
|
43484
|
+
readdir as readdir11,
|
|
43485
|
+
readFile as readFile29,
|
|
43486
|
+
rename as rename20,
|
|
43487
|
+
rm as rm17,
|
|
43488
|
+
stat as stat8,
|
|
43489
|
+
writeFile as writeFile25
|
|
42993
43490
|
} from "fs/promises";
|
|
42994
|
-
import { dirname as
|
|
43491
|
+
import { dirname as dirname23, join as join32, relative as relative21, resolve as resolve23 } from "path";
|
|
42995
43492
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
42996
43493
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
42997
43494
|
var sha2565 = (value) => createHash17("sha256").update(value).digest("hex");
|
|
42998
43495
|
var listFiles = async (root, directory = root) => {
|
|
42999
|
-
const entries = await
|
|
43496
|
+
const entries = await readdir11(directory, { withFileTypes: true });
|
|
43000
43497
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
43001
|
-
const path =
|
|
43498
|
+
const path = join32(directory, entry.name);
|
|
43002
43499
|
if (entry.isDirectory())
|
|
43003
43500
|
return listFiles(root, path);
|
|
43004
43501
|
if (!entry.isFile())
|
|
43005
43502
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
43006
|
-
return [
|
|
43503
|
+
return [relative21(root, path).replaceAll("\\", "/")];
|
|
43007
43504
|
}));
|
|
43008
43505
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
43009
43506
|
};
|
|
43010
43507
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
43011
|
-
const source =
|
|
43508
|
+
const source = join32(root, path);
|
|
43012
43509
|
const [metadata, contents] = await Promise.all([
|
|
43013
|
-
|
|
43014
|
-
|
|
43510
|
+
stat8(source),
|
|
43511
|
+
readFile29(source)
|
|
43015
43512
|
]);
|
|
43016
43513
|
return { bytes: metadata.size, path, sha256: sha2565(contents) };
|
|
43017
43514
|
}));
|
|
43018
43515
|
var releaseIdFor = (value) => `amu_${sha2565(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
43019
43516
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
43020
|
-
const bundleDirectory =
|
|
43021
|
-
const outputRoot =
|
|
43517
|
+
const bundleDirectory = resolve23(options.bundleDirectory);
|
|
43518
|
+
const outputRoot = resolve23(options.outputDirectory);
|
|
43022
43519
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
43023
43520
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
43024
43521
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -43046,38 +43543,38 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
43046
43543
|
value: signature.toString("base64")
|
|
43047
43544
|
}
|
|
43048
43545
|
});
|
|
43049
|
-
await
|
|
43050
|
-
const outputDirectory =
|
|
43051
|
-
const staging = await
|
|
43546
|
+
await mkdir23(outputRoot, { recursive: true });
|
|
43547
|
+
const outputDirectory = join32(outputRoot, manifest.releaseId);
|
|
43548
|
+
const staging = await mkdtemp12(join32(outputRoot, ".stage-"));
|
|
43052
43549
|
try {
|
|
43053
|
-
await cp2(bundleDirectory,
|
|
43550
|
+
await cp2(bundleDirectory, join32(staging, UPDATE_FILES_DIRECTORY), {
|
|
43054
43551
|
force: true,
|
|
43055
43552
|
recursive: true
|
|
43056
43553
|
});
|
|
43057
|
-
await
|
|
43554
|
+
await writeFile25(join32(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
43058
43555
|
`);
|
|
43059
|
-
await
|
|
43556
|
+
await rename20(staging, outputDirectory);
|
|
43060
43557
|
} catch (error) {
|
|
43061
|
-
await
|
|
43558
|
+
await rm17(staging, { force: true, recursive: true });
|
|
43062
43559
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
43063
43560
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
43064
43561
|
throw error;
|
|
43065
43562
|
}
|
|
43066
43563
|
return {
|
|
43067
43564
|
manifest,
|
|
43068
|
-
manifestPath:
|
|
43565
|
+
manifestPath: join32(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
43069
43566
|
outputDirectory
|
|
43070
43567
|
};
|
|
43071
43568
|
};
|
|
43072
43569
|
var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
|
|
43073
|
-
const source =
|
|
43074
|
-
const root =
|
|
43570
|
+
const source = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY, path);
|
|
43571
|
+
const root = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY);
|
|
43075
43572
|
if (!source.startsWith(`${root}/`))
|
|
43076
43573
|
throw new TypeError("Update file escaped its release.");
|
|
43077
|
-
await
|
|
43574
|
+
await mkdir23(dirname23(destination), { recursive: true });
|
|
43078
43575
|
await cp2(source, destination, { force: true });
|
|
43079
43576
|
};
|
|
43080
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
43577
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile29(join32(resolve23(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
43081
43578
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
43082
43579
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
43083
43580
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -43193,17 +43690,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
43193
43690
|
};
|
|
43194
43691
|
var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43195
43692
|
var isPublisher2 = (value) => object5(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
43196
|
-
var
|
|
43197
|
-
const root =
|
|
43198
|
-
const path =
|
|
43199
|
-
const projectRelative =
|
|
43200
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
43693
|
+
var projectPath3 = (projectRoot, requested, label) => {
|
|
43694
|
+
const root = resolve24(projectRoot);
|
|
43695
|
+
const path = resolve24(root, requested);
|
|
43696
|
+
const projectRelative = relative22(root, path);
|
|
43697
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep15}`) || isAbsolute13(projectRelative))
|
|
43201
43698
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
43202
43699
|
return path;
|
|
43203
43700
|
};
|
|
43204
43701
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
43205
|
-
const modulePath =
|
|
43206
|
-
await
|
|
43702
|
+
const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
|
|
43703
|
+
await access21(modulePath).catch(() => {
|
|
43207
43704
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
43208
43705
|
});
|
|
43209
43706
|
const loaded = await import(pathToFileURL4(modulePath).href);
|
|
@@ -43225,7 +43722,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
|
|
|
43225
43722
|
return result;
|
|
43226
43723
|
};
|
|
43227
43724
|
var publishAbsoluteMobileUpdate = async (options) => {
|
|
43228
|
-
const releaseDirectory =
|
|
43725
|
+
const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
|
|
43229
43726
|
const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
|
|
43230
43727
|
const result = await options.publisher.publishUpdate({
|
|
43231
43728
|
manifest,
|
|
@@ -43310,6 +43807,8 @@ export {
|
|
|
43310
43807
|
ABSOLUTE_IOS_RELEASE_FORMAT,
|
|
43311
43808
|
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
43312
43809
|
ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
43810
|
+
ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
43811
|
+
ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
|
|
43313
43812
|
ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
|
|
43314
43813
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
43315
43814
|
ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
|
|
@@ -43373,6 +43872,7 @@ export {
|
|
|
43373
43872
|
assertAbsoluteAndroidInstalledAppUpgrade,
|
|
43374
43873
|
assertAbsoluteDeviceCapabilityPackages,
|
|
43375
43874
|
assertAbsoluteIosInstalledAppUpgrade,
|
|
43875
|
+
auditAbsoluteMobileCiPromotion,
|
|
43376
43876
|
buildAbsoluteAndroidRelease,
|
|
43377
43877
|
buildAbsoluteExpoAndroidWslBundle,
|
|
43378
43878
|
buildAbsoluteIosRelease,
|
|
@@ -43414,6 +43914,7 @@ export {
|
|
|
43414
43914
|
createAbsoluteRemoteIosDevProject,
|
|
43415
43915
|
directAbsoluteProjectPackages,
|
|
43416
43916
|
discoverAbsoluteDeviceCapabilities,
|
|
43917
|
+
discoverAbsoluteMobilePromotionRun,
|
|
43417
43918
|
discoverAbsoluteSyncSchema,
|
|
43418
43919
|
disposeAbsoluteMobilePage,
|
|
43419
43920
|
drainAbsoluteMobileNativeObservability,
|
|
@@ -43436,6 +43937,7 @@ export {
|
|
|
43436
43937
|
inspectAbsoluteAndroidInstalledApp,
|
|
43437
43938
|
inspectAbsoluteBundletool,
|
|
43438
43939
|
inspectAbsoluteIosInstalledApp,
|
|
43940
|
+
inspectAbsoluteMobileCiRun,
|
|
43439
43941
|
inspectAbsoluteMobileRouteMetadata,
|
|
43440
43942
|
inspectAbsoluteMobileUpdateHealth,
|
|
43441
43943
|
inspectAbsoluteMobileUpdateRollout,
|
|
@@ -43488,9 +43990,12 @@ export {
|
|
|
43488
43990
|
parseAbsoluteIosLogLine,
|
|
43489
43991
|
parseAbsoluteMobileBuildPageMetadata,
|
|
43490
43992
|
parseAbsoluteMobileCertificationVerification,
|
|
43993
|
+
parseAbsoluteMobileCiRunStatus,
|
|
43491
43994
|
parseAbsoluteMobileCompatibilityArtifact,
|
|
43492
43995
|
parseAbsoluteMobilePageEnvelope,
|
|
43493
43996
|
parseAbsoluteMobilePageRequest,
|
|
43997
|
+
parseAbsoluteMobilePromotionContext,
|
|
43998
|
+
parseAbsoluteMobilePromotionRunList,
|
|
43494
43999
|
parseAbsoluteMobileUnsignedUpdateManifest,
|
|
43495
44000
|
parseAbsoluteMobileUpdateManifest,
|
|
43496
44001
|
parseAbsoluteMobileUpdateRequest,
|
|
@@ -43567,6 +44072,7 @@ export {
|
|
|
43567
44072
|
verifyAbsoluteMobileReleaseCertification,
|
|
43568
44073
|
verifyAbsoluteMobileUpdateSignature,
|
|
43569
44074
|
waitForAbsoluteIosHmrLog,
|
|
44075
|
+
watchAbsoluteMobileCiRun,
|
|
43570
44076
|
writeAbsoluteCapacitorConfig,
|
|
43571
44077
|
writeAbsoluteExpoProject,
|
|
43572
44078
|
writeAbsoluteMobileCertificationVerification,
|
|
@@ -43575,5 +44081,5 @@ export {
|
|
|
43575
44081
|
writeAbsoluteMobileUpdateRegistry
|
|
43576
44082
|
};
|
|
43577
44083
|
|
|
43578
|
-
//# debugId=
|
|
44084
|
+
//# debugId=199363435B745D2564756E2164756E21
|
|
43579
44085
|
//# sourceMappingURL=index.js.map
|