@absolutejs/absolute 0.20.0-beta.37 → 0.20.0-beta.38
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/README.md +9 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +603 -187
- package/dist/mobile/index.js +17 -3
- package/dist/mobile/index.js.map +3 -3
- package/dist/src/mobile/mobileBundleInspection.d.ts +55 -0
- package/dist/src/mobile/mobileInspect.d.ts +1 -1
- package/dist/src/mobile/releaseDoctor.d.ts +23 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6411,12 +6411,25 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
|
|
|
6411
6411
|
if (candidate)
|
|
6412
6412
|
return candidate;
|
|
6413
6413
|
throw new TypeError("AbsoluteJS mobile push shell module is missing.");
|
|
6414
|
-
}, escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """),
|
|
6414
|
+
}, escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """), contentSecurityPolicy = (productionOrigin) => {
|
|
6415
|
+
const backend = new URL(productionOrigin);
|
|
6416
|
+
const socketOrigin = `${backend.protocol === "https:" ? "wss:" : "ws:"}//${backend.host}`;
|
|
6417
|
+
return [
|
|
6418
|
+
"default-src 'self' data: blob: https:",
|
|
6419
|
+
"base-uri 'none'",
|
|
6420
|
+
"object-src 'none'",
|
|
6421
|
+
"script-src 'self'",
|
|
6422
|
+
"style-src 'self' 'unsafe-inline'",
|
|
6423
|
+
`connect-src 'self' ${backend.origin} ${socketOrigin}`,
|
|
6424
|
+
"form-action 'none'"
|
|
6425
|
+
].join("; ");
|
|
6426
|
+
}, indexHtml = (appName, productionOrigin) => `<!doctype html>
|
|
6415
6427
|
<html>
|
|
6416
6428
|
<head>
|
|
6417
6429
|
<meta charset="utf-8">
|
|
6418
6430
|
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
6419
6431
|
<meta name="color-scheme" content="light dark">
|
|
6432
|
+
<meta http-equiv="Content-Security-Policy" content="${escapeHtml(contentSecurityPolicy(productionOrigin))}">
|
|
6420
6433
|
<title>${escapeHtml(appName)}</title>
|
|
6421
6434
|
</head>
|
|
6422
6435
|
<body>
|
|
@@ -6635,7 +6648,7 @@ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => creat
|
|
|
6635
6648
|
await Promise.all([
|
|
6636
6649
|
writeFile7(join17(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
6637
6650
|
`),
|
|
6638
|
-
writeFile7(join17(staging, INDEX_FILE), indexHtml(options.config.appName)),
|
|
6651
|
+
writeFile7(join17(staging, INDEX_FILE), indexHtml(options.config.appName, options.config.productionOrigin)),
|
|
6639
6652
|
buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
|
|
6640
6653
|
]);
|
|
6641
6654
|
await installBundle(staging, destination);
|
|
@@ -18048,28 +18061,173 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
|
|
|
18048
18061
|
return apply;
|
|
18049
18062
|
};
|
|
18050
18063
|
|
|
18051
|
-
// src/mobile/
|
|
18052
|
-
import {
|
|
18053
|
-
import {
|
|
18054
|
-
|
|
18064
|
+
// src/mobile/mobileBundleInspection.ts
|
|
18065
|
+
import { createHash as createHash12 } from "crypto";
|
|
18066
|
+
import { access as access8, readFile as readFile16, stat as stat2 } from "fs/promises";
|
|
18067
|
+
import { join as join49, relative as relative24, resolve as resolve39 } from "path";
|
|
18068
|
+
var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath = (projectRoot, path) => {
|
|
18069
|
+
const value = relative24(resolve39(projectRoot), resolve39(path)).replaceAll("\\", "/");
|
|
18070
|
+
return value || ".";
|
|
18071
|
+
}, pathExists5 = async (path) => {
|
|
18055
18072
|
try {
|
|
18056
18073
|
await access8(path);
|
|
18057
18074
|
return true;
|
|
18058
18075
|
} catch {
|
|
18059
18076
|
return false;
|
|
18060
18077
|
}
|
|
18078
|
+
}, readObject = async (path) => {
|
|
18079
|
+
const value = JSON.parse(await readFile16(path, "utf8"));
|
|
18080
|
+
if (!isObject2(value))
|
|
18081
|
+
throw new TypeError("JSON root must be an object.");
|
|
18082
|
+
return value;
|
|
18083
|
+
}, requireString = (value, field) => {
|
|
18084
|
+
if (typeof value !== "string" || value.length === 0)
|
|
18085
|
+
throw new TypeError(`${field} must be a non-empty string.`);
|
|
18086
|
+
return value;
|
|
18087
|
+
}, requireStringArray = (value, field) => {
|
|
18088
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
|
|
18089
|
+
throw new TypeError(`${field} must be a string array.`);
|
|
18090
|
+
return value;
|
|
18091
|
+
}, requireBundleFile = async (root, value, field, expectedHash) => {
|
|
18092
|
+
const portable = requireString(value, field);
|
|
18093
|
+
const path = resolve39(root, portable);
|
|
18094
|
+
const normalizedRoot = resolve39(root);
|
|
18095
|
+
if (path === normalizedRoot || !path.startsWith(`${normalizedRoot}/`))
|
|
18096
|
+
throw new TypeError(`${field} must remain inside the mobile bundle.`);
|
|
18097
|
+
if (!(await stat2(path).catch(() => {
|
|
18098
|
+
return;
|
|
18099
|
+
}))?.isFile())
|
|
18100
|
+
throw new TypeError(`${field} does not exist in the mobile bundle.`);
|
|
18101
|
+
if (expectedHash !== undefined) {
|
|
18102
|
+
if (!SHA256_PATTERN.test(expectedHash))
|
|
18103
|
+
throw new TypeError(`${field} has an invalid SHA-256 digest.`);
|
|
18104
|
+
const actual = createHash12("sha256").update(await readFile16(path)).digest("hex");
|
|
18105
|
+
if (actual !== expectedHash)
|
|
18106
|
+
throw new TypeError(`${field} failed its SHA-256 integrity check.`);
|
|
18107
|
+
}
|
|
18108
|
+
return portable;
|
|
18109
|
+
}, inspectAbsoluteMobileBundle = async (config, projectRoot) => {
|
|
18110
|
+
const manifestPath = join49(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
18111
|
+
const manifest = portablePath(projectRoot, manifestPath);
|
|
18112
|
+
if (!await pathExists5(manifestPath))
|
|
18113
|
+
return { manifest, status: "missing" };
|
|
18114
|
+
try {
|
|
18115
|
+
const value = await readObject(manifestPath);
|
|
18116
|
+
if (value.format !== ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT)
|
|
18117
|
+
throw new TypeError("format is not supported by this runtime.");
|
|
18118
|
+
if (requireString(value.appId, "appId") !== config.appId)
|
|
18119
|
+
throw new TypeError("appId does not match the effective mobile config.");
|
|
18120
|
+
if (requireString(value.productionOrigin, "productionOrigin") !== config.productionOrigin)
|
|
18121
|
+
throw new TypeError("productionOrigin does not match the effective mobile config.");
|
|
18122
|
+
const appBuild = requireString(value.appBuild, "appBuild");
|
|
18123
|
+
const runtime = requireString(value.runtime, "runtime");
|
|
18124
|
+
if (runtime !== String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION))
|
|
18125
|
+
throw new TypeError("runtime is not supported by this AbsoluteJS build.");
|
|
18126
|
+
const capabilities = requireStringArray(value.deviceCapabilities, "deviceCapabilities").sort();
|
|
18127
|
+
if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
|
|
18128
|
+
throw new TypeError("pages and routes must be arrays.");
|
|
18129
|
+
const pageIds = new Set;
|
|
18130
|
+
const frameworks7 = new Set;
|
|
18131
|
+
await Promise.all(value.pages.map(async (candidate) => {
|
|
18132
|
+
if (!isObject2(candidate))
|
|
18133
|
+
throw new TypeError("pages contains an invalid entry.");
|
|
18134
|
+
const pageId = requireString(candidate.pageId, "page.pageId");
|
|
18135
|
+
if (pageIds.has(pageId))
|
|
18136
|
+
throw new TypeError("page.pageId values must be unique.");
|
|
18137
|
+
pageIds.add(pageId);
|
|
18138
|
+
const framework = requireString(candidate.framework, "page.framework");
|
|
18139
|
+
if (!MOBILE_FRAMEWORKS.has(framework))
|
|
18140
|
+
throw new TypeError("page.framework is unsupported.");
|
|
18141
|
+
frameworks7.add(framework);
|
|
18142
|
+
const bundleHash = requireString(candidate.bundleHash, "page.bundleHash");
|
|
18143
|
+
requireString(candidate.contract, "page.contract");
|
|
18144
|
+
requireString(candidate.propsSchemaHash, "page.propsSchemaHash");
|
|
18145
|
+
await requireBundleFile(config.bundleDirectory, candidate.localBundlePath, "page.localBundlePath", bundleHash);
|
|
18146
|
+
if (candidate.localStylePath !== undefined) {
|
|
18147
|
+
const styleHash = requireString(candidate.styleBundleHash, "page.styleBundleHash");
|
|
18148
|
+
await requireBundleFile(config.bundleDirectory, candidate.localStylePath, "page.localStylePath", styleHash);
|
|
18149
|
+
} else if (candidate.styleBundleHash !== undefined)
|
|
18150
|
+
throw new TypeError("page.styleBundleHash requires page.localStylePath.");
|
|
18151
|
+
}));
|
|
18152
|
+
const routes = value.routes.map((candidate) => {
|
|
18153
|
+
if (!isObject2(candidate))
|
|
18154
|
+
throw new TypeError("routes contains an invalid entry.");
|
|
18155
|
+
const { method } = candidate;
|
|
18156
|
+
if (method !== "GET" && method !== "HEAD")
|
|
18157
|
+
throw new TypeError("route.method must be GET or HEAD.");
|
|
18158
|
+
const pageId = requireString(candidate.pageId, "route.pageId");
|
|
18159
|
+
if (!pageIds.has(pageId))
|
|
18160
|
+
throw new TypeError("route.pageId references a missing page.");
|
|
18161
|
+
return {
|
|
18162
|
+
method,
|
|
18163
|
+
pageId,
|
|
18164
|
+
pattern: requireString(candidate.pattern, "route.pattern")
|
|
18165
|
+
};
|
|
18166
|
+
});
|
|
18167
|
+
await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) => requireBundleFile(config.bundleDirectory, file, file)));
|
|
18168
|
+
const entryPath = new URL(config.entry, "https://absolute.invalid").pathname;
|
|
18169
|
+
const entryResolved = resolveAbsoluteMobileRoute(routes, entryPath) !== undefined;
|
|
18170
|
+
if (!entryResolved)
|
|
18171
|
+
throw new TypeError("entry is not owned by an embedded route.");
|
|
18172
|
+
return {
|
|
18173
|
+
appBuild,
|
|
18174
|
+
auth: isObject2(value.auth),
|
|
18175
|
+
capabilities,
|
|
18176
|
+
entryResolved,
|
|
18177
|
+
frameworks: [...frameworks7].sort(),
|
|
18178
|
+
manifest,
|
|
18179
|
+
pageCount: value.pages.length,
|
|
18180
|
+
routeCount: value.routes.length,
|
|
18181
|
+
runtime,
|
|
18182
|
+
status: "valid",
|
|
18183
|
+
sync: isObject2(value.sync)
|
|
18184
|
+
};
|
|
18185
|
+
} catch (error) {
|
|
18186
|
+
return {
|
|
18187
|
+
issue: error instanceof Error ? error.message : "The embedded mobile manifest is invalid.",
|
|
18188
|
+
manifest,
|
|
18189
|
+
status: "invalid"
|
|
18190
|
+
};
|
|
18191
|
+
}
|
|
18192
|
+
};
|
|
18193
|
+
var init_mobileBundleInspection = __esm(() => {
|
|
18194
|
+
init_pageProtocol();
|
|
18195
|
+
init_routeMatcher();
|
|
18196
|
+
init_transport();
|
|
18197
|
+
MOBILE_FRAMEWORKS = new Set([
|
|
18198
|
+
"angular",
|
|
18199
|
+
"ember",
|
|
18200
|
+
"html",
|
|
18201
|
+
"htmx",
|
|
18202
|
+
"react",
|
|
18203
|
+
"svelte",
|
|
18204
|
+
"vue"
|
|
18205
|
+
]);
|
|
18206
|
+
SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
18207
|
+
});
|
|
18208
|
+
|
|
18209
|
+
// src/mobile/releaseDoctor.ts
|
|
18210
|
+
import { access as access9, readFile as readFile17, readdir as readdir4 } from "fs/promises";
|
|
18211
|
+
import { dirname as dirname29, extname as extname8, join as join50, relative as relative25 } from "path";
|
|
18212
|
+
var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, EXACT_VERSION_PATTERN, NOT_FOUND3 = -1, LOCK_FILES, MANUAL_REVIEW, pathExists6 = async (path) => {
|
|
18213
|
+
try {
|
|
18214
|
+
await access9(path);
|
|
18215
|
+
return true;
|
|
18216
|
+
} catch {
|
|
18217
|
+
return false;
|
|
18218
|
+
}
|
|
18061
18219
|
}, inspectReleaseAsset = async (path, isDirectory, isFile2) => {
|
|
18062
18220
|
if (isDirectory)
|
|
18063
18221
|
return findHmrAsset(path);
|
|
18064
18222
|
if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
|
|
18065
18223
|
return;
|
|
18066
|
-
const source = await
|
|
18224
|
+
const source = await readFile17(path, "utf8");
|
|
18067
18225
|
return HMR_ASSET_PATTERN.test(source) ? path : undefined;
|
|
18068
18226
|
}, findHmrAsset = async (root) => {
|
|
18069
|
-
if (!await
|
|
18227
|
+
if (!await pathExists6(root))
|
|
18070
18228
|
return;
|
|
18071
18229
|
const entries = await readdir4(root, { withFileTypes: true });
|
|
18072
|
-
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(
|
|
18230
|
+
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(join50(root, entry.name), entry.isDirectory(), entry.isFile())));
|
|
18073
18231
|
return matches.find((match) => match !== undefined);
|
|
18074
18232
|
}, pass = (id, detail, path) => ({ detail, id, path, status: "pass" }), fail5 = (id, detail, path, remediation) => ({
|
|
18075
18233
|
detail,
|
|
@@ -18077,7 +18235,70 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18077
18235
|
path,
|
|
18078
18236
|
remediation,
|
|
18079
18237
|
status: "fail"
|
|
18080
|
-
}),
|
|
18238
|
+
}), warn = (id, detail, path, remediation) => ({
|
|
18239
|
+
detail,
|
|
18240
|
+
id,
|
|
18241
|
+
path,
|
|
18242
|
+
remediation,
|
|
18243
|
+
status: "warn"
|
|
18244
|
+
}), readJsonObject = async (path) => {
|
|
18245
|
+
const value = JSON.parse(await readFile17(path, "utf8"));
|
|
18246
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
18247
|
+
throw new TypeError("JSON root must be an object.");
|
|
18248
|
+
return Object.fromEntries(Object.entries(value));
|
|
18249
|
+
}, packageDeclarations = (manifest) => {
|
|
18250
|
+
const declarations = new Map;
|
|
18251
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
18252
|
+
const value = manifest[field];
|
|
18253
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
18254
|
+
continue;
|
|
18255
|
+
for (const [name, version2] of Object.entries(value))
|
|
18256
|
+
if (typeof version2 === "string")
|
|
18257
|
+
declarations.set(name, version2);
|
|
18258
|
+
}
|
|
18259
|
+
return declarations;
|
|
18260
|
+
}, versionCore = (version2) => version2.split("-")[0]?.split(".").slice(0, 2).join("."), capacitorVersionCheck = async (config, projectRoot) => {
|
|
18261
|
+
const manifestPath = join50(projectRoot, "package.json");
|
|
18262
|
+
try {
|
|
18263
|
+
const manifest = await readJsonObject(manifestPath);
|
|
18264
|
+
const declarations = packageDeclarations(manifest);
|
|
18265
|
+
const names = [
|
|
18266
|
+
"@capacitor/core",
|
|
18267
|
+
"@capacitor/cli",
|
|
18268
|
+
...config.platforms.map((platform6) => `@capacitor/${platform6}`)
|
|
18269
|
+
];
|
|
18270
|
+
const versions = await Promise.all(names.map(async (name) => {
|
|
18271
|
+
const declared = declarations.get(name);
|
|
18272
|
+
if (!declared || !EXACT_VERSION_PATTERN.test(declared))
|
|
18273
|
+
throw new TypeError(`${name} must be a direct exact dependency.`);
|
|
18274
|
+
const installed = await readJsonObject(join50(projectRoot, "node_modules", name, "package.json"));
|
|
18275
|
+
if (installed.version !== declared)
|
|
18276
|
+
throw new TypeError(`${name} does not match its installed version.`);
|
|
18277
|
+
return declared;
|
|
18278
|
+
}));
|
|
18279
|
+
const lines = new Set(versions.map(versionCore));
|
|
18280
|
+
if (lines.size !== 1)
|
|
18281
|
+
throw new TypeError("Capacitor core, CLI, and platform packages must use one major/minor line.");
|
|
18282
|
+
return pass("mobile.capacitor-versions", `Capacitor core, CLI, and configured platforms are pinned and aligned on ${versions[0]}.`, manifestPath);
|
|
18283
|
+
} catch (error) {
|
|
18284
|
+
return fail5("mobile.capacitor-versions", error instanceof Error ? error.message : "Capacitor package versions could not be validated.", manifestPath, "Pin @capacitor/core, @capacitor/cli, and each configured platform to exact versions on the same major/minor line, then reinstall.");
|
|
18285
|
+
}
|
|
18286
|
+
}, dependencyLockCheck = async (projectRoot) => {
|
|
18287
|
+
const present = (await Promise.all(LOCK_FILES.map(async (name) => ({
|
|
18288
|
+
exists: await pathExists6(join50(projectRoot, name)),
|
|
18289
|
+
name
|
|
18290
|
+
})))).find(({ exists: exists3 }) => exists3);
|
|
18291
|
+
return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, join50(projectRoot, present.name)) : fail5("mobile.dependency-lock", "No supported dependency lockfile is present.", projectRoot, "Install dependencies with the project package manager and commit its lockfile before release.");
|
|
18292
|
+
}, productionOriginCheck = (config, projectRoot) => {
|
|
18293
|
+
const origin = new URL(config.productionOrigin);
|
|
18294
|
+
return origin.protocol === "https:" ? pass("mobile.production-origin", "Production transport uses an HTTPS origin.") : fail5("mobile.production-origin", "A loopback development origin cannot be used for a signed release.", projectRoot, "Configure mobile.server.productionOrigin with the deployed HTTPS origin.");
|
|
18295
|
+
}, associationIdentityCheck = (config, projectRoot) => {
|
|
18296
|
+
const missing = [
|
|
18297
|
+
...config.platforms.includes("ios") && !config.appleAppIdPrefix ? ["mobile.deepLinks.apple.appIdPrefix"] : [],
|
|
18298
|
+
...config.platforms.includes("android") && config.androidCertificateFingerprints.length === 0 ? ["mobile.deepLinks.android.sha256CertificateFingerprints"] : []
|
|
18299
|
+
];
|
|
18300
|
+
return missing.length === 0 ? pass("mobile.association-identities", "Deep-link association identities are configured for every release platform.") : fail5("mobile.association-identities", `Release association identity is missing: ${missing.join(", ")}.`, projectRoot, "Configure the Apple application prefix and every Android signing-certificate SHA-256 fingerprint used for release.");
|
|
18301
|
+
}, journalReleaseCheck = async (journalPath, platform6) => await pathExists6(journalPath) ? fail5(`${platform6}.dev-journal`, "A live-reload recovery journal is still active.", journalPath, "Close the development session or run mobile doctor again after stale-session repair.") : pass(`${platform6}.dev-journal`, "No live-reload recovery journal is active.", journalPath), parsedJson = (source) => {
|
|
18081
18302
|
try {
|
|
18082
18303
|
const parsed = JSON.parse(source);
|
|
18083
18304
|
return parsed;
|
|
@@ -18099,30 +18320,138 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18099
18320
|
const allowNavigation = Reflect.get(server, "allowNavigation");
|
|
18100
18321
|
return typeof Reflect.get(server, "url") === "string" || Reflect.get(server, "cleartext") === true || Array.isArray(allowNavigation) && allowNavigation.length > 0;
|
|
18101
18322
|
}, capacitorConfigReleaseCheck = async (nativeConfigPath) => {
|
|
18102
|
-
if (!await
|
|
18323
|
+
if (!await pathExists6(nativeConfigPath)) {
|
|
18103
18324
|
return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
|
|
18104
18325
|
}
|
|
18105
|
-
const unsafe = isUnsafeCapacitorConfig(await
|
|
18326
|
+
const unsafe = isUnsafeCapacitorConfig(await readFile17(nativeConfigPath, "utf8"));
|
|
18106
18327
|
return unsafe ? fail5("android.capacitor-config", "Android Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync android`; do not ship development transport overrides.") : pass("android.capacitor-config", "Android Capacitor config contains no development transport overrides.", nativeConfigPath);
|
|
18328
|
+
}, capacitorIdentityCheck = async (config, platform6, nativeConfigPath) => {
|
|
18329
|
+
try {
|
|
18330
|
+
const parsed = await readJsonObject(nativeConfigPath);
|
|
18331
|
+
if (parsed.appId !== config.appId || parsed.appName !== config.appName)
|
|
18332
|
+
throw new TypeError(`${platform6} packaged application identity does not match mobile config.`);
|
|
18333
|
+
return pass(`${platform6}.app-identity`, `Packaged ${platform6} application identity matches mobile config.`, nativeConfigPath);
|
|
18334
|
+
} catch (error) {
|
|
18335
|
+
return fail5(`${platform6}.app-identity`, error instanceof Error ? error.message : `${platform6} application identity could not be validated.`, nativeConfigPath, `Run \`absolute mobile sync ${platform6}\` and review the generated Capacitor config.`);
|
|
18336
|
+
}
|
|
18107
18337
|
}, manifestReleaseCheck = async (manifestPath) => {
|
|
18108
|
-
if (!await
|
|
18338
|
+
if (!await pathExists6(manifestPath)) {
|
|
18109
18339
|
return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
|
|
18110
18340
|
}
|
|
18111
|
-
const source = await
|
|
18341
|
+
const source = await readFile17(manifestPath, "utf8");
|
|
18112
18342
|
const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
|
|
18113
18343
|
const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
18114
|
-
const networkConfigPath = networkConfigName ?
|
|
18344
|
+
const networkConfigPath = networkConfigName ? join50(dirname29(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
|
|
18115
18345
|
const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
|
|
18116
|
-
const developmentTrustContents = networkConfigPath ? await
|
|
18346
|
+
const developmentTrustContents = networkConfigPath ? await readFile17(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
|
|
18117
18347
|
const developmentTrust = developmentTrustReference || developmentTrustContents;
|
|
18118
18348
|
return cleartext || developmentTrust ? fail5("android.cleartext", developmentTrust ? "Android still references the AbsoluteJS development certificate authority." : "Android explicitly permits cleartext traffic.", manifestPath, "Run `absolute mobile sync android`; do not ship development transport or trust overrides.") : pass("android.cleartext", "Android does not explicitly permit cleartext traffic.", manifestPath);
|
|
18119
18349
|
}, hmrAssetsReleaseCheck = async (publicRoot) => {
|
|
18120
18350
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
18121
18351
|
return hmrAsset ? fail5("android.hmr-assets", "A packaged Android asset contains the development HMR client.", hmrAsset, "Rebuild the production mobile bundle and run Capacitor sync again.") : pass("android.hmr-assets", "Packaged Android assets contain no development HMR markers.", publicRoot);
|
|
18352
|
+
}, embeddedBundleReleaseCheck = async (config, projectRoot, platform6, publicRoot) => {
|
|
18353
|
+
const inspection = await inspectAbsoluteMobileBundle({ ...config, bundleDirectory: publicRoot }, projectRoot);
|
|
18354
|
+
if (inspection.status === "valid")
|
|
18355
|
+
return pass(`${platform6}.bundle-integrity`, `Packaged mobile manifest, runtime, routes, and ${inspection.pageCount ?? 0} page asset(s) passed structural and SHA-256 validation.`, join50(publicRoot, "absolute-mobile-manifest.json"));
|
|
18356
|
+
return fail5(`${platform6}.bundle-integrity`, inspection.status === "missing" ? "The packaged mobile manifest is missing." : `The packaged mobile bundle is invalid: ${inspection.issue ?? "unknown validation error"}`, join50(publicRoot, "absolute-mobile-manifest.json"), "Rebuild the production mobile bundle and run Capacitor sync for this platform.");
|
|
18357
|
+
}, contentSecurityPolicyCheck = async (config, platform6, publicRoot) => {
|
|
18358
|
+
const path = join50(publicRoot, "index.html");
|
|
18359
|
+
try {
|
|
18360
|
+
const source = await readFile17(path, "utf8");
|
|
18361
|
+
const requirements = [
|
|
18362
|
+
"Content-Security-Policy",
|
|
18363
|
+
"default-src 'self'",
|
|
18364
|
+
"base-uri 'none'",
|
|
18365
|
+
"object-src 'none'",
|
|
18366
|
+
"script-src 'self'",
|
|
18367
|
+
"form-action 'none'",
|
|
18368
|
+
config.productionOrigin
|
|
18369
|
+
];
|
|
18370
|
+
const missing = requirements.filter((value) => !source.includes(value));
|
|
18371
|
+
if (missing.length > 0)
|
|
18372
|
+
throw new TypeError("Packaged shell CSP is missing a required AbsoluteJS directive or backend origin.");
|
|
18373
|
+
return pass(`${platform6}.content-security-policy`, "Packaged shell CSP restricts scripts, objects, base URLs, forms, and backend connections.", path);
|
|
18374
|
+
} catch (error) {
|
|
18375
|
+
return fail5(`${platform6}.content-security-policy`, error instanceof Error ? error.message : "Packaged shell CSP could not be validated.", path, "Rebuild the production mobile bundle with the AbsoluteJS-generated shell.");
|
|
18376
|
+
}
|
|
18377
|
+
}, sourceFiles = async (root, extensions) => {
|
|
18378
|
+
if (!await pathExists6(root))
|
|
18379
|
+
return [];
|
|
18380
|
+
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
|
|
18381
|
+
return files.filter((file) => extensions.has(extname8(file))).map((file) => join50(root, file));
|
|
18382
|
+
}, containsPattern = async (paths, pattern) => {
|
|
18383
|
+
const sources = await Promise.all(paths.map((path) => readFile17(path, "utf8")));
|
|
18384
|
+
const index = sources.findIndex((source) => pattern.test(source));
|
|
18385
|
+
return index === NOT_FOUND3 ? undefined : paths[index];
|
|
18386
|
+
}, androidNativeSecurityCheck = async (androidRoot) => {
|
|
18387
|
+
const manifestPath = join50(androidRoot, "app/src/main/AndroidManifest.xml");
|
|
18388
|
+
try {
|
|
18389
|
+
const manifest = await readFile17(manifestPath, "utf8");
|
|
18390
|
+
if (/android:debuggable=["']true["']/u.test(manifest))
|
|
18391
|
+
throw new TypeError("Android release manifest explicitly enables application debugging.");
|
|
18392
|
+
const sources = await sourceFiles(join50(androidRoot, "app/src/main"), new Set([".java", ".kt"]));
|
|
18393
|
+
const debugSource = await containsPattern(sources, /setWebContentsDebuggingEnabled\s*\(\s*true\s*\)/u);
|
|
18394
|
+
if (debugSource)
|
|
18395
|
+
return fail5("android.native-debugging", "Android application source unconditionally enables WebView debugging.", debugSource, "Remove the unconditional WebView debugging call; use the platform debug-build behavior during development.");
|
|
18396
|
+
return pass("android.native-debugging", "Android does not explicitly enable app or WebView debugging in release source.", manifestPath);
|
|
18397
|
+
} catch (error) {
|
|
18398
|
+
return fail5("android.native-debugging", error instanceof Error ? error.message : "Android native debugging configuration could not be validated.", manifestPath, "Remove explicit release debugging settings and rerun mobile sync.");
|
|
18399
|
+
}
|
|
18400
|
+
}, androidExportedComponentsCheck = async (manifestPath) => {
|
|
18401
|
+
const source = await readFile17(manifestPath, "utf8").catch(() => "");
|
|
18402
|
+
const exported = [
|
|
18403
|
+
...source.matchAll(/<(?:activity|activity-alias|provider|receiver|service)\b[^>]*>/giu)
|
|
18404
|
+
].map(([tag]) => tag).filter((tag) => /android:exported=["']true["']/iu.test(tag)).map((tag) => tag.match(/android:name=["']([^"']+)["']/iu)?.[1]).filter((name) => Boolean(name) && name !== ".MainActivity");
|
|
18405
|
+
return exported.length === 0 ? pass("android.exported-components", "No non-launcher Android component is explicitly exported.", manifestPath) : warn("android.exported-components", `${exported.length} non-launcher Android component(s) are exported and require manual authorization review.`, manifestPath, "Confirm each exported component is intentional, permission-protected where appropriate, and documented in the mobile threat model review.");
|
|
18406
|
+
}, androidDeepLinkProjectionCheck = async (config, manifestPath) => {
|
|
18407
|
+
try {
|
|
18408
|
+
const source = await readFile17(manifestPath, "utf8");
|
|
18409
|
+
const required = [
|
|
18410
|
+
'android:autoVerify="true"',
|
|
18411
|
+
"android.intent.category.BROWSABLE",
|
|
18412
|
+
...config.deepLinkHosts.map((host2) => `android:scheme="https" android:host="${host2}"`),
|
|
18413
|
+
...config.deepLinkScheme ? [`android:scheme="${config.deepLinkScheme}"`] : []
|
|
18414
|
+
];
|
|
18415
|
+
if (required.some((value) => !source.includes(value)))
|
|
18416
|
+
throw new TypeError("Android App Link or custom-scheme projection does not match mobile config.");
|
|
18417
|
+
return pass("android.deep-links", "Android verified links and custom scheme match the effective mobile config.", manifestPath);
|
|
18418
|
+
} catch (error) {
|
|
18419
|
+
return fail5("android.deep-links", error instanceof Error ? error.message : "Android deep-link projection could not be validated.", manifestPath, "Run `absolute mobile sync android` and review the AbsoluteJS-owned deep-link region.");
|
|
18420
|
+
}
|
|
18421
|
+
}, iosNativeSecurityCheck = async (iosRoot) => {
|
|
18422
|
+
const entitlementsPath = join50(iosRoot, "App/AbsoluteJS.entitlements");
|
|
18423
|
+
const entitlements = await readFile17(entitlementsPath, "utf8").catch(() => "");
|
|
18424
|
+
if (/<key>get-task-allow<\/key>\s*<true\s*\/>/u.test(entitlements) || /<key>com\.apple\.security\.get-task-allow<\/key>\s*<true\s*\/>/u.test(entitlements))
|
|
18425
|
+
return fail5("ios.native-debugging", "iOS source entitlements explicitly permit debugger attachment.", entitlementsPath, "Remove get-task-allow from source entitlements; Xcode supplies development entitlements only to debug builds.");
|
|
18426
|
+
const sources = await sourceFiles(join50(iosRoot, "App"), new Set([".m", ".mm", ".swift"]));
|
|
18427
|
+
const debugSource = await containsPattern(sources, /\.isInspectable\s*=\s*true|setInspectable\s*\(\s*true\s*\)/u);
|
|
18428
|
+
if (debugSource)
|
|
18429
|
+
return fail5("ios.native-debugging", "iOS application source unconditionally enables WebView inspection.", debugSource, "Remove unconditional WebView inspection from release source.");
|
|
18430
|
+
return pass("ios.native-debugging", "iOS source does not enable release debugger attachment or WebView inspection.", entitlementsPath);
|
|
18431
|
+
}, iosDeepLinkProjectionCheck = async (config, iosRoot) => {
|
|
18432
|
+
const infoPath = join50(iosRoot, "App/App/Info.plist");
|
|
18433
|
+
const entitlementsPath = join50(iosRoot, "App/AbsoluteJS.entitlements");
|
|
18434
|
+
const projectPath = join50(iosRoot, "App/App.xcodeproj/project.pbxproj");
|
|
18435
|
+
try {
|
|
18436
|
+
const [info2, entitlements, project] = await Promise.all([
|
|
18437
|
+
readFile17(infoPath, "utf8"),
|
|
18438
|
+
readFile17(entitlementsPath, "utf8"),
|
|
18439
|
+
readFile17(projectPath, "utf8")
|
|
18440
|
+
]);
|
|
18441
|
+
if (config.deepLinkScheme && (!info2.includes("<key>CFBundleURLTypes</key>") || !info2.includes(`<string>${config.deepLinkScheme}</string>`)))
|
|
18442
|
+
throw new TypeError("iOS custom URL scheme does not match mobile config.");
|
|
18443
|
+
if (config.deepLinkHosts.some((host2) => !entitlements.includes(`<string>applinks:${host2}</string>`)))
|
|
18444
|
+
throw new TypeError("iOS associated domains do not match mobile config.");
|
|
18445
|
+
if (!project.includes("CODE_SIGN_ENTITLEMENTS = App/AbsoluteJS.entitlements;"))
|
|
18446
|
+
throw new TypeError("iOS target does not sign the AbsoluteJS entitlements file.");
|
|
18447
|
+
return pass("ios.deep-links", "iOS universal links, custom scheme, and signed entitlements match mobile config.", entitlementsPath);
|
|
18448
|
+
} catch (error) {
|
|
18449
|
+
return fail5("ios.deep-links", error instanceof Error ? error.message : "iOS deep-link projection could not be validated.", entitlementsPath, "Run `absolute mobile sync ios` and review the AbsoluteJS-owned deep-link and entitlement projection.");
|
|
18450
|
+
}
|
|
18122
18451
|
}, syncSchemaReleaseCheck = (projectRoot) => {
|
|
18123
18452
|
if (!projectUsesAbsoluteSync(projectRoot))
|
|
18124
18453
|
return;
|
|
18125
|
-
const manifestPath =
|
|
18454
|
+
const manifestPath = join50(projectRoot, "package.json");
|
|
18126
18455
|
try {
|
|
18127
18456
|
const schema = discoverAbsoluteSyncSchema(projectRoot);
|
|
18128
18457
|
const versions = schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ");
|
|
@@ -18144,8 +18473,8 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18144
18473
|
}, IOS_USAGE_KEYS, androidDevicePermissionCheck = async (config, permissions) => {
|
|
18145
18474
|
if (!config.platforms.includes("android") || permissions.length === 0)
|
|
18146
18475
|
return;
|
|
18147
|
-
const path =
|
|
18148
|
-
const source = await
|
|
18476
|
+
const path = join50(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
18477
|
+
const source = await readFile17(path, "utf8");
|
|
18149
18478
|
const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
|
|
18150
18479
|
if (missing.length === 0)
|
|
18151
18480
|
return;
|
|
@@ -18153,14 +18482,44 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18153
18482
|
}, iosDevicePermissionCheck = async (config, purposes) => {
|
|
18154
18483
|
if (!config.platforms.includes("ios") || purposes.length === 0)
|
|
18155
18484
|
return;
|
|
18156
|
-
const path =
|
|
18157
|
-
const source = await
|
|
18485
|
+
const path = join50(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
18486
|
+
const source = await readFile17(path, "utf8");
|
|
18158
18487
|
const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
|
|
18159
18488
|
if (missing.length === 0)
|
|
18160
18489
|
return;
|
|
18161
18490
|
return fail5("mobile.device-capabilities", `iOS is missing usage descriptions for: ${missing.join(", ")}.`, path, "Run `absolute mobile sync ios` to regenerate detected device usage descriptions.");
|
|
18491
|
+
}, iosCapabilityProjectionCheck = async (config, requirements) => {
|
|
18492
|
+
if (!config.platforms.includes("ios"))
|
|
18493
|
+
return;
|
|
18494
|
+
const appRoot = join50(config.nativeProjectDirectory, "ios/App/App");
|
|
18495
|
+
const infoPath = join50(appRoot, "Info.plist");
|
|
18496
|
+
const info2 = await readFile17(infoPath, "utf8").catch(() => "");
|
|
18497
|
+
if (requirements.iosSystemBars && !/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<true\s*\/>/u.test(info2))
|
|
18498
|
+
return fail5("mobile.device-capabilities", "iOS system-bar capability is missing its required view-controller setting.", infoPath, "Run `absolute mobile sync ios` to regenerate native capability settings.");
|
|
18499
|
+
if (requirements.iosPrivacyAccessedApis.length > 0) {
|
|
18500
|
+
const privacyPath = join50(appRoot, "PrivacyInfo.xcprivacy");
|
|
18501
|
+
const projectPath = join50(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
18502
|
+
const [privacy, project] = await Promise.all([
|
|
18503
|
+
readFile17(privacyPath, "utf8").catch(() => ""),
|
|
18504
|
+
readFile17(projectPath, "utf8").catch(() => "")
|
|
18505
|
+
]);
|
|
18506
|
+
const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
|
|
18507
|
+
if (missing || !project.includes("PrivacyInfo.xcprivacy in Resources"))
|
|
18508
|
+
return fail5("mobile.device-capabilities", "iOS privacy manifest or target membership does not match detected native capabilities.", privacyPath, "Run `absolute mobile sync ios` to regenerate and target PrivacyInfo.xcprivacy.");
|
|
18509
|
+
}
|
|
18510
|
+
if (requirements.iosPushNotifications) {
|
|
18511
|
+
const entitlementsPath = join50(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
18512
|
+
const delegatePath = join50(appRoot, "AppDelegate.swift");
|
|
18513
|
+
const [entitlements, delegate] = await Promise.all([
|
|
18514
|
+
readFile17(entitlementsPath, "utf8").catch(() => ""),
|
|
18515
|
+
readFile17(delegatePath, "utf8").catch(() => "")
|
|
18516
|
+
]);
|
|
18517
|
+
if (!entitlements.includes("<key>aps-environment</key>") || !delegate.includes("capacitorDidRegisterForRemoteNotifications") || !delegate.includes("capacitorDidFailToRegisterForRemoteNotifications"))
|
|
18518
|
+
return fail5("mobile.device-capabilities", "iOS push entitlement or AppDelegate forwarding does not match detected capabilities.", entitlementsPath, "Run `absolute mobile sync ios` to regenerate native push integration.");
|
|
18519
|
+
}
|
|
18520
|
+
return;
|
|
18162
18521
|
}, deviceCapabilityReleaseCheck = async (config, projectRoot) => {
|
|
18163
|
-
const manifestPath =
|
|
18522
|
+
const manifestPath = join50(projectRoot, "package.json");
|
|
18164
18523
|
try {
|
|
18165
18524
|
const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
|
|
18166
18525
|
assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
|
|
@@ -18171,32 +18530,41 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18171
18530
|
const iosCheck = await iosDevicePermissionCheck(config, requirements.iosUsageDescriptions);
|
|
18172
18531
|
if (iosCheck)
|
|
18173
18532
|
return iosCheck;
|
|
18533
|
+
const iosProjectionCheck = await iosCapabilityProjectionCheck(config, requirements);
|
|
18534
|
+
if (iosProjectionCheck)
|
|
18535
|
+
return iosProjectionCheck;
|
|
18174
18536
|
return pass("mobile.device-capabilities", plan.capabilities.length > 0 ? `Native provider packages and permission declarations match detected capabilities: ${plan.capabilities.join(", ")}.` : "No optional native device capabilities are used.", manifestPath);
|
|
18175
18537
|
} catch (error) {
|
|
18176
18538
|
return fail5("mobile.device-capabilities", error instanceof Error ? error.message : "Native device capability provisioning is invalid.", manifestPath, "Run `absolute mobile sync` and approve the exact capability plugins before releasing.");
|
|
18177
18539
|
}
|
|
18178
18540
|
}, inspectAndroidRelease = async (config, projectRoot) => {
|
|
18179
|
-
const androidRoot =
|
|
18180
|
-
const nativeConfigPath =
|
|
18181
|
-
const manifestPath =
|
|
18182
|
-
const publicRoot =
|
|
18183
|
-
const journalPath =
|
|
18541
|
+
const androidRoot = join50(config.nativeProjectDirectory, "android");
|
|
18542
|
+
const nativeConfigPath = join50(androidRoot, "app", "src", "main", "assets", "capacitor.config.json");
|
|
18543
|
+
const manifestPath = join50(androidRoot, "app", "src", "main", "AndroidManifest.xml");
|
|
18544
|
+
const publicRoot = join50(androidRoot, "app", "src", "main", "assets", "public");
|
|
18545
|
+
const journalPath = join50(projectRoot, ".absolutejs", "mobile", "dev-session", "journal.json");
|
|
18184
18546
|
const checks = await Promise.all([
|
|
18185
18547
|
journalReleaseCheck(journalPath, "android"),
|
|
18186
18548
|
capacitorConfigReleaseCheck(nativeConfigPath),
|
|
18549
|
+
capacitorIdentityCheck(config, "android", nativeConfigPath),
|
|
18187
18550
|
manifestReleaseCheck(manifestPath),
|
|
18188
|
-
hmrAssetsReleaseCheck(publicRoot)
|
|
18551
|
+
hmrAssetsReleaseCheck(publicRoot),
|
|
18552
|
+
embeddedBundleReleaseCheck(config, projectRoot, "android", publicRoot),
|
|
18553
|
+
contentSecurityPolicyCheck(config, "android", publicRoot),
|
|
18554
|
+
androidNativeSecurityCheck(androidRoot),
|
|
18555
|
+
androidExportedComponentsCheck(manifestPath),
|
|
18556
|
+
androidDeepLinkProjectionCheck(config, manifestPath)
|
|
18189
18557
|
]);
|
|
18190
18558
|
return checks.map((check2) => ({
|
|
18191
18559
|
...check2,
|
|
18192
|
-
path: check2.path ?
|
|
18560
|
+
path: check2.path ? relative25(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
18193
18561
|
}));
|
|
18194
18562
|
}, inspectIosRelease = async (config, projectRoot) => {
|
|
18195
|
-
const iosAppRoot =
|
|
18196
|
-
const nativeConfigPath =
|
|
18197
|
-
const infoPath =
|
|
18198
|
-
const publicRoot =
|
|
18199
|
-
const journalPath =
|
|
18563
|
+
const iosAppRoot = join50(config.nativeProjectDirectory, "ios", "App", "App");
|
|
18564
|
+
const nativeConfigPath = join50(iosAppRoot, "capacitor.config.json");
|
|
18565
|
+
const infoPath = join50(iosAppRoot, "Info.plist");
|
|
18566
|
+
const publicRoot = join50(iosAppRoot, "public");
|
|
18567
|
+
const journalPath = join50(projectRoot, ".absolutejs", "mobile", "ios-dev-session", "journal.json");
|
|
18200
18568
|
const checks = [
|
|
18201
18569
|
await journalReleaseCheck(journalPath, "ios")
|
|
18202
18570
|
];
|
|
@@ -18205,27 +18573,59 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18205
18573
|
} else {
|
|
18206
18574
|
checks.push(pass("ios.marketing-version", `The iOS marketing version is ${config.iosVersion}.`));
|
|
18207
18575
|
}
|
|
18208
|
-
if (!await
|
|
18576
|
+
if (!await pathExists6(nativeConfigPath)) {
|
|
18209
18577
|
checks.push(fail5("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
|
|
18210
|
-
} else if (isUnsafeCapacitorConfig(await
|
|
18578
|
+
} else if (isUnsafeCapacitorConfig(await readFile17(nativeConfigPath, "utf8"))) {
|
|
18211
18579
|
checks.push(fail5("ios.capacitor-config", "iOS Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync ios`; do not ship development transport overrides."));
|
|
18212
18580
|
} else {
|
|
18213
18581
|
checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
|
|
18214
18582
|
}
|
|
18215
|
-
|
|
18583
|
+
checks.push(await capacitorIdentityCheck(config, "ios", nativeConfigPath));
|
|
18584
|
+
if (!await pathExists6(infoPath)) {
|
|
18216
18585
|
checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
|
|
18217
18586
|
} else {
|
|
18218
|
-
const info2 = await
|
|
18587
|
+
const info2 = await readFile17(infoPath, "utf8");
|
|
18219
18588
|
checks.push(/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info2) ? fail5("ios.transport-security", "iOS App Transport Security permits arbitrary network loads.", infoPath, "Remove NSAllowsArbitraryLoads from the release Info.plist.") : pass("ios.transport-security", "iOS App Transport Security does not permit arbitrary loads.", infoPath));
|
|
18220
18589
|
}
|
|
18221
18590
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
18222
18591
|
checks.push(hmrAsset ? fail5("ios.hmr-assets", "A packaged iOS asset contains the development HMR client.", hmrAsset, "Rebuild the production mobile bundle and run Capacitor sync again.") : pass("ios.hmr-assets", "Packaged iOS assets contain no development HMR markers.", publicRoot));
|
|
18592
|
+
checks.push(await embeddedBundleReleaseCheck(config, projectRoot, "ios", publicRoot), await contentSecurityPolicyCheck(config, "ios", publicRoot), await iosNativeSecurityCheck(join50(config.nativeProjectDirectory, "ios")), await iosDeepLinkProjectionCheck(config, join50(config.nativeProjectDirectory, "ios")));
|
|
18223
18593
|
return checks.map((check2) => ({
|
|
18224
18594
|
...check2,
|
|
18225
|
-
path: check2.path ?
|
|
18595
|
+
path: check2.path ? relative25(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
18226
18596
|
}));
|
|
18597
|
+
}, createAbsoluteMobileComplianceReport = (config, result) => {
|
|
18598
|
+
const summary = {
|
|
18599
|
+
failed: result.checks.filter(({ status: status2 }) => status2 === "fail").length,
|
|
18600
|
+
passed: result.checks.filter(({ status: status2 }) => status2 === "pass").length,
|
|
18601
|
+
warnings: result.checks.filter(({ status: status2 }) => status2 === "warn").length
|
|
18602
|
+
};
|
|
18603
|
+
return {
|
|
18604
|
+
app: {
|
|
18605
|
+
appId: config.appId,
|
|
18606
|
+
engine: config.engine,
|
|
18607
|
+
platforms: [...config.platforms],
|
|
18608
|
+
productionOrigin: config.productionOrigin
|
|
18609
|
+
},
|
|
18610
|
+
checks: result.checks.map(({ id, status: status2 }) => ({ id, status: status2 })),
|
|
18611
|
+
format: ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT,
|
|
18612
|
+
manualReview: [...MANUAL_REVIEW],
|
|
18613
|
+
ready: result.ready,
|
|
18614
|
+
summary
|
|
18615
|
+
};
|
|
18227
18616
|
}, inspectAbsoluteMobileRelease = async (config, projectRoot) => {
|
|
18228
|
-
const
|
|
18617
|
+
const globalChecks = await Promise.all([
|
|
18618
|
+
Promise.resolve(productionOriginCheck(config, projectRoot)),
|
|
18619
|
+
Promise.resolve(associationIdentityCheck(config, projectRoot)),
|
|
18620
|
+
dependencyLockCheck(projectRoot),
|
|
18621
|
+
capacitorVersionCheck(config, projectRoot)
|
|
18622
|
+
]);
|
|
18623
|
+
const checks = globalChecks.map((check2) => ({
|
|
18624
|
+
...check2,
|
|
18625
|
+
path: check2.path ? relative25(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
18626
|
+
}));
|
|
18627
|
+
if (config.platforms.includes("android"))
|
|
18628
|
+
checks.push(...await inspectAndroidRelease(config, projectRoot));
|
|
18229
18629
|
if (config.platforms.includes("ios")) {
|
|
18230
18630
|
checks.push(...await inspectIosRelease(config, projectRoot));
|
|
18231
18631
|
}
|
|
@@ -18233,25 +18633,41 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
18233
18633
|
if (syncSchema) {
|
|
18234
18634
|
checks.push({
|
|
18235
18635
|
...syncSchema,
|
|
18236
|
-
path: syncSchema.path ?
|
|
18636
|
+
path: syncSchema.path ? relative25(projectRoot, syncSchema.path).replaceAll("\\", "/") || "." : undefined
|
|
18237
18637
|
});
|
|
18238
18638
|
}
|
|
18239
18639
|
const deviceCapabilities = await deviceCapabilityReleaseCheck(config, projectRoot);
|
|
18240
18640
|
checks.push({
|
|
18241
18641
|
...deviceCapabilities,
|
|
18242
|
-
path: deviceCapabilities.path ?
|
|
18642
|
+
path: deviceCapabilities.path ? relative25(projectRoot, deviceCapabilities.path).replaceAll("\\", "/") || "." : undefined
|
|
18243
18643
|
});
|
|
18244
18644
|
return {
|
|
18245
18645
|
checks,
|
|
18246
|
-
ready: checks.length > 0 && checks.every((check2) => check2.status
|
|
18646
|
+
ready: checks.length > 0 && checks.every((check2) => check2.status !== "fail")
|
|
18247
18647
|
};
|
|
18248
18648
|
};
|
|
18249
18649
|
var init_releaseDoctor = __esm(() => {
|
|
18650
|
+
init_mobileBundleInspection();
|
|
18250
18651
|
init_nativeAuth();
|
|
18251
18652
|
init_syncSchema();
|
|
18252
18653
|
init_deviceCapabilities();
|
|
18253
18654
|
HMR_ASSET_PATTERN = /(?:__HMR_WS__|hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
|
|
18254
18655
|
RELEASE_ASSET_EXTENSIONS = new Set([".html", ".js", ".mjs"]);
|
|
18656
|
+
EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
18657
|
+
LOCK_FILES = [
|
|
18658
|
+
"bun.lock",
|
|
18659
|
+
"bun.lockb",
|
|
18660
|
+
"package-lock.json",
|
|
18661
|
+
"pnpm-lock.yaml",
|
|
18662
|
+
"yarn.lock"
|
|
18663
|
+
];
|
|
18664
|
+
MANUAL_REVIEW = [
|
|
18665
|
+
"physical-device",
|
|
18666
|
+
"store-privacy-questionnaire",
|
|
18667
|
+
"privacy-policy",
|
|
18668
|
+
"signing-key-custody",
|
|
18669
|
+
"native-sdk-data-practices"
|
|
18670
|
+
];
|
|
18255
18671
|
IOS_USAGE_KEYS = {
|
|
18256
18672
|
camera: "NSCameraUsageDescription",
|
|
18257
18673
|
"location-always": "NSLocationAlwaysAndWhenInUseUsageDescription",
|
|
@@ -18262,19 +18678,19 @@ var init_releaseDoctor = __esm(() => {
|
|
|
18262
18678
|
});
|
|
18263
18679
|
|
|
18264
18680
|
// src/mobile/androidRelease.ts
|
|
18265
|
-
import { createHash as
|
|
18681
|
+
import { createHash as createHash13 } from "crypto";
|
|
18266
18682
|
import {
|
|
18267
|
-
access as
|
|
18683
|
+
access as access10,
|
|
18268
18684
|
copyFile as copyFile5,
|
|
18269
18685
|
mkdir as mkdir12,
|
|
18270
18686
|
mkdtemp as mkdtemp5,
|
|
18271
|
-
readFile as
|
|
18687
|
+
readFile as readFile18,
|
|
18272
18688
|
rename as rename12,
|
|
18273
18689
|
rm as rm8,
|
|
18274
|
-
stat as
|
|
18690
|
+
stat as stat3,
|
|
18275
18691
|
writeFile as writeFile14
|
|
18276
18692
|
} from "fs/promises";
|
|
18277
|
-
import { dirname as dirname30, isAbsolute as isAbsolute7, join as
|
|
18693
|
+
import { dirname as dirname30, isAbsolute as isAbsolute7, join as join51, relative as relative26, resolve as resolve40, sep as sep6 } from "path";
|
|
18278
18694
|
var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
|
|
18279
18695
|
if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
|
|
18280
18696
|
throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
|
|
@@ -18284,9 +18700,9 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18284
18700
|
appId: value.appId,
|
|
18285
18701
|
runtime: value.runtime
|
|
18286
18702
|
};
|
|
18287
|
-
},
|
|
18703
|
+
}, pathExists7 = async (path) => {
|
|
18288
18704
|
try {
|
|
18289
|
-
await
|
|
18705
|
+
await access10(path);
|
|
18290
18706
|
return true;
|
|
18291
18707
|
} catch {
|
|
18292
18708
|
return false;
|
|
@@ -18324,22 +18740,22 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18324
18740
|
artifactPath
|
|
18325
18741
|
]);
|
|
18326
18742
|
return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
|
|
18327
|
-
}, sha256File2 = async (path) =>
|
|
18328
|
-
const root =
|
|
18329
|
-
const output =
|
|
18330
|
-
const projectRelative =
|
|
18743
|
+
}, sha256File2 = async (path) => createHash13("sha256").update(await readFile18(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
|
|
18744
|
+
const root = resolve40(projectRoot);
|
|
18745
|
+
const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
|
|
18746
|
+
const projectRelative = relative26(root, output);
|
|
18331
18747
|
if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute7(projectRelative)) {
|
|
18332
18748
|
throw new TypeError("mobile build --outdir must remain inside the project.");
|
|
18333
18749
|
}
|
|
18334
18750
|
return output;
|
|
18335
18751
|
}, installRelease2 = async (artifactPath, metadata, outputRoot) => {
|
|
18336
|
-
const releaseRoot =
|
|
18752
|
+
const releaseRoot = join51(outputRoot, metadata.releaseId);
|
|
18337
18753
|
const artifactName = "app-release.aab";
|
|
18338
|
-
const destination =
|
|
18339
|
-
if (await
|
|
18340
|
-
const existing = requireManifestIdentity(JSON.parse(await
|
|
18754
|
+
const destination = join51(releaseRoot, artifactName);
|
|
18755
|
+
if (await pathExists7(releaseRoot)) {
|
|
18756
|
+
const existing = requireManifestIdentity(JSON.parse(await readFile18(join51(releaseRoot, "release.json"), "utf8")), metadata);
|
|
18341
18757
|
const [installedBytes, installedSha256] = await Promise.all([
|
|
18342
|
-
|
|
18758
|
+
stat3(destination).then(({ size }) => size),
|
|
18343
18759
|
sha256File2(destination)
|
|
18344
18760
|
]);
|
|
18345
18761
|
if (installedBytes !== metadata.bytes || installedSha256 !== metadata.sha256) {
|
|
@@ -18348,14 +18764,14 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18348
18764
|
return { artifactPath: destination, metadata: existing, releaseRoot };
|
|
18349
18765
|
}
|
|
18350
18766
|
await mkdir12(dirname30(releaseRoot), { recursive: true });
|
|
18351
|
-
const staging = await mkdtemp5(
|
|
18767
|
+
const staging = await mkdtemp5(join51(dirname30(releaseRoot), ".android-stage-"));
|
|
18352
18768
|
try {
|
|
18353
|
-
await copyFile5(artifactPath,
|
|
18769
|
+
await copyFile5(artifactPath, join51(staging, artifactName));
|
|
18354
18770
|
const complete = {
|
|
18355
18771
|
...metadata,
|
|
18356
18772
|
artifact: artifactName
|
|
18357
18773
|
};
|
|
18358
|
-
await writeFile14(
|
|
18774
|
+
await writeFile14(join51(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
|
|
18359
18775
|
`, { flag: "wx" });
|
|
18360
18776
|
await rename12(staging, releaseRoot);
|
|
18361
18777
|
return { artifactPath: destination, metadata: complete, releaseRoot };
|
|
@@ -18380,18 +18796,18 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18380
18796
|
if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
|
|
18381
18797
|
throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
|
|
18382
18798
|
}
|
|
18383
|
-
const projectRoot =
|
|
18799
|
+
const projectRoot = resolve40(options.projectRoot);
|
|
18384
18800
|
const host2 = options.host ?? detectAbsoluteMobileHost();
|
|
18385
18801
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
|
|
18386
|
-
const nativeDirectory =
|
|
18387
|
-
const manifest = requireManifest2(JSON.parse(await
|
|
18802
|
+
const nativeDirectory = join51(options.config.nativeProjectDirectory, "android");
|
|
18803
|
+
const manifest = requireManifest2(JSON.parse(await readFile18(join51(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
|
|
18388
18804
|
if (manifest.appId !== options.config.appId) {
|
|
18389
18805
|
throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
|
|
18390
18806
|
}
|
|
18391
18807
|
let { versionCode } = options;
|
|
18392
18808
|
if (options.prepareVersionCode) {
|
|
18393
18809
|
const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
|
|
18394
|
-
const buildIdentity =
|
|
18810
|
+
const buildIdentity = createHash13("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
|
|
18395
18811
|
versionCode = await options.prepareVersionCode(buildIdentity);
|
|
18396
18812
|
}
|
|
18397
18813
|
if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
|
|
@@ -18410,7 +18826,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18410
18826
|
run: options.run,
|
|
18411
18827
|
task: "bundleRelease"
|
|
18412
18828
|
});
|
|
18413
|
-
if (!await
|
|
18829
|
+
if (!await pathExists7(artifactPath)) {
|
|
18414
18830
|
throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
|
|
18415
18831
|
}
|
|
18416
18832
|
const capture = options.capture ?? defaultCapture4;
|
|
@@ -18422,7 +18838,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
18422
18838
|
throw new TypeError("Android Gradle produced an unsigned App Bundle. Configure the release signingConfig in the source-owned Android project (prefer external Gradle properties), or pass --unsigned only for a non-publishable build.");
|
|
18423
18839
|
}
|
|
18424
18840
|
const [bytes, sha2562] = await Promise.all([
|
|
18425
|
-
|
|
18841
|
+
stat3(artifactPath).then(({ size }) => size),
|
|
18426
18842
|
sha256File2(artifactPath)
|
|
18427
18843
|
]);
|
|
18428
18844
|
const releaseId = `amobile_android_${sha2562}`;
|
|
@@ -18486,7 +18902,7 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
|
18486
18902
|
};
|
|
18487
18903
|
|
|
18488
18904
|
// src/mobile/iosConformance.ts
|
|
18489
|
-
import { readFile as
|
|
18905
|
+
import { readFile as readFile19, stat as stat4 } from "fs/promises";
|
|
18490
18906
|
var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
18491
18907
|
const match = HMR_LINE.exec(line);
|
|
18492
18908
|
if (!match)
|
|
@@ -18515,13 +18931,13 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
|
18515
18931
|
const sleep2 = options.sleep ?? Bun.sleep;
|
|
18516
18932
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
18517
18933
|
const deadline = Date.now() + timeoutMs;
|
|
18518
|
-
let offset = options.startOffset ?? await
|
|
18934
|
+
let offset = options.startOffset ?? await stat4(options.logPath).then(({ size }) => size).catch(() => 0);
|
|
18519
18935
|
let buffered = "";
|
|
18520
18936
|
const poll = async () => {
|
|
18521
18937
|
if (Date.now() > deadline)
|
|
18522
18938
|
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
18523
18939
|
options.signal?.throwIfAborted();
|
|
18524
|
-
const contents = await
|
|
18940
|
+
const contents = await readFile19(options.logPath).catch(() => Buffer.alloc(0));
|
|
18525
18941
|
if (contents.byteLength < offset) {
|
|
18526
18942
|
offset = 0;
|
|
18527
18943
|
buffered = "";
|
|
@@ -18545,8 +18961,8 @@ var init_iosConformance = __esm(() => {
|
|
|
18545
18961
|
});
|
|
18546
18962
|
|
|
18547
18963
|
// src/mobile/nativeTestReport.ts
|
|
18548
|
-
import { mkdir as mkdir13, readFile as
|
|
18549
|
-
import { join as
|
|
18964
|
+
import { mkdir as mkdir13, readFile as readFile20, writeFile as writeFile15 } from "fs/promises";
|
|
18965
|
+
import { join as join52 } from "path";
|
|
18550
18966
|
var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sanitizeNativeReportText = (value) => value.replace(nativeCredentialPattern, "[REDACTED]").replace(bearerPattern, "Bearer [REDACTED]").replace(secretPattern, "$1$2[REDACTED]").replace(coordinatePattern, "$1$2[REDACTED]").replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/giu, "$1?[REDACTED]"), markdownCell = (value) => sanitizeNativeReportText(value).replaceAll("|", "\\|").replaceAll(`
|
|
18551
18967
|
`, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
|
|
18552
18968
|
const target = `${run.targetKind} ${run.targetId}`;
|
|
@@ -18648,7 +19064,7 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
|
|
|
18648
19064
|
reportVersion: 1,
|
|
18649
19065
|
run: options.run
|
|
18650
19066
|
}), readPackageVersionForNativeReport = async (packageJsonPath) => {
|
|
18651
|
-
const manifest = JSON.parse(await
|
|
19067
|
+
const manifest = JSON.parse(await readFile20(packageJsonPath, "utf8"));
|
|
18652
19068
|
if (typeof manifest !== "object" || manifest === null)
|
|
18653
19069
|
return "unknown";
|
|
18654
19070
|
const version2 = Reflect.get(manifest, "version");
|
|
@@ -18685,8 +19101,8 @@ ${table(report.manualChecks)}
|
|
|
18685
19101
|
`;
|
|
18686
19102
|
}, writeAbsoluteNativeTestReport = async (directory, report) => {
|
|
18687
19103
|
await mkdir13(directory, { recursive: true });
|
|
18688
|
-
const jsonPath =
|
|
18689
|
-
const markdownPath =
|
|
19104
|
+
const jsonPath = join52(directory, "report.json");
|
|
19105
|
+
const markdownPath = join52(directory, "report.md");
|
|
18690
19106
|
await Promise.all([
|
|
18691
19107
|
writeFile15(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
18692
19108
|
`),
|
|
@@ -18904,8 +19320,8 @@ var init_androidTestReport = __esm(() => {
|
|
|
18904
19320
|
});
|
|
18905
19321
|
|
|
18906
19322
|
// src/mobile/releasePublisher.ts
|
|
18907
|
-
import { access as
|
|
18908
|
-
import { isAbsolute as isAbsolute8, relative as
|
|
19323
|
+
import { access as access11 } from "fs/promises";
|
|
19324
|
+
import { isAbsolute as isAbsolute8, relative as relative27, resolve as resolve41, sep as sep7 } from "path";
|
|
18909
19325
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
18910
19326
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
18911
19327
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -18927,16 +19343,16 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
|
18927
19343
|
}
|
|
18928
19344
|
return versionCode;
|
|
18929
19345
|
}, isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher = (value) => isRecord14(value) && typeof value.publish === "function", publisherModulePath = (projectRoot, requested) => {
|
|
18930
|
-
const root =
|
|
18931
|
-
const path =
|
|
18932
|
-
const projectRelative =
|
|
19346
|
+
const root = resolve41(projectRoot);
|
|
19347
|
+
const path = resolve41(root, requested);
|
|
19348
|
+
const projectRelative = relative27(root, path);
|
|
18933
19349
|
if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute8(projectRelative)) {
|
|
18934
19350
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
18935
19351
|
}
|
|
18936
19352
|
return path;
|
|
18937
19353
|
}, loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
18938
19354
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
18939
|
-
await
|
|
19355
|
+
await access11(modulePath).catch(() => {
|
|
18940
19356
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
18941
19357
|
});
|
|
18942
19358
|
const loaded = await import(pathToFileURL2(modulePath).href);
|
|
@@ -18997,103 +19413,103 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
|
18997
19413
|
var init_releasePublisher = () => {};
|
|
18998
19414
|
|
|
18999
19415
|
// src/mobile/mobileInspect.ts
|
|
19000
|
-
import { access as
|
|
19001
|
-
import { join as
|
|
19002
|
-
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES,
|
|
19003
|
-
const value =
|
|
19416
|
+
import { access as access12, readFile as readFile21, stat as stat5 } from "fs/promises";
|
|
19417
|
+
import { join as join53, relative as relative28, resolve as resolve42 } from "path";
|
|
19418
|
+
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWORKS2, isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath2 = (projectRoot, path) => {
|
|
19419
|
+
const value = relative28(resolve42(projectRoot), resolve42(path)).replaceAll("\\", "/");
|
|
19004
19420
|
return value || ".";
|
|
19005
|
-
},
|
|
19421
|
+
}, pathExists8 = async (path) => {
|
|
19006
19422
|
try {
|
|
19007
|
-
await
|
|
19423
|
+
await access12(path);
|
|
19008
19424
|
return true;
|
|
19009
19425
|
} catch {
|
|
19010
19426
|
return false;
|
|
19011
19427
|
}
|
|
19012
|
-
},
|
|
19013
|
-
const value = JSON.parse(await
|
|
19014
|
-
if (!
|
|
19428
|
+
}, readObject2 = async (path) => {
|
|
19429
|
+
const value = JSON.parse(await readFile21(path, "utf8"));
|
|
19430
|
+
if (!isObject3(value))
|
|
19015
19431
|
throw new TypeError("JSON root must be an object.");
|
|
19016
19432
|
return value;
|
|
19017
|
-
},
|
|
19433
|
+
}, requireString2 = (value, field) => {
|
|
19018
19434
|
if (typeof value !== "string" || value.length === 0)
|
|
19019
19435
|
throw new TypeError(`${field} must be a non-empty string.`);
|
|
19020
19436
|
return value;
|
|
19021
|
-
},
|
|
19437
|
+
}, requireStringArray2 = (value, field) => {
|
|
19022
19438
|
if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
|
|
19023
19439
|
throw new TypeError(`${field} must be a string array.`);
|
|
19024
19440
|
return value;
|
|
19025
|
-
},
|
|
19026
|
-
const portable =
|
|
19027
|
-
const path =
|
|
19028
|
-
const normalizedRoot =
|
|
19441
|
+
}, requireBundleFile2 = async (root, value, field) => {
|
|
19442
|
+
const portable = requireString2(value, field);
|
|
19443
|
+
const path = resolve42(root, portable);
|
|
19444
|
+
const normalizedRoot = resolve42(root);
|
|
19029
19445
|
if (path === normalizedRoot || !path.startsWith(`${normalizedRoot}/`))
|
|
19030
19446
|
throw new TypeError(`${field} must remain inside the mobile bundle.`);
|
|
19031
|
-
if (!(await
|
|
19447
|
+
if (!(await stat5(path).catch(() => {
|
|
19032
19448
|
return;
|
|
19033
19449
|
}))?.isFile())
|
|
19034
19450
|
throw new TypeError(`${field} does not exist in the mobile bundle.`);
|
|
19035
19451
|
return portable;
|
|
19036
19452
|
}, inspectBundle = async (config, projectRoot) => {
|
|
19037
|
-
const manifestPath =
|
|
19038
|
-
const manifest =
|
|
19039
|
-
if (!await
|
|
19453
|
+
const manifestPath = join53(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
19454
|
+
const manifest = portablePath2(projectRoot, manifestPath);
|
|
19455
|
+
if (!await pathExists8(manifestPath))
|
|
19040
19456
|
return { manifest, status: "missing" };
|
|
19041
19457
|
try {
|
|
19042
|
-
const value = await
|
|
19458
|
+
const value = await readObject2(manifestPath);
|
|
19043
19459
|
if (value.format !== 1)
|
|
19044
19460
|
throw new TypeError("format is not supported by this runtime.");
|
|
19045
|
-
if (
|
|
19461
|
+
if (requireString2(value.appId, "appId") !== config.appId)
|
|
19046
19462
|
throw new TypeError("appId does not match the effective mobile config.");
|
|
19047
|
-
if (
|
|
19463
|
+
if (requireString2(value.productionOrigin, "productionOrigin") !== config.productionOrigin)
|
|
19048
19464
|
throw new TypeError("productionOrigin does not match the effective mobile config.");
|
|
19049
|
-
const appBuild =
|
|
19050
|
-
const runtime =
|
|
19051
|
-
const capabilities =
|
|
19465
|
+
const appBuild = requireString2(value.appBuild, "appBuild");
|
|
19466
|
+
const runtime = requireString2(value.runtime, "runtime");
|
|
19467
|
+
const capabilities = requireStringArray2(value.deviceCapabilities, "deviceCapabilities").sort();
|
|
19052
19468
|
if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
|
|
19053
19469
|
throw new TypeError("pages and routes must be arrays.");
|
|
19054
19470
|
const pageIds = new Set;
|
|
19055
19471
|
const frameworks7 = new Set;
|
|
19056
19472
|
await Promise.all(value.pages.map(async (candidate) => {
|
|
19057
|
-
if (!
|
|
19473
|
+
if (!isObject3(candidate))
|
|
19058
19474
|
throw new TypeError("pages contains an invalid entry.");
|
|
19059
|
-
const pageId =
|
|
19475
|
+
const pageId = requireString2(candidate.pageId, "page.pageId");
|
|
19060
19476
|
if (pageIds.has(pageId))
|
|
19061
19477
|
throw new TypeError("page.pageId values must be unique.");
|
|
19062
19478
|
pageIds.add(pageId);
|
|
19063
|
-
const framework =
|
|
19064
|
-
if (!
|
|
19479
|
+
const framework = requireString2(candidate.framework, "page.framework");
|
|
19480
|
+
if (!MOBILE_FRAMEWORKS2.has(framework))
|
|
19065
19481
|
throw new TypeError("page.framework is unsupported.");
|
|
19066
19482
|
frameworks7.add(framework);
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
await
|
|
19483
|
+
requireString2(candidate.bundleHash, "page.bundleHash");
|
|
19484
|
+
requireString2(candidate.contract, "page.contract");
|
|
19485
|
+
requireString2(candidate.propsSchemaHash, "page.propsSchemaHash");
|
|
19486
|
+
await requireBundleFile2(config.bundleDirectory, candidate.localBundlePath, "page.localBundlePath");
|
|
19071
19487
|
if (candidate.localStylePath !== undefined)
|
|
19072
|
-
await
|
|
19488
|
+
await requireBundleFile2(config.bundleDirectory, candidate.localStylePath, "page.localStylePath");
|
|
19073
19489
|
}));
|
|
19074
19490
|
const routes = value.routes.map((candidate) => {
|
|
19075
|
-
if (!
|
|
19491
|
+
if (!isObject3(candidate))
|
|
19076
19492
|
throw new TypeError("routes contains an invalid entry.");
|
|
19077
19493
|
const { method } = candidate;
|
|
19078
19494
|
if (method !== "GET" && method !== "HEAD")
|
|
19079
19495
|
throw new TypeError("route.method must be GET or HEAD.");
|
|
19080
|
-
const pageId =
|
|
19496
|
+
const pageId = requireString2(candidate.pageId, "route.pageId");
|
|
19081
19497
|
if (!pageIds.has(pageId))
|
|
19082
19498
|
throw new TypeError("route.pageId references a missing page.");
|
|
19083
19499
|
return {
|
|
19084
19500
|
method,
|
|
19085
19501
|
pageId,
|
|
19086
|
-
pattern:
|
|
19502
|
+
pattern: requireString2(candidate.pattern, "route.pattern")
|
|
19087
19503
|
};
|
|
19088
19504
|
});
|
|
19089
|
-
await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) =>
|
|
19505
|
+
await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) => requireBundleFile2(config.bundleDirectory, file, file)));
|
|
19090
19506
|
const entryPath = new URL(config.entry, "https://absolute.invalid").pathname;
|
|
19091
19507
|
const entryResolved = resolveAbsoluteMobileRoute(routes, entryPath) !== undefined;
|
|
19092
19508
|
if (!entryResolved)
|
|
19093
19509
|
throw new TypeError("entry is not owned by an embedded route.");
|
|
19094
19510
|
return {
|
|
19095
19511
|
appBuild,
|
|
19096
|
-
auth:
|
|
19512
|
+
auth: isObject3(value.auth),
|
|
19097
19513
|
capabilities,
|
|
19098
19514
|
entryResolved,
|
|
19099
19515
|
frameworks: [...frameworks7].sort(),
|
|
@@ -19102,7 +19518,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19102
19518
|
routeCount: value.routes.length,
|
|
19103
19519
|
runtime,
|
|
19104
19520
|
status: "valid",
|
|
19105
|
-
sync:
|
|
19521
|
+
sync: isObject3(value.sync)
|
|
19106
19522
|
};
|
|
19107
19523
|
} catch (error) {
|
|
19108
19524
|
return {
|
|
@@ -19112,18 +19528,18 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19112
19528
|
};
|
|
19113
19529
|
}
|
|
19114
19530
|
}, addPackageDeclarations = (declarations, value) => {
|
|
19115
|
-
if (!
|
|
19531
|
+
if (!isObject3(value))
|
|
19116
19532
|
return;
|
|
19117
19533
|
for (const [name, declared] of Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
19118
19534
|
declarations.set(name, declared);
|
|
19119
19535
|
}, packageInspections = async (projectRoot, additionalNames) => {
|
|
19120
|
-
const project = await
|
|
19536
|
+
const project = await readObject2(join53(projectRoot, "package.json"));
|
|
19121
19537
|
const declarations = new Map;
|
|
19122
19538
|
for (const field of ["dependencies", "devDependencies"])
|
|
19123
19539
|
addPackageDeclarations(declarations, project[field]);
|
|
19124
19540
|
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
19125
19541
|
return Promise.all(names.map(async (name) => {
|
|
19126
|
-
const installedManifest = await
|
|
19542
|
+
const installedManifest = await readObject2(join53(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
19127
19543
|
return;
|
|
19128
19544
|
});
|
|
19129
19545
|
const installed = installedManifest?.version;
|
|
@@ -19160,22 +19576,22 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, MOBILE_FRAMEWOR
|
|
|
19160
19576
|
config: {
|
|
19161
19577
|
appId: config.appId,
|
|
19162
19578
|
appName: config.appName,
|
|
19163
|
-
bundleDirectory:
|
|
19579
|
+
bundleDirectory: portablePath2(projectRoot, config.bundleDirectory),
|
|
19164
19580
|
deepLinkHosts: config.deepLinkHosts,
|
|
19165
19581
|
deepLinkScheme: config.deepLinkScheme,
|
|
19166
19582
|
engine: config.engine,
|
|
19167
19583
|
entry: config.entry,
|
|
19168
19584
|
iosVersion: config.iosVersion,
|
|
19169
|
-
nativeProjectDirectory:
|
|
19585
|
+
nativeProjectDirectory: portablePath2(projectRoot, config.nativeProjectDirectory),
|
|
19170
19586
|
platforms: config.platforms,
|
|
19171
19587
|
productionOrigin: config.productionOrigin
|
|
19172
19588
|
},
|
|
19173
19589
|
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
19174
19590
|
nativeProjects: await Promise.all(config.platforms.map(async (platform6) => {
|
|
19175
|
-
const path =
|
|
19591
|
+
const path = join53(config.nativeProjectDirectory, platform6);
|
|
19176
19592
|
return {
|
|
19177
|
-
initialized: await
|
|
19178
|
-
path:
|
|
19593
|
+
initialized: await pathExists8(path),
|
|
19594
|
+
path: portablePath2(projectRoot, path),
|
|
19179
19595
|
platform: platform6
|
|
19180
19596
|
};
|
|
19181
19597
|
})),
|
|
@@ -19239,7 +19655,7 @@ var init_mobileInspect = __esm(() => {
|
|
|
19239
19655
|
"@absolutejs/sync-capacitor",
|
|
19240
19656
|
"@capacitor-community/sqlite"
|
|
19241
19657
|
]);
|
|
19242
|
-
|
|
19658
|
+
MOBILE_FRAMEWORKS2 = new Set([
|
|
19243
19659
|
"angular",
|
|
19244
19660
|
"ember",
|
|
19245
19661
|
"html",
|
|
@@ -19255,11 +19671,11 @@ var exports_mobile = {};
|
|
|
19255
19671
|
__export(exports_mobile, {
|
|
19256
19672
|
runMobile: () => runMobile
|
|
19257
19673
|
});
|
|
19258
|
-
import { access as
|
|
19259
|
-
import { join as
|
|
19674
|
+
import { access as access13, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
|
|
19675
|
+
import { join as join54, resolve as resolve43 } from "path";
|
|
19260
19676
|
import { createInterface } from "readline/promises";
|
|
19261
|
-
var
|
|
19262
|
-
const manifest = JSON.parse(await
|
|
19677
|
+
var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
|
|
19678
|
+
const manifest = JSON.parse(await readFile22(join54(projectRoot, "package.json"), "utf8"));
|
|
19263
19679
|
if (!isRecord15(manifest))
|
|
19264
19680
|
throw new TypeError("Application package.json must contain an object.");
|
|
19265
19681
|
const names = new Set;
|
|
@@ -19272,7 +19688,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19272
19688
|
return names;
|
|
19273
19689
|
}, resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
19274
19690
|
try {
|
|
19275
|
-
const manifest = JSON.parse(await
|
|
19691
|
+
const manifest = JSON.parse(await readFile22(join54(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
19276
19692
|
return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
19277
19693
|
} catch {
|
|
19278
19694
|
return;
|
|
@@ -19303,7 +19719,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19303
19719
|
await installApprovedPackages(projectRoot, args, `AbsoluteJS detected native device capabilities (${capabilityPlan.capabilities.join(", ")}). Install only their required Capacitor plugins now?`, capabilityPackages);
|
|
19304
19720
|
}, valueAfter = (args, flag) => {
|
|
19305
19721
|
const index = args.indexOf(flag);
|
|
19306
|
-
return index ===
|
|
19722
|
+
return index === NOT_FOUND4 ? undefined : args[index + 1];
|
|
19307
19723
|
}, valuesAfter = (args, flag) => args.flatMap((value, index) => {
|
|
19308
19724
|
const next = args[index + 1];
|
|
19309
19725
|
return value === flag && next !== undefined ? [next] : [];
|
|
@@ -19313,9 +19729,9 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19313
19729
|
}
|
|
19314
19730
|
return value;
|
|
19315
19731
|
}, capacitorExecutable = async (projectRoot) => {
|
|
19316
|
-
const executable =
|
|
19732
|
+
const executable = join54(projectRoot, "node_modules", ".bin", "cap");
|
|
19317
19733
|
try {
|
|
19318
|
-
await
|
|
19734
|
+
await access13(executable);
|
|
19319
19735
|
return executable;
|
|
19320
19736
|
} catch {
|
|
19321
19737
|
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
@@ -19412,7 +19828,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19412
19828
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
|
|
19413
19829
|
}, associations = async (args) => {
|
|
19414
19830
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19415
|
-
const outputDirectory =
|
|
19831
|
+
const outputDirectory = resolve43(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
19416
19832
|
if (args.includes("--verify")) {
|
|
19417
19833
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
19418
19834
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -19457,7 +19873,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19457
19873
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19458
19874
|
const result = await inspectAbsoluteMobileRelease(mobile, projectRoot);
|
|
19459
19875
|
if (args.includes("--json")) {
|
|
19460
|
-
console.log(JSON.stringify(result, null, 2));
|
|
19876
|
+
console.log(JSON.stringify(createAbsoluteMobileComplianceReport(mobile, result), null, 2));
|
|
19461
19877
|
} else {
|
|
19462
19878
|
result.checks.forEach((check2) => {
|
|
19463
19879
|
console.log(`${doctorMark(check2.status)} ${check2.detail}${check2.path ? ` (${check2.path})` : ""}`);
|
|
@@ -19465,8 +19881,8 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19465
19881
|
console.log(` ${check2.remediation}`);
|
|
19466
19882
|
});
|
|
19467
19883
|
console.log(result.ready ? `
|
|
19468
|
-
Mobile release
|
|
19469
|
-
Mobile release
|
|
19884
|
+
Mobile release security and compliance checks passed.` : `
|
|
19885
|
+
Mobile release security and compliance checks failed.`);
|
|
19470
19886
|
}
|
|
19471
19887
|
if (!result.ready) {
|
|
19472
19888
|
throw new TypeError("Mobile release validation failed. Resolve every failed check before signing or publishing the app.");
|
|
@@ -19645,7 +20061,7 @@ Mobile release transport checks failed.`);
|
|
|
19645
20061
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
19646
20062
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
19647
20063
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
19648
|
-
console.log(`Metadata: ${
|
|
20064
|
+
console.log(`Metadata: ${join54(release.releaseRoot, "release.json")}`);
|
|
19649
20065
|
return release;
|
|
19650
20066
|
} finally {
|
|
19651
20067
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -19748,7 +20164,7 @@ Mobile release transport checks failed.`);
|
|
|
19748
20164
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
19749
20165
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
|
|
19750
20166
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
19751
|
-
console.log(`Metadata: ${
|
|
20167
|
+
console.log(`Metadata: ${join54(release.releaseRoot, "release.json")}`);
|
|
19752
20168
|
return release;
|
|
19753
20169
|
} finally {
|
|
19754
20170
|
sendTelemetryEvent("mobile:ios-release-build", {
|
|
@@ -19855,7 +20271,7 @@ Mobile release transport checks failed.`);
|
|
|
19855
20271
|
checks.push({
|
|
19856
20272
|
id: "sync.storage-schema",
|
|
19857
20273
|
label: `Offline schema ${schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ")}`,
|
|
19858
|
-
path:
|
|
20274
|
+
path: join54(projectRoot, "package.json"),
|
|
19859
20275
|
platform: "host",
|
|
19860
20276
|
status: "pass"
|
|
19861
20277
|
});
|
|
@@ -19863,7 +20279,7 @@ Mobile release transport checks failed.`);
|
|
|
19863
20279
|
checks.push({
|
|
19864
20280
|
id: "sync.storage-schema",
|
|
19865
20281
|
label: "Offline schema metadata is invalid",
|
|
19866
|
-
path:
|
|
20282
|
+
path: join54(projectRoot, "package.json"),
|
|
19867
20283
|
platform: "host",
|
|
19868
20284
|
remediation: error instanceof Error ? error.message : String(error),
|
|
19869
20285
|
status: "fail"
|
|
@@ -19948,7 +20364,7 @@ Emulator setup verification:`);
|
|
|
19948
20364
|
}
|
|
19949
20365
|
return { https: args.includes("--https"), port };
|
|
19950
20366
|
}
|
|
19951
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
20367
|
+
const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
19952
20368
|
if (instances.length !== 1) {
|
|
19953
20369
|
throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
|
|
19954
20370
|
}
|
|
@@ -19991,8 +20407,8 @@ Emulator setup verification:`);
|
|
|
19991
20407
|
}
|
|
19992
20408
|
return selected;
|
|
19993
20409
|
}, safeArtifactRoot = (projectRoot, value) => {
|
|
19994
|
-
const root =
|
|
19995
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
20410
|
+
const root = resolve43(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
20411
|
+
if (root !== projectRoot && !root.startsWith(`${resolve43(projectRoot)}/`)) {
|
|
19996
20412
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
19997
20413
|
}
|
|
19998
20414
|
return root;
|
|
@@ -20017,10 +20433,10 @@ Emulator setup verification:`);
|
|
|
20017
20433
|
});
|
|
20018
20434
|
}, writeAndroidFailureArtifacts = async (options) => {
|
|
20019
20435
|
await mkdir14(options.artifactRoot, { recursive: true });
|
|
20020
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
20436
|
+
const screenshot = options.session ? await options.session.screenshot(join54(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
20021
20437
|
return;
|
|
20022
20438
|
}) : undefined;
|
|
20023
|
-
const diagnosticPath =
|
|
20439
|
+
const diagnosticPath = join54(options.artifactRoot, "android-failure.json");
|
|
20024
20440
|
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
20025
20441
|
diagnostics: options.session?.diagnostics ?? [],
|
|
20026
20442
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -20084,7 +20500,7 @@ Emulator setup verification:`);
|
|
|
20084
20500
|
console.log(JSON.stringify(report, null, 2));
|
|
20085
20501
|
else
|
|
20086
20502
|
printAndroidTestReport(report);
|
|
20087
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
20503
|
+
const screenshot = reportRoot ? await session.screenshot(join54(artifactRoot, "android-emulator.png")) : undefined;
|
|
20088
20504
|
await writeRequestedAndroidReport({
|
|
20089
20505
|
adb,
|
|
20090
20506
|
args,
|
|
@@ -20152,14 +20568,14 @@ Emulator setup verification:`);
|
|
|
20152
20568
|
const port = Number(explicit);
|
|
20153
20569
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
20154
20570
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
20155
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
20571
|
+
const instance2 = listLiveInstances().find((candidate) => resolve43(candidate.cwd) === resolve43(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
20156
20572
|
return {
|
|
20157
20573
|
https: instance2?.https ?? args.includes("--https"),
|
|
20158
20574
|
instance: instance2,
|
|
20159
20575
|
port
|
|
20160
20576
|
};
|
|
20161
20577
|
}
|
|
20162
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
20578
|
+
const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
20163
20579
|
if (instances.length !== 1)
|
|
20164
20580
|
throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
|
|
20165
20581
|
const [instance] = instances;
|
|
@@ -20214,7 +20630,7 @@ Emulator setup verification:`);
|
|
|
20214
20630
|
if (!instance)
|
|
20215
20631
|
return;
|
|
20216
20632
|
const index = instance.command.indexOf("--ios-device");
|
|
20217
|
-
return index ===
|
|
20633
|
+
return index === NOT_FOUND4 ? undefined : instance.command[index + 1];
|
|
20218
20634
|
}, physicalIosCapture = async (options) => {
|
|
20219
20635
|
const remoteName = valueAfter(options.args, "--remote");
|
|
20220
20636
|
if (remoteName && options.instance.iosRemoteMac && remoteName !== options.instance.iosRemoteMac)
|
|
@@ -20274,7 +20690,7 @@ Emulator setup verification:`);
|
|
|
20274
20690
|
return result;
|
|
20275
20691
|
}, writeIosFailureArtifacts = async (options) => {
|
|
20276
20692
|
await mkdir14(options.artifactRoot, { recursive: true });
|
|
20277
|
-
const screenshot =
|
|
20693
|
+
const screenshot = join54(options.artifactRoot, "ios-failure.png");
|
|
20278
20694
|
const screenshotResult = captureCommand4([
|
|
20279
20695
|
options.xcrun,
|
|
20280
20696
|
"simctl",
|
|
@@ -20283,7 +20699,7 @@ Emulator setup verification:`);
|
|
|
20283
20699
|
"screenshot",
|
|
20284
20700
|
screenshot
|
|
20285
20701
|
]);
|
|
20286
|
-
const diagnosticPath =
|
|
20702
|
+
const diagnosticPath = join54(options.artifactRoot, "ios-failure.json");
|
|
20287
20703
|
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
20288
20704
|
appId: options.appId,
|
|
20289
20705
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -20300,7 +20716,7 @@ Emulator setup verification:`);
|
|
|
20300
20716
|
};
|
|
20301
20717
|
}, nativeReportRoot = (args, projectRoot, platform6) => {
|
|
20302
20718
|
const index = args.indexOf("--report");
|
|
20303
|
-
if (index ===
|
|
20719
|
+
if (index === NOT_FOUND4)
|
|
20304
20720
|
return;
|
|
20305
20721
|
const candidate = args[index + 1];
|
|
20306
20722
|
const explicit = candidate?.startsWith("--") ? undefined : candidate;
|
|
@@ -20309,8 +20725,8 @@ Emulator setup verification:`);
|
|
|
20309
20725
|
}, absolutejsVersionForReport = async () => {
|
|
20310
20726
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
20311
20727
|
const versions = await Promise.all([
|
|
20312
|
-
|
|
20313
|
-
|
|
20728
|
+
resolve43(import.meta.dir, "..", "..", "package.json"),
|
|
20729
|
+
resolve43(import.meta.dir, "..", "..", "..", "package.json")
|
|
20314
20730
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
20315
20731
|
for (const version2 of versions) {
|
|
20316
20732
|
if (version2 === "unknown")
|
|
@@ -20529,7 +20945,7 @@ Emulator setup verification:`);
|
|
|
20529
20945
|
], "iOS app launch");
|
|
20530
20946
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
20531
20947
|
await mkdir14(artifactRoot, { recursive: true });
|
|
20532
|
-
const screenshot =
|
|
20948
|
+
const screenshot = join54(artifactRoot, "ios-simulator.png");
|
|
20533
20949
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
20534
20950
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
20535
20951
|
const report = {
|
|
@@ -20740,10 +21156,10 @@ var exports_typecheck = {};
|
|
|
20740
21156
|
__export(exports_typecheck, {
|
|
20741
21157
|
typecheck: () => typecheck
|
|
20742
21158
|
});
|
|
20743
|
-
import { resolve as
|
|
21159
|
+
import { resolve as resolve44, join as join55 } from "path";
|
|
20744
21160
|
import { existsSync as existsSync42, readFileSync as readFileSync40 } from "fs";
|
|
20745
21161
|
import { mkdir as mkdir15, writeFile as writeFile17 } from "fs/promises";
|
|
20746
|
-
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) =>
|
|
21162
|
+
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve44(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
|
|
20747
21163
|
if (!existsSync42(resolveConfigPath(configPath2))) {
|
|
20748
21164
|
const defaultService = {};
|
|
20749
21165
|
return [defaultService];
|
|
@@ -20765,7 +21181,7 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
|
|
|
20765
21181
|
const exitCode = await proc.exited;
|
|
20766
21182
|
return { exitCode, name, output: (stdout + stderr).trim() };
|
|
20767
21183
|
}, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
|
|
20768
|
-
const local =
|
|
21184
|
+
const local = resolve44("node_modules", ".bin", name);
|
|
20769
21185
|
return existsSync42(local) ? local : null;
|
|
20770
21186
|
}, ANSI_COLOR_REGEX, ANSI_PURPLE_REGEX, ANSI_CYAN_REGEX, ANSI_TOKEN_END_REGEX, stripAnsi4 = (str) => str.replace(ANSI_COLOR_REGEX, ""), formatSvelteOutput = (output) => {
|
|
20771
21187
|
const cwd = `${process.cwd()}/`;
|
|
@@ -20813,15 +21229,15 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20813
21229
|
return formatted;
|
|
20814
21230
|
}, ABSOLUTE_INTERNAL_EXCLUDES, resolveAbsoluteTypeFile = (fileName) => {
|
|
20815
21231
|
const candidates = [
|
|
20816
|
-
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
21232
|
+
resolve44("node_modules/@absolutejs/absolute/dist/types", fileName),
|
|
21233
|
+
resolve44(import.meta.dir, "../types", fileName),
|
|
21234
|
+
resolve44(import.meta.dir, "../../types", fileName),
|
|
21235
|
+
resolve44(import.meta.dir, "../../../types", fileName)
|
|
20820
21236
|
];
|
|
20821
21237
|
return candidates.find((candidate) => existsSync42(candidate)) ?? candidates[0];
|
|
20822
21238
|
}, ABSOLUTE_TYPECHECK_FILES, readProjectTsconfig = () => {
|
|
20823
21239
|
try {
|
|
20824
|
-
return JSON.parse(readFileSync40(
|
|
21240
|
+
return JSON.parse(readFileSync40(resolve44("tsconfig.json"), "utf-8"));
|
|
20825
21241
|
} catch {
|
|
20826
21242
|
return {};
|
|
20827
21243
|
}
|
|
@@ -20849,27 +21265,27 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20849
21265
|
console.error("\x1B[31m\u2717\x1B[0m vue-tsc is required for Vue type checking. Install it: bun add -d vue-tsc");
|
|
20850
21266
|
process.exit(1);
|
|
20851
21267
|
}
|
|
20852
|
-
const vueTsconfigPath =
|
|
21268
|
+
const vueTsconfigPath = join55(cacheDir, "tsconfig.vue-check.json");
|
|
20853
21269
|
await writeFile17(vueTsconfigPath, JSON.stringify({
|
|
20854
21270
|
compilerOptions: {
|
|
20855
21271
|
rootDir: ".."
|
|
20856
21272
|
},
|
|
20857
21273
|
exclude: getProjectTypecheckExcludes(),
|
|
20858
|
-
extends:
|
|
21274
|
+
extends: resolve44("tsconfig.json"),
|
|
20859
21275
|
include: getProjectTypecheckIncludes()
|
|
20860
21276
|
}, null, "\t"));
|
|
20861
21277
|
const base = [
|
|
20862
21278
|
vueTscBin,
|
|
20863
21279
|
"--noEmit",
|
|
20864
21280
|
"--project",
|
|
20865
|
-
|
|
21281
|
+
resolve44(vueTsconfigPath),
|
|
20866
21282
|
"--pretty"
|
|
20867
21283
|
];
|
|
20868
21284
|
const cached = await run("vue-tsc", [
|
|
20869
21285
|
...base,
|
|
20870
21286
|
"--incremental",
|
|
20871
21287
|
"--tsBuildInfoFile",
|
|
20872
|
-
|
|
21288
|
+
join55(cacheDir, "vue-tsc.tsbuildinfo")
|
|
20873
21289
|
]);
|
|
20874
21290
|
if (cached.exitCode === 0 || cached.output.length > 0)
|
|
20875
21291
|
return cached;
|
|
@@ -20880,7 +21296,7 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20880
21296
|
console.error("\x1B[31m\u2717\x1B[0m @angular/compiler-cli is required for Angular type checking. Install it: bun add -d @angular/compiler-cli");
|
|
20881
21297
|
process.exit(1);
|
|
20882
21298
|
}
|
|
20883
|
-
const angularTsconfigPath =
|
|
21299
|
+
const angularTsconfigPath = join55(cacheDir, "tsconfig.angular-check.json");
|
|
20884
21300
|
await writeFile17(angularTsconfigPath, JSON.stringify({
|
|
20885
21301
|
angularCompilerOptions: {
|
|
20886
21302
|
strictTemplates: true
|
|
@@ -20890,32 +21306,32 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20890
21306
|
rootDir: ".."
|
|
20891
21307
|
},
|
|
20892
21308
|
exclude: ABSOLUTE_INTERNAL_EXCLUDES.map(toGeneratedConfigPath),
|
|
20893
|
-
extends:
|
|
21309
|
+
extends: resolve44("tsconfig.json"),
|
|
20894
21310
|
include: [`../${angularDir}/**/*`]
|
|
20895
21311
|
}, null, "\t"));
|
|
20896
|
-
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(
|
|
21312
|
+
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve44(angularTsconfigPath))}`);
|
|
20897
21313
|
}, buildTscCheck = (cacheDir) => {
|
|
20898
21314
|
const tscBin = findBin("tsc");
|
|
20899
21315
|
if (!tscBin) {
|
|
20900
21316
|
console.error("\x1B[31m\u2717\x1B[0m typescript is required for type checking. Install it: bun add -d typescript");
|
|
20901
21317
|
process.exit(1);
|
|
20902
21318
|
}
|
|
20903
|
-
const tscConfigPath =
|
|
21319
|
+
const tscConfigPath = join55(cacheDir, "tsconfig.typecheck.json");
|
|
20904
21320
|
return writeFile17(tscConfigPath, JSON.stringify({
|
|
20905
21321
|
compilerOptions: {
|
|
20906
21322
|
rootDir: ".."
|
|
20907
21323
|
},
|
|
20908
21324
|
exclude: getProjectTypecheckExcludes(),
|
|
20909
|
-
extends:
|
|
21325
|
+
extends: resolve44("tsconfig.json"),
|
|
20910
21326
|
include: getProjectTypecheckIncludes()
|
|
20911
21327
|
}, null, "\t")).then(() => run("tsc", [
|
|
20912
21328
|
tscBin,
|
|
20913
21329
|
"--noEmit",
|
|
20914
21330
|
"--project",
|
|
20915
|
-
|
|
21331
|
+
resolve44(tscConfigPath),
|
|
20916
21332
|
"--incremental",
|
|
20917
21333
|
"--tsBuildInfoFile",
|
|
20918
|
-
|
|
21334
|
+
join55(cacheDir, "tsc.tsbuildinfo"),
|
|
20919
21335
|
"--pretty"
|
|
20920
21336
|
]));
|
|
20921
21337
|
}, buildSvelteCheck = async (cacheDir, svelteDir) => {
|
|
@@ -20924,16 +21340,16 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20924
21340
|
console.error("\x1B[31m\u2717\x1B[0m svelte-check is required for Svelte type checking. Install it: bun add -d svelte-check");
|
|
20925
21341
|
process.exit(1);
|
|
20926
21342
|
}
|
|
20927
|
-
const svelteTsconfigPath =
|
|
21343
|
+
const svelteTsconfigPath = join55(cacheDir, "tsconfig.svelte-check.json");
|
|
20928
21344
|
await writeFile17(svelteTsconfigPath, JSON.stringify({
|
|
20929
|
-
extends:
|
|
21345
|
+
extends: resolve44("tsconfig.json"),
|
|
20930
21346
|
files: ABSOLUTE_TYPECHECK_FILES,
|
|
20931
21347
|
include: [`../${svelteDir}/**/*`]
|
|
20932
21348
|
}, null, "\t"));
|
|
20933
21349
|
return run("svelte-check", [
|
|
20934
21350
|
svelteBin,
|
|
20935
21351
|
"--tsconfig",
|
|
20936
|
-
|
|
21352
|
+
resolve44(svelteTsconfigPath),
|
|
20937
21353
|
"--threshold",
|
|
20938
21354
|
"error",
|
|
20939
21355
|
"--compiler-warnings",
|
|
@@ -21127,11 +21543,11 @@ var DEFAULT_RELAY_PORT = 8787, DEFAULT_REQUEST_TIMEOUT_MS = 30000, headersToObje
|
|
|
21127
21543
|
url: url.pathname + url.search,
|
|
21128
21544
|
...bodyBytes && bodyBytes.length > 0 ? { bodyBase64: Buffer.from(bodyBytes).toString("base64") } : {}
|
|
21129
21545
|
};
|
|
21130
|
-
const responsePromise = new Promise((
|
|
21131
|
-
pending.set(id,
|
|
21546
|
+
const responsePromise = new Promise((resolve45) => {
|
|
21547
|
+
pending.set(id, resolve45);
|
|
21132
21548
|
});
|
|
21133
21549
|
client.send(encodeTunnelMessage(message));
|
|
21134
|
-
const timeout = new Promise((
|
|
21550
|
+
const timeout = new Promise((resolve45) => setTimeout(() => resolve45({ id, message: "timeout", type: "error" }), requestTimeoutMs));
|
|
21135
21551
|
const result = await Promise.race([responsePromise, timeout]);
|
|
21136
21552
|
pending.delete(id);
|
|
21137
21553
|
if (result.type === "error") {
|