@absolutejs/absolute 0.20.0-beta.36 → 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 +16 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +840 -143
- package/dist/dev/serverBootstrap.js +28 -0
- package/dist/mobile/index.js +17 -3
- package/dist/mobile/index.js.map +3 -3
- package/dist/src/dev/serverEntryCopies.d.ts +3 -0
- package/dist/src/mobile/mobileBundleInspection.d.ts +55 -0
- package/dist/src/mobile/mobileInspect.d.ts +110 -0
- 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);
|
|
@@ -18996,16 +19412,270 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
|
18996
19412
|
};
|
|
18997
19413
|
var init_releasePublisher = () => {};
|
|
18998
19414
|
|
|
19415
|
+
// src/mobile/mobileInspect.ts
|
|
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("\\", "/");
|
|
19420
|
+
return value || ".";
|
|
19421
|
+
}, pathExists8 = async (path) => {
|
|
19422
|
+
try {
|
|
19423
|
+
await access12(path);
|
|
19424
|
+
return true;
|
|
19425
|
+
} catch {
|
|
19426
|
+
return false;
|
|
19427
|
+
}
|
|
19428
|
+
}, readObject2 = async (path) => {
|
|
19429
|
+
const value = JSON.parse(await readFile21(path, "utf8"));
|
|
19430
|
+
if (!isObject3(value))
|
|
19431
|
+
throw new TypeError("JSON root must be an object.");
|
|
19432
|
+
return value;
|
|
19433
|
+
}, requireString2 = (value, field) => {
|
|
19434
|
+
if (typeof value !== "string" || value.length === 0)
|
|
19435
|
+
throw new TypeError(`${field} must be a non-empty string.`);
|
|
19436
|
+
return value;
|
|
19437
|
+
}, requireStringArray2 = (value, field) => {
|
|
19438
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
|
|
19439
|
+
throw new TypeError(`${field} must be a string array.`);
|
|
19440
|
+
return value;
|
|
19441
|
+
}, requireBundleFile2 = async (root, value, field) => {
|
|
19442
|
+
const portable = requireString2(value, field);
|
|
19443
|
+
const path = resolve42(root, portable);
|
|
19444
|
+
const normalizedRoot = resolve42(root);
|
|
19445
|
+
if (path === normalizedRoot || !path.startsWith(`${normalizedRoot}/`))
|
|
19446
|
+
throw new TypeError(`${field} must remain inside the mobile bundle.`);
|
|
19447
|
+
if (!(await stat5(path).catch(() => {
|
|
19448
|
+
return;
|
|
19449
|
+
}))?.isFile())
|
|
19450
|
+
throw new TypeError(`${field} does not exist in the mobile bundle.`);
|
|
19451
|
+
return portable;
|
|
19452
|
+
}, inspectBundle = async (config, projectRoot) => {
|
|
19453
|
+
const manifestPath = join53(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
19454
|
+
const manifest = portablePath2(projectRoot, manifestPath);
|
|
19455
|
+
if (!await pathExists8(manifestPath))
|
|
19456
|
+
return { manifest, status: "missing" };
|
|
19457
|
+
try {
|
|
19458
|
+
const value = await readObject2(manifestPath);
|
|
19459
|
+
if (value.format !== 1)
|
|
19460
|
+
throw new TypeError("format is not supported by this runtime.");
|
|
19461
|
+
if (requireString2(value.appId, "appId") !== config.appId)
|
|
19462
|
+
throw new TypeError("appId does not match the effective mobile config.");
|
|
19463
|
+
if (requireString2(value.productionOrigin, "productionOrigin") !== config.productionOrigin)
|
|
19464
|
+
throw new TypeError("productionOrigin does not match the effective mobile config.");
|
|
19465
|
+
const appBuild = requireString2(value.appBuild, "appBuild");
|
|
19466
|
+
const runtime = requireString2(value.runtime, "runtime");
|
|
19467
|
+
const capabilities = requireStringArray2(value.deviceCapabilities, "deviceCapabilities").sort();
|
|
19468
|
+
if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
|
|
19469
|
+
throw new TypeError("pages and routes must be arrays.");
|
|
19470
|
+
const pageIds = new Set;
|
|
19471
|
+
const frameworks7 = new Set;
|
|
19472
|
+
await Promise.all(value.pages.map(async (candidate) => {
|
|
19473
|
+
if (!isObject3(candidate))
|
|
19474
|
+
throw new TypeError("pages contains an invalid entry.");
|
|
19475
|
+
const pageId = requireString2(candidate.pageId, "page.pageId");
|
|
19476
|
+
if (pageIds.has(pageId))
|
|
19477
|
+
throw new TypeError("page.pageId values must be unique.");
|
|
19478
|
+
pageIds.add(pageId);
|
|
19479
|
+
const framework = requireString2(candidate.framework, "page.framework");
|
|
19480
|
+
if (!MOBILE_FRAMEWORKS2.has(framework))
|
|
19481
|
+
throw new TypeError("page.framework is unsupported.");
|
|
19482
|
+
frameworks7.add(framework);
|
|
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");
|
|
19487
|
+
if (candidate.localStylePath !== undefined)
|
|
19488
|
+
await requireBundleFile2(config.bundleDirectory, candidate.localStylePath, "page.localStylePath");
|
|
19489
|
+
}));
|
|
19490
|
+
const routes = value.routes.map((candidate) => {
|
|
19491
|
+
if (!isObject3(candidate))
|
|
19492
|
+
throw new TypeError("routes contains an invalid entry.");
|
|
19493
|
+
const { method } = candidate;
|
|
19494
|
+
if (method !== "GET" && method !== "HEAD")
|
|
19495
|
+
throw new TypeError("route.method must be GET or HEAD.");
|
|
19496
|
+
const pageId = requireString2(candidate.pageId, "route.pageId");
|
|
19497
|
+
if (!pageIds.has(pageId))
|
|
19498
|
+
throw new TypeError("route.pageId references a missing page.");
|
|
19499
|
+
return {
|
|
19500
|
+
method,
|
|
19501
|
+
pageId,
|
|
19502
|
+
pattern: requireString2(candidate.pattern, "route.pattern")
|
|
19503
|
+
};
|
|
19504
|
+
});
|
|
19505
|
+
await Promise.all(["index.html", "absolute-mobile-bootstrap.js"].map((file) => requireBundleFile2(config.bundleDirectory, file, file)));
|
|
19506
|
+
const entryPath = new URL(config.entry, "https://absolute.invalid").pathname;
|
|
19507
|
+
const entryResolved = resolveAbsoluteMobileRoute(routes, entryPath) !== undefined;
|
|
19508
|
+
if (!entryResolved)
|
|
19509
|
+
throw new TypeError("entry is not owned by an embedded route.");
|
|
19510
|
+
return {
|
|
19511
|
+
appBuild,
|
|
19512
|
+
auth: isObject3(value.auth),
|
|
19513
|
+
capabilities,
|
|
19514
|
+
entryResolved,
|
|
19515
|
+
frameworks: [...frameworks7].sort(),
|
|
19516
|
+
manifest,
|
|
19517
|
+
pageCount: value.pages.length,
|
|
19518
|
+
routeCount: value.routes.length,
|
|
19519
|
+
runtime,
|
|
19520
|
+
status: "valid",
|
|
19521
|
+
sync: isObject3(value.sync)
|
|
19522
|
+
};
|
|
19523
|
+
} catch (error) {
|
|
19524
|
+
return {
|
|
19525
|
+
issue: error instanceof Error ? error.message : "The embedded mobile manifest is invalid.",
|
|
19526
|
+
manifest,
|
|
19527
|
+
status: "invalid"
|
|
19528
|
+
};
|
|
19529
|
+
}
|
|
19530
|
+
}, addPackageDeclarations = (declarations, value) => {
|
|
19531
|
+
if (!isObject3(value))
|
|
19532
|
+
return;
|
|
19533
|
+
for (const [name, declared] of Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
19534
|
+
declarations.set(name, declared);
|
|
19535
|
+
}, packageInspections = async (projectRoot, additionalNames) => {
|
|
19536
|
+
const project = await readObject2(join53(projectRoot, "package.json"));
|
|
19537
|
+
const declarations = new Map;
|
|
19538
|
+
for (const field of ["dependencies", "devDependencies"])
|
|
19539
|
+
addPackageDeclarations(declarations, project[field]);
|
|
19540
|
+
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
19541
|
+
return Promise.all(names.map(async (name) => {
|
|
19542
|
+
const installedManifest = await readObject2(join53(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
19543
|
+
return;
|
|
19544
|
+
});
|
|
19545
|
+
const installed = installedManifest?.version;
|
|
19546
|
+
return {
|
|
19547
|
+
declared: declarations.get(name) ?? "transitive",
|
|
19548
|
+
...typeof installed === "string" ? { installed } : {},
|
|
19549
|
+
name
|
|
19550
|
+
};
|
|
19551
|
+
}));
|
|
19552
|
+
}, inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) => {
|
|
19553
|
+
const bundle = await inspectBundle(config, projectRoot);
|
|
19554
|
+
let currentCapabilities = [];
|
|
19555
|
+
let capabilityIssue;
|
|
19556
|
+
let plugins = [];
|
|
19557
|
+
try {
|
|
19558
|
+
const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
|
|
19559
|
+
currentCapabilities = plan.capabilities;
|
|
19560
|
+
plugins = plan.requiredPackages;
|
|
19561
|
+
} catch {
|
|
19562
|
+
capabilityIssue = "Native capability metadata could not be resolved.";
|
|
19563
|
+
}
|
|
19564
|
+
const pluginNames = plugins.map((spec) => spec.slice(0, spec.lastIndexOf("@")));
|
|
19565
|
+
const releaseInspection = await (options.inspectRelease ?? inspectAbsoluteMobileRelease)(config, projectRoot).catch(() => ({ checks: [], ready: false }));
|
|
19566
|
+
return {
|
|
19567
|
+
bundle,
|
|
19568
|
+
capabilities: {
|
|
19569
|
+
current: currentCapabilities,
|
|
19570
|
+
...bundle.capabilities ? {
|
|
19571
|
+
embeddedMatchesCurrent: JSON.stringify(bundle.capabilities) === JSON.stringify(currentCapabilities)
|
|
19572
|
+
} : {},
|
|
19573
|
+
...capabilityIssue ? { issue: capabilityIssue } : {},
|
|
19574
|
+
plugins
|
|
19575
|
+
},
|
|
19576
|
+
config: {
|
|
19577
|
+
appId: config.appId,
|
|
19578
|
+
appName: config.appName,
|
|
19579
|
+
bundleDirectory: portablePath2(projectRoot, config.bundleDirectory),
|
|
19580
|
+
deepLinkHosts: config.deepLinkHosts,
|
|
19581
|
+
deepLinkScheme: config.deepLinkScheme,
|
|
19582
|
+
engine: config.engine,
|
|
19583
|
+
entry: config.entry,
|
|
19584
|
+
iosVersion: config.iosVersion,
|
|
19585
|
+
nativeProjectDirectory: portablePath2(projectRoot, config.nativeProjectDirectory),
|
|
19586
|
+
platforms: config.platforms,
|
|
19587
|
+
productionOrigin: config.productionOrigin
|
|
19588
|
+
},
|
|
19589
|
+
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
19590
|
+
nativeProjects: await Promise.all(config.platforms.map(async (platform6) => {
|
|
19591
|
+
const path = join53(config.nativeProjectDirectory, platform6);
|
|
19592
|
+
return {
|
|
19593
|
+
initialized: await pathExists8(path),
|
|
19594
|
+
path: portablePath2(projectRoot, path),
|
|
19595
|
+
platform: platform6
|
|
19596
|
+
};
|
|
19597
|
+
})),
|
|
19598
|
+
packages: await packageInspections(projectRoot, pluginNames),
|
|
19599
|
+
release: {
|
|
19600
|
+
checks: releaseInspection.checks.map(({ id, status: status2 }) => ({
|
|
19601
|
+
id,
|
|
19602
|
+
status: status2
|
|
19603
|
+
})),
|
|
19604
|
+
ready: releaseInspection.ready
|
|
19605
|
+
},
|
|
19606
|
+
runtime: { absolutejs: options.absolutejsVersion ?? "unknown" }
|
|
19607
|
+
};
|
|
19608
|
+
}, yesNo = (value) => value ? "yes" : "no", renderAbsoluteMobileProjectInspection = (report) => {
|
|
19609
|
+
const lines = [
|
|
19610
|
+
`AbsoluteJS mobile inspection (format ${report.format})`,
|
|
19611
|
+
"",
|
|
19612
|
+
`App: ${report.config.appName} (${report.config.appId})`,
|
|
19613
|
+
`Engine: ${report.config.engine}`,
|
|
19614
|
+
`Platforms: ${report.config.platforms.join(", ")}`,
|
|
19615
|
+
`Entry: ${report.config.entry}`,
|
|
19616
|
+
`Production origin: ${report.config.productionOrigin}`,
|
|
19617
|
+
`AbsoluteJS: ${report.runtime.absolutejs}`,
|
|
19618
|
+
"",
|
|
19619
|
+
`Bundle: ${report.bundle.status} (${report.bundle.manifest})`
|
|
19620
|
+
];
|
|
19621
|
+
if (report.bundle.status === "valid") {
|
|
19622
|
+
lines.push(` Build/runtime: ${report.bundle.appBuild} / ${report.bundle.runtime}`, ` Pages/routes: ${report.bundle.pageCount} / ${report.bundle.routeCount}`, ` Frameworks: ${report.bundle.frameworks?.join(", ") || "none"}`, ` Auth/Sync: ${yesNo(report.bundle.auth === true)} / ${yesNo(report.bundle.sync === true)}`);
|
|
19623
|
+
} else if (report.bundle.issue)
|
|
19624
|
+
lines.push(` Issue: ${report.bundle.issue}`);
|
|
19625
|
+
lines.push("", `Capabilities: ${report.capabilities.current.join(", ") || "none"}`, `Native plugins: ${report.capabilities.plugins.join(", ") || "none"}`);
|
|
19626
|
+
if (report.capabilities.embeddedMatchesCurrent !== undefined)
|
|
19627
|
+
lines.push(`Embedded capabilities current: ${yesNo(report.capabilities.embeddedMatchesCurrent)}`);
|
|
19628
|
+
if (report.capabilities.issue)
|
|
19629
|
+
lines.push(`Capability issue: ${report.capabilities.issue}`);
|
|
19630
|
+
lines.push("", "Native projects:");
|
|
19631
|
+
for (const project of report.nativeProjects)
|
|
19632
|
+
lines.push(` ${project.platform}: ${project.initialized ? "initialized" : "missing"} (${project.path})`);
|
|
19633
|
+
lines.push("", "Runtime packages:");
|
|
19634
|
+
for (const runtimePackage of report.packages)
|
|
19635
|
+
lines.push(` ${runtimePackage.name}: ${runtimePackage.installed ?? "not installed"} (declared ${runtimePackage.declared})`);
|
|
19636
|
+
const failed = report.release.checks.filter((check2) => check2.status === "fail").length;
|
|
19637
|
+
const warned = report.release.checks.filter((check2) => check2.status === "warn").length;
|
|
19638
|
+
lines.push("", `Release projection: ${report.release.ready ? "ready" : "not ready"} (${failed} failed, ${warned} warnings)`);
|
|
19639
|
+
return `${lines.join(`
|
|
19640
|
+
`)}
|
|
19641
|
+
`;
|
|
19642
|
+
};
|
|
19643
|
+
var init_mobileInspect = __esm(() => {
|
|
19644
|
+
init_deviceCapabilities();
|
|
19645
|
+
init_releaseDoctor();
|
|
19646
|
+
init_routeMatcher();
|
|
19647
|
+
MOBILE_PACKAGE_NAMES = new Set([
|
|
19648
|
+
"@absolutejs/absolute",
|
|
19649
|
+
"@absolutejs/auth",
|
|
19650
|
+
"@absolutejs/devices",
|
|
19651
|
+
"@absolutejs/devices-capacitor",
|
|
19652
|
+
"@absolutejs/http",
|
|
19653
|
+
"@absolutejs/pwa",
|
|
19654
|
+
"@absolutejs/sync",
|
|
19655
|
+
"@absolutejs/sync-capacitor",
|
|
19656
|
+
"@capacitor-community/sqlite"
|
|
19657
|
+
]);
|
|
19658
|
+
MOBILE_FRAMEWORKS2 = new Set([
|
|
19659
|
+
"angular",
|
|
19660
|
+
"ember",
|
|
19661
|
+
"html",
|
|
19662
|
+
"htmx",
|
|
19663
|
+
"react",
|
|
19664
|
+
"svelte",
|
|
19665
|
+
"vue"
|
|
19666
|
+
]);
|
|
19667
|
+
});
|
|
19668
|
+
|
|
18999
19669
|
// src/cli/scripts/mobile.ts
|
|
19000
19670
|
var exports_mobile = {};
|
|
19001
19671
|
__export(exports_mobile, {
|
|
19002
19672
|
runMobile: () => runMobile
|
|
19003
19673
|
});
|
|
19004
|
-
import { access as
|
|
19005
|
-
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";
|
|
19006
19676
|
import { createInterface } from "readline/promises";
|
|
19007
|
-
var
|
|
19008
|
-
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"));
|
|
19009
19679
|
if (!isRecord15(manifest))
|
|
19010
19680
|
throw new TypeError("Application package.json must contain an object.");
|
|
19011
19681
|
const names = new Set;
|
|
@@ -19018,7 +19688,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19018
19688
|
return names;
|
|
19019
19689
|
}, resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
19020
19690
|
try {
|
|
19021
|
-
const manifest = JSON.parse(await
|
|
19691
|
+
const manifest = JSON.parse(await readFile22(join54(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
19022
19692
|
return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
19023
19693
|
} catch {
|
|
19024
19694
|
return;
|
|
@@ -19049,7 +19719,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19049
19719
|
await installApprovedPackages(projectRoot, args, `AbsoluteJS detected native device capabilities (${capabilityPlan.capabilities.join(", ")}). Install only their required Capacitor plugins now?`, capabilityPackages);
|
|
19050
19720
|
}, valueAfter = (args, flag) => {
|
|
19051
19721
|
const index = args.indexOf(flag);
|
|
19052
|
-
return index ===
|
|
19722
|
+
return index === NOT_FOUND4 ? undefined : args[index + 1];
|
|
19053
19723
|
}, valuesAfter = (args, flag) => args.flatMap((value, index) => {
|
|
19054
19724
|
const next = args[index + 1];
|
|
19055
19725
|
return value === flag && next !== undefined ? [next] : [];
|
|
@@ -19059,9 +19729,9 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19059
19729
|
}
|
|
19060
19730
|
return value;
|
|
19061
19731
|
}, capacitorExecutable = async (projectRoot) => {
|
|
19062
|
-
const executable =
|
|
19732
|
+
const executable = join54(projectRoot, "node_modules", ".bin", "cap");
|
|
19063
19733
|
try {
|
|
19064
|
-
await
|
|
19734
|
+
await access13(executable);
|
|
19065
19735
|
return executable;
|
|
19066
19736
|
} catch {
|
|
19067
19737
|
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
@@ -19083,6 +19753,17 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19083
19753
|
const config = await loadConfig(configPath2);
|
|
19084
19754
|
const mobile = normalizeAbsoluteMobileConfig(requireMobileConfig(config.mobile), projectRoot);
|
|
19085
19755
|
return { mobile, projectRoot };
|
|
19756
|
+
}, inspectMobile = async (args) => {
|
|
19757
|
+
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19758
|
+
const report = await inspectAbsoluteMobileProject(mobile, projectRoot, {
|
|
19759
|
+
absolutejsVersion: await absolutejsVersionForReport()
|
|
19760
|
+
});
|
|
19761
|
+
if (args.includes("--json")) {
|
|
19762
|
+
console.log(JSON.stringify(report, null, 2));
|
|
19763
|
+
return report;
|
|
19764
|
+
}
|
|
19765
|
+
console.log(renderAbsoluteMobileProjectInspection(report).trimEnd());
|
|
19766
|
+
return report;
|
|
19086
19767
|
}, remoteProfilePath = () => process.env.ABSOLUTE_REMOTE_MAC_PROFILE_PATH || undefined, pairRemoteMac = async (args) => {
|
|
19087
19768
|
if (args[0] !== "mac" || !args[1] || !args[2])
|
|
19088
19769
|
throw new TypeError("Usage: absolute mobile pair mac <name> <user@host> [--port n] [--workspace path]");
|
|
@@ -19147,7 +19828,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19147
19828
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
|
|
19148
19829
|
}, associations = async (args) => {
|
|
19149
19830
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19150
|
-
const outputDirectory =
|
|
19831
|
+
const outputDirectory = resolve43(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
19151
19832
|
if (args.includes("--verify")) {
|
|
19152
19833
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
19153
19834
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -19192,7 +19873,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19192
19873
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
19193
19874
|
const result = await inspectAbsoluteMobileRelease(mobile, projectRoot);
|
|
19194
19875
|
if (args.includes("--json")) {
|
|
19195
|
-
console.log(JSON.stringify(result, null, 2));
|
|
19876
|
+
console.log(JSON.stringify(createAbsoluteMobileComplianceReport(mobile, result), null, 2));
|
|
19196
19877
|
} else {
|
|
19197
19878
|
result.checks.forEach((check2) => {
|
|
19198
19879
|
console.log(`${doctorMark(check2.status)} ${check2.detail}${check2.path ? ` (${check2.path})` : ""}`);
|
|
@@ -19200,8 +19881,8 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
19200
19881
|
console.log(` ${check2.remediation}`);
|
|
19201
19882
|
});
|
|
19202
19883
|
console.log(result.ready ? `
|
|
19203
|
-
Mobile release
|
|
19204
|
-
Mobile release
|
|
19884
|
+
Mobile release security and compliance checks passed.` : `
|
|
19885
|
+
Mobile release security and compliance checks failed.`);
|
|
19205
19886
|
}
|
|
19206
19887
|
if (!result.ready) {
|
|
19207
19888
|
throw new TypeError("Mobile release validation failed. Resolve every failed check before signing or publishing the app.");
|
|
@@ -19380,7 +20061,7 @@ Mobile release transport checks failed.`);
|
|
|
19380
20061
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
19381
20062
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
19382
20063
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
19383
|
-
console.log(`Metadata: ${
|
|
20064
|
+
console.log(`Metadata: ${join54(release.releaseRoot, "release.json")}`);
|
|
19384
20065
|
return release;
|
|
19385
20066
|
} finally {
|
|
19386
20067
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -19483,7 +20164,7 @@ Mobile release transport checks failed.`);
|
|
|
19483
20164
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
19484
20165
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
|
|
19485
20166
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
19486
|
-
console.log(`Metadata: ${
|
|
20167
|
+
console.log(`Metadata: ${join54(release.releaseRoot, "release.json")}`);
|
|
19487
20168
|
return release;
|
|
19488
20169
|
} finally {
|
|
19489
20170
|
sendTelemetryEvent("mobile:ios-release-build", {
|
|
@@ -19590,7 +20271,7 @@ Mobile release transport checks failed.`);
|
|
|
19590
20271
|
checks.push({
|
|
19591
20272
|
id: "sync.storage-schema",
|
|
19592
20273
|
label: `Offline schema ${schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ")}`,
|
|
19593
|
-
path:
|
|
20274
|
+
path: join54(projectRoot, "package.json"),
|
|
19594
20275
|
platform: "host",
|
|
19595
20276
|
status: "pass"
|
|
19596
20277
|
});
|
|
@@ -19598,7 +20279,7 @@ Mobile release transport checks failed.`);
|
|
|
19598
20279
|
checks.push({
|
|
19599
20280
|
id: "sync.storage-schema",
|
|
19600
20281
|
label: "Offline schema metadata is invalid",
|
|
19601
|
-
path:
|
|
20282
|
+
path: join54(projectRoot, "package.json"),
|
|
19602
20283
|
platform: "host",
|
|
19603
20284
|
remediation: error instanceof Error ? error.message : String(error),
|
|
19604
20285
|
status: "fail"
|
|
@@ -19683,7 +20364,7 @@ Emulator setup verification:`);
|
|
|
19683
20364
|
}
|
|
19684
20365
|
return { https: args.includes("--https"), port };
|
|
19685
20366
|
}
|
|
19686
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
20367
|
+
const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
19687
20368
|
if (instances.length !== 1) {
|
|
19688
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>.");
|
|
19689
20370
|
}
|
|
@@ -19726,8 +20407,8 @@ Emulator setup verification:`);
|
|
|
19726
20407
|
}
|
|
19727
20408
|
return selected;
|
|
19728
20409
|
}, safeArtifactRoot = (projectRoot, value) => {
|
|
19729
|
-
const root =
|
|
19730
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
20410
|
+
const root = resolve43(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
20411
|
+
if (root !== projectRoot && !root.startsWith(`${resolve43(projectRoot)}/`)) {
|
|
19731
20412
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
19732
20413
|
}
|
|
19733
20414
|
return root;
|
|
@@ -19752,10 +20433,10 @@ Emulator setup verification:`);
|
|
|
19752
20433
|
});
|
|
19753
20434
|
}, writeAndroidFailureArtifacts = async (options) => {
|
|
19754
20435
|
await mkdir14(options.artifactRoot, { recursive: true });
|
|
19755
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
20436
|
+
const screenshot = options.session ? await options.session.screenshot(join54(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
19756
20437
|
return;
|
|
19757
20438
|
}) : undefined;
|
|
19758
|
-
const diagnosticPath =
|
|
20439
|
+
const diagnosticPath = join54(options.artifactRoot, "android-failure.json");
|
|
19759
20440
|
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
19760
20441
|
diagnostics: options.session?.diagnostics ?? [],
|
|
19761
20442
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -19819,7 +20500,7 @@ Emulator setup verification:`);
|
|
|
19819
20500
|
console.log(JSON.stringify(report, null, 2));
|
|
19820
20501
|
else
|
|
19821
20502
|
printAndroidTestReport(report);
|
|
19822
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
20503
|
+
const screenshot = reportRoot ? await session.screenshot(join54(artifactRoot, "android-emulator.png")) : undefined;
|
|
19823
20504
|
await writeRequestedAndroidReport({
|
|
19824
20505
|
adb,
|
|
19825
20506
|
args,
|
|
@@ -19887,14 +20568,14 @@ Emulator setup verification:`);
|
|
|
19887
20568
|
const port = Number(explicit);
|
|
19888
20569
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
19889
20570
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
19890
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
20571
|
+
const instance2 = listLiveInstances().find((candidate) => resolve43(candidate.cwd) === resolve43(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
19891
20572
|
return {
|
|
19892
20573
|
https: instance2?.https ?? args.includes("--https"),
|
|
19893
20574
|
instance: instance2,
|
|
19894
20575
|
port
|
|
19895
20576
|
};
|
|
19896
20577
|
}
|
|
19897
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
20578
|
+
const instances = listLiveInstances().filter((instance2) => resolve43(instance2.cwd) === resolve43(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
19898
20579
|
if (instances.length !== 1)
|
|
19899
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>.");
|
|
19900
20581
|
const [instance] = instances;
|
|
@@ -19949,7 +20630,7 @@ Emulator setup verification:`);
|
|
|
19949
20630
|
if (!instance)
|
|
19950
20631
|
return;
|
|
19951
20632
|
const index = instance.command.indexOf("--ios-device");
|
|
19952
|
-
return index ===
|
|
20633
|
+
return index === NOT_FOUND4 ? undefined : instance.command[index + 1];
|
|
19953
20634
|
}, physicalIosCapture = async (options) => {
|
|
19954
20635
|
const remoteName = valueAfter(options.args, "--remote");
|
|
19955
20636
|
if (remoteName && options.instance.iosRemoteMac && remoteName !== options.instance.iosRemoteMac)
|
|
@@ -20009,7 +20690,7 @@ Emulator setup verification:`);
|
|
|
20009
20690
|
return result;
|
|
20010
20691
|
}, writeIosFailureArtifacts = async (options) => {
|
|
20011
20692
|
await mkdir14(options.artifactRoot, { recursive: true });
|
|
20012
|
-
const screenshot =
|
|
20693
|
+
const screenshot = join54(options.artifactRoot, "ios-failure.png");
|
|
20013
20694
|
const screenshotResult = captureCommand4([
|
|
20014
20695
|
options.xcrun,
|
|
20015
20696
|
"simctl",
|
|
@@ -20018,7 +20699,7 @@ Emulator setup verification:`);
|
|
|
20018
20699
|
"screenshot",
|
|
20019
20700
|
screenshot
|
|
20020
20701
|
]);
|
|
20021
|
-
const diagnosticPath =
|
|
20702
|
+
const diagnosticPath = join54(options.artifactRoot, "ios-failure.json");
|
|
20022
20703
|
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
20023
20704
|
appId: options.appId,
|
|
20024
20705
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
@@ -20035,7 +20716,7 @@ Emulator setup verification:`);
|
|
|
20035
20716
|
};
|
|
20036
20717
|
}, nativeReportRoot = (args, projectRoot, platform6) => {
|
|
20037
20718
|
const index = args.indexOf("--report");
|
|
20038
|
-
if (index ===
|
|
20719
|
+
if (index === NOT_FOUND4)
|
|
20039
20720
|
return;
|
|
20040
20721
|
const candidate = args[index + 1];
|
|
20041
20722
|
const explicit = candidate?.startsWith("--") ? undefined : candidate;
|
|
@@ -20044,8 +20725,8 @@ Emulator setup verification:`);
|
|
|
20044
20725
|
}, absolutejsVersionForReport = async () => {
|
|
20045
20726
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
20046
20727
|
const versions = await Promise.all([
|
|
20047
|
-
|
|
20048
|
-
|
|
20728
|
+
resolve43(import.meta.dir, "..", "..", "package.json"),
|
|
20729
|
+
resolve43(import.meta.dir, "..", "..", "..", "package.json")
|
|
20049
20730
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
20050
20731
|
for (const version2 of versions) {
|
|
20051
20732
|
if (version2 === "unknown")
|
|
@@ -20264,7 +20945,7 @@ Emulator setup verification:`);
|
|
|
20264
20945
|
], "iOS app launch");
|
|
20265
20946
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
20266
20947
|
await mkdir14(artifactRoot, { recursive: true });
|
|
20267
|
-
const screenshot =
|
|
20948
|
+
const screenshot = join54(artifactRoot, "ios-simulator.png");
|
|
20268
20949
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
20269
20950
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
20270
20951
|
const report = {
|
|
@@ -20377,6 +21058,10 @@ Emulator setup verification:`);
|
|
|
20377
21058
|
await doctor(args.slice(1));
|
|
20378
21059
|
return;
|
|
20379
21060
|
}
|
|
21061
|
+
if (command === "inspect") {
|
|
21062
|
+
await inspectMobile(args.slice(1));
|
|
21063
|
+
return;
|
|
21064
|
+
}
|
|
20380
21065
|
if (command === "test" && args[1] === "android") {
|
|
20381
21066
|
await testAndroid(args.slice(2));
|
|
20382
21067
|
return;
|
|
@@ -20401,7 +21086,7 @@ Emulator setup verification:`);
|
|
|
20401
21086
|
await publishIos(args.slice(2));
|
|
20402
21087
|
return;
|
|
20403
21088
|
}
|
|
20404
|
-
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
|
|
21089
|
+
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
|
|
20405
21090
|
};
|
|
20406
21091
|
var init_mobile = __esm(() => {
|
|
20407
21092
|
init_dependencies();
|
|
@@ -20435,6 +21120,7 @@ var init_mobile = __esm(() => {
|
|
|
20435
21120
|
init_nativeAuth();
|
|
20436
21121
|
init_syncSchema();
|
|
20437
21122
|
init_deviceCapabilities();
|
|
21123
|
+
init_mobileInspect();
|
|
20438
21124
|
CAPACITOR_PACKAGES = [
|
|
20439
21125
|
"@capacitor/core",
|
|
20440
21126
|
"@capacitor/app",
|
|
@@ -20470,10 +21156,10 @@ var exports_typecheck = {};
|
|
|
20470
21156
|
__export(exports_typecheck, {
|
|
20471
21157
|
typecheck: () => typecheck
|
|
20472
21158
|
});
|
|
20473
|
-
import { resolve as
|
|
21159
|
+
import { resolve as resolve44, join as join55 } from "path";
|
|
20474
21160
|
import { existsSync as existsSync42, readFileSync as readFileSync40 } from "fs";
|
|
20475
21161
|
import { mkdir as mkdir15, writeFile as writeFile17 } from "fs/promises";
|
|
20476
|
-
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) => {
|
|
20477
21163
|
if (!existsSync42(resolveConfigPath(configPath2))) {
|
|
20478
21164
|
const defaultService = {};
|
|
20479
21165
|
return [defaultService];
|
|
@@ -20495,7 +21181,7 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
|
|
|
20495
21181
|
const exitCode = await proc.exited;
|
|
20496
21182
|
return { exitCode, name, output: (stdout + stderr).trim() };
|
|
20497
21183
|
}, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
|
|
20498
|
-
const local =
|
|
21184
|
+
const local = resolve44("node_modules", ".bin", name);
|
|
20499
21185
|
return existsSync42(local) ? local : null;
|
|
20500
21186
|
}, ANSI_COLOR_REGEX, ANSI_PURPLE_REGEX, ANSI_CYAN_REGEX, ANSI_TOKEN_END_REGEX, stripAnsi4 = (str) => str.replace(ANSI_COLOR_REGEX, ""), formatSvelteOutput = (output) => {
|
|
20501
21187
|
const cwd = `${process.cwd()}/`;
|
|
@@ -20543,15 +21229,15 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20543
21229
|
return formatted;
|
|
20544
21230
|
}, ABSOLUTE_INTERNAL_EXCLUDES, resolveAbsoluteTypeFile = (fileName) => {
|
|
20545
21231
|
const candidates = [
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
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)
|
|
20550
21236
|
];
|
|
20551
21237
|
return candidates.find((candidate) => existsSync42(candidate)) ?? candidates[0];
|
|
20552
21238
|
}, ABSOLUTE_TYPECHECK_FILES, readProjectTsconfig = () => {
|
|
20553
21239
|
try {
|
|
20554
|
-
return JSON.parse(readFileSync40(
|
|
21240
|
+
return JSON.parse(readFileSync40(resolve44("tsconfig.json"), "utf-8"));
|
|
20555
21241
|
} catch {
|
|
20556
21242
|
return {};
|
|
20557
21243
|
}
|
|
@@ -20579,27 +21265,27 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20579
21265
|
console.error("\x1B[31m\u2717\x1B[0m vue-tsc is required for Vue type checking. Install it: bun add -d vue-tsc");
|
|
20580
21266
|
process.exit(1);
|
|
20581
21267
|
}
|
|
20582
|
-
const vueTsconfigPath =
|
|
21268
|
+
const vueTsconfigPath = join55(cacheDir, "tsconfig.vue-check.json");
|
|
20583
21269
|
await writeFile17(vueTsconfigPath, JSON.stringify({
|
|
20584
21270
|
compilerOptions: {
|
|
20585
21271
|
rootDir: ".."
|
|
20586
21272
|
},
|
|
20587
21273
|
exclude: getProjectTypecheckExcludes(),
|
|
20588
|
-
extends:
|
|
21274
|
+
extends: resolve44("tsconfig.json"),
|
|
20589
21275
|
include: getProjectTypecheckIncludes()
|
|
20590
21276
|
}, null, "\t"));
|
|
20591
21277
|
const base = [
|
|
20592
21278
|
vueTscBin,
|
|
20593
21279
|
"--noEmit",
|
|
20594
21280
|
"--project",
|
|
20595
|
-
|
|
21281
|
+
resolve44(vueTsconfigPath),
|
|
20596
21282
|
"--pretty"
|
|
20597
21283
|
];
|
|
20598
21284
|
const cached = await run("vue-tsc", [
|
|
20599
21285
|
...base,
|
|
20600
21286
|
"--incremental",
|
|
20601
21287
|
"--tsBuildInfoFile",
|
|
20602
|
-
|
|
21288
|
+
join55(cacheDir, "vue-tsc.tsbuildinfo")
|
|
20603
21289
|
]);
|
|
20604
21290
|
if (cached.exitCode === 0 || cached.output.length > 0)
|
|
20605
21291
|
return cached;
|
|
@@ -20610,7 +21296,7 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20610
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");
|
|
20611
21297
|
process.exit(1);
|
|
20612
21298
|
}
|
|
20613
|
-
const angularTsconfigPath =
|
|
21299
|
+
const angularTsconfigPath = join55(cacheDir, "tsconfig.angular-check.json");
|
|
20614
21300
|
await writeFile17(angularTsconfigPath, JSON.stringify({
|
|
20615
21301
|
angularCompilerOptions: {
|
|
20616
21302
|
strictTemplates: true
|
|
@@ -20620,32 +21306,32 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20620
21306
|
rootDir: ".."
|
|
20621
21307
|
},
|
|
20622
21308
|
exclude: ABSOLUTE_INTERNAL_EXCLUDES.map(toGeneratedConfigPath),
|
|
20623
|
-
extends:
|
|
21309
|
+
extends: resolve44("tsconfig.json"),
|
|
20624
21310
|
include: [`../${angularDir}/**/*`]
|
|
20625
21311
|
}, null, "\t"));
|
|
20626
|
-
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(
|
|
21312
|
+
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve44(angularTsconfigPath))}`);
|
|
20627
21313
|
}, buildTscCheck = (cacheDir) => {
|
|
20628
21314
|
const tscBin = findBin("tsc");
|
|
20629
21315
|
if (!tscBin) {
|
|
20630
21316
|
console.error("\x1B[31m\u2717\x1B[0m typescript is required for type checking. Install it: bun add -d typescript");
|
|
20631
21317
|
process.exit(1);
|
|
20632
21318
|
}
|
|
20633
|
-
const tscConfigPath =
|
|
21319
|
+
const tscConfigPath = join55(cacheDir, "tsconfig.typecheck.json");
|
|
20634
21320
|
return writeFile17(tscConfigPath, JSON.stringify({
|
|
20635
21321
|
compilerOptions: {
|
|
20636
21322
|
rootDir: ".."
|
|
20637
21323
|
},
|
|
20638
21324
|
exclude: getProjectTypecheckExcludes(),
|
|
20639
|
-
extends:
|
|
21325
|
+
extends: resolve44("tsconfig.json"),
|
|
20640
21326
|
include: getProjectTypecheckIncludes()
|
|
20641
21327
|
}, null, "\t")).then(() => run("tsc", [
|
|
20642
21328
|
tscBin,
|
|
20643
21329
|
"--noEmit",
|
|
20644
21330
|
"--project",
|
|
20645
|
-
|
|
21331
|
+
resolve44(tscConfigPath),
|
|
20646
21332
|
"--incremental",
|
|
20647
21333
|
"--tsBuildInfoFile",
|
|
20648
|
-
|
|
21334
|
+
join55(cacheDir, "tsc.tsbuildinfo"),
|
|
20649
21335
|
"--pretty"
|
|
20650
21336
|
]));
|
|
20651
21337
|
}, buildSvelteCheck = async (cacheDir, svelteDir) => {
|
|
@@ -20654,16 +21340,16 @@ Found ${errorCount} error${suffix}.`;
|
|
|
20654
21340
|
console.error("\x1B[31m\u2717\x1B[0m svelte-check is required for Svelte type checking. Install it: bun add -d svelte-check");
|
|
20655
21341
|
process.exit(1);
|
|
20656
21342
|
}
|
|
20657
|
-
const svelteTsconfigPath =
|
|
21343
|
+
const svelteTsconfigPath = join55(cacheDir, "tsconfig.svelte-check.json");
|
|
20658
21344
|
await writeFile17(svelteTsconfigPath, JSON.stringify({
|
|
20659
|
-
extends:
|
|
21345
|
+
extends: resolve44("tsconfig.json"),
|
|
20660
21346
|
files: ABSOLUTE_TYPECHECK_FILES,
|
|
20661
21347
|
include: [`../${svelteDir}/**/*`]
|
|
20662
21348
|
}, null, "\t"));
|
|
20663
21349
|
return run("svelte-check", [
|
|
20664
21350
|
svelteBin,
|
|
20665
21351
|
"--tsconfig",
|
|
20666
|
-
|
|
21352
|
+
resolve44(svelteTsconfigPath),
|
|
20667
21353
|
"--threshold",
|
|
20668
21354
|
"error",
|
|
20669
21355
|
"--compiler-warnings",
|
|
@@ -20857,11 +21543,11 @@ var DEFAULT_RELAY_PORT = 8787, DEFAULT_REQUEST_TIMEOUT_MS = 30000, headersToObje
|
|
|
20857
21543
|
url: url.pathname + url.search,
|
|
20858
21544
|
...bodyBytes && bodyBytes.length > 0 ? { bodyBase64: Buffer.from(bodyBytes).toString("base64") } : {}
|
|
20859
21545
|
};
|
|
20860
|
-
const responsePromise = new Promise((
|
|
20861
|
-
pending.set(id,
|
|
21546
|
+
const responsePromise = new Promise((resolve45) => {
|
|
21547
|
+
pending.set(id, resolve45);
|
|
20862
21548
|
});
|
|
20863
21549
|
client.send(encodeTunnelMessage(message));
|
|
20864
|
-
const timeout = new Promise((
|
|
21550
|
+
const timeout = new Promise((resolve45) => setTimeout(() => resolve45({ id, message: "timeout", type: "error" }), requestTimeoutMs));
|
|
20865
21551
|
const result = await Promise.race([responsePromise, timeout]);
|
|
20866
21552
|
pending.delete(id);
|
|
20867
21553
|
if (result.type === "error") {
|
|
@@ -22472,6 +23158,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
22472
23158
|
const serverEntryBasename = absServerEntry.slice(absServerEntry.lastIndexOf("/") + 1);
|
|
22473
23159
|
const configBasename = configPath2 ? configPath2.slice(configPath2.lastIndexOf("/") + 1) : "absolute.config.ts";
|
|
22474
23160
|
const ATOMIC_RECOVERY_WINDOW_MS = 1000;
|
|
23161
|
+
const ATOMIC_RECOVERY_DELAY_MS = 25;
|
|
22475
23162
|
const recentlyHandled = new Map;
|
|
22476
23163
|
const handleCandidate = (filename) => {
|
|
22477
23164
|
if (filename.includes("/") || filename.includes("\\"))
|
|
@@ -22515,18 +23202,28 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
22515
23202
|
handleCandidate(entry.name);
|
|
22516
23203
|
}
|
|
22517
23204
|
};
|
|
23205
|
+
let atomicRecoveryTimer;
|
|
23206
|
+
const scheduleAtomicRecovery = () => {
|
|
23207
|
+
if (atomicRecoveryTimer)
|
|
23208
|
+
clearTimeout(atomicRecoveryTimer);
|
|
23209
|
+
atomicRecoveryTimer = setTimeout(() => {
|
|
23210
|
+
atomicRecoveryTimer = undefined;
|
|
23211
|
+
recoveryScan();
|
|
23212
|
+
}, ATOMIC_RECOVERY_DELAY_MS);
|
|
23213
|
+
};
|
|
22518
23214
|
const watcher = watch3(serverEntryDir, { recursive: false }, (event, filename) => {
|
|
22519
23215
|
if (!filename)
|
|
22520
23216
|
return;
|
|
22521
23217
|
if (isAtomicWriteTemp(filename)) {
|
|
22522
|
-
if (event === "rename")
|
|
22523
|
-
|
|
22524
|
-
}
|
|
23218
|
+
if (event === "rename")
|
|
23219
|
+
scheduleAtomicRecovery();
|
|
22525
23220
|
return;
|
|
22526
23221
|
}
|
|
22527
23222
|
handleCandidate(filename);
|
|
22528
23223
|
});
|
|
22529
23224
|
const closeWatcher = () => {
|
|
23225
|
+
if (atomicRecoveryTimer)
|
|
23226
|
+
clearTimeout(atomicRecoveryTimer);
|
|
22530
23227
|
try {
|
|
22531
23228
|
watcher.close();
|
|
22532
23229
|
} catch {}
|
|
@@ -24763,7 +25460,7 @@ if (command === "dev") {
|
|
|
24763
25460
|
console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
|
|
24764
25461
|
console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
|
|
24765
25462
|
console.error(" compile [entry] [--outdir dir] [--outfile path] Compile standalone executable");
|
|
24766
|
-
console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
|
|
25463
|
+
console.error(" mobile <init|sync|inspect|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
|
|
24767
25464
|
console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
|
|
24768
25465
|
console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
|
|
24769
25466
|
console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");
|