@absolutejs/absolute 0.20.0-beta.110 → 0.20.0-beta.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/changelog.json +27 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/{mobile-12y4gtsq.js → mobile-k7gpwfs8.js} +615 -124
- package/dist/mobile/index.js +1467 -1048
- package/dist/mobile/index.js.map +8 -7
- package/dist/src/mobile/ciPromotionAudit.d.ts +103 -0
- package/dist/src/mobile/ciWorkflow.d.ts +1 -1
- package/dist/src/mobile/index.d.ts +1 -0
- package/package.json +1 -1
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,1057 @@ 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 = 1;
|
|
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 isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33570
|
+
var requiredString = (value, name) => {
|
|
33571
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
33572
|
+
throw new TypeError(`${name} is invalid.`);
|
|
33573
|
+
return value.trim();
|
|
33574
|
+
};
|
|
33575
|
+
var optionalString = (value, name) => {
|
|
33576
|
+
if (value === null || value === undefined)
|
|
33577
|
+
return null;
|
|
33578
|
+
return requiredString(value, name);
|
|
33579
|
+
};
|
|
33580
|
+
var runId = (value, name) => {
|
|
33581
|
+
if (!RUN_ID_PATTERN.test(value))
|
|
33582
|
+
throw new TypeError(`${name} is invalid.`);
|
|
33583
|
+
return value;
|
|
33584
|
+
};
|
|
33585
|
+
var repositoryArgs = (repository) => {
|
|
33586
|
+
if (repository === undefined)
|
|
33587
|
+
return [];
|
|
33588
|
+
if (!REPOSITORY_PATTERN2.test(repository))
|
|
33589
|
+
throw new TypeError("GitHub repository must be owner/name.");
|
|
33590
|
+
return ["--repo", repository];
|
|
33591
|
+
};
|
|
33592
|
+
var projectPath = (projectRoot, requested) => {
|
|
33593
|
+
const root = resolve19(projectRoot);
|
|
33594
|
+
const path = resolve19(root, requested);
|
|
33595
|
+
const portable = relative17(root, path);
|
|
33596
|
+
if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute11(portable))
|
|
33597
|
+
throw new TypeError("Mobile CI audit output must remain inside the project.");
|
|
33598
|
+
return path;
|
|
33599
|
+
};
|
|
33600
|
+
var defaultRunner = async (command, options) => {
|
|
33601
|
+
const child = Bun.spawn(command, {
|
|
33602
|
+
cwd: options.cwd,
|
|
33603
|
+
env: options.env,
|
|
33604
|
+
stderr: "pipe",
|
|
33605
|
+
stdin: "ignore",
|
|
33606
|
+
stdout: "pipe"
|
|
33607
|
+
});
|
|
33608
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
33609
|
+
child.exited,
|
|
33610
|
+
new Response(child.stderr).text(),
|
|
33611
|
+
new Response(child.stdout).text()
|
|
33612
|
+
]);
|
|
33613
|
+
return { exitCode, stderr, stdout };
|
|
33614
|
+
};
|
|
33615
|
+
var requireCommand = async (run, command, options, label) => {
|
|
33616
|
+
const result = await run(command, options);
|
|
33617
|
+
if (result.exitCode !== 0)
|
|
33618
|
+
throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
|
|
33619
|
+
if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
|
|
33620
|
+
throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
|
|
33621
|
+
return result;
|
|
33622
|
+
};
|
|
33623
|
+
var parseJob = (value) => {
|
|
33624
|
+
if (!isRecord13(value))
|
|
33625
|
+
throw new TypeError("GitHub run job is invalid.");
|
|
33626
|
+
return {
|
|
33627
|
+
conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
|
|
33628
|
+
name: requiredString(value.name, "GitHub job name"),
|
|
33629
|
+
status: requiredString(value.status, "GitHub job status")
|
|
33630
|
+
};
|
|
33631
|
+
};
|
|
33632
|
+
var inspectAbsoluteMobileCiRun = async (options) => {
|
|
33633
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
33634
|
+
const result = await requireCommand(options.run ?? defaultRunner, [
|
|
33635
|
+
"gh",
|
|
33636
|
+
"run",
|
|
33637
|
+
"view",
|
|
33638
|
+
requestedRunId,
|
|
33639
|
+
...repositoryArgs(options.repository),
|
|
33640
|
+
"--json",
|
|
33641
|
+
"conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
|
|
33642
|
+
], { cwd: options.projectRoot }, "GitHub workflow inspection");
|
|
33643
|
+
return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
|
|
33644
|
+
};
|
|
33645
|
+
var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
|
|
33646
|
+
if (!isRecord13(value) || !Array.isArray(value.jobs))
|
|
33647
|
+
throw new TypeError("GitHub workflow run response is invalid.");
|
|
33648
|
+
const { databaseId } = value;
|
|
33649
|
+
if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
|
|
33650
|
+
throw new TypeError("GitHub workflow run identity does not match.");
|
|
33651
|
+
return {
|
|
33652
|
+
conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
|
|
33653
|
+
createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
|
|
33654
|
+
headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
|
|
33655
|
+
headSha: requiredString(value.headSha, "GitHub run headSha"),
|
|
33656
|
+
jobs: value.jobs.map(parseJob),
|
|
33657
|
+
runId: requestedRunId,
|
|
33658
|
+
startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
|
|
33659
|
+
status: requiredString(value.status, "GitHub run status"),
|
|
33660
|
+
updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
|
|
33661
|
+
url: requiredString(value.url, "GitHub run URL"),
|
|
33662
|
+
workflowName: requiredString(value.workflowName, "GitHub workflow name")
|
|
33663
|
+
};
|
|
33664
|
+
};
|
|
33665
|
+
var parseAbsoluteMobilePromotionContext = (value) => {
|
|
33666
|
+
if (!isRecord13(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
|
|
33667
|
+
throw new TypeError("Mobile promotion context is invalid.");
|
|
33668
|
+
const { platform: platform3 } = value;
|
|
33669
|
+
const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
|
|
33670
|
+
if (sourceArtifact !== `absolute-mobile-${platform3}`)
|
|
33671
|
+
throw new TypeError("Mobile promotion source artifact is invalid.");
|
|
33672
|
+
return {
|
|
33673
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
|
|
33674
|
+
platform: platform3,
|
|
33675
|
+
promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
|
|
33676
|
+
sourceArtifact,
|
|
33677
|
+
sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
|
|
33678
|
+
};
|
|
33679
|
+
};
|
|
33680
|
+
var watchAbsoluteMobileCiRun = async (options) => {
|
|
33681
|
+
const requestedRunId = runId(options.runId, "GitHub run ID");
|
|
33682
|
+
await requireCommand(options.run ?? defaultRunner, [
|
|
33683
|
+
"gh",
|
|
33684
|
+
"run",
|
|
33685
|
+
"watch",
|
|
33686
|
+
requestedRunId,
|
|
33687
|
+
...repositoryArgs(options.repository),
|
|
33688
|
+
"--exit-status"
|
|
33689
|
+
], { cwd: options.projectRoot }, "GitHub workflow watch");
|
|
33690
|
+
return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
|
|
33691
|
+
};
|
|
33692
|
+
var readBoundedJson = async (path, label) => {
|
|
33693
|
+
const metadata = await stat7(path).catch(() => null);
|
|
33694
|
+
if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
|
|
33695
|
+
throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
|
|
33696
|
+
const value = JSON.parse(await readFile22(path, "utf8"));
|
|
33697
|
+
return value;
|
|
33698
|
+
};
|
|
33699
|
+
var findFiles = async (root, name) => {
|
|
33700
|
+
const found = [];
|
|
33701
|
+
let visited = 0;
|
|
33702
|
+
const walk = async (directory) => {
|
|
33703
|
+
const entries = await readdir7(directory, { withFileTypes: true });
|
|
33704
|
+
await Promise.all(entries.map(async (entry) => {
|
|
33705
|
+
visited += 1;
|
|
33706
|
+
if (visited > MAX_SEARCH_FILES)
|
|
33707
|
+
throw new TypeError("Downloaded GitHub artifact contains too many files.");
|
|
33708
|
+
const path = join24(directory, entry.name);
|
|
33709
|
+
if (entry.isDirectory()) {
|
|
33710
|
+
await walk(path);
|
|
33711
|
+
return;
|
|
33712
|
+
}
|
|
33713
|
+
if (entry.isFile() && entry.name === name)
|
|
33714
|
+
found.push(path);
|
|
33715
|
+
}));
|
|
33716
|
+
};
|
|
33717
|
+
await walk(root);
|
|
33718
|
+
return found;
|
|
33719
|
+
};
|
|
33720
|
+
var inferPromotionPlatform = (status) => {
|
|
33721
|
+
const matched = status.jobs.flatMap((job) => {
|
|
33722
|
+
if (job.name === "Promote exact certified Android release")
|
|
33723
|
+
return ["android"];
|
|
33724
|
+
if (job.name === "Promote exact certified iOS release")
|
|
33725
|
+
return ["ios"];
|
|
33726
|
+
return [];
|
|
33727
|
+
});
|
|
33728
|
+
if (matched.length !== 1)
|
|
33729
|
+
throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
|
|
33730
|
+
const [platform3] = matched;
|
|
33731
|
+
if (!platform3)
|
|
33732
|
+
throw new TypeError("Mobile promotion platform is missing.");
|
|
33733
|
+
return platform3;
|
|
33734
|
+
};
|
|
33735
|
+
var onlyPath = (paths, label) => {
|
|
33736
|
+
const [path] = paths;
|
|
33737
|
+
if (paths.length !== 1 || !path)
|
|
33738
|
+
throw new TypeError(`${label} must contain exactly one matching file.`);
|
|
33739
|
+
return path;
|
|
33740
|
+
};
|
|
33741
|
+
var canonicalJson2 = (value) => {
|
|
33742
|
+
if (Array.isArray(value))
|
|
33743
|
+
return `[${value.map((entry) => canonicalJson2(entry)).join(",")}]`;
|
|
33744
|
+
if (isRecord13(value))
|
|
33745
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
|
|
33746
|
+
return JSON.stringify(value);
|
|
33747
|
+
};
|
|
33748
|
+
var publicationSummary = (value, platform3, release, certification) => {
|
|
33749
|
+
if (!isRecord13(value) || !isRecord13(value.record) || !isRecord13(value.record.metadata))
|
|
33750
|
+
throw new TypeError("Mobile promotion receipt is invalid.");
|
|
33751
|
+
const { metadata } = value.record;
|
|
33752
|
+
if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
|
|
33753
|
+
throw new TypeError("Mobile promotion receipt release identity does not match.");
|
|
33754
|
+
if (!isRecord13(value.certification) || !isRecord13(value.certification.provenance))
|
|
33755
|
+
throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
|
|
33756
|
+
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)
|
|
33757
|
+
throw new TypeError("Mobile promotion certification receipt does not match.");
|
|
33758
|
+
requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
|
|
33759
|
+
requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
|
|
33760
|
+
const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
|
|
33761
|
+
if (Number.isNaN(Date.parse(provenanceTime)))
|
|
33762
|
+
throw new TypeError("Mobile promotion provenance time is invalid.");
|
|
33763
|
+
if (typeof value.reused !== "boolean")
|
|
33764
|
+
throw new TypeError("Mobile promotion reuse status is invalid.");
|
|
33765
|
+
const channel = isRecord13(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
|
|
33766
|
+
if (isRecord13(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
|
|
33767
|
+
throw new TypeError("Mobile promotion channel receipt does not match.");
|
|
33768
|
+
const providerReceipt = platform3 === "android" ? value.googlePlay : value.appStoreConnect;
|
|
33769
|
+
if (providerReceipt === undefined)
|
|
33770
|
+
return {
|
|
33771
|
+
channel,
|
|
33772
|
+
provider: "registry",
|
|
33773
|
+
reused: value.reused,
|
|
33774
|
+
stage: null
|
|
33775
|
+
};
|
|
33776
|
+
if (!isRecord13(providerReceipt) || !isRecord13(providerReceipt.receipt))
|
|
33777
|
+
throw new TypeError("Mobile store promotion receipt is invalid.");
|
|
33778
|
+
if (typeof providerReceipt.reused !== "boolean")
|
|
33779
|
+
throw new TypeError("Mobile store promotion reuse status is invalid.");
|
|
33780
|
+
const expectedProvider = platform3 === "android" ? "google-play" : "app-store-connect";
|
|
33781
|
+
if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
|
|
33782
|
+
throw new TypeError("Mobile store promotion receipt does not match.");
|
|
33783
|
+
return {
|
|
33784
|
+
channel,
|
|
33785
|
+
provider: expectedProvider,
|
|
33786
|
+
reused: providerReceipt.reused,
|
|
33787
|
+
stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
|
|
33788
|
+
};
|
|
33789
|
+
};
|
|
33790
|
+
var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
|
|
33791
|
+
|
|
33792
|
+
- Status: **VERIFIED**
|
|
33793
|
+
- Platform: ${audit.platform}
|
|
33794
|
+
- Release: \`${audit.release.releaseId}\`
|
|
33795
|
+
- Artifact SHA-256: \`${audit.release.sha256}\`
|
|
33796
|
+
- Source workflow run: ${audit.github.sourceRunId}
|
|
33797
|
+
- Promotion workflow run: ${audit.github.promotionRunId}
|
|
33798
|
+
- Workflow: ${audit.github.workflowName}
|
|
33799
|
+
- GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
|
|
33800
|
+
- Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
|
|
33801
|
+
- Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
|
|
33802
|
+
- Verified at: ${audit.verifiedAt}
|
|
33803
|
+
|
|
33804
|
+
This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
|
|
33805
|
+
`;
|
|
33806
|
+
var auditAbsoluteMobileCiPromotion = async (options) => {
|
|
33807
|
+
const runner = options.run ?? defaultRunner;
|
|
33808
|
+
const promotionRunId = runId(options.runId, "GitHub promotion run ID");
|
|
33809
|
+
const status = await inspectAbsoluteMobileCiRun({
|
|
33810
|
+
projectRoot: options.projectRoot,
|
|
33811
|
+
...options.repository ? { repository: options.repository } : {},
|
|
33812
|
+
run: runner,
|
|
33813
|
+
runId: promotionRunId
|
|
33814
|
+
});
|
|
33815
|
+
if (status.status !== "completed" || status.conclusion !== "success")
|
|
33816
|
+
throw new TypeError("Mobile promotion run has not completed successfully.");
|
|
33817
|
+
const platform3 = inferPromotionPlatform(status);
|
|
33818
|
+
const output = projectPath(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
|
|
33819
|
+
if (await access18(output).then(() => true).catch(() => false))
|
|
33820
|
+
throw new TypeError(`Mobile CI audit output already exists: ${output}`);
|
|
33821
|
+
await mkdir18(dirname16(output), { recursive: true });
|
|
33822
|
+
const staging = await mkdtemp11(join24(dirname16(output), ".promotion-audit-"));
|
|
33823
|
+
try {
|
|
33824
|
+
const evidenceRoot = join24(staging, "promotion-evidence");
|
|
33825
|
+
await mkdir18(evidenceRoot);
|
|
33826
|
+
await requireCommand(runner, [
|
|
33827
|
+
"gh",
|
|
33828
|
+
"run",
|
|
33829
|
+
"download",
|
|
33830
|
+
promotionRunId,
|
|
33831
|
+
...repositoryArgs(options.repository),
|
|
33832
|
+
"--name",
|
|
33833
|
+
`absolute-mobile-${platform3}-promotion-${promotionRunId}`,
|
|
33834
|
+
"--dir",
|
|
33835
|
+
evidenceRoot
|
|
33836
|
+
], { cwd: options.projectRoot }, "GitHub promotion artifact download");
|
|
33837
|
+
const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
|
|
33838
|
+
let context;
|
|
33839
|
+
if (contextPaths.length === 1)
|
|
33840
|
+
context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
|
|
33841
|
+
else if (contextPaths.length > 1)
|
|
33842
|
+
throw new TypeError("Promotion artifact contains duplicate contexts.");
|
|
33843
|
+
if (context && context.promotionRunId !== promotionRunId)
|
|
33844
|
+
throw new TypeError("Promotion context run identity does not match.");
|
|
33845
|
+
if (context && context.platform !== platform3)
|
|
33846
|
+
throw new TypeError("Promotion context platform does not match.");
|
|
33847
|
+
const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
|
|
33848
|
+
if (!sourceRunId)
|
|
33849
|
+
throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
|
|
33850
|
+
if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
|
|
33851
|
+
throw new TypeError("Requested source run does not match promotion context.");
|
|
33852
|
+
const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform3}`;
|
|
33853
|
+
const sourceRoot = join24(staging, "source-release");
|
|
33854
|
+
await mkdir18(sourceRoot);
|
|
33855
|
+
await requireCommand(runner, [
|
|
33856
|
+
"gh",
|
|
33857
|
+
"run",
|
|
33858
|
+
"download",
|
|
33859
|
+
sourceRunId,
|
|
33860
|
+
...repositoryArgs(options.repository),
|
|
33861
|
+
"--name",
|
|
33862
|
+
sourceArtifact,
|
|
33863
|
+
"--dir",
|
|
33864
|
+
sourceRoot
|
|
33865
|
+
], { cwd: options.projectRoot }, "GitHub source release download");
|
|
33866
|
+
const releaseMetadata = await findFiles(sourceRoot, "release.json");
|
|
33867
|
+
if (releaseMetadata.length !== 1)
|
|
33868
|
+
throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
|
|
33869
|
+
const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
|
|
33870
|
+
const release = platform3 === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
|
|
33871
|
+
const certificationPaths = await findFiles(evidenceRoot, "certification.json");
|
|
33872
|
+
const verificationPaths = await findFiles(evidenceRoot, "verification.json");
|
|
33873
|
+
const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
|
|
33874
|
+
if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
|
|
33875
|
+
throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
|
|
33876
|
+
const certificationPath = onlyPath(certificationPaths, "Promotion certification");
|
|
33877
|
+
const verificationPath = onlyPath(verificationPaths, "Promotion verification");
|
|
33878
|
+
const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
|
|
33879
|
+
const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
|
|
33880
|
+
const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
|
|
33881
|
+
const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
|
|
33882
|
+
if (verification.identity.sha !== status.headSha)
|
|
33883
|
+
throw new TypeError("Sigstore workflow commit does not match the promotion run.");
|
|
33884
|
+
if (options.repository && verification.identity.repository !== options.repository)
|
|
33885
|
+
throw new TypeError("Sigstore repository does not match the requested repository.");
|
|
33886
|
+
const bundlePath = `${certificationPath}.sigstore.json`;
|
|
33887
|
+
const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
|
|
33888
|
+
if (canonicalJson2(bundle) !== canonicalJson2(verification.bundle))
|
|
33889
|
+
throw new TypeError("Sigstore bundle does not match verification envelope.");
|
|
33890
|
+
await requireCommand(runner, [
|
|
33891
|
+
"bunx",
|
|
33892
|
+
"@absolutejs/attest@0.2.0",
|
|
33893
|
+
"verify-blobs",
|
|
33894
|
+
certificationPath
|
|
33895
|
+
], {
|
|
33896
|
+
cwd: options.projectRoot,
|
|
33897
|
+
env: {
|
|
33898
|
+
...process.env,
|
|
33899
|
+
GITHUB_REF: verification.identity.ref,
|
|
33900
|
+
GITHUB_REPOSITORY: verification.identity.repository,
|
|
33901
|
+
GITHUB_SHA: verification.identity.sha,
|
|
33902
|
+
GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
|
|
33903
|
+
}
|
|
33904
|
+
}, "Sigstore certification verification");
|
|
33905
|
+
const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform3, release, certification);
|
|
33906
|
+
const audit = {
|
|
33907
|
+
certification: {
|
|
33908
|
+
certificationId: certification.certificationId,
|
|
33909
|
+
requirement: certification.requirement,
|
|
33910
|
+
strength: certification.strength
|
|
33911
|
+
},
|
|
33912
|
+
format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
33913
|
+
github: {
|
|
33914
|
+
conclusion: status.conclusion,
|
|
33915
|
+
promotionRunId,
|
|
33916
|
+
repository: verification.identity.repository,
|
|
33917
|
+
sourceRunId,
|
|
33918
|
+
url: status.url,
|
|
33919
|
+
workflowName: status.workflowName
|
|
33920
|
+
},
|
|
33921
|
+
platform: platform3,
|
|
33922
|
+
provenance: verification.identity,
|
|
33923
|
+
publication,
|
|
33924
|
+
release: {
|
|
33925
|
+
appId: release.metadata.appId,
|
|
33926
|
+
bytes: release.metadata.bytes,
|
|
33927
|
+
engine: release.metadata.engine,
|
|
33928
|
+
releaseId: release.metadata.releaseId,
|
|
33929
|
+
runtime: release.metadata.runtime,
|
|
33930
|
+
sha256: release.metadata.sha256
|
|
33931
|
+
},
|
|
33932
|
+
status: "verified",
|
|
33933
|
+
verifiedAt: new Date().toISOString()
|
|
33934
|
+
};
|
|
33935
|
+
await Promise.all([
|
|
33936
|
+
writeFile18(join24(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
|
|
33937
|
+
`, { mode: 384 }),
|
|
33938
|
+
writeFile18(join24(staging, "audit.md"), renderAuditMarkdown(audit), {
|
|
33939
|
+
mode: 384
|
|
33940
|
+
})
|
|
33941
|
+
]);
|
|
33942
|
+
await rename14(staging, output);
|
|
33943
|
+
return { audit, directory: output };
|
|
33944
|
+
} catch (error) {
|
|
33945
|
+
await rm14(staging, { force: true, recursive: true });
|
|
33946
|
+
throw error;
|
|
33947
|
+
}
|
|
33948
|
+
};
|
|
33949
|
+
// src/mobile/ciWorkflow.ts
|
|
33950
|
+
import { existsSync as existsSync4 } from "fs";
|
|
33951
|
+
import { access as access19, mkdir as mkdir19, readFile as readFile23, writeFile as writeFile19 } from "fs/promises";
|
|
33952
|
+
import { dirname as dirname17, extname as extname5, relative as relative18, resolve as resolve20, sep as sep13 } from "path";
|
|
33953
|
+
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 3;
|
|
33954
|
+
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
33955
|
+
var CI_ENV_INDENTATION = 6;
|
|
33956
|
+
var RESERVED_SECRET_NAMES = new Set([
|
|
33957
|
+
"ABSOLUTE_ANDROID_KEYSTORE_BASE64",
|
|
33958
|
+
"ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
|
|
33959
|
+
"ABSOLUTE_ANDROID_KEY_ALIAS",
|
|
33960
|
+
"ABSOLUTE_ANDROID_KEY_PASSWORD",
|
|
33961
|
+
"ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
|
|
33962
|
+
"ABSOLUTE_IOS_CERTIFICATE_BASE64",
|
|
33963
|
+
"ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
|
|
33964
|
+
"ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
|
|
33965
|
+
"ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
|
|
33966
|
+
"ABSOLUTE_IOS_DEVELOPMENT_TEAM",
|
|
33967
|
+
"APP_STORE_CONNECT_ISSUER_ID",
|
|
33968
|
+
"APP_STORE_CONNECT_KEY_ID",
|
|
33969
|
+
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
33970
|
+
]);
|
|
33971
|
+
var exists9 = async (path) => {
|
|
33972
|
+
try {
|
|
33973
|
+
await access19(path);
|
|
33974
|
+
return true;
|
|
33975
|
+
} catch {
|
|
33976
|
+
return false;
|
|
33977
|
+
}
|
|
33978
|
+
};
|
|
33979
|
+
var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
33980
|
+
var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
33981
|
+
const root = resolve20(projectRoot);
|
|
33982
|
+
const path = resolve20(root, value);
|
|
33983
|
+
const portable = relative18(root, path).replaceAll("\\", "/");
|
|
33984
|
+
if (portable === ".." || portable.startsWith(`..${sep13}`) || portable.startsWith("../") || portable === "") {
|
|
33985
|
+
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
33986
|
+
}
|
|
33987
|
+
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
33988
|
+
throw new TypeError(`${field2} contains an unsafe path.`);
|
|
33989
|
+
if (!options.allowMissing && !existsSync4(path))
|
|
33990
|
+
throw new TypeError(`${field2} does not exist inside the project.`);
|
|
33991
|
+
return portable;
|
|
33992
|
+
};
|
|
33993
|
+
var workflowOutputPath = (projectRoot, value) => {
|
|
33994
|
+
const root = resolve20(projectRoot);
|
|
33995
|
+
const workflows = resolve20(root, ".github/workflows");
|
|
33996
|
+
const path = resolve20(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
33997
|
+
const portable = relative18(workflows, path);
|
|
33998
|
+
if (portable === ".." || portable.startsWith(`..${sep13}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
|
|
33999
|
+
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
34000
|
+
}
|
|
34001
|
+
return path;
|
|
34002
|
+
};
|
|
34003
|
+
var normalizeSecretEnvironment = (values = []) => {
|
|
34004
|
+
const names = [...new Set(values)].sort();
|
|
34005
|
+
for (const name of names) {
|
|
34006
|
+
if (!SECRET_NAME_PATTERN.test(name))
|
|
34007
|
+
throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
|
|
34008
|
+
if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
|
|
34009
|
+
throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
|
|
34010
|
+
}
|
|
34011
|
+
}
|
|
34012
|
+
return names;
|
|
34013
|
+
};
|
|
34014
|
+
var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
|
|
34015
|
+
`);
|
|
34016
|
+
var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
|
|
34017
|
+
ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
|
|
34018
|
+
ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
|
|
34019
|
+
var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
|
|
34020
|
+
args+=(--config "$ABSOLUTE_CONFIG_PATH")
|
|
34021
|
+
fi`;
|
|
34022
|
+
var installSteps = ` - name: Check out source
|
|
34023
|
+
uses: actions/checkout@v6
|
|
34024
|
+
- name: Install Bun
|
|
34025
|
+
uses: oven-sh/setup-bun@v2
|
|
34026
|
+
- name: Install exact dependencies
|
|
34027
|
+
run: bun ci`;
|
|
34028
|
+
var bundleAuditSteps = ` - name: Prepare production mobile bundle
|
|
34029
|
+
shell: bash
|
|
34030
|
+
run: |
|
|
34031
|
+
args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
|
|
34032
|
+
${appendConfigArgument}
|
|
34033
|
+
"\${args[@]}"
|
|
34034
|
+
- name: Run cryptographic mobile bundle audit
|
|
34035
|
+
id: mobile-bundle-audit
|
|
34036
|
+
continue-on-error: true
|
|
34037
|
+
shell: bash
|
|
34038
|
+
run: |
|
|
34039
|
+
mkdir -p .absolutejs/mobile-ci
|
|
34040
|
+
args=(bunx absolute mobile inspect --json --require-bundle)
|
|
34041
|
+
${appendConfigArgument}
|
|
34042
|
+
"\${args[@]}" > .absolutejs/mobile-ci/inspection.json
|
|
34043
|
+
- name: Upload mobile inspection report
|
|
34044
|
+
if: always()
|
|
34045
|
+
uses: actions/upload-artifact@v7
|
|
34046
|
+
with:
|
|
34047
|
+
name: absolute-mobile-inspection
|
|
34048
|
+
path: .absolutejs/mobile-ci/inspection.json
|
|
34049
|
+
if-no-files-found: error
|
|
34050
|
+
retention-days: 30
|
|
34051
|
+
include-hidden-files: true
|
|
34052
|
+
- name: Enforce mobile bundle audit
|
|
34053
|
+
if: steps.mobile-bundle-audit.outcome != 'success'
|
|
34054
|
+
run: exit 1`;
|
|
34055
|
+
var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
|
|
34056
|
+
id: mobile-release-audit
|
|
34057
|
+
continue-on-error: true
|
|
34058
|
+
shell: bash
|
|
34059
|
+
run: |
|
|
34060
|
+
mkdir -p .absolutejs/mobile-ci
|
|
34061
|
+
args=(bunx absolute mobile doctor release ${platform3} --json)
|
|
34062
|
+
${appendConfigArgument}
|
|
34063
|
+
"\${args[@]}" > .absolutejs/mobile-ci/compliance.json
|
|
34064
|
+
- name: Upload mobile compliance report
|
|
34065
|
+
if: always()
|
|
34066
|
+
uses: actions/upload-artifact@v7
|
|
34067
|
+
with:
|
|
34068
|
+
name: absolute-mobile-compliance-\${{ github.job }}
|
|
34069
|
+
path: .absolutejs/mobile-ci/compliance.json
|
|
34070
|
+
if-no-files-found: error
|
|
34071
|
+
retention-days: 30
|
|
34072
|
+
include-hidden-files: true
|
|
34073
|
+
- name: Enforce mobile release audit
|
|
34074
|
+
if: steps.mobile-release-audit.outcome != 'success'
|
|
34075
|
+
run: exit 1`;
|
|
34076
|
+
var platformInput = (platforms) => {
|
|
34077
|
+
const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
|
|
34078
|
+
return ` platform:
|
|
34079
|
+
description: Native platform to build
|
|
34080
|
+
required: true
|
|
34081
|
+
type: choice
|
|
34082
|
+
default: ${choices[0]}
|
|
33166
34083
|
options:
|
|
33167
34084
|
${choices.map((value) => ` - ${value}`).join(`
|
|
33168
34085
|
`)}`;
|
|
@@ -33575,6 +34492,10 @@ var promotionSharedSteps = (platform3) => ` - name: Restore exact ${platfor
|
|
|
33575
34492
|
path: .absolutejs/mobile/releases/${platform3}
|
|
33576
34493
|
run-id: \${{ inputs.source_run_id }}
|
|
33577
34494
|
github-token: \${{ github.token }}
|
|
34495
|
+
- name: Record bounded promotion context
|
|
34496
|
+
shell: bash
|
|
34497
|
+
run: |
|
|
34498
|
+
bun -e 'const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!sourceRunId?.match(/^[1-9][0-9]*$/) || !promotionRunId?.match(/^[1-9][0-9]*$/)) throw new Error("Invalid promotion run identity"); await Bun.write(".absolutejs/mobile-ci/promotion-context.json", JSON.stringify({ format: 1, platform: ${JSON.stringify(platform3)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform3}`)}, sourceRunId }, null, 2) + "\\n");'
|
|
33578
34499
|
- name: Import and verify exact certification
|
|
33579
34500
|
shell: bash
|
|
33580
34501
|
run: |
|
|
@@ -33613,6 +34534,7 @@ var androidPromotionJob = (options) => {
|
|
|
33613
34534
|
env:
|
|
33614
34535
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
33615
34536
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
34537
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
33616
34538
|
ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
|
|
33617
34539
|
ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
|
|
33618
34540
|
GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
|
|
@@ -33652,6 +34574,7 @@ ${promotionSharedSteps("android")}
|
|
|
33652
34574
|
name: absolute-mobile-android-promotion-\${{ github.run_id }}
|
|
33653
34575
|
path: |
|
|
33654
34576
|
.absolutejs/mobile-ci/imported-certification/
|
|
34577
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
33655
34578
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
33656
34579
|
if-no-files-found: error
|
|
33657
34580
|
retention-days: 90
|
|
@@ -33676,6 +34599,7 @@ var iosPromotionJob = (options) => {
|
|
|
33676
34599
|
env:
|
|
33677
34600
|
ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
|
|
33678
34601
|
ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
|
|
34602
|
+
ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
|
|
33679
34603
|
ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
|
|
33680
34604
|
ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
|
|
33681
34605
|
APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
|
@@ -33717,6 +34641,7 @@ ${promotionSharedSteps("ios")}
|
|
|
33717
34641
|
name: absolute-mobile-ios-promotion-\${{ github.run_id }}
|
|
33718
34642
|
path: |
|
|
33719
34643
|
.absolutejs/mobile-ci/imported-certification/
|
|
34644
|
+
.absolutejs/mobile-ci/promotion-context.json
|
|
33720
34645
|
.absolutejs/mobile-ci/promotion-receipt.json
|
|
33721
34646
|
if-no-files-found: error
|
|
33722
34647
|
retention-days: 90
|
|
@@ -33753,9 +34678,9 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
|
|
|
33753
34678
|
].sort();
|
|
33754
34679
|
const includePublishing = options.includePublishing === true;
|
|
33755
34680
|
const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
|
|
33756
|
-
const serverEntry =
|
|
33757
|
-
const configPath = options.configPath ?
|
|
33758
|
-
const registryModule =
|
|
34681
|
+
const serverEntry = projectPath2(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
|
|
34682
|
+
const configPath = options.configPath ? projectPath2(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
|
|
34683
|
+
const registryModule = projectPath2(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
|
|
33759
34684
|
const environment = commandEnvironment2({
|
|
33760
34685
|
configPath,
|
|
33761
34686
|
registryModule,
|
|
@@ -33819,12 +34744,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
33819
34744
|
var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
33820
34745
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
33821
34746
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
33822
|
-
const previous = await
|
|
34747
|
+
const previous = await exists9(path) ? await readFile23(path, "utf8") : undefined;
|
|
33823
34748
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
33824
|
-
throw new TypeError(`${
|
|
34749
|
+
throw new TypeError(`${relative18(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
33825
34750
|
if (previous !== generated.workflow) {
|
|
33826
|
-
await
|
|
33827
|
-
await
|
|
34751
|
+
await mkdir19(dirname17(path), { recursive: true });
|
|
34752
|
+
await writeFile19(path, generated.workflow);
|
|
33828
34753
|
}
|
|
33829
34754
|
return {
|
|
33830
34755
|
changed: previous !== generated.workflow,
|
|
@@ -33842,20 +34767,20 @@ init_materializedBundle();
|
|
|
33842
34767
|
init_mobilePreview();
|
|
33843
34768
|
|
|
33844
34769
|
// src/mobile/nativeDeepLinks.ts
|
|
33845
|
-
import { readFile as
|
|
33846
|
-
import { join as
|
|
34770
|
+
import { readFile as readFile24, rename as rename15, writeFile as writeFile20 } from "fs/promises";
|
|
34771
|
+
import { join as join27 } from "path";
|
|
33847
34772
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->";
|
|
33848
34773
|
var END_MARKER = "<!-- absolutejs:deep-links:end -->";
|
|
33849
34774
|
var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
|
|
33850
34775
|
var NOT_FOUND = -1;
|
|
33851
34776
|
var escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
33852
34777
|
var writeChangedFile = async (path, source) => {
|
|
33853
|
-
const current = await
|
|
34778
|
+
const current = await readFile24(path, "utf8");
|
|
33854
34779
|
if (current === source)
|
|
33855
34780
|
return false;
|
|
33856
34781
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
33857
|
-
await
|
|
33858
|
-
await
|
|
34782
|
+
await writeFile20(temporary, source, { flag: "wx" });
|
|
34783
|
+
await rename15(temporary, path);
|
|
33859
34784
|
return true;
|
|
33860
34785
|
};
|
|
33861
34786
|
var replaceManagedRegion = (source, region, insertAt) => {
|
|
@@ -33900,8 +34825,8 @@ ${hosts}
|
|
|
33900
34825
|
`;
|
|
33901
34826
|
};
|
|
33902
34827
|
var configureAndroid = async (config) => {
|
|
33903
|
-
const path =
|
|
33904
|
-
const source = await
|
|
34828
|
+
const path = join27(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
34829
|
+
const source = await readFile24(path, "utf8");
|
|
33905
34830
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
33906
34831
|
if (mainActivity === NOT_FOUND) {
|
|
33907
34832
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -33926,8 +34851,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
|
|
|
33926
34851
|
${END_MARKER}
|
|
33927
34852
|
`;
|
|
33928
34853
|
var configureIosInfo = async (config) => {
|
|
33929
|
-
const path =
|
|
33930
|
-
const source = await
|
|
34854
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
34855
|
+
const source = await readFile24(path, "utf8");
|
|
33931
34856
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
33932
34857
|
${END_MARKER}
|
|
33933
34858
|
`;
|
|
@@ -33950,10 +34875,10 @@ ${domains}
|
|
|
33950
34875
|
`;
|
|
33951
34876
|
};
|
|
33952
34877
|
var configureIosEntitlements = async (config) => {
|
|
33953
|
-
const path =
|
|
34878
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
33954
34879
|
let current = "";
|
|
33955
34880
|
try {
|
|
33956
|
-
current = await
|
|
34881
|
+
current = await readFile24(path, "utf8");
|
|
33957
34882
|
} catch (error) {
|
|
33958
34883
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
33959
34884
|
throw error;
|
|
@@ -33963,13 +34888,13 @@ var configureIosEntitlements = async (config) => {
|
|
|
33963
34888
|
if (current === source)
|
|
33964
34889
|
return false;
|
|
33965
34890
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
33966
|
-
await
|
|
33967
|
-
await
|
|
34891
|
+
await writeFile20(temporary, source, { flag: "wx" });
|
|
34892
|
+
await rename15(temporary, path);
|
|
33968
34893
|
return true;
|
|
33969
34894
|
};
|
|
33970
34895
|
var configureIosProject = async (config) => {
|
|
33971
|
-
const path =
|
|
33972
|
-
const source = await
|
|
34896
|
+
const path = join27(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
34897
|
+
const source = await readFile24(path, "utf8");
|
|
33973
34898
|
const declarations = [
|
|
33974
34899
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
33975
34900
|
].map((match) => match[1]);
|
|
@@ -34008,8 +34933,8 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
|
|
|
34008
34933
|
};
|
|
34009
34934
|
// src/mobile/nativeDeviceCapabilities.ts
|
|
34010
34935
|
init_deviceCapabilities();
|
|
34011
|
-
import { readFile as
|
|
34012
|
-
import { join as
|
|
34936
|
+
import { readFile as readFile25, rename as rename16, writeFile as writeFile21 } from "fs/promises";
|
|
34937
|
+
import { join as join28 } from "path";
|
|
34013
34938
|
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
|
|
34014
34939
|
var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
|
|
34015
34940
|
var NOT_FOUND2 = -1;
|
|
@@ -34019,12 +34944,12 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
|
|
|
34019
34944
|
var PUSH_END_MARKER = "absolutejs:push-notifications:end";
|
|
34020
34945
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
34021
34946
|
var writeChangedFile2 = async (path, source) => {
|
|
34022
|
-
const current = await
|
|
34947
|
+
const current = await readFile25(path, "utf8");
|
|
34023
34948
|
if (current === source)
|
|
34024
34949
|
return false;
|
|
34025
34950
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34026
|
-
await
|
|
34027
|
-
await
|
|
34951
|
+
await writeFile21(temporary, source, { flag: "wx" });
|
|
34952
|
+
await rename16(temporary, path);
|
|
34028
34953
|
return true;
|
|
34029
34954
|
};
|
|
34030
34955
|
var writeOptionalChangedFile = async (path, source) => {
|
|
@@ -34032,14 +34957,14 @@ var writeOptionalChangedFile = async (path, source) => {
|
|
|
34032
34957
|
if (current === source)
|
|
34033
34958
|
return false;
|
|
34034
34959
|
if (current === null) {
|
|
34035
|
-
await
|
|
34960
|
+
await writeFile21(path, source, { flag: "wx" });
|
|
34036
34961
|
return true;
|
|
34037
34962
|
}
|
|
34038
34963
|
return writeChangedFile2(path, source);
|
|
34039
34964
|
};
|
|
34040
34965
|
var optionalSource = async (path) => {
|
|
34041
34966
|
try {
|
|
34042
|
-
return await
|
|
34967
|
+
return await readFile25(path, "utf8");
|
|
34043
34968
|
} catch (error) {
|
|
34044
34969
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34045
34970
|
return null;
|
|
@@ -34153,15 +35078,15 @@ var writeIosPrivacyManifest = async (path, current, source) => {
|
|
|
34153
35078
|
return false;
|
|
34154
35079
|
if (current !== null)
|
|
34155
35080
|
return writeChangedFile2(path, source);
|
|
34156
|
-
await
|
|
35081
|
+
await writeFile21(path, source, { flag: "wx" });
|
|
34157
35082
|
return true;
|
|
34158
35083
|
};
|
|
34159
35084
|
var configureIosPrivacyProject = async (config, requirements) => {
|
|
34160
35085
|
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
34161
35086
|
return false;
|
|
34162
|
-
const
|
|
34163
|
-
const project = await
|
|
34164
|
-
return writeChangedFile2(
|
|
35087
|
+
const projectPath3 = join28(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
35088
|
+
const project = await readFile25(projectPath3, "utf8");
|
|
35089
|
+
return writeChangedFile2(projectPath3, addIosPrivacyProjectReference(project));
|
|
34165
35090
|
};
|
|
34166
35091
|
var addIosPrivacyProjectReference = (source) => {
|
|
34167
35092
|
const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
|
|
@@ -34212,8 +35137,8 @@ ${next.slice(index)}`;
|
|
|
34212
35137
|
return next;
|
|
34213
35138
|
};
|
|
34214
35139
|
var configureIos2 = async (config, plan) => {
|
|
34215
|
-
const path =
|
|
34216
|
-
const source = await
|
|
35140
|
+
const path = join28(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
35141
|
+
const source = await readFile25(path, "utf8");
|
|
34217
35142
|
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
34218
35143
|
const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
|
|
34219
35144
|
const ownedStart = source.indexOf(START_MARKER2);
|
|
@@ -34233,7 +35158,7 @@ ${content}
|
|
|
34233
35158
|
${END_MARKER2}
|
|
34234
35159
|
` : "";
|
|
34235
35160
|
const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
|
|
34236
|
-
const privacyPath =
|
|
35161
|
+
const privacyPath = join28(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
|
|
34237
35162
|
const privacyCurrent = await optionalSource(privacyPath);
|
|
34238
35163
|
const privacySource = privacyManifestSource(privacyCurrent, requirements);
|
|
34239
35164
|
const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
|
|
@@ -34244,7 +35169,7 @@ ${content}
|
|
|
34244
35169
|
return infoChanged || privacyChanged || projectChanged || pushChanged;
|
|
34245
35170
|
};
|
|
34246
35171
|
var configureIosPushNotifications = async (config, enabled) => {
|
|
34247
|
-
const entitlementsPath =
|
|
35172
|
+
const entitlementsPath = join28(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
34248
35173
|
const entitlements = await optionalSource(entitlementsPath);
|
|
34249
35174
|
if (entitlements === null && !enabled)
|
|
34250
35175
|
return false;
|
|
@@ -34256,7 +35181,7 @@ var configureIosPushNotifications = async (config, enabled) => {
|
|
|
34256
35181
|
<!-- ${PUSH_END_MARKER} -->
|
|
34257
35182
|
` : "";
|
|
34258
35183
|
const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
|
|
34259
|
-
const delegatePath =
|
|
35184
|
+
const delegatePath = join28(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
34260
35185
|
const delegate = await optionalSource(delegatePath);
|
|
34261
35186
|
if (delegate === null && !enabled)
|
|
34262
35187
|
return false;
|
|
@@ -34305,8 +35230,8 @@ var replacePushRegion = (source, region, insertion) => {
|
|
|
34305
35230
|
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
34306
35231
|
};
|
|
34307
35232
|
var configureAndroid2 = async (config, plan) => {
|
|
34308
|
-
const path =
|
|
34309
|
-
const source = await
|
|
35233
|
+
const path = join28(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
35234
|
+
const source = await readFile25(path, "utf8");
|
|
34310
35235
|
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
34311
35236
|
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
34312
35237
|
`);
|
|
@@ -34341,7 +35266,7 @@ ${content}
|
|
|
34341
35266
|
throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
|
|
34342
35267
|
const [manifestChanged, firebaseChanged] = await Promise.all([
|
|
34343
35268
|
writeChangedFile2(path, nextManifest),
|
|
34344
|
-
writeOptionalChangedFile(
|
|
35269
|
+
writeOptionalChangedFile(join28(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
|
|
34345
35270
|
]);
|
|
34346
35271
|
return manifestChanged || firebaseChanged;
|
|
34347
35272
|
};
|
|
@@ -34360,14 +35285,14 @@ init_nativeAuth();
|
|
|
34360
35285
|
|
|
34361
35286
|
// src/mobile/nativeUpdates.ts
|
|
34362
35287
|
import {
|
|
34363
|
-
mkdir as
|
|
34364
|
-
readFile as
|
|
34365
|
-
readdir as
|
|
34366
|
-
rename as
|
|
34367
|
-
rm as
|
|
34368
|
-
writeFile as
|
|
35288
|
+
mkdir as mkdir20,
|
|
35289
|
+
readFile as readFile26,
|
|
35290
|
+
readdir as readdir8,
|
|
35291
|
+
rename as rename17,
|
|
35292
|
+
rm as rm15,
|
|
35293
|
+
writeFile as writeFile22
|
|
34369
35294
|
} from "fs/promises";
|
|
34370
|
-
import { dirname as
|
|
35295
|
+
import { dirname as dirname19, join as join29 } from "path";
|
|
34371
35296
|
var START = "// absolutejs:mobile-updates:start";
|
|
34372
35297
|
var END = "// absolutejs:mobile-updates:end";
|
|
34373
35298
|
var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
|
|
@@ -34375,32 +35300,32 @@ var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
|
|
|
34375
35300
|
var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
|
|
34376
35301
|
var optionalSource2 = async (path) => {
|
|
34377
35302
|
try {
|
|
34378
|
-
return await
|
|
35303
|
+
return await readFile26(path, "utf8");
|
|
34379
35304
|
} catch (error) {
|
|
34380
35305
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34381
35306
|
return null;
|
|
34382
35307
|
throw error;
|
|
34383
35308
|
}
|
|
34384
35309
|
};
|
|
34385
|
-
var
|
|
35310
|
+
var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34386
35311
|
var writeChanged = async (path, source) => {
|
|
34387
35312
|
const current = await optionalSource2(path);
|
|
34388
35313
|
if (current === source)
|
|
34389
35314
|
return false;
|
|
34390
|
-
await
|
|
35315
|
+
await mkdir20(dirname19(path), { recursive: true });
|
|
34391
35316
|
if (current === null) {
|
|
34392
|
-
await
|
|
35317
|
+
await writeFile22(path, source, { flag: "wx" });
|
|
34393
35318
|
return true;
|
|
34394
35319
|
}
|
|
34395
35320
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34396
|
-
await
|
|
34397
|
-
await
|
|
35321
|
+
await writeFile22(temporary, source, { flag: "wx" });
|
|
35322
|
+
await rename17(temporary, path);
|
|
34398
35323
|
return true;
|
|
34399
35324
|
};
|
|
34400
35325
|
var removeOwnedFile = async (path) => {
|
|
34401
35326
|
if (await optionalSource2(path) === null)
|
|
34402
35327
|
return false;
|
|
34403
|
-
await
|
|
35328
|
+
await rm15(path);
|
|
34404
35329
|
return true;
|
|
34405
35330
|
};
|
|
34406
35331
|
var replaceMarkedRegion = (source, start, end, region, insertion, error) => {
|
|
@@ -34537,9 +35462,9 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
|
|
|
34537
35462
|
${PLUGIN_END}
|
|
34538
35463
|
`;
|
|
34539
35464
|
var configureIos3 = async (config, enabled) => {
|
|
34540
|
-
const path =
|
|
34541
|
-
const capacitorConfigPath =
|
|
34542
|
-
let source = await
|
|
35465
|
+
const path = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
35466
|
+
const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
35467
|
+
let source = await readFile26(path, "utf8");
|
|
34543
35468
|
const launch2 = source.indexOf("didFinishLaunchingWithOptions");
|
|
34544
35469
|
const brace = launch2 < 0 ? -1 : source.indexOf("{", launch2);
|
|
34545
35470
|
const launchRegion = enabled ? ` ${START}
|
|
@@ -34550,11 +35475,11 @@ var configureIos3 = async (config, enabled) => {
|
|
|
34550
35475
|
`, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
|
|
34551
35476
|
const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
|
|
34552
35477
|
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
|
|
35478
|
+
const capacitorConfigSource2 = await readFile26(capacitorConfigPath, "utf8");
|
|
34554
35479
|
let capacitorConfig;
|
|
34555
35480
|
try {
|
|
34556
35481
|
const value = JSON.parse(capacitorConfigSource2);
|
|
34557
|
-
if (!
|
|
35482
|
+
if (!isRecord15(value))
|
|
34558
35483
|
throw new TypeError;
|
|
34559
35484
|
capacitorConfig = value;
|
|
34560
35485
|
} catch {
|
|
@@ -34723,19 +35648,19 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
|
|
|
34723
35648
|
var androidActivityInRoot = async (root) => {
|
|
34724
35649
|
let entries;
|
|
34725
35650
|
try {
|
|
34726
|
-
entries = await
|
|
35651
|
+
entries = await readdir8(root, { recursive: true });
|
|
34727
35652
|
} catch (error) {
|
|
34728
35653
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34729
35654
|
return;
|
|
34730
35655
|
throw error;
|
|
34731
35656
|
}
|
|
34732
35657
|
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
34733
|
-
return candidate ?
|
|
35658
|
+
return candidate ? join29(root, candidate) : undefined;
|
|
34734
35659
|
};
|
|
34735
35660
|
var androidActivity = async (config) => {
|
|
34736
35661
|
const roots = [
|
|
34737
|
-
|
|
34738
|
-
|
|
35662
|
+
join29(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
35663
|
+
join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
34739
35664
|
];
|
|
34740
35665
|
const candidates = await Promise.all(roots.map(androidActivityInRoot));
|
|
34741
35666
|
const candidate = candidates.find((value) => value !== undefined);
|
|
@@ -34834,12 +35759,12 @@ var injectAndroidActivity = (source, enabled) => {
|
|
|
34834
35759
|
};
|
|
34835
35760
|
var configureAndroid3 = async (config, enabled) => {
|
|
34836
35761
|
const activityPath = await androidActivity(config);
|
|
34837
|
-
const activity = await
|
|
35762
|
+
const activity = await readFile26(activityPath, "utf8");
|
|
34838
35763
|
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
34839
35764
|
if (!packageName)
|
|
34840
35765
|
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
34841
35766
|
const packagePath = packageName.replaceAll(".", "/");
|
|
34842
|
-
const pluginPath =
|
|
35767
|
+
const pluginPath = join29(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
|
|
34843
35768
|
const changed = await Promise.all([
|
|
34844
35769
|
writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
|
|
34845
35770
|
enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
|
|
@@ -34859,14 +35784,14 @@ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) =>
|
|
|
34859
35784
|
};
|
|
34860
35785
|
// src/mobile/nativeObservability.ts
|
|
34861
35786
|
import {
|
|
34862
|
-
mkdir as
|
|
34863
|
-
readFile as
|
|
34864
|
-
readdir as
|
|
34865
|
-
rename as
|
|
34866
|
-
rm as
|
|
34867
|
-
writeFile as
|
|
35787
|
+
mkdir as mkdir21,
|
|
35788
|
+
readFile as readFile27,
|
|
35789
|
+
readdir as readdir9,
|
|
35790
|
+
rename as rename18,
|
|
35791
|
+
rm as rm16,
|
|
35792
|
+
writeFile as writeFile23
|
|
34868
35793
|
} from "fs/promises";
|
|
34869
|
-
import { dirname as
|
|
35794
|
+
import { dirname as dirname20, join as join30 } from "path";
|
|
34870
35795
|
var START2 = "// absolutejs:native-observability:start";
|
|
34871
35796
|
var END2 = "// absolutejs:native-observability:end";
|
|
34872
35797
|
var PLUGIN_START2 = "// absolutejs:native-observability-plugin:start";
|
|
@@ -34874,7 +35799,7 @@ var PLUGIN_END2 = "// absolutejs:native-observability-plugin:end";
|
|
|
34874
35799
|
var ANDROID_PLUGIN2 = "AbsoluteMobileObservabilityPlugin.java";
|
|
34875
35800
|
var optionalSource3 = async (path) => {
|
|
34876
35801
|
try {
|
|
34877
|
-
return await
|
|
35802
|
+
return await readFile27(path, "utf8");
|
|
34878
35803
|
} catch (error) {
|
|
34879
35804
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
34880
35805
|
return null;
|
|
@@ -34885,23 +35810,23 @@ var writeChanged2 = async (path, source) => {
|
|
|
34885
35810
|
const current = await optionalSource3(path);
|
|
34886
35811
|
if (current === source)
|
|
34887
35812
|
return false;
|
|
34888
|
-
await
|
|
35813
|
+
await mkdir21(dirname20(path), { recursive: true });
|
|
34889
35814
|
if (current === null) {
|
|
34890
|
-
await
|
|
35815
|
+
await writeFile23(path, source, { flag: "wx" });
|
|
34891
35816
|
return true;
|
|
34892
35817
|
}
|
|
34893
35818
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
34894
|
-
await
|
|
34895
|
-
await
|
|
35819
|
+
await writeFile23(temporary, source, { flag: "wx" });
|
|
35820
|
+
await rename18(temporary, path);
|
|
34896
35821
|
return true;
|
|
34897
35822
|
};
|
|
34898
35823
|
var removeOwnedFile2 = async (path) => {
|
|
34899
35824
|
if (await optionalSource3(path) === null)
|
|
34900
35825
|
return false;
|
|
34901
|
-
await
|
|
35826
|
+
await rm16(path);
|
|
34902
35827
|
return true;
|
|
34903
35828
|
};
|
|
34904
|
-
var
|
|
35829
|
+
var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34905
35830
|
var replaceMarkedRegion2 = (source, start, end, region, insertion, error) => {
|
|
34906
35831
|
const existingStart = source.indexOf(start);
|
|
34907
35832
|
const existingEnd = source.indexOf(end);
|
|
@@ -35034,9 +35959,9 @@ public final class AbsoluteMobileObservabilityPlugin: CAPPlugin, CAPBridgedPlugi
|
|
|
35034
35959
|
${PLUGIN_END2}
|
|
35035
35960
|
`;
|
|
35036
35961
|
var configureIos4 = async (config, enabled) => {
|
|
35037
|
-
const path =
|
|
35038
|
-
const capacitorConfigPath =
|
|
35039
|
-
let source = await
|
|
35962
|
+
const path = join30(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
35963
|
+
const capacitorConfigPath = join30(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
35964
|
+
let source = await readFile27(path, "utf8");
|
|
35040
35965
|
if (enabled && !source.includes("import MetricKit")) {
|
|
35041
35966
|
const capacitorImport = source.lastIndexOf("import Capacitor");
|
|
35042
35967
|
if (capacitorImport < 0)
|
|
@@ -35050,11 +35975,11 @@ ${source.slice(newline + 1)}`;
|
|
|
35050
35975
|
source = source.replace(`import MetricKit
|
|
35051
35976
|
`, "");
|
|
35052
35977
|
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
|
|
35978
|
+
const capacitorConfigSource2 = await readFile27(capacitorConfigPath, "utf8");
|
|
35054
35979
|
let capacitorConfig;
|
|
35055
35980
|
try {
|
|
35056
35981
|
const value = JSON.parse(capacitorConfigSource2);
|
|
35057
|
-
if (!
|
|
35982
|
+
if (!isRecord16(value))
|
|
35058
35983
|
throw new TypeError;
|
|
35059
35984
|
capacitorConfig = value;
|
|
35060
35985
|
} catch {
|
|
@@ -35288,19 +36213,19 @@ public final class AbsoluteMobileObservabilityPlugin extends Plugin {
|
|
|
35288
36213
|
var androidActivityInRoot2 = async (root) => {
|
|
35289
36214
|
let entries;
|
|
35290
36215
|
try {
|
|
35291
|
-
entries = await
|
|
36216
|
+
entries = await readdir9(root, { recursive: true });
|
|
35292
36217
|
} catch (error) {
|
|
35293
36218
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
35294
36219
|
return;
|
|
35295
36220
|
throw error;
|
|
35296
36221
|
}
|
|
35297
36222
|
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
35298
|
-
return candidate ?
|
|
36223
|
+
return candidate ? join30(root, candidate) : undefined;
|
|
35299
36224
|
};
|
|
35300
36225
|
var androidActivity2 = async (config) => {
|
|
35301
36226
|
const roots = [
|
|
35302
|
-
|
|
35303
|
-
|
|
36227
|
+
join30(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
36228
|
+
join30(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
35304
36229
|
];
|
|
35305
36230
|
const candidates = await Promise.all(roots.map(androidActivityInRoot2));
|
|
35306
36231
|
const candidate = candidates.find((value) => value !== undefined);
|
|
@@ -35349,148 +36274,14 @@ var injectAndroidActivity2 = (source, enabled) => {
|
|
|
35349
36274
|
return `${source.slice(0, from)}${source.slice(through)}`;
|
|
35350
36275
|
return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
35351
36276
|
}
|
|
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
|
-
}
|
|
36277
|
+
if (!enabled)
|
|
36278
|
+
return source;
|
|
35486
36279
|
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
36280
|
if (onCreate >= 0) {
|
|
35488
36281
|
const brace = source.indexOf("{", onCreate);
|
|
35489
36282
|
const insert = source.indexOf(`
|
|
35490
36283
|
`, 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)}`;
|
|
36284
|
+
return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
|
|
35494
36285
|
}
|
|
35495
36286
|
const close = source.lastIndexOf("}");
|
|
35496
36287
|
if (close < 0)
|
|
@@ -35498,633 +36289,254 @@ var injectRegistration = (source) => {
|
|
|
35498
36289
|
const separator = source[close - 1] === `
|
|
35499
36290
|
` ? "" : `
|
|
35500
36291
|
`;
|
|
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);
|
|
36292
|
+
return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
|
|
35583
36293
|
};
|
|
35584
|
-
var
|
|
35585
|
-
const
|
|
35586
|
-
const activity = await
|
|
36294
|
+
var configureAndroid4 = async (config, enabled) => {
|
|
36295
|
+
const activityPath = await androidActivity2(config);
|
|
36296
|
+
const activity = await readFile27(activityPath, "utf8");
|
|
35587
36297
|
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
|
-
};
|
|
36298
|
+
if (!packageName)
|
|
36299
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
36300
|
+
const pluginPath = join30(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
|
|
36301
|
+
const changed = await Promise.all([
|
|
36302
|
+
writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
|
|
36303
|
+
enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
|
|
36304
|
+
]);
|
|
36305
|
+
return changed.some(Boolean);
|
|
35765
36306
|
};
|
|
35766
|
-
var
|
|
35767
|
-
|
|
35768
|
-
|
|
35769
|
-
|
|
36307
|
+
var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
|
|
36308
|
+
if (config.engine !== "capacitor")
|
|
36309
|
+
return { changed: false };
|
|
36310
|
+
const enabled = config.observability !== undefined;
|
|
36311
|
+
const changed = [];
|
|
36312
|
+
if (platforms.includes("ios"))
|
|
36313
|
+
changed.push(await configureIos4(config, enabled));
|
|
36314
|
+
if (platforms.includes("android"))
|
|
36315
|
+
changed.push(await configureAndroid4(config, enabled));
|
|
36316
|
+
return { changed: changed.some(Boolean) };
|
|
36317
|
+
};
|
|
36318
|
+
// src/mobile/nativeReleaseReadiness.ts
|
|
36319
|
+
import { mkdir as mkdir22, readFile as readFile28, readdir as readdir10, rename as rename19, writeFile as writeFile24 } from "fs/promises";
|
|
36320
|
+
import { dirname as dirname21, join as join31 } from "path";
|
|
36321
|
+
var START3 = "// absolutejs:release-readiness:start";
|
|
36322
|
+
var END3 = "// absolutejs:release-readiness:end";
|
|
36323
|
+
var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
|
|
36324
|
+
var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
|
|
36325
|
+
var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
|
|
36326
|
+
var optionalSource4 = async (path) => {
|
|
35770
36327
|
try {
|
|
35771
|
-
|
|
35772
|
-
} catch {
|
|
35773
|
-
|
|
36328
|
+
return await readFile28(path, "utf8");
|
|
36329
|
+
} catch (error) {
|
|
36330
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
36331
|
+
return null;
|
|
36332
|
+
throw error;
|
|
35774
36333
|
}
|
|
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
36334
|
};
|
|
35788
|
-
var
|
|
35789
|
-
|
|
35790
|
-
|
|
35791
|
-
if (strength === "installed")
|
|
36335
|
+
var writeChanged3 = async (path, source) => {
|
|
36336
|
+
const current = await optionalSource4(path);
|
|
36337
|
+
if (current === source)
|
|
35792
36338
|
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;
|
|
36339
|
+
await mkdir22(dirname21(path), { recursive: true });
|
|
36340
|
+
if (current === null) {
|
|
36341
|
+
await writeFile24(path, source, { flag: "wx" });
|
|
36342
|
+
return true;
|
|
35808
36343
|
}
|
|
35809
|
-
const
|
|
35810
|
-
|
|
35811
|
-
|
|
35812
|
-
|
|
35813
|
-
throw new TypeError("iOS release certification contains invalid evidence strength.");
|
|
36344
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
36345
|
+
await writeFile24(temporary, source, { flag: "wx" });
|
|
36346
|
+
await rename19(temporary, path);
|
|
36347
|
+
return true;
|
|
35814
36348
|
};
|
|
35815
|
-
var
|
|
35816
|
-
|
|
35817
|
-
|
|
35818
|
-
|
|
35819
|
-
|
|
35820
|
-
|
|
35821
|
-
|
|
35822
|
-
|
|
35823
|
-
|
|
36349
|
+
var activityInRoot = async (root) => {
|
|
36350
|
+
let entries;
|
|
36351
|
+
try {
|
|
36352
|
+
entries = await readdir10(root, { recursive: true });
|
|
36353
|
+
} catch (error) {
|
|
36354
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
36355
|
+
return;
|
|
36356
|
+
throw error;
|
|
36357
|
+
}
|
|
36358
|
+
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
36359
|
+
return candidate ? join31(root, candidate) : undefined;
|
|
35824
36360
|
};
|
|
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
|
-
};
|
|
36361
|
+
var activityPath = async (config) => {
|
|
36362
|
+
const roots = [
|
|
36363
|
+
join31(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
36364
|
+
join31(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
36365
|
+
];
|
|
36366
|
+
const candidates = await Promise.all(roots.map(activityInRoot));
|
|
36367
|
+
const candidate = candidates.find((value) => value !== undefined);
|
|
36368
|
+
if (candidate)
|
|
36369
|
+
return candidate;
|
|
36370
|
+
throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
|
|
35853
36371
|
};
|
|
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")
|
|
36372
|
+
var registrationRegion = (kotlin, generatedMethod) => {
|
|
36373
|
+
if (generatedMethod)
|
|
36374
|
+
return kotlin ? ` ${START3}
|
|
36375
|
+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
36376
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
36377
|
+
super.onCreate(savedInstanceState)
|
|
35891
36378
|
}
|
|
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 };
|
|
36379
|
+
${END3}
|
|
36380
|
+
` : ` ${START3}
|
|
36381
|
+
@Override
|
|
36382
|
+
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
36383
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
36384
|
+
super.onCreate(savedInstanceState);
|
|
36385
|
+
}
|
|
36386
|
+
${END3}
|
|
36387
|
+
`;
|
|
36388
|
+
return kotlin ? ` ${START3}
|
|
36389
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
36390
|
+
${END3}
|
|
36391
|
+
` : ` ${START3}
|
|
36392
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
36393
|
+
${END3}
|
|
36394
|
+
`;
|
|
35935
36395
|
};
|
|
35936
|
-
var
|
|
35937
|
-
const
|
|
35938
|
-
const
|
|
35939
|
-
const
|
|
35940
|
-
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
|
|
35944
|
-
|
|
35945
|
-
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
35952
|
-
|
|
35953
|
-
|
|
35954
|
-
|
|
36396
|
+
var injectRegistration = (source) => {
|
|
36397
|
+
const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
|
|
36398
|
+
const existingStart = source.indexOf(START3);
|
|
36399
|
+
const existingEnd = source.indexOf(END3);
|
|
36400
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
36401
|
+
throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
|
|
36402
|
+
if (existingStart >= 0) {
|
|
36403
|
+
const from = source.lastIndexOf(`
|
|
36404
|
+
`, existingStart) + 1;
|
|
36405
|
+
const newline = source.indexOf(`
|
|
36406
|
+
`, existingEnd + END3.length);
|
|
36407
|
+
const through = newline < 0 ? source.length : newline + 1;
|
|
36408
|
+
const generatedMethod = source.slice(from, through).includes("onCreate(");
|
|
36409
|
+
return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
36410
|
+
}
|
|
36411
|
+
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);
|
|
36412
|
+
if (onCreate >= 0) {
|
|
36413
|
+
const brace = source.indexOf("{", onCreate);
|
|
36414
|
+
const insert = source.indexOf(`
|
|
36415
|
+
`, brace) + 1;
|
|
36416
|
+
if (insert <= 0)
|
|
36417
|
+
throw new TypeError("Could not inject Android release readiness into MainActivity.");
|
|
36418
|
+
return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
|
|
36419
|
+
}
|
|
36420
|
+
const close = source.lastIndexOf("}");
|
|
36421
|
+
if (close < 0)
|
|
36422
|
+
throw new TypeError("Could not find the Android MainActivity class body.");
|
|
36423
|
+
const separator = source[close - 1] === `
|
|
36424
|
+
` ? "" : `
|
|
36425
|
+
`;
|
|
36426
|
+
return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
|
|
36427
|
+
};
|
|
36428
|
+
var pluginSource = (packageName) => `package ${packageName};
|
|
35955
36429
|
|
|
35956
|
-
|
|
36430
|
+
import android.util.Log;
|
|
36431
|
+
import com.getcapacitor.JSObject;
|
|
36432
|
+
import com.getcapacitor.Plugin;
|
|
36433
|
+
import com.getcapacitor.PluginCall;
|
|
36434
|
+
import com.getcapacitor.PluginMethod;
|
|
36435
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
35957
36436
|
|
|
35958
|
-
|
|
35959
|
-
|
|
35960
|
-
|
|
36437
|
+
@CapacitorPlugin(name = "AbsoluteReleaseReadiness")
|
|
36438
|
+
public final class AbsoluteReleaseReadinessPlugin extends Plugin {
|
|
36439
|
+
@PluginMethod
|
|
36440
|
+
public void ready(PluginCall call) {
|
|
36441
|
+
Log.i("AbsoluteJS", "Capacitor embedded web content ready");
|
|
36442
|
+
call.resolve(new JSObject().put("ready", true));
|
|
36443
|
+
}
|
|
36444
|
+
}
|
|
35961
36445
|
`;
|
|
35962
|
-
}
|
|
35963
|
-
|
|
36446
|
+
var iosPluginRegion3 = `${IOS_PLUGIN_START}
|
|
36447
|
+
@objc(AbsoluteReleaseReadinessPlugin)
|
|
36448
|
+
public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
36449
|
+
public let identifier = "AbsoluteReleaseReadinessPlugin"
|
|
36450
|
+
public let jsName = "AbsoluteReleaseReadiness"
|
|
36451
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
36452
|
+
CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
|
|
36453
|
+
]
|
|
36454
|
+
|
|
36455
|
+
@objc public func ready(_ call: CAPPluginCall) {
|
|
36456
|
+
let info = Bundle.main.infoDictionary ?? [:]
|
|
36457
|
+
let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
|
|
36458
|
+
let build = info["CFBundleVersion"] as? String ?? "unknown"
|
|
36459
|
+
NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
|
|
36460
|
+
call.resolve(["ready": true])
|
|
36461
|
+
}
|
|
36462
|
+
}
|
|
36463
|
+
${IOS_PLUGIN_END}
|
|
35964
36464
|
`;
|
|
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;
|
|
36465
|
+
var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
|
|
36466
|
+
const from = appDelegate.lastIndexOf(`
|
|
36467
|
+
`, existingStart) + 1;
|
|
36468
|
+
const newline = appDelegate.indexOf(`
|
|
36469
|
+
`, existingEnd + IOS_PLUGIN_END.length);
|
|
36470
|
+
const through = newline < 0 ? appDelegate.length : newline + 1;
|
|
36471
|
+
return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
|
|
35976
36472
|
};
|
|
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-"));
|
|
36473
|
+
var configureIos5 = async (config) => {
|
|
36474
|
+
const appDelegatePath = join31(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
36475
|
+
const capacitorConfigPath = join31(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
36476
|
+
const appDelegate = await readFile28(appDelegatePath, "utf8");
|
|
36477
|
+
const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
|
|
36478
|
+
const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
|
|
36479
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
36480
|
+
throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
|
|
36481
|
+
const source = existingStart < 0 ? `${appDelegate.trimEnd()}
|
|
36482
|
+
|
|
36483
|
+
${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
|
|
36484
|
+
let capacitorConfig;
|
|
36004
36485
|
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 });
|
|
36486
|
+
capacitorConfig = JSON.parse(await readFile28(capacitorConfigPath, "utf8"));
|
|
36487
|
+
} catch {
|
|
36488
|
+
throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
|
|
36020
36489
|
}
|
|
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")));
|
|
36490
|
+
if (typeof capacitorConfig !== "object" || capacitorConfig === null)
|
|
36491
|
+
throw new TypeError("iOS capacitor.config.json must contain an object.");
|
|
36492
|
+
const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
|
|
36493
|
+
if (!Array.isArray(packageClassList))
|
|
36494
|
+
throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
|
|
36495
|
+
const classes = packageClassList.filter((value) => typeof value === "string");
|
|
36496
|
+
const nextConfig = `${JSON.stringify({
|
|
36497
|
+
...capacitorConfig,
|
|
36498
|
+
packageClassList: [
|
|
36499
|
+
...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
|
|
36500
|
+
]
|
|
36501
|
+
}, null, "\t")}
|
|
36502
|
+
`;
|
|
36503
|
+
const changed = await Promise.all([
|
|
36504
|
+
writeChanged3(appDelegatePath, source),
|
|
36505
|
+
writeChanged3(capacitorConfigPath, nextConfig)
|
|
36506
|
+
]);
|
|
36507
|
+
return changed.some(Boolean);
|
|
36104
36508
|
};
|
|
36105
|
-
var
|
|
36106
|
-
const
|
|
36107
|
-
const
|
|
36108
|
-
|
|
36109
|
-
|
|
36110
|
-
|
|
36111
|
-
|
|
36112
|
-
|
|
36113
|
-
|
|
36509
|
+
var configureAndroid5 = async (config) => {
|
|
36510
|
+
const mainActivityPath = await activityPath(config);
|
|
36511
|
+
const activity = await readFile28(mainActivityPath, "utf8");
|
|
36512
|
+
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
36513
|
+
if (!packageName)
|
|
36514
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
36515
|
+
const pluginPath = join31(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
|
|
36516
|
+
const changed = await Promise.all([
|
|
36517
|
+
writeChanged3(mainActivityPath, injectRegistration(activity)),
|
|
36518
|
+
writeChanged3(pluginPath, pluginSource(packageName))
|
|
36519
|
+
]);
|
|
36520
|
+
return changed.some(Boolean);
|
|
36114
36521
|
};
|
|
36115
|
-
var
|
|
36116
|
-
|
|
36117
|
-
|
|
36118
|
-
|
|
36119
|
-
|
|
36120
|
-
|
|
36121
|
-
|
|
36122
|
-
}
|
|
36123
|
-
return path;
|
|
36522
|
+
var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
|
|
36523
|
+
if (config.engine !== "capacitor")
|
|
36524
|
+
return { changed: false };
|
|
36525
|
+
const changed = await Promise.all([
|
|
36526
|
+
platforms.includes("android") ? configureAndroid5(config) : false,
|
|
36527
|
+
platforms.includes("ios") ? configureIos5(config) : false
|
|
36528
|
+
]);
|
|
36529
|
+
return { changed: changed.some(Boolean) };
|
|
36124
36530
|
};
|
|
36531
|
+
|
|
36532
|
+
// src/mobile/index.ts
|
|
36533
|
+
init_pageProtocol();
|
|
36534
|
+
init_producerContext();
|
|
36535
|
+
init_releaseArtifact();
|
|
36536
|
+
|
|
36125
36537
|
// src/mobile/releasePublisher.ts
|
|
36126
|
-
import { access as
|
|
36127
|
-
import { isAbsolute as
|
|
36538
|
+
import { access as access20 } from "fs/promises";
|
|
36539
|
+
import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve21, sep as sep14 } from "path";
|
|
36128
36540
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
36129
36541
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
36130
36542
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -36158,24 +36570,24 @@ var requireCertificationReceipt = (publication, certification, requirement, rele
|
|
|
36158
36570
|
if (verification && (!receipt.provenance || receipt.provenance.subject !== releaseId))
|
|
36159
36571
|
throw new TypeError("Native release registry did not retain trusted certification provenance.");
|
|
36160
36572
|
};
|
|
36161
|
-
var
|
|
36162
|
-
var isPublisher = (value) =>
|
|
36573
|
+
var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36574
|
+
var isPublisher = (value) => isRecord17(value) && typeof value.publish === "function";
|
|
36163
36575
|
var publisherModulePath = (projectRoot, requested) => {
|
|
36164
|
-
const root =
|
|
36165
|
-
const path =
|
|
36166
|
-
const projectRelative =
|
|
36167
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
36576
|
+
const root = resolve21(projectRoot);
|
|
36577
|
+
const path = resolve21(root, requested);
|
|
36578
|
+
const projectRelative = relative19(root, path);
|
|
36579
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative)) {
|
|
36168
36580
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
36169
36581
|
}
|
|
36170
36582
|
return path;
|
|
36171
36583
|
};
|
|
36172
36584
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
36173
36585
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
36174
|
-
await
|
|
36586
|
+
await access20(modulePath).catch(() => {
|
|
36175
36587
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
36176
36588
|
});
|
|
36177
36589
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
36178
|
-
const publisher =
|
|
36590
|
+
const publisher = isRecord17(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
36179
36591
|
if (!isPublisher(publisher)) {
|
|
36180
36592
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
36181
36593
|
}
|
|
@@ -36257,7 +36669,7 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
36257
36669
|
init_pageProtocol();
|
|
36258
36670
|
init_releaseArtifact();
|
|
36259
36671
|
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
36260
|
-
import { dirname as
|
|
36672
|
+
import { dirname as dirname22, extname as extname6, relative as relative20, resolve as resolve22 } from "path";
|
|
36261
36673
|
import ts2 from "typescript";
|
|
36262
36674
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
36263
36675
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
@@ -36308,7 +36720,7 @@ var PAGE_HANDLERS = new Map([
|
|
|
36308
36720
|
]
|
|
36309
36721
|
]);
|
|
36310
36722
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
36311
|
-
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(
|
|
36723
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname22(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
|
|
36312
36724
|
var createProgram = (entry, projectRoot) => {
|
|
36313
36725
|
const configPath2 = findTsconfig(entry, projectRoot);
|
|
36314
36726
|
if (!configPath2) {
|
|
@@ -36320,7 +36732,7 @@ var createProgram = (entry, projectRoot) => {
|
|
|
36320
36732
|
target: ts2.ScriptTarget.ESNext
|
|
36321
36733
|
});
|
|
36322
36734
|
}
|
|
36323
|
-
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys,
|
|
36735
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname22(configPath2));
|
|
36324
36736
|
if (!parsed.fileNames.includes(entry))
|
|
36325
36737
|
parsed.fileNames.push(entry);
|
|
36326
36738
|
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
@@ -36426,7 +36838,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
|
|
|
36426
36838
|
const declaration = symbol?.declarations?.[0];
|
|
36427
36839
|
const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
|
|
36428
36840
|
const exportedName = symbol?.name ?? expression.getText(sourceFile);
|
|
36429
|
-
const source = posixPath(
|
|
36841
|
+
const source = posixPath(relative20(projectRoot, file));
|
|
36430
36842
|
return `${source}#${exportedName}`;
|
|
36431
36843
|
};
|
|
36432
36844
|
var resolveAlias = (symbol, checker) => {
|
|
@@ -36665,7 +37077,7 @@ var analyzeProgram = (program, projectRoot) => {
|
|
|
36665
37077
|
const checker = program.getTypeChecker();
|
|
36666
37078
|
const analyzed = new Map;
|
|
36667
37079
|
for (const sourceFile of program.getSourceFiles()) {
|
|
36668
|
-
const resolvedFile =
|
|
37080
|
+
const resolvedFile = resolve22(sourceFile.fileName);
|
|
36669
37081
|
if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
|
|
36670
37082
|
continue;
|
|
36671
37083
|
const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
|
|
@@ -36758,14 +37170,14 @@ var transformFile = (source, fileName, analysis) => {
|
|
|
36758
37170
|
};
|
|
36759
37171
|
var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
|
|
36760
37172
|
var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
36761
|
-
const projectRoot =
|
|
36762
|
-
const entry =
|
|
37173
|
+
const projectRoot = resolve22(options.projectRoot ?? process.cwd());
|
|
37174
|
+
const entry = resolve22(options.entry);
|
|
36763
37175
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
36764
37176
|
return {
|
|
36765
37177
|
name: "absolute-mobile-route-metadata",
|
|
36766
37178
|
setup(build) {
|
|
36767
37179
|
build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
|
|
36768
|
-
const analysis = analyzed.get(
|
|
37180
|
+
const analysis = analyzed.get(resolve22(path));
|
|
36769
37181
|
if (!analysis)
|
|
36770
37182
|
return;
|
|
36771
37183
|
const source = await Bun.file(path).text();
|
|
@@ -36778,11 +37190,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
|
36778
37190
|
};
|
|
36779
37191
|
};
|
|
36780
37192
|
var inspectAbsoluteMobileRouteMetadata = (options) => {
|
|
36781
|
-
const projectRoot =
|
|
36782
|
-
const entry =
|
|
37193
|
+
const projectRoot = resolve22(options.projectRoot ?? process.cwd());
|
|
37194
|
+
const entry = resolve22(options.entry);
|
|
36783
37195
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
36784
37196
|
return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
|
|
36785
|
-
file: posixPath(
|
|
37197
|
+
file: posixPath(relative20(projectRoot, file)),
|
|
36786
37198
|
metadata
|
|
36787
37199
|
})));
|
|
36788
37200
|
};
|
|
@@ -37116,7 +37528,7 @@ var createCapacitor = (win) => {
|
|
|
37116
37528
|
callbackId
|
|
37117
37529
|
}, callback);
|
|
37118
37530
|
};
|
|
37119
|
-
const p = new Promise((
|
|
37531
|
+
const p = new Promise((resolve23) => call.then(() => resolve23({ remove })));
|
|
37120
37532
|
p.remove = async () => {
|
|
37121
37533
|
console.warn(`Using addListener() without 'await' is deprecated.`);
|
|
37122
37534
|
await remove();
|
|
@@ -37321,11 +37733,11 @@ class CapacitorCookiesPluginWeb extends WebPlugin {
|
|
|
37321
37733
|
var CapacitorCookies = registerPlugin("CapacitorCookies", {
|
|
37322
37734
|
web: () => new CapacitorCookiesPluginWeb
|
|
37323
37735
|
});
|
|
37324
|
-
var readBlobAsBase64 = async (blob) => new Promise((
|
|
37736
|
+
var readBlobAsBase64 = async (blob) => new Promise((resolve23, reject) => {
|
|
37325
37737
|
const reader = new FileReader;
|
|
37326
37738
|
reader.onload = () => {
|
|
37327
37739
|
const base64String = reader.result;
|
|
37328
|
-
|
|
37740
|
+
resolve23(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
|
|
37329
37741
|
};
|
|
37330
37742
|
reader.onerror = (error) => reject(error);
|
|
37331
37743
|
reader.readAsDataURL(blob);
|
|
@@ -37501,7 +37913,7 @@ var REPORT_KINDS = new Set([
|
|
|
37501
37913
|
"signal"
|
|
37502
37914
|
]);
|
|
37503
37915
|
var nativeObservability = registerPlugin("AbsoluteMobileObservability");
|
|
37504
|
-
var
|
|
37916
|
+
var isRecord18 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37505
37917
|
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
37918
|
var redactValue = (value, depth = 0) => {
|
|
37507
37919
|
if (depth >= MAX_REDACTION_DEPTH)
|
|
@@ -37512,7 +37924,7 @@ var redactValue = (value, depth = 0) => {
|
|
|
37512
37924
|
return value;
|
|
37513
37925
|
if (Array.isArray(value))
|
|
37514
37926
|
return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
|
|
37515
|
-
if (!
|
|
37927
|
+
if (!isRecord18(value))
|
|
37516
37928
|
return String(value).slice(0, MAX_STRING_LENGTH);
|
|
37517
37929
|
return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
|
|
37518
37930
|
key,
|
|
@@ -37520,7 +37932,7 @@ var redactValue = (value, depth = 0) => {
|
|
|
37520
37932
|
]));
|
|
37521
37933
|
};
|
|
37522
37934
|
var parseReport = (value) => {
|
|
37523
|
-
if (!
|
|
37935
|
+
if (!isRecord18(value) || !isRecord18(value.details))
|
|
37524
37936
|
return;
|
|
37525
37937
|
if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
|
|
37526
37938
|
return;
|
|
@@ -37531,7 +37943,7 @@ var parseReport = (value) => {
|
|
|
37531
37943
|
if (value.platform !== "android" && value.platform !== "ios")
|
|
37532
37944
|
return;
|
|
37533
37945
|
const details = redactValue(value.details);
|
|
37534
|
-
if (!
|
|
37946
|
+
if (!isRecord18(details))
|
|
37535
37947
|
return;
|
|
37536
37948
|
if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
|
|
37537
37949
|
return;
|
|
@@ -41985,8 +42397,8 @@ var createBeacon = (options) => {
|
|
|
41985
42397
|
if (await fontAvailable(element))
|
|
41986
42398
|
return;
|
|
41987
42399
|
if (confirmationMs > 0) {
|
|
41988
|
-
await new Promise((
|
|
41989
|
-
const timer2 = window.setTimeout(
|
|
42400
|
+
await new Promise((resolve23) => {
|
|
42401
|
+
const timer2 = window.setTimeout(resolve23, confirmationMs);
|
|
41990
42402
|
cleanups.push(() => window.clearTimeout(timer2));
|
|
41991
42403
|
});
|
|
41992
42404
|
if (document.visibilityState === "hidden")
|
|
@@ -42974,51 +43386,51 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
42974
43386
|
};
|
|
42975
43387
|
};
|
|
42976
43388
|
// src/mobile/updatePublisher.ts
|
|
42977
|
-
import { access as
|
|
42978
|
-
import { isAbsolute as
|
|
43389
|
+
import { access as access21 } from "fs/promises";
|
|
43390
|
+
import { isAbsolute as isAbsolute13, relative as relative22, resolve as resolve24, sep as sep15 } from "path";
|
|
42979
43391
|
import { pathToFileURL as pathToFileURL4 } from "url";
|
|
42980
43392
|
|
|
42981
43393
|
// src/mobile/updateSigning.ts
|
|
42982
43394
|
import { createHash as createHash17, sign, verify } from "crypto";
|
|
42983
43395
|
import {
|
|
42984
43396
|
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
|
|
43397
|
+
mkdir as mkdir23,
|
|
43398
|
+
mkdtemp as mkdtemp12,
|
|
43399
|
+
readdir as readdir11,
|
|
43400
|
+
readFile as readFile29,
|
|
43401
|
+
rename as rename20,
|
|
43402
|
+
rm as rm17,
|
|
43403
|
+
stat as stat8,
|
|
43404
|
+
writeFile as writeFile25
|
|
42993
43405
|
} from "fs/promises";
|
|
42994
|
-
import { dirname as
|
|
43406
|
+
import { dirname as dirname23, join as join32, relative as relative21, resolve as resolve23 } from "path";
|
|
42995
43407
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
42996
43408
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
42997
43409
|
var sha2565 = (value) => createHash17("sha256").update(value).digest("hex");
|
|
42998
43410
|
var listFiles = async (root, directory = root) => {
|
|
42999
|
-
const entries = await
|
|
43411
|
+
const entries = await readdir11(directory, { withFileTypes: true });
|
|
43000
43412
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
43001
|
-
const path =
|
|
43413
|
+
const path = join32(directory, entry.name);
|
|
43002
43414
|
if (entry.isDirectory())
|
|
43003
43415
|
return listFiles(root, path);
|
|
43004
43416
|
if (!entry.isFile())
|
|
43005
43417
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
43006
|
-
return [
|
|
43418
|
+
return [relative21(root, path).replaceAll("\\", "/")];
|
|
43007
43419
|
}));
|
|
43008
43420
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
43009
43421
|
};
|
|
43010
43422
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
43011
|
-
const source =
|
|
43423
|
+
const source = join32(root, path);
|
|
43012
43424
|
const [metadata, contents] = await Promise.all([
|
|
43013
|
-
|
|
43014
|
-
|
|
43425
|
+
stat8(source),
|
|
43426
|
+
readFile29(source)
|
|
43015
43427
|
]);
|
|
43016
43428
|
return { bytes: metadata.size, path, sha256: sha2565(contents) };
|
|
43017
43429
|
}));
|
|
43018
43430
|
var releaseIdFor = (value) => `amu_${sha2565(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
43019
43431
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
43020
|
-
const bundleDirectory =
|
|
43021
|
-
const outputRoot =
|
|
43432
|
+
const bundleDirectory = resolve23(options.bundleDirectory);
|
|
43433
|
+
const outputRoot = resolve23(options.outputDirectory);
|
|
43022
43434
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
43023
43435
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
43024
43436
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -43046,38 +43458,38 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
43046
43458
|
value: signature.toString("base64")
|
|
43047
43459
|
}
|
|
43048
43460
|
});
|
|
43049
|
-
await
|
|
43050
|
-
const outputDirectory =
|
|
43051
|
-
const staging = await
|
|
43461
|
+
await mkdir23(outputRoot, { recursive: true });
|
|
43462
|
+
const outputDirectory = join32(outputRoot, manifest.releaseId);
|
|
43463
|
+
const staging = await mkdtemp12(join32(outputRoot, ".stage-"));
|
|
43052
43464
|
try {
|
|
43053
|
-
await cp2(bundleDirectory,
|
|
43465
|
+
await cp2(bundleDirectory, join32(staging, UPDATE_FILES_DIRECTORY), {
|
|
43054
43466
|
force: true,
|
|
43055
43467
|
recursive: true
|
|
43056
43468
|
});
|
|
43057
|
-
await
|
|
43469
|
+
await writeFile25(join32(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
43058
43470
|
`);
|
|
43059
|
-
await
|
|
43471
|
+
await rename20(staging, outputDirectory);
|
|
43060
43472
|
} catch (error) {
|
|
43061
|
-
await
|
|
43473
|
+
await rm17(staging, { force: true, recursive: true });
|
|
43062
43474
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
43063
43475
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
43064
43476
|
throw error;
|
|
43065
43477
|
}
|
|
43066
43478
|
return {
|
|
43067
43479
|
manifest,
|
|
43068
|
-
manifestPath:
|
|
43480
|
+
manifestPath: join32(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
43069
43481
|
outputDirectory
|
|
43070
43482
|
};
|
|
43071
43483
|
};
|
|
43072
43484
|
var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
|
|
43073
|
-
const source =
|
|
43074
|
-
const root =
|
|
43485
|
+
const source = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY, path);
|
|
43486
|
+
const root = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY);
|
|
43075
43487
|
if (!source.startsWith(`${root}/`))
|
|
43076
43488
|
throw new TypeError("Update file escaped its release.");
|
|
43077
|
-
await
|
|
43489
|
+
await mkdir23(dirname23(destination), { recursive: true });
|
|
43078
43490
|
await cp2(source, destination, { force: true });
|
|
43079
43491
|
};
|
|
43080
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
43492
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile29(join32(resolve23(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
43081
43493
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
43082
43494
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
43083
43495
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -43193,17 +43605,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
43193
43605
|
};
|
|
43194
43606
|
var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43195
43607
|
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(`..${
|
|
43608
|
+
var projectPath3 = (projectRoot, requested, label) => {
|
|
43609
|
+
const root = resolve24(projectRoot);
|
|
43610
|
+
const path = resolve24(root, requested);
|
|
43611
|
+
const projectRelative = relative22(root, path);
|
|
43612
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep15}`) || isAbsolute13(projectRelative))
|
|
43201
43613
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
43202
43614
|
return path;
|
|
43203
43615
|
};
|
|
43204
43616
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
43205
|
-
const modulePath =
|
|
43206
|
-
await
|
|
43617
|
+
const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
|
|
43618
|
+
await access21(modulePath).catch(() => {
|
|
43207
43619
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
43208
43620
|
});
|
|
43209
43621
|
const loaded = await import(pathToFileURL4(modulePath).href);
|
|
@@ -43225,7 +43637,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
|
|
|
43225
43637
|
return result;
|
|
43226
43638
|
};
|
|
43227
43639
|
var publishAbsoluteMobileUpdate = async (options) => {
|
|
43228
|
-
const releaseDirectory =
|
|
43640
|
+
const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
|
|
43229
43641
|
const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
|
|
43230
43642
|
const result = await options.publisher.publishUpdate({
|
|
43231
43643
|
manifest,
|
|
@@ -43310,6 +43722,8 @@ export {
|
|
|
43310
43722
|
ABSOLUTE_IOS_RELEASE_FORMAT,
|
|
43311
43723
|
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
43312
43724
|
ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
|
|
43725
|
+
ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
|
|
43726
|
+
ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
|
|
43313
43727
|
ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
|
|
43314
43728
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
43315
43729
|
ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
|
|
@@ -43373,6 +43787,7 @@ export {
|
|
|
43373
43787
|
assertAbsoluteAndroidInstalledAppUpgrade,
|
|
43374
43788
|
assertAbsoluteDeviceCapabilityPackages,
|
|
43375
43789
|
assertAbsoluteIosInstalledAppUpgrade,
|
|
43790
|
+
auditAbsoluteMobileCiPromotion,
|
|
43376
43791
|
buildAbsoluteAndroidRelease,
|
|
43377
43792
|
buildAbsoluteExpoAndroidWslBundle,
|
|
43378
43793
|
buildAbsoluteIosRelease,
|
|
@@ -43436,6 +43851,7 @@ export {
|
|
|
43436
43851
|
inspectAbsoluteAndroidInstalledApp,
|
|
43437
43852
|
inspectAbsoluteBundletool,
|
|
43438
43853
|
inspectAbsoluteIosInstalledApp,
|
|
43854
|
+
inspectAbsoluteMobileCiRun,
|
|
43439
43855
|
inspectAbsoluteMobileRouteMetadata,
|
|
43440
43856
|
inspectAbsoluteMobileUpdateHealth,
|
|
43441
43857
|
inspectAbsoluteMobileUpdateRollout,
|
|
@@ -43488,9 +43904,11 @@ export {
|
|
|
43488
43904
|
parseAbsoluteIosLogLine,
|
|
43489
43905
|
parseAbsoluteMobileBuildPageMetadata,
|
|
43490
43906
|
parseAbsoluteMobileCertificationVerification,
|
|
43907
|
+
parseAbsoluteMobileCiRunStatus,
|
|
43491
43908
|
parseAbsoluteMobileCompatibilityArtifact,
|
|
43492
43909
|
parseAbsoluteMobilePageEnvelope,
|
|
43493
43910
|
parseAbsoluteMobilePageRequest,
|
|
43911
|
+
parseAbsoluteMobilePromotionContext,
|
|
43494
43912
|
parseAbsoluteMobileUnsignedUpdateManifest,
|
|
43495
43913
|
parseAbsoluteMobileUpdateManifest,
|
|
43496
43914
|
parseAbsoluteMobileUpdateRequest,
|
|
@@ -43567,6 +43985,7 @@ export {
|
|
|
43567
43985
|
verifyAbsoluteMobileReleaseCertification,
|
|
43568
43986
|
verifyAbsoluteMobileUpdateSignature,
|
|
43569
43987
|
waitForAbsoluteIosHmrLog,
|
|
43988
|
+
watchAbsoluteMobileCiRun,
|
|
43570
43989
|
writeAbsoluteCapacitorConfig,
|
|
43571
43990
|
writeAbsoluteExpoProject,
|
|
43572
43991
|
writeAbsoluteMobileCertificationVerification,
|
|
@@ -43575,5 +43994,5 @@ export {
|
|
|
43575
43994
|
writeAbsoluteMobileUpdateRegistry
|
|
43576
43995
|
};
|
|
43577
43996
|
|
|
43578
|
-
//# debugId=
|
|
43997
|
+
//# debugId=1FBA634844700C1464756E2164756E21
|
|
43579
43998
|
//# sourceMappingURL=index.js.map
|