@absolutejs/absolute 0.20.0-beta.59 → 0.20.0-beta.60
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/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +828 -254
- package/dist/index.js +54 -4
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +656 -17
- package/dist/mobile/index.js.map +16 -9
- package/dist/mobile/remoteMacAgentEntry.js +204 -204
- package/dist/mobile/shellBootstrap.js +1 -0
- package/dist/mobile/shellUpdate.js +456 -0
- package/dist/src/mobile/capacitorBundle.d.ts +7 -0
- package/dist/src/mobile/config.d.ts +5 -0
- package/dist/src/mobile/index.d.ts +7 -0
- package/dist/src/mobile/mobileBundleInspection.d.ts +8 -0
- package/dist/src/mobile/mobileInspect.d.ts +6 -0
- package/dist/src/mobile/nativeUpdates.d.ts +4 -0
- package/dist/src/mobile/shellBootstrap.d.ts +1 -0
- package/dist/src/mobile/shellUpdate.d.ts +2 -0
- package/dist/src/mobile/transport.d.ts +6 -0
- package/dist/src/mobile/updateClient.d.ts +40 -0
- package/dist/src/mobile/updateProtocol.d.ts +51 -0
- package/dist/src/mobile/updatePublisher.d.ts +66 -0
- package/dist/src/mobile/updateRollout.d.ts +16 -0
- package/dist/src/mobile/updateRuntime.d.ts +27 -0
- package/dist/src/mobile/updateSigning.d.ts +20 -0
- package/dist/types/build.d.ts +9 -0
- package/package.json +8 -8
package/dist/cli/index.js
CHANGED
|
@@ -718,7 +718,8 @@ var init_portScan = () => {};
|
|
|
718
718
|
|
|
719
719
|
// src/mobile/config.ts
|
|
720
720
|
import { resolve as resolve2 } from "path";
|
|
721
|
-
|
|
721
|
+
import { createPublicKey } from "crypto";
|
|
722
|
+
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field) => {
|
|
722
723
|
const root = resolve2(projectRoot);
|
|
723
724
|
const path = resolve2(root, value);
|
|
724
725
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
@@ -791,7 +792,52 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
791
792
|
}
|
|
792
793
|
return value.match(/.{2}/g)?.join(":") ?? value;
|
|
793
794
|
}))
|
|
794
|
-
].sort(),
|
|
795
|
+
].sort(), normalizeUpdates = (config, productionOrigin) => {
|
|
796
|
+
if (!config.updates)
|
|
797
|
+
return;
|
|
798
|
+
const channel = requireText(config.updates.channel ?? "production", "mobile.updates.channel");
|
|
799
|
+
if (!UPDATE_NAME_PATTERN.test(channel))
|
|
800
|
+
throw new TypeError("mobile.updates.channel contains unsupported characters.");
|
|
801
|
+
const manifestUrl = new URL(config.updates.manifestUrl ?? `/__absolute/mobile/updates/${encodeURIComponent(channel)}/update.json`, `${productionOrigin}/`);
|
|
802
|
+
const loopback = manifestUrl.hostname === "localhost" || manifestUrl.hostname === "127.0.0.1" || manifestUrl.hostname === "[::1]";
|
|
803
|
+
if (manifestUrl.protocol !== "https:" && !loopback)
|
|
804
|
+
throw new TypeError("mobile.updates.manifestUrl must use HTTPS outside loopback development.");
|
|
805
|
+
if (manifestUrl.username || manifestUrl.password || manifestUrl.hash)
|
|
806
|
+
throw new TypeError("mobile.updates.manifestUrl cannot contain credentials or a fragment.");
|
|
807
|
+
const entries = Object.entries(config.updates.publicKeys).sort(([left], [right]) => left.localeCompare(right));
|
|
808
|
+
if (entries.length === 0)
|
|
809
|
+
throw new TypeError("mobile.updates.publicKeys must contain at least one key.");
|
|
810
|
+
const publicKeys = Object.fromEntries(entries.map(([keyId, key]) => {
|
|
811
|
+
if (!UPDATE_NAME_PATTERN.test(keyId))
|
|
812
|
+
throw new TypeError("mobile.updates.publicKeys contains an invalid key ID.");
|
|
813
|
+
const normalized = requireText(key, `mobile.updates.publicKeys.${keyId}`);
|
|
814
|
+
if (!UPDATE_PUBLIC_KEY_PATTERN.test(normalized))
|
|
815
|
+
throw new TypeError(`mobile.updates.publicKeys.${keyId} must be base64-encoded ECDSA P-256 SPKI DER.`);
|
|
816
|
+
let decoded;
|
|
817
|
+
try {
|
|
818
|
+
decoded = Buffer.from(normalized, "base64");
|
|
819
|
+
} catch {
|
|
820
|
+
throw new TypeError(`mobile.updates.publicKeys.${keyId} must be canonical base64.`);
|
|
821
|
+
}
|
|
822
|
+
let keyType;
|
|
823
|
+
let namedCurve;
|
|
824
|
+
try {
|
|
825
|
+
const publicKey = createPublicKey({
|
|
826
|
+
format: "der",
|
|
827
|
+
key: decoded,
|
|
828
|
+
type: "spki"
|
|
829
|
+
});
|
|
830
|
+
keyType = publicKey.asymmetricKeyType;
|
|
831
|
+
namedCurve = publicKey.asymmetricKeyDetails?.namedCurve;
|
|
832
|
+
} catch {
|
|
833
|
+
keyType = undefined;
|
|
834
|
+
}
|
|
835
|
+
if (decoded.toString("base64") !== normalized || keyType !== "ec" || namedCurve !== "prime256v1")
|
|
836
|
+
throw new TypeError(`mobile.updates.publicKeys.${keyId} is not an ECDSA P-256 SPKI public key.`);
|
|
837
|
+
return [keyId, normalized];
|
|
838
|
+
}));
|
|
839
|
+
return { channel, manifestUrl: manifestUrl.href, publicKeys };
|
|
840
|
+
}, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
|
|
795
841
|
if (segment === "*" && (index !== count - 1 || count === 1)) {
|
|
796
842
|
throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
|
|
797
843
|
}
|
|
@@ -849,6 +895,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
849
895
|
if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
|
|
850
896
|
throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
|
|
851
897
|
}
|
|
898
|
+
const updates = normalizeUpdates(config, productionOrigin);
|
|
852
899
|
return {
|
|
853
900
|
androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
|
|
854
901
|
appId,
|
|
@@ -865,7 +912,8 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
865
912
|
nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? (config.engine === "expo" ? ".absolutejs/mobile/expo" : "mobile"), "mobile.nativeProject.directory"),
|
|
866
913
|
platforms: normalizePlatforms(config.platforms),
|
|
867
914
|
productionOrigin,
|
|
868
|
-
pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
|
|
915
|
+
pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile"),
|
|
916
|
+
...updates ? { updates } : {}
|
|
869
917
|
};
|
|
870
918
|
};
|
|
871
919
|
var init_config = __esm(() => {
|
|
@@ -873,6 +921,8 @@ var init_config = __esm(() => {
|
|
|
873
921
|
SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
|
|
874
922
|
APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
|
|
875
923
|
CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
|
|
924
|
+
UPDATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
925
|
+
UPDATE_PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
876
926
|
HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
|
|
877
927
|
EXPO_RESERVED_ROUTE_PREFIXES = new Set([
|
|
878
928
|
"_expo",
|
|
@@ -9060,6 +9110,11 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
|
|
|
9060
9110
|
if (candidate)
|
|
9061
9111
|
return candidate;
|
|
9062
9112
|
throw new TypeError("AbsoluteJS Expo device bridge module is missing.");
|
|
9113
|
+
}, shellUpdateModule = () => {
|
|
9114
|
+
const candidate = ["js", "ts"].map((extension) => join21(import.meta.dir, `shellUpdate.${extension}`)).find(existsSync10);
|
|
9115
|
+
if (candidate)
|
|
9116
|
+
return candidate;
|
|
9117
|
+
throw new TypeError("AbsoluteJS mobile update shell module is missing.");
|
|
9063
9118
|
}, escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """), contentSecurityPolicy = (productionOrigin) => {
|
|
9064
9119
|
const backend = new URL(productionOrigin);
|
|
9065
9120
|
const socketOrigin = `${backend.protocol === "https:" ? "wss:" : "ws:"}//${backend.host}`;
|
|
@@ -9114,7 +9169,7 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
|
|
|
9114
9169
|
if (!resolved.startsWith(`${resolve16(packageDirectory)}/`))
|
|
9115
9170
|
throw new TypeError(`${specifier} has an unsafe import entry.`);
|
|
9116
9171
|
return resolved;
|
|
9117
|
-
}, buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, deviceCapabilities, projectRoot) => {
|
|
9172
|
+
}, buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, updates, deviceCapabilities, projectRoot) => {
|
|
9118
9173
|
const capacitor = engine !== "expo";
|
|
9119
9174
|
const shellCapabilities = capacitor ? deviceCapabilities.capabilities : [];
|
|
9120
9175
|
const modulePath = shellBootstrapModule();
|
|
@@ -9122,12 +9177,13 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
|
|
|
9122
9177
|
const syncFactory = capacitor ? "installAbsoluteMobileShellSync" : "installAbsoluteExpoShellSync";
|
|
9123
9178
|
const authImport = auth ? `import { ${authFactory} } from ${JSON.stringify(capacitor ? shellAuthModule() : shellExpoAuthModule())};
|
|
9124
9179
|
` : "";
|
|
9125
|
-
const options = auth ? `{ createAuth: ${authFactory}${sync ? `, installSync: ${syncFactory}` : ""} }` : "";
|
|
9126
9180
|
const syncImport = sync ? `import { ${syncFactory} } from ${JSON.stringify(capacitor ? shellSyncModule() : shellExpoSyncModule())};
|
|
9127
9181
|
` : "";
|
|
9128
9182
|
const pushIndex = shellCapabilities.indexOf("pushNotifications");
|
|
9129
9183
|
const push = pushIndex !== -1;
|
|
9130
9184
|
const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
|
|
9185
|
+
` : "";
|
|
9186
|
+
const updateImport = updates ? `import { installAbsoluteMobileShellUpdates } from ${JSON.stringify(shellUpdateModule())};
|
|
9131
9187
|
` : "";
|
|
9132
9188
|
const capabilityImports = (await Promise.all(shellCapabilities.map(async (name, index) => {
|
|
9133
9189
|
const provider = deviceCapabilities.providers[name];
|
|
@@ -9144,15 +9200,21 @@ const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absolu
|
|
|
9144
9200
|
const baseAdapterModule = capacitor ? await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor") : shellExpoDevicesModule();
|
|
9145
9201
|
const adapterImport = capacitor ? `import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};` : `import { createAbsoluteExpoBridgeFetch, installAbsoluteExpoWebDeviceAdapter } from ${JSON.stringify(baseAdapterModule)};`;
|
|
9146
9202
|
const adapterInstall = capacitor ? `installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });` : `installAbsoluteExpoWebDeviceAdapter(${JSON.stringify(deviceCapabilities.capabilities)});`;
|
|
9147
|
-
|
|
9148
|
-
if (capacitor)
|
|
9149
|
-
shellOptions = options;
|
|
9203
|
+
const shellOptionProperties = [];
|
|
9150
9204
|
if (push) {
|
|
9151
|
-
|
|
9152
|
-
}
|
|
9205
|
+
shellOptionProperties.push("createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options)", "beforeSignOut: absoluteMobilePush.beforeSignOut", "connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)");
|
|
9206
|
+
} else if (auth)
|
|
9207
|
+
shellOptionProperties.push(`createAuth: ${authFactory}`);
|
|
9208
|
+
else if (!capacitor)
|
|
9209
|
+
shellOptionProperties.push("createFetch: createAbsoluteExpoBridgeFetch");
|
|
9210
|
+
if (sync)
|
|
9211
|
+
shellOptionProperties.push(`installSync: ${syncFactory}`);
|
|
9212
|
+
if (updates)
|
|
9213
|
+
shellOptionProperties.push("installUpdates: installAbsoluteMobileShellUpdates");
|
|
9214
|
+
const shellOptions = `{ ${shellOptionProperties.join(", ")} }`;
|
|
9153
9215
|
await writeFile8(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
|
|
9154
9216
|
${adapterImport}
|
|
9155
|
-
${authImport}${syncImport}${pushImport}${capabilityImports}
|
|
9217
|
+
${authImport}${syncImport}${pushImport}${updateImport}${capabilityImports}
|
|
9156
9218
|
${pushSetup}${adapterInstall}
|
|
9157
9219
|
void startAbsoluteMobileShell(${shellOptions});
|
|
9158
9220
|
`);
|
|
@@ -9287,10 +9349,12 @@ void startAbsoluteMobileShell(${shellOptions});
|
|
|
9287
9349
|
deviceCapabilities: options.deviceCapabilities.capabilities,
|
|
9288
9350
|
entry: options.config.entry,
|
|
9289
9351
|
format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
9352
|
+
nativeRuntime: options.runtimeFingerprint,
|
|
9290
9353
|
pages,
|
|
9291
9354
|
productionOrigin: options.config.productionOrigin,
|
|
9292
9355
|
routes: options.artifact.routes,
|
|
9293
9356
|
runtime: options.artifact.runtime,
|
|
9357
|
+
...options.config.updates ? { updates: options.config.updates } : {},
|
|
9294
9358
|
...options.sync ? {
|
|
9295
9359
|
sync: {
|
|
9296
9360
|
background: {
|
|
@@ -9310,7 +9374,7 @@ void startAbsoluteMobileShell(${shellOptions});
|
|
|
9310
9374
|
writeFile8(join21(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
9311
9375
|
`),
|
|
9312
9376
|
writeFile8(join21(staging, INDEX_FILE), indexHtml(options.config.appName, options.config.productionOrigin)),
|
|
9313
|
-
buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.deviceCapabilities, options.projectRoot)
|
|
9377
|
+
buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.config.engine === "capacitor" && options.config.updates !== undefined, options.deviceCapabilities, options.projectRoot)
|
|
9314
9378
|
]);
|
|
9315
9379
|
await installBundle(staging, destination);
|
|
9316
9380
|
return manifest;
|
|
@@ -9484,6 +9548,136 @@ var init_materializedBundle = __esm(() => {
|
|
|
9484
9548
|
BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
|
|
9485
9549
|
});
|
|
9486
9550
|
|
|
9551
|
+
// src/mobile/updateProtocol.ts
|
|
9552
|
+
var ABSOLUTE_MOBILE_UPDATE_FORMAT = 1, ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES, ABSOLUTE_MOBILE_UPDATE_MAX_FILES = 1e4, ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES, ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM = "ecdsa-p256-sha256", HASH_PATTERN, RELEASE_PATTERN, KEY_ID_PATTERN, CHANNEL_PATTERN, isClassification = (value) => value === "bug-fix" || value === "content" || value === "security", object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), canonicalValue = (value) => {
|
|
9553
|
+
if (Array.isArray(value))
|
|
9554
|
+
return value.map(canonicalValue);
|
|
9555
|
+
if (!object3(value))
|
|
9556
|
+
return value;
|
|
9557
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
|
|
9558
|
+
}, absoluteMobileUpdateSigningPayload = (manifest) => new TextEncoder().encode(canonicalizeAbsoluteMobileUpdate(manifest)), canonicalizeAbsoluteMobileUpdate = (value) => JSON.stringify(canonicalValue(value)), requireText2 = (value, field) => {
|
|
9559
|
+
if (typeof value !== "string" || value.length === 0)
|
|
9560
|
+
throw new TypeError(`${field} must be a non-empty string.`);
|
|
9561
|
+
return value;
|
|
9562
|
+
}, normalizeAbsoluteMobileUpdatePath = (value) => {
|
|
9563
|
+
const path = requireText2(value, "Update file path").replaceAll("\\", "/");
|
|
9564
|
+
if (path.startsWith("/") || path.includes("\x00") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
9565
|
+
throw new TypeError("Update file paths must be normalized relative paths.");
|
|
9566
|
+
}
|
|
9567
|
+
return path;
|
|
9568
|
+
}, parseFile = (value) => {
|
|
9569
|
+
if (!object3(value))
|
|
9570
|
+
throw new TypeError("Update files must be objects.");
|
|
9571
|
+
const path = normalizeAbsoluteMobileUpdatePath(value.path);
|
|
9572
|
+
if (typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES) {
|
|
9573
|
+
throw new TypeError(`Update file ${path} has an invalid byte length.`);
|
|
9574
|
+
}
|
|
9575
|
+
if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256))
|
|
9576
|
+
throw new TypeError(`Update file ${path} has an invalid SHA-256 digest.`);
|
|
9577
|
+
return { bytes: value.bytes, path, sha256: value.sha256 };
|
|
9578
|
+
}, parseAbsoluteMobileUnsignedUpdateManifest = (value) => {
|
|
9579
|
+
if (!object3(value) || value.format !== ABSOLUTE_MOBILE_UPDATE_FORMAT)
|
|
9580
|
+
throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
|
|
9581
|
+
const appId = requireText2(value.appId, "Update appId");
|
|
9582
|
+
const channel = requireText2(value.channel, "Update channel");
|
|
9583
|
+
if (!CHANNEL_PATTERN.test(channel))
|
|
9584
|
+
throw new TypeError("Update channel contains unsupported characters.");
|
|
9585
|
+
if (!isClassification(value.classification))
|
|
9586
|
+
throw new TypeError("Update classification is invalid.");
|
|
9587
|
+
const createdAt = requireText2(value.createdAt, "Update createdAt");
|
|
9588
|
+
if (!Number.isFinite(Date.parse(createdAt)) || new Date(createdAt).toISOString() !== createdAt)
|
|
9589
|
+
throw new TypeError("Update createdAt must be a canonical ISO timestamp.");
|
|
9590
|
+
if (!Array.isArray(value.files) || value.files.length === 0)
|
|
9591
|
+
throw new TypeError("An update must contain at least one file.");
|
|
9592
|
+
if (value.files.length > ABSOLUTE_MOBILE_UPDATE_MAX_FILES)
|
|
9593
|
+
throw new TypeError("Update contains too many files.");
|
|
9594
|
+
const files = value.files.map(parseFile);
|
|
9595
|
+
const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path));
|
|
9596
|
+
if (files.some((file, index) => file.path !== sorted[index]?.path))
|
|
9597
|
+
throw new TypeError("Update files must be sorted by path.");
|
|
9598
|
+
if (new Set(files.map(({ path }) => path)).size !== files.length)
|
|
9599
|
+
throw new TypeError("Update file paths must be unique.");
|
|
9600
|
+
if (files.reduce((total, file) => total + file.bytes, 0) > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
|
|
9601
|
+
throw new TypeError("Update exceeds the maximum uncompressed size.");
|
|
9602
|
+
if (typeof value.releaseId !== "string" || !RELEASE_PATTERN.test(value.releaseId))
|
|
9603
|
+
throw new TypeError("Update releaseId is invalid.");
|
|
9604
|
+
if (typeof value.runtimeFingerprint !== "string" || !HASH_PATTERN.test(value.runtimeFingerprint))
|
|
9605
|
+
throw new TypeError("Update runtime fingerprint is invalid.");
|
|
9606
|
+
if (value.withinSubmittedPurpose !== true)
|
|
9607
|
+
throw new TypeError("OTA updates must attest that they remain within the submitted app purpose.");
|
|
9608
|
+
return {
|
|
9609
|
+
appId,
|
|
9610
|
+
channel,
|
|
9611
|
+
classification: value.classification,
|
|
9612
|
+
createdAt,
|
|
9613
|
+
files,
|
|
9614
|
+
format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
|
|
9615
|
+
releaseId: value.releaseId,
|
|
9616
|
+
runtimeFingerprint: value.runtimeFingerprint,
|
|
9617
|
+
withinSubmittedPurpose: true
|
|
9618
|
+
};
|
|
9619
|
+
}, parseAbsoluteMobileUpdateManifest = (value) => {
|
|
9620
|
+
if (!object3(value))
|
|
9621
|
+
throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
|
|
9622
|
+
const { signature: signatureValue, ...unsignedValue } = value;
|
|
9623
|
+
const unsigned = parseAbsoluteMobileUnsignedUpdateManifest(unsignedValue);
|
|
9624
|
+
if (!object3(signatureValue))
|
|
9625
|
+
throw new TypeError("Update signature is missing.");
|
|
9626
|
+
if (signatureValue.algorithm !== ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM)
|
|
9627
|
+
throw new TypeError("Update signature algorithm is unsupported.");
|
|
9628
|
+
const keyId = requireText2(signatureValue.keyId, "Update signature keyId");
|
|
9629
|
+
if (!KEY_ID_PATTERN.test(keyId))
|
|
9630
|
+
throw new TypeError("Update signature keyId is invalid.");
|
|
9631
|
+
const signature = requireText2(signatureValue.value, "Update signature value");
|
|
9632
|
+
let signatureBytes;
|
|
9633
|
+
try {
|
|
9634
|
+
signatureBytes = Uint8Array.from(atob(signature), (character) => character.charCodeAt(0));
|
|
9635
|
+
} catch {
|
|
9636
|
+
signatureBytes = new Uint8Array;
|
|
9637
|
+
}
|
|
9638
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(signature) || signatureBytes.byteLength !== 64 || btoa(String.fromCharCode(...signatureBytes)) !== signature)
|
|
9639
|
+
throw new TypeError("Update signature is not canonical base64.");
|
|
9640
|
+
return {
|
|
9641
|
+
...unsigned,
|
|
9642
|
+
signature: {
|
|
9643
|
+
algorithm: ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
|
|
9644
|
+
keyId,
|
|
9645
|
+
value: signature
|
|
9646
|
+
}
|
|
9647
|
+
};
|
|
9648
|
+
}, unsignedAbsoluteMobileUpdate = (manifest) => {
|
|
9649
|
+
const { signature: _signature, ...unsigned } = manifest;
|
|
9650
|
+
return unsigned;
|
|
9651
|
+
};
|
|
9652
|
+
var init_updateProtocol = __esm(() => {
|
|
9653
|
+
ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
9654
|
+
ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
9655
|
+
HASH_PATTERN = /^[a-f0-9]{64}$/u;
|
|
9656
|
+
RELEASE_PATTERN = /^amu_[a-f0-9]{64}$/u;
|
|
9657
|
+
KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
9658
|
+
CHANNEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
9659
|
+
});
|
|
9660
|
+
|
|
9661
|
+
// src/mobile/updateRuntime.ts
|
|
9662
|
+
import { createHash as createHash12 } from "crypto";
|
|
9663
|
+
var ABSOLUTE_MOBILE_SHELL_ABI = 1, ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT = 1, createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
|
|
9664
|
+
appId: options.config.appId,
|
|
9665
|
+
auth: options.auth ?? null,
|
|
9666
|
+
deepLinks: {
|
|
9667
|
+
hosts: options.config.deepLinkHosts,
|
|
9668
|
+
...options.config.deepLinkScheme ? { scheme: options.config.deepLinkScheme } : {}
|
|
9669
|
+
},
|
|
9670
|
+
deviceCapabilities: options.deviceCapabilities,
|
|
9671
|
+
engine: options.config.engine,
|
|
9672
|
+
format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
|
|
9673
|
+
shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
|
|
9674
|
+
syncSchema: options.syncSchema ?? null,
|
|
9675
|
+
updates: options.config.updates ?? null
|
|
9676
|
+
}), fingerprintAbsoluteMobileUpdateRuntime = (descriptor) => createHash12("sha256").update(canonicalizeAbsoluteMobileUpdate(descriptor)).digest("hex");
|
|
9677
|
+
var init_updateRuntime = __esm(() => {
|
|
9678
|
+
init_updateProtocol();
|
|
9679
|
+
});
|
|
9680
|
+
|
|
9487
9681
|
// src/mobile/buildPipeline.ts
|
|
9488
9682
|
import { readFile as readFile12 } from "fs/promises";
|
|
9489
9683
|
import { join as join23, resolve as resolve17 } from "path";
|
|
@@ -9556,6 +9750,12 @@ var isElysiaApp = (value) => typeof value === "object" && value !== null && type
|
|
|
9556
9750
|
const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
|
|
9557
9751
|
const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
|
|
9558
9752
|
const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot, mobile.engine);
|
|
9753
|
+
const runtimeFingerprint = fingerprintAbsoluteMobileUpdateRuntime(createAbsoluteMobileUpdateRuntimeDescriptor({
|
|
9754
|
+
...auth ? { auth } : {},
|
|
9755
|
+
config: mobile,
|
|
9756
|
+
deviceCapabilities,
|
|
9757
|
+
...syncSchema ? { syncSchema } : {}
|
|
9758
|
+
}));
|
|
9559
9759
|
const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
|
|
9560
9760
|
if (usesPush && !auth)
|
|
9561
9761
|
throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
|
|
@@ -9584,6 +9784,7 @@ var isElysiaApp = (value) => typeof value === "object" && value !== null && type
|
|
|
9584
9784
|
config: mobile,
|
|
9585
9785
|
deviceCapabilities,
|
|
9586
9786
|
projectRoot: options.projectRoot,
|
|
9787
|
+
runtimeFingerprint,
|
|
9587
9788
|
...sync ? { sync: true } : {},
|
|
9588
9789
|
...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
|
|
9589
9790
|
});
|
|
@@ -9606,6 +9807,7 @@ var init_buildPipeline = __esm(() => {
|
|
|
9606
9807
|
init_syncSchema();
|
|
9607
9808
|
init_deviceCapabilities();
|
|
9608
9809
|
init_expoProject();
|
|
9810
|
+
init_updateRuntime();
|
|
9609
9811
|
});
|
|
9610
9812
|
|
|
9611
9813
|
// src/mobile/routeMetadataTransform.ts
|
|
@@ -9635,8 +9837,8 @@ var ROUTE_METHODS, SOURCE_FILTER, PAGE_HANDLERS, posixPath = (value) => value.re
|
|
|
9635
9837
|
if (ts5.isStringLiteralLike(property.name))
|
|
9636
9838
|
return property.name.text;
|
|
9637
9839
|
return;
|
|
9638
|
-
}, objectPropertyExpression = (
|
|
9639
|
-
const property =
|
|
9840
|
+
}, objectPropertyExpression = (object4, name) => {
|
|
9841
|
+
const property = object4.properties.find((candidate) => propertyName(candidate) === name);
|
|
9640
9842
|
if (property && ts5.isPropertyAssignment(property)) {
|
|
9641
9843
|
return property.initializer;
|
|
9642
9844
|
}
|
|
@@ -9822,8 +10024,8 @@ var ROUTE_METHODS, SOURCE_FILTER, PAGE_HANDLERS, posixPath = (value) => value.re
|
|
|
9822
10024
|
if (!ts5.isCallExpression(expression))
|
|
9823
10025
|
return;
|
|
9824
10026
|
return callableObject(expression, checker);
|
|
9825
|
-
}, objectAssetKey = (
|
|
9826
|
-
for (const property of [...
|
|
10027
|
+
}, objectAssetKey = (object4, name, checker, bindings = new Map) => {
|
|
10028
|
+
for (const property of [...object4.properties].reverse()) {
|
|
9827
10029
|
if (propertyName(property) === name && ts5.isShorthandPropertyAssignment(property)) {
|
|
9828
10030
|
return assetKeyWithBindings(property.name, checker, bindings);
|
|
9829
10031
|
}
|
|
@@ -11512,9 +11714,9 @@ __export(exports_lintProof, {
|
|
|
11512
11714
|
writeLintProof: () => writeLintProof
|
|
11513
11715
|
});
|
|
11514
11716
|
import {
|
|
11515
|
-
createHash as
|
|
11717
|
+
createHash as createHash13,
|
|
11516
11718
|
createPrivateKey,
|
|
11517
|
-
createPublicKey,
|
|
11719
|
+
createPublicKey as createPublicKey2,
|
|
11518
11720
|
sign,
|
|
11519
11721
|
verify
|
|
11520
11722
|
} from "crypto";
|
|
@@ -11555,7 +11757,7 @@ var DEFAULT_PROOF_LOCATION = ".absolutejs/lint-proof.json", PROOF_CONTRACT_VERSI
|
|
|
11555
11757
|
lintFingerprint: proof.lintFingerprint,
|
|
11556
11758
|
sourceTree: proof.sourceTree
|
|
11557
11759
|
})
|
|
11558
|
-
].join("\x00")), publicKeyId = (key) =>
|
|
11760
|
+
].join("\x00")), publicKeyId = (key) => createHash13("sha256").update(key.export({ format: "der", type: "spki" })).digest("hex"), readEd25519PrivateKey = (cwd, location) => {
|
|
11559
11761
|
const path = resolve24(cwd, location);
|
|
11560
11762
|
if (isInside3(realpathSync(gitRoot(cwd)), realpathSync(path))) {
|
|
11561
11763
|
throw new Error("lint proof signing key must live outside the Git working tree");
|
|
@@ -11566,7 +11768,7 @@ var DEFAULT_PROOF_LOCATION = ".absolutejs/lint-proof.json", PROOF_CONTRACT_VERSI
|
|
|
11566
11768
|
}
|
|
11567
11769
|
return key;
|
|
11568
11770
|
}, readEd25519PublicKey = (cwd, location) => {
|
|
11569
|
-
const key =
|
|
11771
|
+
const key = createPublicKey2(readFileSync18(resolve24(cwd, location)));
|
|
11570
11772
|
if (key.asymmetricKeyType !== "ed25519") {
|
|
11571
11773
|
throw new Error("trusted lint proof key must be an Ed25519 public key");
|
|
11572
11774
|
}
|
|
@@ -11649,7 +11851,7 @@ var DEFAULT_PROOF_LOCATION = ".absolutejs/lint-proof.json", PROOF_CONTRACT_VERSI
|
|
|
11649
11851
|
} finally {
|
|
11650
11852
|
rmSync5(temporaryDirectory, { force: true, recursive: true });
|
|
11651
11853
|
}
|
|
11652
|
-
}, proofFingerprint = (cwd) =>
|
|
11854
|
+
}, proofFingerprint = (cwd) => createHash13("sha256").update(`absolute-lint-proof:${PROOF_CONTRACT_VERSION}\x00`).update(createEslintCacheFingerprint(cwd)).update("\x00").update(createEslintConfigDigest(cwd)).digest("hex"), createLintProof = (command, options = {}) => {
|
|
11653
11855
|
const cwd = options.cwd ?? process.cwd();
|
|
11654
11856
|
const proofLocation = options.proofLocation ?? DEFAULT_PROOF_LOCATION;
|
|
11655
11857
|
return {
|
|
@@ -11667,7 +11869,7 @@ var DEFAULT_PROOF_LOCATION = ".absolutejs/lint-proof.json", PROOF_CONTRACT_VERSI
|
|
|
11667
11869
|
const proof = createLintProof(command, { cwd, proofLocation });
|
|
11668
11870
|
if (options.signingKeyLocation) {
|
|
11669
11871
|
const privateKey = readEd25519PrivateKey(cwd, options.signingKeyLocation);
|
|
11670
|
-
const publicKey =
|
|
11872
|
+
const publicKey = createPublicKey2(privateKey);
|
|
11671
11873
|
proof.attestation = {
|
|
11672
11874
|
algorithm: "ed25519",
|
|
11673
11875
|
keyId: publicKeyId(publicKey),
|
|
@@ -13357,7 +13559,7 @@ var CONFIG_CANDIDATES2, RUNTIME_FIELDS, findConfigPath = (cwd, override) => {
|
|
|
13357
13559
|
return { opaque: false, value: items };
|
|
13358
13560
|
}
|
|
13359
13561
|
if (ts7.isObjectLiteralExpression(node)) {
|
|
13360
|
-
const
|
|
13562
|
+
const object4 = {};
|
|
13361
13563
|
for (const property of node.properties) {
|
|
13362
13564
|
if (!ts7.isPropertyAssignment(property) || !(ts7.isIdentifier(property.name) || ts7.isStringLiteral(property.name))) {
|
|
13363
13565
|
return { opaque: true, value: undefined };
|
|
@@ -13365,18 +13567,18 @@ var CONFIG_CANDIDATES2, RUNTIME_FIELDS, findConfigPath = (cwd, override) => {
|
|
|
13365
13567
|
const result = evalLiteral(property.initializer);
|
|
13366
13568
|
if (result.opaque)
|
|
13367
13569
|
return { opaque: true, value: undefined };
|
|
13368
|
-
|
|
13570
|
+
object4[property.name.text] = result.value;
|
|
13369
13571
|
}
|
|
13370
|
-
return { opaque: false, value:
|
|
13572
|
+
return { opaque: false, value: object4 };
|
|
13371
13573
|
}
|
|
13372
13574
|
return { opaque: true, value: undefined };
|
|
13373
13575
|
}, readCurrent = (configPath2) => {
|
|
13374
13576
|
const current = {};
|
|
13375
13577
|
const opaqueKeys = [];
|
|
13376
|
-
const { object:
|
|
13377
|
-
if (!
|
|
13578
|
+
const { object: object4 } = parseConfigObject(configPath2);
|
|
13579
|
+
if (!object4)
|
|
13378
13580
|
return { current, opaqueKeys };
|
|
13379
|
-
for (const property of
|
|
13581
|
+
for (const property of object4.properties) {
|
|
13380
13582
|
if (!ts7.isPropertyAssignment(property) || !(ts7.isIdentifier(property.name) || ts7.isStringLiteral(property.name))) {
|
|
13381
13583
|
continue;
|
|
13382
13584
|
}
|
|
@@ -14094,8 +14296,8 @@ export const navData: NavItem[] = [];
|
|
|
14094
14296
|
};
|
|
14095
14297
|
visit(sourceFile);
|
|
14096
14298
|
return found;
|
|
14097
|
-
}, readStringProperty = (
|
|
14098
|
-
const property =
|
|
14299
|
+
}, readStringProperty = (object4, name) => {
|
|
14300
|
+
const property = object4.properties.find((candidate) => ts10.isPropertyAssignment(candidate) && ts10.isIdentifier(candidate.name) && candidate.name.text === name);
|
|
14099
14301
|
if (!property || !ts10.isStringLiteralLike(property.initializer)) {
|
|
14100
14302
|
return null;
|
|
14101
14303
|
}
|
|
@@ -14558,18 +14760,18 @@ var lineStartOffset = (text2, position) => {
|
|
|
14558
14760
|
`)
|
|
14559
14761
|
index -= 1;
|
|
14560
14762
|
return index;
|
|
14561
|
-
}, indentBefore2 = (text2, position) => text2.slice(lineStartOffset(text2, position), position), findProperty = (
|
|
14763
|
+
}, indentBefore2 = (text2, position) => text2.slice(lineStartOffset(text2, position), position), findProperty = (object4, name) => object4.properties.find((property) => ts11.isPropertyAssignment(property) && (ts11.isIdentifier(property.name) || ts11.isStringLiteral(property.name)) && property.name.text === name), applyAbsoluteConfigEdit = (configPath2, request) => {
|
|
14562
14764
|
try {
|
|
14563
14765
|
const text2 = readFileSync26(configPath2, "utf-8");
|
|
14564
14766
|
const sourceFile = ts11.createSourceFile(configPath2, text2, ts11.ScriptTarget.Latest, true);
|
|
14565
|
-
const
|
|
14566
|
-
if (!
|
|
14767
|
+
const object4 = findConfigObject(sourceFile);
|
|
14768
|
+
if (!object4) {
|
|
14567
14769
|
return {
|
|
14568
14770
|
message: "Could not find defineConfig({ ... }) in the config file.",
|
|
14569
14771
|
ok: false
|
|
14570
14772
|
};
|
|
14571
14773
|
}
|
|
14572
|
-
const existing = findProperty(
|
|
14774
|
+
const existing = findProperty(object4, request.name);
|
|
14573
14775
|
if (request.remove) {
|
|
14574
14776
|
if (!existing)
|
|
14575
14777
|
return { message: `${request.name} is not set`, ok: true };
|
|
@@ -14590,7 +14792,7 @@ var lineStartOffset = (text2, position) => {
|
|
|
14590
14792
|
writeFileSync14(configPath2, text2.slice(0, start2) + valueText + text2.slice(end), "utf-8");
|
|
14591
14793
|
return { message: `Updated ${request.name}`, ok: true };
|
|
14592
14794
|
}
|
|
14593
|
-
const { properties } =
|
|
14795
|
+
const { properties } = object4;
|
|
14594
14796
|
const entry = `${request.name}: ${valueText}`;
|
|
14595
14797
|
if (properties.length > 0) {
|
|
14596
14798
|
const last = properties[properties.length - 1];
|
|
@@ -14609,11 +14811,11 @@ var lineStartOffset = (text2, position) => {
|
|
|
14609
14811
|
${indent}${entry}`;
|
|
14610
14812
|
writeFileSync14(configPath2, text2.slice(0, insertionIndex) + insertion + text2.slice(insertionIndex), "utf-8");
|
|
14611
14813
|
} else {
|
|
14612
|
-
const insertionIndex =
|
|
14613
|
-
const indent = `${indentBefore2(text2,
|
|
14814
|
+
const insertionIndex = object4.getStart(sourceFile) + 1;
|
|
14815
|
+
const indent = `${indentBefore2(text2, object4.getStart(sourceFile))} `;
|
|
14614
14816
|
const insertion = `
|
|
14615
14817
|
${indent}${entry}
|
|
14616
|
-
${indentBefore2(text2,
|
|
14818
|
+
${indentBefore2(text2, object4.getStart(sourceFile))}`;
|
|
14617
14819
|
writeFileSync14(configPath2, text2.slice(0, insertionIndex) + insertion + text2.slice(insertionIndex), "utf-8");
|
|
14618
14820
|
}
|
|
14619
14821
|
return { message: `Updated ${request.name}`, ok: true };
|
|
@@ -15309,10 +15511,10 @@ var AUTH_PACKAGE = "@absolutejs/auth", CONFIG_CANDIDATES3, findAuthSettingsPath
|
|
|
15309
15511
|
}, readCurrent2 = (configPath2) => {
|
|
15310
15512
|
const current = {};
|
|
15311
15513
|
const opaqueKeys = [];
|
|
15312
|
-
const { object:
|
|
15313
|
-
if (!
|
|
15514
|
+
const { object: object4 } = parseAuthSettingsObject(configPath2);
|
|
15515
|
+
if (!object4)
|
|
15314
15516
|
return { current, opaqueKeys };
|
|
15315
|
-
for (const property of
|
|
15517
|
+
for (const property of object4.properties) {
|
|
15316
15518
|
if (!ts12.isPropertyAssignment(property) || !(ts12.isIdentifier(property.name) || ts12.isStringLiteral(property.name))) {
|
|
15317
15519
|
continue;
|
|
15318
15520
|
}
|
|
@@ -15425,11 +15627,11 @@ var AUTH_PACKAGE2 = "@absolutejs/auth", REPO_URL = "https://github.com/absolutej
|
|
|
15425
15627
|
return null;
|
|
15426
15628
|
}
|
|
15427
15629
|
return property.initializer.properties.length;
|
|
15428
|
-
}, readConfigKeys = (
|
|
15630
|
+
}, readConfigKeys = (object4) => {
|
|
15429
15631
|
const keys = new Set;
|
|
15430
15632
|
let providerCount = null;
|
|
15431
|
-
const usesSpread =
|
|
15432
|
-
for (const property of
|
|
15633
|
+
const usesSpread = object4.properties.some((property) => ts13.isSpreadAssignment(property));
|
|
15634
|
+
for (const property of object4.properties) {
|
|
15433
15635
|
const { name } = property;
|
|
15434
15636
|
if (name === undefined || !ts13.isIdentifier(name))
|
|
15435
15637
|
continue;
|
|
@@ -19461,14 +19663,72 @@ var init_nativeBackgroundSync = __esm(() => {
|
|
|
19461
19663
|
init_nativeAuth();
|
|
19462
19664
|
});
|
|
19463
19665
|
|
|
19666
|
+
// src/mobile/nativeUpdates.ts
|
|
19667
|
+
import { readFile as readFile16, rename as rename12, writeFile as writeFile13 } from "fs/promises";
|
|
19668
|
+
import { join as join51 } from "path";
|
|
19669
|
+
var START = "// absolutejs:mobile-updates:start", END = "// absolutejs:mobile-updates:end", writeChanged2 = async (path, source) => {
|
|
19670
|
+
const current = await readFile16(path, "utf8");
|
|
19671
|
+
if (current === source)
|
|
19672
|
+
return false;
|
|
19673
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
19674
|
+
await writeFile13(temporary, source, { flag: "wx" });
|
|
19675
|
+
await rename12(temporary, path);
|
|
19676
|
+
return true;
|
|
19677
|
+
}, replaceRegion2 = (source, region) => {
|
|
19678
|
+
const start2 = source.indexOf(START);
|
|
19679
|
+
const end = source.indexOf(END);
|
|
19680
|
+
if (start2 < 0 !== end < 0 || end < start2)
|
|
19681
|
+
throw new TypeError("AbsoluteJS mobile update markers are malformed.");
|
|
19682
|
+
if (start2 >= 0) {
|
|
19683
|
+
const from = source.lastIndexOf(`
|
|
19684
|
+
`, start2) + 1;
|
|
19685
|
+
const newline = source.indexOf(`
|
|
19686
|
+
`, end + END.length);
|
|
19687
|
+
return `${source.slice(0, from)}${region}${source.slice(newline < 0 ? source.length : newline + 1)}`;
|
|
19688
|
+
}
|
|
19689
|
+
if (!region)
|
|
19690
|
+
return source;
|
|
19691
|
+
const launch = source.indexOf("didFinishLaunchingWithOptions");
|
|
19692
|
+
const brace = launch < 0 ? -1 : source.indexOf("{", launch);
|
|
19693
|
+
const insert = brace < 0 ? -1 : source.indexOf(`
|
|
19694
|
+
`, brace) + 1;
|
|
19695
|
+
if (insert <= 0)
|
|
19696
|
+
throw new TypeError("Could not find a safe iOS location for mobile update recovery.");
|
|
19697
|
+
return `${source.slice(0, insert)}${region}${source.slice(insert)}`;
|
|
19698
|
+
}, iosRecoveryRegion, applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
|
|
19699
|
+
if (config.engine !== "capacitor" || !platforms.includes("ios"))
|
|
19700
|
+
return { changed: false };
|
|
19701
|
+
const path = join51(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
19702
|
+
const source = await readFile16(path, "utf8");
|
|
19703
|
+
const updated = replaceRegion2(source, config.updates ? iosRecoveryRegion : "");
|
|
19704
|
+
return { changed: await writeChanged2(path, updated) };
|
|
19705
|
+
};
|
|
19706
|
+
var init_nativeUpdates = __esm(() => {
|
|
19707
|
+
iosRecoveryRegion = ` ${START}
|
|
19708
|
+
// A confirmed Capacitor snapshot lives in Library/NoCloud and is not
|
|
19709
|
+
// restored during device migration. Clear only a dangling pointer so
|
|
19710
|
+
// Capacitor falls back to the store-signed embedded bundle.
|
|
19711
|
+
if let persisted = UserDefaults.standard.string(forKey: "serverBasePath"), !persisted.isEmpty,
|
|
19712
|
+
let library = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first {
|
|
19713
|
+
let snapshot = library
|
|
19714
|
+
.appendingPathComponent("NoCloud/ionic_built_snapshots", isDirectory: true)
|
|
19715
|
+
.appendingPathComponent(URL(fileURLWithPath: persisted).lastPathComponent, isDirectory: true)
|
|
19716
|
+
if !FileManager.default.fileExists(atPath: snapshot.path) {
|
|
19717
|
+
UserDefaults.standard.removeObject(forKey: "serverBasePath")
|
|
19718
|
+
}
|
|
19719
|
+
}
|
|
19720
|
+
${END}
|
|
19721
|
+
`;
|
|
19722
|
+
});
|
|
19723
|
+
|
|
19464
19724
|
// src/mobile/associationFiles.ts
|
|
19465
19725
|
import {
|
|
19466
19726
|
access as access9,
|
|
19467
19727
|
mkdir as mkdir11,
|
|
19468
|
-
readFile as
|
|
19469
|
-
rename as
|
|
19728
|
+
readFile as readFile17,
|
|
19729
|
+
rename as rename13,
|
|
19470
19730
|
rm as rm9,
|
|
19471
|
-
writeFile as
|
|
19731
|
+
writeFile as writeFile14
|
|
19472
19732
|
} from "fs/promises";
|
|
19473
19733
|
import { resolve as resolve37 } from "path";
|
|
19474
19734
|
import { Elysia } from "elysia";
|
|
@@ -19523,7 +19783,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
19523
19783
|
}, writeAtomic = async (path, source) => {
|
|
19524
19784
|
let current;
|
|
19525
19785
|
try {
|
|
19526
|
-
current = await
|
|
19786
|
+
current = await readFile17(path, "utf8");
|
|
19527
19787
|
} catch (error) {
|
|
19528
19788
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
19529
19789
|
throw error;
|
|
@@ -19532,8 +19792,8 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
19532
19792
|
if (current === source)
|
|
19533
19793
|
return false;
|
|
19534
19794
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
19535
|
-
await
|
|
19536
|
-
await
|
|
19795
|
+
await writeFile14(temporary, source, { flag: "wx" });
|
|
19796
|
+
await rename13(temporary, path);
|
|
19537
19797
|
return true;
|
|
19538
19798
|
}, exists3 = async (path) => {
|
|
19539
19799
|
try {
|
|
@@ -19546,7 +19806,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
19546
19806
|
const path = resolve37(root, OWNERSHIP_FILE);
|
|
19547
19807
|
let ownership;
|
|
19548
19808
|
try {
|
|
19549
|
-
ownership = JSON.parse(await
|
|
19809
|
+
ownership = JSON.parse(await readFile17(path, "utf8"));
|
|
19550
19810
|
} catch {
|
|
19551
19811
|
throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
|
|
19552
19812
|
}
|
|
@@ -19559,12 +19819,12 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
19559
19819
|
await assertOwnedOutput(root);
|
|
19560
19820
|
const backup = `${root}.${crypto.randomUUID()}.previous`;
|
|
19561
19821
|
if (hasCurrent)
|
|
19562
|
-
await
|
|
19822
|
+
await rename13(root, backup);
|
|
19563
19823
|
try {
|
|
19564
|
-
await
|
|
19824
|
+
await rename13(temporary, root);
|
|
19565
19825
|
} catch (error) {
|
|
19566
19826
|
if (hasCurrent)
|
|
19567
|
-
await
|
|
19827
|
+
await rename13(backup, root);
|
|
19568
19828
|
throw error;
|
|
19569
19829
|
}
|
|
19570
19830
|
if (hasCurrent)
|
|
@@ -19654,7 +19914,7 @@ var init_associationFiles = __esm(() => {
|
|
|
19654
19914
|
});
|
|
19655
19915
|
|
|
19656
19916
|
// src/mobile/androidWebView.ts
|
|
19657
|
-
import { mkdir as mkdir12, writeFile as
|
|
19917
|
+
import { mkdir as mkdir12, writeFile as writeFile15 } from "fs/promises";
|
|
19658
19918
|
import { dirname as dirname29, resolve as resolve38 } from "path";
|
|
19659
19919
|
|
|
19660
19920
|
class CdpConnection {
|
|
@@ -19930,7 +20190,7 @@ var CDP_COMMAND_TIMEOUT_MS = 1e4, WEBVIEW_ATTACH_TIMEOUT_MS = 30000, WEBVIEW_POL
|
|
|
19930
20190
|
}
|
|
19931
20191
|
const absolutePath = resolve38(path);
|
|
19932
20192
|
await mkdir12(dirname29(absolutePath), { recursive: true });
|
|
19933
|
-
await
|
|
20193
|
+
await writeFile15(absolutePath, Buffer.from(data, "base64"));
|
|
19934
20194
|
return absolutePath;
|
|
19935
20195
|
},
|
|
19936
20196
|
tap: async (coordinateX, coordinateY) => {
|
|
@@ -20061,9 +20321,9 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
|
|
|
20061
20321
|
};
|
|
20062
20322
|
|
|
20063
20323
|
// src/mobile/mobileBundleInspection.ts
|
|
20064
|
-
import { createHash as
|
|
20065
|
-
import { access as access10, readFile as
|
|
20066
|
-
import { join as
|
|
20324
|
+
import { createHash as createHash14 } from "crypto";
|
|
20325
|
+
import { access as access10, readFile as readFile18, stat as stat2 } from "fs/promises";
|
|
20326
|
+
import { join as join52, relative as relative25, resolve as resolve39 } from "path";
|
|
20067
20327
|
var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath = (projectRoot, path) => {
|
|
20068
20328
|
const value = relative25(resolve39(projectRoot), resolve39(path)).replaceAll("\\", "/");
|
|
20069
20329
|
return value || ".";
|
|
@@ -20075,7 +20335,7 @@ var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "
|
|
|
20075
20335
|
return false;
|
|
20076
20336
|
}
|
|
20077
20337
|
}, readObject = async (path) => {
|
|
20078
|
-
const value = JSON.parse(await
|
|
20338
|
+
const value = JSON.parse(await readFile18(path, "utf8"));
|
|
20079
20339
|
if (!isObject2(value))
|
|
20080
20340
|
throw new TypeError("JSON root must be an object.");
|
|
20081
20341
|
return value;
|
|
@@ -20100,13 +20360,13 @@ var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "
|
|
|
20100
20360
|
if (expectedHash !== undefined) {
|
|
20101
20361
|
if (!SHA256_PATTERN.test(expectedHash))
|
|
20102
20362
|
throw new TypeError(`${field} has an invalid SHA-256 digest.`);
|
|
20103
|
-
const actual =
|
|
20363
|
+
const actual = createHash14("sha256").update(await readFile18(path)).digest("hex");
|
|
20104
20364
|
if (actual !== expectedHash)
|
|
20105
20365
|
throw new TypeError(`${field} failed its SHA-256 integrity check.`);
|
|
20106
20366
|
}
|
|
20107
20367
|
return portable;
|
|
20108
20368
|
}, inspectAbsoluteMobileBundle = async (config, projectRoot) => {
|
|
20109
|
-
const manifestPath =
|
|
20369
|
+
const manifestPath = join52(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
20110
20370
|
const manifest = portablePath(projectRoot, manifestPath);
|
|
20111
20371
|
if (!await pathExists5(manifestPath))
|
|
20112
20372
|
return { manifest, status: "missing" };
|
|
@@ -20120,9 +20380,14 @@ var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "
|
|
|
20120
20380
|
throw new TypeError("productionOrigin does not match the effective mobile config.");
|
|
20121
20381
|
const appBuild = requireString(value.appBuild, "appBuild");
|
|
20122
20382
|
const runtime = requireString(value.runtime, "runtime");
|
|
20383
|
+
const nativeRuntime = requireString(value.nativeRuntime, "nativeRuntime");
|
|
20384
|
+
if (!SHA256_PATTERN.test(nativeRuntime))
|
|
20385
|
+
throw new TypeError("nativeRuntime must be a SHA-256 fingerprint.");
|
|
20123
20386
|
if (runtime !== String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION))
|
|
20124
20387
|
throw new TypeError("runtime is not supported by this AbsoluteJS build.");
|
|
20125
20388
|
const capabilities = requireStringArray(value.deviceCapabilities, "deviceCapabilities").sort();
|
|
20389
|
+
if (JSON.stringify(value.updates ?? null) !== JSON.stringify(config.updates ?? null))
|
|
20390
|
+
throw new TypeError("Embedded update trust configuration does not match mobile config.");
|
|
20126
20391
|
if (!Array.isArray(value.pages) || !Array.isArray(value.routes))
|
|
20127
20392
|
throw new TypeError("pages and routes must be arrays.");
|
|
20128
20393
|
const pageIds = new Set;
|
|
@@ -20175,11 +20440,13 @@ var MOBILE_FRAMEWORKS, SHA256_PATTERN, isObject2 = (value) => typeof value === "
|
|
|
20175
20440
|
entryResolved,
|
|
20176
20441
|
frameworks: [...frameworks7].sort(),
|
|
20177
20442
|
manifest,
|
|
20443
|
+
nativeRuntime,
|
|
20178
20444
|
pageCount: value.pages.length,
|
|
20179
20445
|
routeCount: value.routes.length,
|
|
20180
20446
|
runtime,
|
|
20181
20447
|
status: "valid",
|
|
20182
|
-
sync: isObject2(value.sync)
|
|
20448
|
+
sync: isObject2(value.sync),
|
|
20449
|
+
updates: isObject2(value.updates)
|
|
20183
20450
|
};
|
|
20184
20451
|
} catch (error) {
|
|
20185
20452
|
return {
|
|
@@ -20206,8 +20473,8 @@ var init_mobileBundleInspection = __esm(() => {
|
|
|
20206
20473
|
});
|
|
20207
20474
|
|
|
20208
20475
|
// src/mobile/releaseDoctor.ts
|
|
20209
|
-
import { access as access11, readFile as
|
|
20210
|
-
import { dirname as dirname30, extname as extname8, join as
|
|
20476
|
+
import { access as access11, readFile as readFile19, readdir as readdir5 } from "fs/promises";
|
|
20477
|
+
import { dirname as dirname30, extname as extname8, join as join53, relative as relative26 } from "path";
|
|
20211
20478
|
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) => {
|
|
20212
20479
|
try {
|
|
20213
20480
|
await access11(path);
|
|
@@ -20220,13 +20487,13 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20220
20487
|
return findHmrAsset(path);
|
|
20221
20488
|
if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
|
|
20222
20489
|
return;
|
|
20223
|
-
const source = await
|
|
20490
|
+
const source = await readFile19(path, "utf8");
|
|
20224
20491
|
return HMR_ASSET_PATTERN.test(source) ? path : undefined;
|
|
20225
20492
|
}, findHmrAsset = async (root) => {
|
|
20226
20493
|
if (!await pathExists6(root))
|
|
20227
20494
|
return;
|
|
20228
20495
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
20229
|
-
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(
|
|
20496
|
+
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(join53(root, entry.name), entry.isDirectory(), entry.isFile())));
|
|
20230
20497
|
return matches.find((match) => match !== undefined);
|
|
20231
20498
|
}, pass = (id, detail, path) => ({ detail, id, path, status: "pass" }), fail5 = (id, detail, path, remediation) => ({
|
|
20232
20499
|
detail,
|
|
@@ -20241,7 +20508,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20241
20508
|
remediation,
|
|
20242
20509
|
status: "warn"
|
|
20243
20510
|
}), isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJsonObject = async (path) => {
|
|
20244
|
-
const value = JSON.parse(await
|
|
20511
|
+
const value = JSON.parse(await readFile19(path, "utf8"));
|
|
20245
20512
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
20246
20513
|
throw new TypeError("JSON root must be an object.");
|
|
20247
20514
|
return Object.fromEntries(Object.entries(value));
|
|
@@ -20257,7 +20524,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20257
20524
|
}
|
|
20258
20525
|
return declarations;
|
|
20259
20526
|
}, versionCore = (version2) => version2.split("-")[0]?.split(".").slice(0, 2).join("."), capacitorVersionCheck = async (config, projectRoot) => {
|
|
20260
|
-
const manifestPath =
|
|
20527
|
+
const manifestPath = join53(projectRoot, "package.json");
|
|
20261
20528
|
try {
|
|
20262
20529
|
const manifest = await readJsonObject(manifestPath);
|
|
20263
20530
|
const declarations = packageDeclarations(manifest);
|
|
@@ -20270,7 +20537,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20270
20537
|
const declared = declarations.get(name);
|
|
20271
20538
|
if (!declared || !EXACT_VERSION_PATTERN.test(declared))
|
|
20272
20539
|
throw new TypeError(`${name} must be a direct exact dependency.`);
|
|
20273
|
-
const installed = await readJsonObject(
|
|
20540
|
+
const installed = await readJsonObject(join53(projectRoot, "node_modules", name, "package.json"));
|
|
20274
20541
|
if (installed.version !== declared)
|
|
20275
20542
|
throw new TypeError(`${name} does not match its installed version.`);
|
|
20276
20543
|
return declared;
|
|
@@ -20291,7 +20558,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20291
20558
|
const actual = installed.split(".").map(Number);
|
|
20292
20559
|
return actual[0] === expected[0] && actual[1] === expected[1] && (actual[2] ?? NOT_FOUND3) >= (expected[2] ?? 0);
|
|
20293
20560
|
}, expoVersionCheck = async (config) => {
|
|
20294
|
-
const manifestPath =
|
|
20561
|
+
const manifestPath = join53(config.nativeProjectDirectory, "package.json");
|
|
20295
20562
|
try {
|
|
20296
20563
|
const manifest = await readJsonObject(manifestPath);
|
|
20297
20564
|
const declarations = packageDeclarations(manifest);
|
|
@@ -20301,13 +20568,13 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20301
20568
|
throw new TypeError(`Generated Expo project is missing ${missingRequired}.`);
|
|
20302
20569
|
const installedVersions = await Promise.all([...declarations].map(async ([name, declared]) => ({
|
|
20303
20570
|
declared,
|
|
20304
|
-
installed: await readJsonObject(
|
|
20571
|
+
installed: await readJsonObject(join53(config.nativeProjectDirectory, "node_modules", name, "package.json")),
|
|
20305
20572
|
name
|
|
20306
20573
|
})));
|
|
20307
20574
|
const mismatch = installedVersions.find(({ declared, installed }) => typeof installed.version !== "string" || !satisfiesGeneratedVersion(declared, installed.version));
|
|
20308
20575
|
if (mismatch)
|
|
20309
20576
|
throw new TypeError(`Generated Expo dependency ${mismatch.name}@${mismatch.declared} does not match its installed version.`);
|
|
20310
|
-
if (!await pathExists6(
|
|
20577
|
+
if (!await pathExists6(join53(config.nativeProjectDirectory, "bun.lock")))
|
|
20311
20578
|
throw new TypeError("Generated Expo dependency lockfile is missing.");
|
|
20312
20579
|
return pass("mobile.expo-versions", `Generated Expo SDK dependencies are pinned, installed, and locked (${declarations.get("expo")}).`, manifestPath);
|
|
20313
20580
|
} catch (error) {
|
|
@@ -20315,10 +20582,10 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20315
20582
|
}
|
|
20316
20583
|
}, dependencyLockCheck = async (projectRoot) => {
|
|
20317
20584
|
const present = (await Promise.all(LOCK_FILES.map(async (name) => ({
|
|
20318
|
-
exists: await pathExists6(
|
|
20585
|
+
exists: await pathExists6(join53(projectRoot, name)),
|
|
20319
20586
|
name
|
|
20320
20587
|
})))).find(({ exists: exists4 }) => exists4);
|
|
20321
|
-
return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`,
|
|
20588
|
+
return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, join53(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.");
|
|
20322
20589
|
}, productionOriginCheck = (config, projectRoot) => {
|
|
20323
20590
|
const origin = new URL(config.productionOrigin);
|
|
20324
20591
|
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.");
|
|
@@ -20353,7 +20620,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20353
20620
|
if (!await pathExists6(nativeConfigPath)) {
|
|
20354
20621
|
return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
|
|
20355
20622
|
}
|
|
20356
|
-
const unsafe = isUnsafeCapacitorConfig(await
|
|
20623
|
+
const unsafe = isUnsafeCapacitorConfig(await readFile19(nativeConfigPath, "utf8"));
|
|
20357
20624
|
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);
|
|
20358
20625
|
}, capacitorIdentityCheck = async (config, platform6, nativeConfigPath) => {
|
|
20359
20626
|
try {
|
|
@@ -20368,12 +20635,12 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20368
20635
|
if (!await pathExists6(manifestPath)) {
|
|
20369
20636
|
return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
|
|
20370
20637
|
}
|
|
20371
|
-
const source = await
|
|
20638
|
+
const source = await readFile19(manifestPath, "utf8");
|
|
20372
20639
|
const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
|
|
20373
20640
|
const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
20374
|
-
const networkConfigPath = networkConfigName ?
|
|
20641
|
+
const networkConfigPath = networkConfigName ? join53(dirname30(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
|
|
20375
20642
|
const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
|
|
20376
|
-
const developmentTrustContents = networkConfigPath ? await
|
|
20643
|
+
const developmentTrustContents = networkConfigPath ? await readFile19(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
|
|
20377
20644
|
const developmentTrust = developmentTrustReference || developmentTrustContents;
|
|
20378
20645
|
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);
|
|
20379
20646
|
}, hmrAssetsReleaseCheck = async (publicRoot) => {
|
|
@@ -20382,12 +20649,12 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20382
20649
|
}, embeddedBundleReleaseCheck = async (config, projectRoot, platform6, publicRoot) => {
|
|
20383
20650
|
const inspection = await inspectAbsoluteMobileBundle({ ...config, bundleDirectory: publicRoot }, projectRoot);
|
|
20384
20651
|
if (inspection.status === "valid")
|
|
20385
|
-
return pass(`${platform6}.bundle-integrity`, `Packaged mobile manifest, runtime, routes, and ${inspection.pageCount ?? 0} page asset(s) passed structural and SHA-256 validation.`,
|
|
20386
|
-
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"}`,
|
|
20652
|
+
return pass(`${platform6}.bundle-integrity`, `Packaged mobile manifest, runtime, routes, and ${inspection.pageCount ?? 0} page asset(s) passed structural and SHA-256 validation.`, join53(publicRoot, "absolute-mobile-manifest.json"));
|
|
20653
|
+
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"}`, join53(publicRoot, "absolute-mobile-manifest.json"), "Rebuild the production mobile bundle and run Capacitor sync for this platform.");
|
|
20387
20654
|
}, contentSecurityPolicyCheck = async (config, platform6, publicRoot) => {
|
|
20388
|
-
const path =
|
|
20655
|
+
const path = join53(publicRoot, "index.html");
|
|
20389
20656
|
try {
|
|
20390
|
-
const source = await
|
|
20657
|
+
const source = await readFile19(path, "utf8");
|
|
20391
20658
|
const requirements = [
|
|
20392
20659
|
"Content-Security-Policy",
|
|
20393
20660
|
"default-src 'self'",
|
|
@@ -20405,7 +20672,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20405
20672
|
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.");
|
|
20406
20673
|
}
|
|
20407
20674
|
}, expoApplicationConfigCheck = async (config) => {
|
|
20408
|
-
const path =
|
|
20675
|
+
const path = join53(config.nativeProjectDirectory, "app.json");
|
|
20409
20676
|
try {
|
|
20410
20677
|
const root = await readJsonObject(path);
|
|
20411
20678
|
if (!isRecord14(root.expo))
|
|
@@ -20425,12 +20692,12 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20425
20692
|
return fail5("expo.app-config", error instanceof Error ? error.message : "Generated Expo application config could not be validated.", path, "Run `absolute mobile build <platform>`; do not edit the generated Expo project.");
|
|
20426
20693
|
}
|
|
20427
20694
|
}, expoEmbeddedAssetsCheck = async (config) => {
|
|
20428
|
-
const generated =
|
|
20429
|
-
const assetsRoot =
|
|
20695
|
+
const generated = join53(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
|
|
20696
|
+
const assetsRoot = join53(config.nativeProjectDirectory, "assets", "absolute");
|
|
20430
20697
|
try {
|
|
20431
20698
|
const [source, manifest] = await Promise.all([
|
|
20432
|
-
|
|
20433
|
-
readJsonObject(
|
|
20699
|
+
readFile19(generated, "utf8"),
|
|
20700
|
+
readJsonObject(join53(config.bundleDirectory, "absolute-mobile-manifest.json"))
|
|
20434
20701
|
]);
|
|
20435
20702
|
if (source.includes("embedded AbsoluteJS bundle is unavailable") || typeof manifest.appBuild !== "string" || !source.includes(JSON.stringify(manifest.appBuild)) || !source.includes(JSON.stringify(config.productionOrigin))) {
|
|
20436
20703
|
throw new TypeError("Generated Expo assets do not contain the prepared production release identity.");
|
|
@@ -20450,8 +20717,8 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20450
20717
|
if (!embedded)
|
|
20451
20718
|
return false;
|
|
20452
20719
|
const [left, right] = await Promise.all([
|
|
20453
|
-
|
|
20454
|
-
|
|
20720
|
+
readFile19(join53(config.bundleDirectory, path)),
|
|
20721
|
+
readFile19(join53(assetsRoot, embedded))
|
|
20455
20722
|
]);
|
|
20456
20723
|
return left.equals(right);
|
|
20457
20724
|
}));
|
|
@@ -20465,18 +20732,18 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20465
20732
|
if (!await pathExists6(root))
|
|
20466
20733
|
return [];
|
|
20467
20734
|
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
|
|
20468
|
-
return files.filter((file) => extensions.has(extname8(file))).map((file) =>
|
|
20735
|
+
return files.filter((file) => extensions.has(extname8(file))).map((file) => join53(root, file));
|
|
20469
20736
|
}, containsPattern = async (paths, pattern) => {
|
|
20470
|
-
const sources = await Promise.all(paths.map((path) =>
|
|
20737
|
+
const sources = await Promise.all(paths.map((path) => readFile19(path, "utf8")));
|
|
20471
20738
|
const index = sources.findIndex((source) => pattern.test(source));
|
|
20472
20739
|
return index === NOT_FOUND3 ? undefined : paths[index];
|
|
20473
20740
|
}, androidNativeSecurityCheck = async (androidRoot) => {
|
|
20474
|
-
const manifestPath =
|
|
20741
|
+
const manifestPath = join53(androidRoot, "app/src/main/AndroidManifest.xml");
|
|
20475
20742
|
try {
|
|
20476
|
-
const manifest = await
|
|
20743
|
+
const manifest = await readFile19(manifestPath, "utf8");
|
|
20477
20744
|
if (/android:debuggable=["']true["']/u.test(manifest))
|
|
20478
20745
|
throw new TypeError("Android release manifest explicitly enables application debugging.");
|
|
20479
|
-
const sources = await sourceFiles(
|
|
20746
|
+
const sources = await sourceFiles(join53(androidRoot, "app/src/main"), new Set([".java", ".kt"]));
|
|
20480
20747
|
const debugSource = await containsPattern(sources, /setWebContentsDebuggingEnabled\s*\(\s*true\s*\)/u);
|
|
20481
20748
|
if (debugSource)
|
|
20482
20749
|
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.");
|
|
@@ -20485,14 +20752,14 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20485
20752
|
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.");
|
|
20486
20753
|
}
|
|
20487
20754
|
}, androidExportedComponentsCheck = async (manifestPath) => {
|
|
20488
|
-
const source = await
|
|
20755
|
+
const source = await readFile19(manifestPath, "utf8").catch(() => "");
|
|
20489
20756
|
const exported = [
|
|
20490
20757
|
...source.matchAll(/<(?:activity|activity-alias|provider|receiver|service)\b[^>]*>/giu)
|
|
20491
20758
|
].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");
|
|
20492
20759
|
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.");
|
|
20493
20760
|
}, androidDeepLinkProjectionCheck = async (config, manifestPath) => {
|
|
20494
20761
|
try {
|
|
20495
|
-
const source = await
|
|
20762
|
+
const source = await readFile19(manifestPath, "utf8");
|
|
20496
20763
|
const hasWebHost = (host2) => [...source.matchAll(/<data\b[^>]*>/giu)].some(([tag]) => tag.includes('android:scheme="https"') && tag.includes(`android:host="${host2}"`));
|
|
20497
20764
|
if (!source.includes('android:autoVerify="true"') || !source.includes("android.intent.category.BROWSABLE") || config.deepLinkHosts.some((host2) => !hasWebHost(host2)) || config.deepLinkScheme && !source.includes(`android:scheme="${config.deepLinkScheme}"`))
|
|
20498
20765
|
throw new TypeError("Android App Link or custom-scheme projection does not match mobile config.");
|
|
@@ -20512,14 +20779,14 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20512
20779
|
return fail5("ios.native-debugging", "iOS application source unconditionally enables WebView inspection.", debugSource, "Remove unconditional WebView inspection from release source.");
|
|
20513
20780
|
return pass("ios.native-debugging", "iOS source does not enable release debugger attachment or WebView inspection.", entitlementPaths[0] ?? iosRoot);
|
|
20514
20781
|
}, iosDeepLinkProjectionCheck = async (config, iosRoot) => {
|
|
20515
|
-
const infoPath =
|
|
20516
|
-
const entitlementsPath =
|
|
20517
|
-
const projectPath =
|
|
20782
|
+
const infoPath = join53(iosRoot, "App/App/Info.plist");
|
|
20783
|
+
const entitlementsPath = join53(iosRoot, "App/AbsoluteJS.entitlements");
|
|
20784
|
+
const projectPath = join53(iosRoot, "App/App.xcodeproj/project.pbxproj");
|
|
20518
20785
|
try {
|
|
20519
20786
|
const [info2, entitlements, project] = await Promise.all([
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20787
|
+
readFile19(infoPath, "utf8"),
|
|
20788
|
+
readFile19(entitlementsPath, "utf8"),
|
|
20789
|
+
readFile19(projectPath, "utf8")
|
|
20523
20790
|
]);
|
|
20524
20791
|
if (config.deepLinkScheme && (!info2.includes("<key>CFBundleURLTypes</key>") || !info2.includes(`<string>${config.deepLinkScheme}</string>`)))
|
|
20525
20792
|
throw new TypeError("iOS custom URL scheme does not match mobile config.");
|
|
@@ -20534,7 +20801,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20534
20801
|
}, syncSchemaReleaseCheck = (projectRoot) => {
|
|
20535
20802
|
if (!projectUsesAbsoluteSync(projectRoot))
|
|
20536
20803
|
return;
|
|
20537
|
-
const manifestPath =
|
|
20804
|
+
const manifestPath = join53(projectRoot, "package.json");
|
|
20538
20805
|
try {
|
|
20539
20806
|
const schema = discoverAbsoluteSyncSchema(projectRoot);
|
|
20540
20807
|
const versions = schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ");
|
|
@@ -20556,8 +20823,8 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20556
20823
|
}, IOS_USAGE_KEYS, androidDevicePermissionCheck = async (config, permissions) => {
|
|
20557
20824
|
if (!config.platforms.includes("android") || permissions.length === 0)
|
|
20558
20825
|
return;
|
|
20559
|
-
const path =
|
|
20560
|
-
const source = await
|
|
20826
|
+
const path = join53(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
20827
|
+
const source = await readFile19(path, "utf8");
|
|
20561
20828
|
const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
|
|
20562
20829
|
if (missing.length === 0)
|
|
20563
20830
|
return;
|
|
@@ -20565,9 +20832,9 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20565
20832
|
}, iosDevicePermissionCheck = async (config, purposes) => {
|
|
20566
20833
|
if (!config.platforms.includes("ios") || purposes.length === 0)
|
|
20567
20834
|
return;
|
|
20568
|
-
const iosRoot =
|
|
20569
|
-
const path = config.engine === "expo" ? await uniqueExpoIosFile(iosRoot, "**/Info.plist", "Info.plist") :
|
|
20570
|
-
const source = await
|
|
20835
|
+
const iosRoot = join53(config.nativeProjectDirectory, "ios");
|
|
20836
|
+
const path = config.engine === "expo" ? await uniqueExpoIosFile(iosRoot, "**/Info.plist", "Info.plist") : join53(iosRoot, "App/App/Info.plist");
|
|
20837
|
+
const source = await readFile19(path, "utf8");
|
|
20571
20838
|
const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
|
|
20572
20839
|
if (missing.length === 0)
|
|
20573
20840
|
return;
|
|
@@ -20576,7 +20843,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20576
20843
|
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
20577
20844
|
return;
|
|
20578
20845
|
const privacyPath = await uniqueExpoIosFile(iosRoot, "**/PrivacyInfo.xcprivacy", "privacy manifest");
|
|
20579
|
-
const privacy = await
|
|
20846
|
+
const privacy = await readFile19(privacyPath, "utf8");
|
|
20580
20847
|
const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
|
|
20581
20848
|
if (!missing && project.includes(privacyPath.split(/[\\/]/u).at(-1) ?? ""))
|
|
20582
20849
|
return;
|
|
@@ -20585,14 +20852,14 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20585
20852
|
if (!requirements.iosPushNotifications)
|
|
20586
20853
|
return;
|
|
20587
20854
|
const entitlementsPath = await uniqueExpoIosFile(iosRoot, "**/*.entitlements", "entitlements");
|
|
20588
|
-
const entitlements = await
|
|
20855
|
+
const entitlements = await readFile19(entitlementsPath, "utf8");
|
|
20589
20856
|
if (entitlements.includes("<key>aps-environment</key>"))
|
|
20590
20857
|
return;
|
|
20591
20858
|
return fail5("mobile.device-capabilities", "Expo iOS push entitlement does not match detected capabilities.", entitlementsPath, "Run `absolute mobile build ios` to regenerate native push integration.");
|
|
20592
20859
|
}, expoIosCapabilityProjectionCheck = async (config, requirements) => {
|
|
20593
|
-
const iosRoot =
|
|
20860
|
+
const iosRoot = join53(config.nativeProjectDirectory, "ios");
|
|
20594
20861
|
const projectPath = await uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project");
|
|
20595
|
-
const project = await
|
|
20862
|
+
const project = await readFile19(projectPath, "utf8");
|
|
20596
20863
|
const privacy = await expoIosPrivacyCapabilityCheck(iosRoot, project, requirements);
|
|
20597
20864
|
if (privacy)
|
|
20598
20865
|
return privacy;
|
|
@@ -20602,41 +20869,41 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20602
20869
|
return;
|
|
20603
20870
|
if (config.engine === "expo")
|
|
20604
20871
|
return expoIosCapabilityProjectionCheck(config, requirements);
|
|
20605
|
-
const appRoot =
|
|
20606
|
-
const infoPath =
|
|
20607
|
-
const info2 = await
|
|
20872
|
+
const appRoot = join53(config.nativeProjectDirectory, "ios/App/App");
|
|
20873
|
+
const infoPath = join53(appRoot, "Info.plist");
|
|
20874
|
+
const info2 = await readFile19(infoPath, "utf8").catch(() => "");
|
|
20608
20875
|
if (requirements.iosSystemBars && !/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<true\s*\/>/u.test(info2))
|
|
20609
20876
|
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.");
|
|
20610
20877
|
if (requirements.iosPrivacyAccessedApis.length > 0) {
|
|
20611
|
-
const privacyPath =
|
|
20612
|
-
const projectPath =
|
|
20878
|
+
const privacyPath = join53(appRoot, "PrivacyInfo.xcprivacy");
|
|
20879
|
+
const projectPath = join53(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
20613
20880
|
const [privacy, project] = await Promise.all([
|
|
20614
|
-
|
|
20615
|
-
|
|
20881
|
+
readFile19(privacyPath, "utf8").catch(() => ""),
|
|
20882
|
+
readFile19(projectPath, "utf8").catch(() => "")
|
|
20616
20883
|
]);
|
|
20617
20884
|
const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
|
|
20618
20885
|
if (missing || !project.includes("PrivacyInfo.xcprivacy in Resources"))
|
|
20619
20886
|
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.");
|
|
20620
20887
|
}
|
|
20621
20888
|
if (requirements.iosPushNotifications) {
|
|
20622
|
-
const entitlementsPath =
|
|
20623
|
-
const delegatePath =
|
|
20889
|
+
const entitlementsPath = join53(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
20890
|
+
const delegatePath = join53(appRoot, "AppDelegate.swift");
|
|
20624
20891
|
const [entitlements, delegate] = await Promise.all([
|
|
20625
|
-
|
|
20626
|
-
|
|
20892
|
+
readFile19(entitlementsPath, "utf8").catch(() => ""),
|
|
20893
|
+
readFile19(delegatePath, "utf8").catch(() => "")
|
|
20627
20894
|
]);
|
|
20628
20895
|
if (!entitlements.includes("<key>aps-environment</key>") || !delegate.includes("capacitorDidRegisterForRemoteNotifications") || !delegate.includes("capacitorDidFailToRegisterForRemoteNotifications"))
|
|
20629
20896
|
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.");
|
|
20630
20897
|
}
|
|
20631
20898
|
return;
|
|
20632
20899
|
}, deviceCapabilityReleaseCheck = async (config, projectRoot) => {
|
|
20633
|
-
const manifestPath =
|
|
20900
|
+
const manifestPath = join53(projectRoot, "package.json");
|
|
20634
20901
|
try {
|
|
20635
20902
|
const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
|
|
20636
20903
|
const assertPackages = async () => {
|
|
20637
20904
|
if (config.engine === "capacitor")
|
|
20638
20905
|
return assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
|
|
20639
|
-
const generated = await readJsonObject(
|
|
20906
|
+
const generated = await readJsonObject(join53(config.nativeProjectDirectory, "package.json"));
|
|
20640
20907
|
const declarations = packageDeclarations(generated);
|
|
20641
20908
|
const missing = plan.requiredPackages.filter((spec) => {
|
|
20642
20909
|
const separator = spec.lastIndexOf("@");
|
|
@@ -20662,11 +20929,11 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20662
20929
|
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.");
|
|
20663
20930
|
}
|
|
20664
20931
|
}, inspectAndroidRelease = async (config, projectRoot) => {
|
|
20665
|
-
const androidRoot =
|
|
20666
|
-
const nativeConfigPath =
|
|
20667
|
-
const manifestPath =
|
|
20668
|
-
const publicRoot =
|
|
20669
|
-
const journalPath =
|
|
20932
|
+
const androidRoot = join53(config.nativeProjectDirectory, "android");
|
|
20933
|
+
const nativeConfigPath = join53(androidRoot, "app", "src", "main", "assets", "capacitor.config.json");
|
|
20934
|
+
const manifestPath = join53(androidRoot, "app", "src", "main", "AndroidManifest.xml");
|
|
20935
|
+
const publicRoot = join53(androidRoot, "app", "src", "main", "assets", "public");
|
|
20936
|
+
const journalPath = join53(projectRoot, ".absolutejs", "mobile", "dev-session", "journal.json");
|
|
20670
20937
|
const checks = await Promise.all([
|
|
20671
20938
|
journalReleaseCheck(journalPath, "android"),
|
|
20672
20939
|
capacitorConfigReleaseCheck(nativeConfigPath),
|
|
@@ -20684,9 +20951,9 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20684
20951
|
path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
20685
20952
|
}));
|
|
20686
20953
|
}, inspectExpoAndroidRelease = async (config, projectRoot) => {
|
|
20687
|
-
const androidRoot =
|
|
20688
|
-
const manifestPath =
|
|
20689
|
-
const journalPath =
|
|
20954
|
+
const androidRoot = join53(config.nativeProjectDirectory, "android");
|
|
20955
|
+
const manifestPath = join53(androidRoot, "app", "src", "main", "AndroidManifest.xml");
|
|
20956
|
+
const journalPath = join53(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
|
|
20690
20957
|
const checks = await Promise.all([
|
|
20691
20958
|
journalReleaseCheck(journalPath, "android"),
|
|
20692
20959
|
expoApplicationConfigCheck(config),
|
|
@@ -20710,7 +20977,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20710
20977
|
const [path] = paths;
|
|
20711
20978
|
if (!path)
|
|
20712
20979
|
throw new TypeError(`Generated Expo iOS project is missing its ${label}.`);
|
|
20713
|
-
return
|
|
20980
|
+
return join53(iosRoot, path);
|
|
20714
20981
|
}, expoIosNativeProjectionCheck = async (config, iosRoot) => {
|
|
20715
20982
|
try {
|
|
20716
20983
|
const [infoPath, entitlementsPath, projectPath] = await Promise.all([
|
|
@@ -20719,9 +20986,9 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20719
20986
|
uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project")
|
|
20720
20987
|
]);
|
|
20721
20988
|
const [info2, entitlements, project] = await Promise.all([
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20989
|
+
readFile19(infoPath, "utf8"),
|
|
20990
|
+
readFile19(entitlementsPath, "utf8"),
|
|
20991
|
+
readFile19(projectPath, "utf8")
|
|
20725
20992
|
]);
|
|
20726
20993
|
if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info2))
|
|
20727
20994
|
throw new TypeError("Expo iOS App Transport Security permits arbitrary network loads.");
|
|
@@ -20736,8 +21003,8 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20736
21003
|
return fail5("ios.expo-native-projection", error instanceof Error ? error.message : "Expo iOS native projection could not be validated.", iosRoot, "Run `absolute mobile build ios` to regenerate the production Expo CNG project.");
|
|
20737
21004
|
}
|
|
20738
21005
|
}, inspectExpoIosRelease = async (config, projectRoot) => {
|
|
20739
|
-
const iosRoot =
|
|
20740
|
-
const journalPath =
|
|
21006
|
+
const iosRoot = join53(config.nativeProjectDirectory, "ios");
|
|
21007
|
+
const journalPath = join53(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
|
|
20741
21008
|
const checks = [
|
|
20742
21009
|
await journalReleaseCheck(journalPath, "ios")
|
|
20743
21010
|
];
|
|
@@ -20756,11 +21023,11 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20756
21023
|
path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
20757
21024
|
}));
|
|
20758
21025
|
}, inspectIosRelease = async (config, projectRoot) => {
|
|
20759
|
-
const iosAppRoot =
|
|
20760
|
-
const nativeConfigPath =
|
|
20761
|
-
const infoPath =
|
|
20762
|
-
const publicRoot =
|
|
20763
|
-
const journalPath =
|
|
21026
|
+
const iosAppRoot = join53(config.nativeProjectDirectory, "ios", "App", "App");
|
|
21027
|
+
const nativeConfigPath = join53(iosAppRoot, "capacitor.config.json");
|
|
21028
|
+
const infoPath = join53(iosAppRoot, "Info.plist");
|
|
21029
|
+
const publicRoot = join53(iosAppRoot, "public");
|
|
21030
|
+
const journalPath = join53(projectRoot, ".absolutejs", "mobile", "ios-dev-session", "journal.json");
|
|
20764
21031
|
const checks = [
|
|
20765
21032
|
await journalReleaseCheck(journalPath, "ios")
|
|
20766
21033
|
];
|
|
@@ -20771,7 +21038,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20771
21038
|
}
|
|
20772
21039
|
if (!await pathExists6(nativeConfigPath)) {
|
|
20773
21040
|
checks.push(fail5("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
|
|
20774
|
-
} else if (isUnsafeCapacitorConfig(await
|
|
21041
|
+
} else if (isUnsafeCapacitorConfig(await readFile19(nativeConfigPath, "utf8"))) {
|
|
20775
21042
|
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."));
|
|
20776
21043
|
} else {
|
|
20777
21044
|
checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
|
|
@@ -20780,12 +21047,12 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
|
|
|
20780
21047
|
if (!await pathExists6(infoPath)) {
|
|
20781
21048
|
checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
|
|
20782
21049
|
} else {
|
|
20783
|
-
const info2 = await
|
|
21050
|
+
const info2 = await readFile19(infoPath, "utf8");
|
|
20784
21051
|
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));
|
|
20785
21052
|
}
|
|
20786
21053
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
20787
21054
|
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));
|
|
20788
|
-
checks.push(await embeddedBundleReleaseCheck(config, projectRoot, "ios", publicRoot), await contentSecurityPolicyCheck(config, "ios", publicRoot), await iosNativeSecurityCheck(
|
|
21055
|
+
checks.push(await embeddedBundleReleaseCheck(config, projectRoot, "ios", publicRoot), await contentSecurityPolicyCheck(config, "ios", publicRoot), await iosNativeSecurityCheck(join53(config.nativeProjectDirectory, "ios")), await iosDeepLinkProjectionCheck(config, join53(config.nativeProjectDirectory, "ios")));
|
|
20789
21056
|
return checks.map((check2) => ({
|
|
20790
21057
|
...check2,
|
|
20791
21058
|
path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
|
|
@@ -20873,20 +21140,20 @@ var init_releaseDoctor = __esm(() => {
|
|
|
20873
21140
|
});
|
|
20874
21141
|
|
|
20875
21142
|
// src/mobile/androidRelease.ts
|
|
20876
|
-
import { createHash as
|
|
21143
|
+
import { createHash as createHash15 } from "crypto";
|
|
20877
21144
|
import {
|
|
20878
21145
|
access as access12,
|
|
20879
21146
|
copyFile as copyFile5,
|
|
20880
21147
|
mkdir as mkdir13,
|
|
20881
21148
|
mkdtemp as mkdtemp7,
|
|
20882
|
-
readFile as
|
|
21149
|
+
readFile as readFile20,
|
|
20883
21150
|
realpath as realpath2,
|
|
20884
|
-
rename as
|
|
21151
|
+
rename as rename14,
|
|
20885
21152
|
rm as rm10,
|
|
20886
21153
|
stat as stat3,
|
|
20887
|
-
writeFile as
|
|
21154
|
+
writeFile as writeFile16
|
|
20888
21155
|
} from "fs/promises";
|
|
20889
|
-
import { dirname as dirname31, isAbsolute as isAbsolute7, join as
|
|
21156
|
+
import { dirname as dirname31, isAbsolute as isAbsolute7, join as join54, relative as relative27, resolve as resolve40, sep as sep7 } from "path";
|
|
20890
21157
|
var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
|
|
20891
21158
|
if (!isRecord15(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
|
|
20892
21159
|
throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
|
|
@@ -20950,17 +21217,17 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value ==
|
|
|
20950
21217
|
]);
|
|
20951
21218
|
if (result.exitCode !== 0)
|
|
20952
21219
|
throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
|
|
20953
|
-
}, sha256File2 = async (path) =>
|
|
21220
|
+
}, sha256File2 = async (path) => createHash15("sha256").update(await readFile20(path)).digest("hex"), fingerprintExpoAndroidProject = async (nativeDirectory) => {
|
|
20954
21221
|
const root = await realpath2(nativeDirectory);
|
|
20955
21222
|
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
|
|
20956
21223
|
const records = await Promise.all(files.filter((path) => {
|
|
20957
21224
|
const parts = path.replaceAll("\\", "/").split("/");
|
|
20958
21225
|
return !parts.includes(".gradle") && !parts.includes("build");
|
|
20959
21226
|
}).sort().map(async (path) => {
|
|
20960
|
-
const contents = await
|
|
20961
|
-
return `${path.replaceAll("\\", "/")}\x00${
|
|
21227
|
+
const contents = await readFile20(join54(root, path));
|
|
21228
|
+
return `${path.replaceAll("\\", "/")}\x00${createHash15("sha256").update(contents).digest("hex")}\x00`;
|
|
20962
21229
|
}));
|
|
20963
|
-
return
|
|
21230
|
+
return createHash15("sha256").update(records.join("")).digest("hex");
|
|
20964
21231
|
}, safeOutputDirectory2 = (projectRoot, requested) => {
|
|
20965
21232
|
const root = resolve40(projectRoot);
|
|
20966
21233
|
const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
|
|
@@ -20970,11 +21237,11 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value ==
|
|
|
20970
21237
|
}
|
|
20971
21238
|
return output;
|
|
20972
21239
|
}, installRelease2 = async (artifactPath, metadata, outputRoot) => {
|
|
20973
|
-
const releaseRoot =
|
|
21240
|
+
const releaseRoot = join54(outputRoot, metadata.releaseId);
|
|
20974
21241
|
const artifactName = "app-release.aab";
|
|
20975
|
-
const destination =
|
|
21242
|
+
const destination = join54(releaseRoot, artifactName);
|
|
20976
21243
|
if (await pathExists7(releaseRoot)) {
|
|
20977
|
-
const existing = requireManifestIdentity(JSON.parse(await
|
|
21244
|
+
const existing = requireManifestIdentity(JSON.parse(await readFile20(join54(releaseRoot, "release.json"), "utf8")), metadata);
|
|
20978
21245
|
const [installedBytes, installedSha256] = await Promise.all([
|
|
20979
21246
|
stat3(destination).then(({ size }) => size),
|
|
20980
21247
|
sha256File2(destination)
|
|
@@ -20985,16 +21252,16 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value ==
|
|
|
20985
21252
|
return { artifactPath: destination, metadata: existing, releaseRoot };
|
|
20986
21253
|
}
|
|
20987
21254
|
await mkdir13(dirname31(releaseRoot), { recursive: true });
|
|
20988
|
-
const staging = await mkdtemp7(
|
|
21255
|
+
const staging = await mkdtemp7(join54(dirname31(releaseRoot), ".android-stage-"));
|
|
20989
21256
|
try {
|
|
20990
|
-
await copyFile5(artifactPath,
|
|
21257
|
+
await copyFile5(artifactPath, join54(staging, artifactName));
|
|
20991
21258
|
const complete = {
|
|
20992
21259
|
...metadata,
|
|
20993
21260
|
artifact: artifactName
|
|
20994
21261
|
};
|
|
20995
|
-
await
|
|
21262
|
+
await writeFile16(join54(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
|
|
20996
21263
|
`, { flag: "wx" });
|
|
20997
|
-
await
|
|
21264
|
+
await rename14(staging, releaseRoot);
|
|
20998
21265
|
return { artifactPath: destination, metadata: complete, releaseRoot };
|
|
20999
21266
|
} finally {
|
|
21000
21267
|
await rm10(staging, { force: true, recursive: true }).catch(() => {
|
|
@@ -21023,8 +21290,8 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value ==
|
|
|
21023
21290
|
throw new TypeError("Expo Android production builds from WSL are not available yet. Run the generated CI workflow on Linux or build from native Windows while the WSL projection is completed.");
|
|
21024
21291
|
}
|
|
21025
21292
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
|
|
21026
|
-
const nativeDirectory =
|
|
21027
|
-
const manifest = requireManifest2(JSON.parse(await
|
|
21293
|
+
const nativeDirectory = join54(options.config.nativeProjectDirectory, "android");
|
|
21294
|
+
const manifest = requireManifest2(JSON.parse(await readFile20(join54(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
|
|
21028
21295
|
if (manifest.appId !== options.config.appId) {
|
|
21029
21296
|
throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
|
|
21030
21297
|
}
|
|
@@ -21033,7 +21300,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value ==
|
|
|
21033
21300
|
const nativeFingerprint = options.config.engine === "expo" ? await fingerprintExpoAndroidProject(nativeDirectory) : await fingerprintAbsoluteAndroidNativeProject({
|
|
21034
21301
|
nativeDirectory
|
|
21035
21302
|
});
|
|
21036
|
-
const buildIdentity =
|
|
21303
|
+
const buildIdentity = createHash15("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
|
|
21037
21304
|
versionCode = await options.prepareVersionCode(buildIdentity);
|
|
21038
21305
|
}
|
|
21039
21306
|
if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
|
|
@@ -21138,7 +21405,7 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
|
21138
21405
|
};
|
|
21139
21406
|
|
|
21140
21407
|
// src/mobile/iosConformance.ts
|
|
21141
|
-
import { readFile as
|
|
21408
|
+
import { readFile as readFile21, stat as stat4 } from "fs/promises";
|
|
21142
21409
|
var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
21143
21410
|
const match = HMR_LINE.exec(line);
|
|
21144
21411
|
if (!match)
|
|
@@ -21173,7 +21440,7 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
|
21173
21440
|
if (Date.now() > deadline)
|
|
21174
21441
|
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
21175
21442
|
options.signal?.throwIfAborted();
|
|
21176
|
-
const contents = await
|
|
21443
|
+
const contents = await readFile21(options.logPath).catch(() => Buffer.alloc(0));
|
|
21177
21444
|
if (contents.byteLength < offset) {
|
|
21178
21445
|
offset = 0;
|
|
21179
21446
|
buffered = "";
|
|
@@ -21197,8 +21464,8 @@ var init_iosConformance = __esm(() => {
|
|
|
21197
21464
|
});
|
|
21198
21465
|
|
|
21199
21466
|
// src/mobile/nativeTestReport.ts
|
|
21200
|
-
import { mkdir as mkdir14, readFile as
|
|
21201
|
-
import { join as
|
|
21467
|
+
import { mkdir as mkdir14, readFile as readFile22, writeFile as writeFile17 } from "fs/promises";
|
|
21468
|
+
import { join as join55 } from "path";
|
|
21202
21469
|
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(`
|
|
21203
21470
|
`, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
|
|
21204
21471
|
const target = `${run.targetKind} ${run.targetId}`;
|
|
@@ -21300,7 +21567,7 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
|
|
|
21300
21567
|
reportVersion: 1,
|
|
21301
21568
|
run: options.run
|
|
21302
21569
|
}), readPackageVersionForNativeReport = async (packageJsonPath) => {
|
|
21303
|
-
const manifest = JSON.parse(await
|
|
21570
|
+
const manifest = JSON.parse(await readFile22(packageJsonPath, "utf8"));
|
|
21304
21571
|
if (typeof manifest !== "object" || manifest === null)
|
|
21305
21572
|
return "unknown";
|
|
21306
21573
|
const version2 = Reflect.get(manifest, "version");
|
|
@@ -21337,12 +21604,12 @@ ${table(report.manualChecks)}
|
|
|
21337
21604
|
`;
|
|
21338
21605
|
}, writeAbsoluteNativeTestReport = async (directory, report) => {
|
|
21339
21606
|
await mkdir14(directory, { recursive: true });
|
|
21340
|
-
const jsonPath =
|
|
21341
|
-
const markdownPath =
|
|
21607
|
+
const jsonPath = join55(directory, "report.json");
|
|
21608
|
+
const markdownPath = join55(directory, "report.md");
|
|
21342
21609
|
await Promise.all([
|
|
21343
|
-
|
|
21610
|
+
writeFile17(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
21344
21611
|
`),
|
|
21345
|
-
|
|
21612
|
+
writeFile17(markdownPath, renderAbsoluteNativeTestReport(report))
|
|
21346
21613
|
]);
|
|
21347
21614
|
return { directory, jsonPath, markdownPath };
|
|
21348
21615
|
};
|
|
@@ -21697,8 +21964,8 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
|
21697
21964
|
var init_releasePublisher = () => {};
|
|
21698
21965
|
|
|
21699
21966
|
// src/mobile/mobileInspect.ts
|
|
21700
|
-
import { access as access14, readFile as
|
|
21701
|
-
import { join as
|
|
21967
|
+
import { access as access14, readFile as readFile23 } from "fs/promises";
|
|
21968
|
+
import { join as join56, relative as relative29, resolve as resolve42 } from "path";
|
|
21702
21969
|
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), portablePath2 = (projectRoot, path) => {
|
|
21703
21970
|
const value = relative29(resolve42(projectRoot), resolve42(path)).replaceAll("\\", "/");
|
|
21704
21971
|
return value || ".";
|
|
@@ -21710,7 +21977,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
|
|
|
21710
21977
|
return false;
|
|
21711
21978
|
}
|
|
21712
21979
|
}, readObject2 = async (path) => {
|
|
21713
|
-
const value = JSON.parse(await
|
|
21980
|
+
const value = JSON.parse(await readFile23(path, "utf8"));
|
|
21714
21981
|
if (!isObject3(value))
|
|
21715
21982
|
throw new TypeError("JSON root must be an object.");
|
|
21716
21983
|
return value;
|
|
@@ -21720,13 +21987,13 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
|
|
|
21720
21987
|
for (const [name, declared] of Object.entries(value).filter((entry) => typeof entry[1] === "string"))
|
|
21721
21988
|
declarations.set(name, declared);
|
|
21722
21989
|
}, packageInspections = async (projectRoot, additionalNames) => {
|
|
21723
|
-
const project = await readObject2(
|
|
21990
|
+
const project = await readObject2(join56(projectRoot, "package.json"));
|
|
21724
21991
|
const declarations = new Map;
|
|
21725
21992
|
for (const field of ["dependencies", "devDependencies"])
|
|
21726
21993
|
addPackageDeclarations(declarations, project[field]);
|
|
21727
21994
|
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
21728
21995
|
return Promise.all(names.map(async (name) => {
|
|
21729
|
-
const installedManifest = await readObject2(
|
|
21996
|
+
const installedManifest = await readObject2(join56(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
21730
21997
|
return;
|
|
21731
21998
|
});
|
|
21732
21999
|
const installed = installedManifest?.version;
|
|
@@ -21775,7 +22042,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
|
|
|
21775
22042
|
},
|
|
21776
22043
|
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
21777
22044
|
nativeProjects: await Promise.all(config.platforms.map(async (platform6) => {
|
|
21778
|
-
const path =
|
|
22045
|
+
const path = join56(config.nativeProjectDirectory, platform6);
|
|
21779
22046
|
return {
|
|
21780
22047
|
initialized: await pathExists8(path),
|
|
21781
22048
|
path: portablePath2(projectRoot, path),
|
|
@@ -21846,7 +22113,7 @@ var init_mobileInspect = __esm(() => {
|
|
|
21846
22113
|
|
|
21847
22114
|
// src/mobile/ciWorkflow.ts
|
|
21848
22115
|
import { existsSync as existsSync43 } from "fs";
|
|
21849
|
-
import { access as access15, mkdir as mkdir15, readFile as
|
|
22116
|
+
import { access as access15, mkdir as mkdir15, readFile as readFile24, writeFile as writeFile18 } from "fs/promises";
|
|
21850
22117
|
import { dirname as dirname32, extname as extname9, relative as relative30, resolve as resolve43, sep as sep9 } from "path";
|
|
21851
22118
|
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTATION = 6, RESERVED_SECRET_NAMES, exists4 = async (path) => {
|
|
21852
22119
|
try {
|
|
@@ -22260,12 +22527,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
22260
22527
|
}, writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
22261
22528
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
22262
22529
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
22263
|
-
const previous = await exists4(path) ? await
|
|
22530
|
+
const previous = await exists4(path) ? await readFile24(path, "utf8") : undefined;
|
|
22264
22531
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
22265
22532
|
throw new TypeError(`${relative30(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
22266
22533
|
if (previous !== generated.workflow) {
|
|
22267
22534
|
await mkdir15(dirname32(path), { recursive: true });
|
|
22268
|
-
await
|
|
22535
|
+
await writeFile18(path, generated.workflow);
|
|
22269
22536
|
}
|
|
22270
22537
|
return {
|
|
22271
22538
|
changed: previous !== generated.workflow,
|
|
@@ -22322,16 +22589,171 @@ var init_ciWorkflow = __esm(() => {
|
|
|
22322
22589
|
run: exit 1`;
|
|
22323
22590
|
});
|
|
22324
22591
|
|
|
22592
|
+
// src/mobile/updateSigning.ts
|
|
22593
|
+
import { createHash as createHash16, sign as sign2, verify as verify2 } from "crypto";
|
|
22594
|
+
import {
|
|
22595
|
+
cp as cp4,
|
|
22596
|
+
mkdir as mkdir16,
|
|
22597
|
+
mkdtemp as mkdtemp8,
|
|
22598
|
+
readdir as readdir6,
|
|
22599
|
+
readFile as readFile25,
|
|
22600
|
+
rename as rename15,
|
|
22601
|
+
rm as rm11,
|
|
22602
|
+
stat as stat5,
|
|
22603
|
+
writeFile as writeFile19
|
|
22604
|
+
} from "fs/promises";
|
|
22605
|
+
import { dirname as dirname33, join as join57, relative as relative31, resolve as resolve44 } from "path";
|
|
22606
|
+
var UPDATE_MANIFEST_FILE = "update.json", UPDATE_FILES_DIRECTORY = "files", sha2562 = (value) => createHash16("sha256").update(value).digest("hex"), listFiles = async (root, directory = root) => {
|
|
22607
|
+
const entries = await readdir6(directory, { withFileTypes: true });
|
|
22608
|
+
const paths = await Promise.all(entries.map(async (entry) => {
|
|
22609
|
+
const path = join57(directory, entry.name);
|
|
22610
|
+
if (entry.isDirectory())
|
|
22611
|
+
return listFiles(root, path);
|
|
22612
|
+
if (!entry.isFile())
|
|
22613
|
+
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
22614
|
+
return [relative31(root, path).replaceAll("\\", "/")];
|
|
22615
|
+
}));
|
|
22616
|
+
return paths.flat().sort();
|
|
22617
|
+
}, inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
22618
|
+
const source = join57(root, path);
|
|
22619
|
+
const [metadata, contents] = await Promise.all([
|
|
22620
|
+
stat5(source),
|
|
22621
|
+
readFile25(source)
|
|
22622
|
+
]);
|
|
22623
|
+
return { bytes: metadata.size, path, sha256: sha2562(contents) };
|
|
22624
|
+
})), releaseIdFor = (value) => `amu_${sha2562(canonicalizeAbsoluteMobileUpdate(value))}`, buildAbsoluteMobileUpdate = async (options) => {
|
|
22625
|
+
const bundleDirectory = resolve44(options.bundleDirectory);
|
|
22626
|
+
const outputRoot = resolve44(options.outputDirectory);
|
|
22627
|
+
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
22628
|
+
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
22629
|
+
const paths = await listFiles(bundleDirectory);
|
|
22630
|
+
const files = await inspectFiles(bundleDirectory, paths);
|
|
22631
|
+
const withoutId = {
|
|
22632
|
+
appId: options.appId,
|
|
22633
|
+
channel: options.channel,
|
|
22634
|
+
classification: options.classification,
|
|
22635
|
+
createdAt: (options.createdAt ?? new Date).toISOString(),
|
|
22636
|
+
files,
|
|
22637
|
+
format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
|
|
22638
|
+
runtimeFingerprint: options.runtimeFingerprint,
|
|
22639
|
+
withinSubmittedPurpose: true
|
|
22640
|
+
};
|
|
22641
|
+
const unsigned = {
|
|
22642
|
+
...withoutId,
|
|
22643
|
+
releaseId: releaseIdFor(withoutId)
|
|
22644
|
+
};
|
|
22645
|
+
const signature = sign2("sha256", absoluteMobileUpdateSigningPayload(unsigned), { dsaEncoding: "ieee-p1363", key: options.privateKey });
|
|
22646
|
+
const manifest = parseAbsoluteMobileUpdateManifest({
|
|
22647
|
+
...unsigned,
|
|
22648
|
+
signature: {
|
|
22649
|
+
algorithm: "ecdsa-p256-sha256",
|
|
22650
|
+
keyId: options.keyId,
|
|
22651
|
+
value: signature.toString("base64")
|
|
22652
|
+
}
|
|
22653
|
+
});
|
|
22654
|
+
await mkdir16(outputRoot, { recursive: true });
|
|
22655
|
+
const outputDirectory = join57(outputRoot, manifest.releaseId);
|
|
22656
|
+
const staging = await mkdtemp8(join57(outputRoot, ".stage-"));
|
|
22657
|
+
try {
|
|
22658
|
+
await cp4(bundleDirectory, join57(staging, UPDATE_FILES_DIRECTORY), {
|
|
22659
|
+
force: true,
|
|
22660
|
+
recursive: true
|
|
22661
|
+
});
|
|
22662
|
+
await writeFile19(join57(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
22663
|
+
`);
|
|
22664
|
+
await rename15(staging, outputDirectory);
|
|
22665
|
+
} catch (error) {
|
|
22666
|
+
await rm11(staging, { force: true, recursive: true });
|
|
22667
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
22668
|
+
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
22669
|
+
throw error;
|
|
22670
|
+
}
|
|
22671
|
+
return {
|
|
22672
|
+
manifest,
|
|
22673
|
+
manifestPath: join57(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
22674
|
+
outputDirectory
|
|
22675
|
+
};
|
|
22676
|
+
}, readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile25(join57(resolve44(directory), UPDATE_MANIFEST_FILE), "utf8"))), verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
22677
|
+
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
22678
|
+
const valid = verify2("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
22679
|
+
if (!valid)
|
|
22680
|
+
throw new TypeError("Mobile update signature verification failed.");
|
|
22681
|
+
return manifest;
|
|
22682
|
+
};
|
|
22683
|
+
var init_updateSigning = __esm(() => {
|
|
22684
|
+
init_updateProtocol();
|
|
22685
|
+
});
|
|
22686
|
+
|
|
22687
|
+
// src/mobile/updatePublisher.ts
|
|
22688
|
+
import { access as access16 } from "fs/promises";
|
|
22689
|
+
import { isAbsolute as isAbsolute9, relative as relative32, resolve as resolve45, sep as sep10 } from "path";
|
|
22690
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
22691
|
+
var object4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher2 = (value) => object4(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function", projectPath2 = (projectRoot, requested, label) => {
|
|
22692
|
+
const root = resolve45(projectRoot);
|
|
22693
|
+
const path = resolve45(root, requested);
|
|
22694
|
+
const projectRelative = relative32(root, path);
|
|
22695
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep10}`) || isAbsolute9(projectRelative))
|
|
22696
|
+
throw new TypeError(`${label} must remain inside the project.`);
|
|
22697
|
+
return path;
|
|
22698
|
+
}, loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
22699
|
+
const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
|
|
22700
|
+
await access16(modulePath).catch(() => {
|
|
22701
|
+
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
22702
|
+
});
|
|
22703
|
+
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
22704
|
+
const publisher = object4(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
22705
|
+
if (!isPublisher2(publisher))
|
|
22706
|
+
throw new TypeError("Mobile update registry must implement publishUpdate, promoteUpdate, and rollbackUpdate.");
|
|
22707
|
+
return publisher;
|
|
22708
|
+
}, promoteAbsoluteMobileUpdate = async (options) => {
|
|
22709
|
+
const result = await options.publisher.promoteUpdate({
|
|
22710
|
+
appId: options.appId,
|
|
22711
|
+
channel: options.channel,
|
|
22712
|
+
releaseId: options.releaseId,
|
|
22713
|
+
rollout: options.rollout,
|
|
22714
|
+
signal: options.signal
|
|
22715
|
+
});
|
|
22716
|
+
if (result.appId !== options.appId || result.channel !== options.channel || result.releaseId !== options.releaseId || result.rollout !== options.rollout || result.stage !== "promoted")
|
|
22717
|
+
throw new TypeError("Mobile update registry returned a different promotion identity.");
|
|
22718
|
+
return result;
|
|
22719
|
+
}, publishAbsoluteMobileUpdate = async (options) => {
|
|
22720
|
+
const releaseDirectory = projectPath2(options.projectRoot, options.releaseDirectory, "mobile update release directory");
|
|
22721
|
+
const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
|
|
22722
|
+
const result = await options.publisher.publishUpdate({
|
|
22723
|
+
manifest,
|
|
22724
|
+
releaseDirectory,
|
|
22725
|
+
rollout: options.rollout,
|
|
22726
|
+
signal: options.signal
|
|
22727
|
+
});
|
|
22728
|
+
if (result.appId !== manifest.appId || result.channel !== manifest.channel || result.releaseId !== manifest.releaseId || result.rollout !== options.rollout || result.stage !== "published" || typeof result.reused !== "boolean")
|
|
22729
|
+
throw new TypeError("Mobile update registry returned a different publication identity.");
|
|
22730
|
+
return result;
|
|
22731
|
+
}, rollbackAbsoluteMobileUpdate = async (options) => {
|
|
22732
|
+
const result = await options.publisher.rollbackUpdate({
|
|
22733
|
+
appId: options.appId,
|
|
22734
|
+
channel: options.channel,
|
|
22735
|
+
...options.releaseId ? { releaseId: options.releaseId } : {},
|
|
22736
|
+
signal: options.signal
|
|
22737
|
+
});
|
|
22738
|
+
if (result.appId !== options.appId || result.channel !== options.channel || result.releaseId !== options.releaseId || result.stage !== "rolled-back")
|
|
22739
|
+
throw new TypeError("Mobile update registry returned a different rollback identity.");
|
|
22740
|
+
return result;
|
|
22741
|
+
};
|
|
22742
|
+
var init_updatePublisher = __esm(() => {
|
|
22743
|
+
init_updateSigning();
|
|
22744
|
+
});
|
|
22745
|
+
|
|
22325
22746
|
// src/cli/scripts/mobile.ts
|
|
22326
22747
|
var exports_mobile = {};
|
|
22327
22748
|
__export(exports_mobile, {
|
|
22328
22749
|
runMobile: () => runMobile
|
|
22329
22750
|
});
|
|
22330
|
-
import { access as
|
|
22331
|
-
import {
|
|
22751
|
+
import { access as access17, mkdir as mkdir17, readFile as readFile26, writeFile as writeFile20 } from "fs/promises";
|
|
22752
|
+
import { createPublicKey as createPublicKey3 } from "crypto";
|
|
22753
|
+
import { join as join58, relative as relative33, resolve as resolve46 } from "path";
|
|
22332
22754
|
import { createInterface } from "readline/promises";
|
|
22333
22755
|
var NOT_FOUND4 = -1, isRecord17 = (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) => {
|
|
22334
|
-
const manifest = JSON.parse(await
|
|
22756
|
+
const manifest = JSON.parse(await readFile26(join58(projectRoot, "package.json"), "utf8"));
|
|
22335
22757
|
if (!isRecord17(manifest))
|
|
22336
22758
|
throw new TypeError("Application package.json must contain an object.");
|
|
22337
22759
|
const names = new Set;
|
|
@@ -22344,7 +22766,7 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22344
22766
|
return names;
|
|
22345
22767
|
}, resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
22346
22768
|
try {
|
|
22347
|
-
const manifest = JSON.parse(await
|
|
22769
|
+
const manifest = JSON.parse(await readFile26(join58(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
22348
22770
|
return isRecord17(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
22349
22771
|
} catch {
|
|
22350
22772
|
return;
|
|
@@ -22361,9 +22783,10 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22361
22783
|
const name = packageNameFromSpec(spec);
|
|
22362
22784
|
const needsInstall = !installed.has(name) || exactPackages.has(name) && await resolvedPackageVersion(projectRoot, name) !== exactVersionFromSpec(spec);
|
|
22363
22785
|
return needsInstall ? spec : undefined;
|
|
22364
|
-
}))).filter((spec) => spec !== undefined), ensureCapacitorPackages = async (projectRoot, args) => {
|
|
22786
|
+
}))).filter((spec) => spec !== undefined), ensureCapacitorPackages = async (projectRoot, args, mobile) => {
|
|
22365
22787
|
const specs = [
|
|
22366
22788
|
...CAPACITOR_PACKAGE_SPECS,
|
|
22789
|
+
...mobile?.updates ? ["@capacitor/filesystem@8.1.3"] : [],
|
|
22367
22790
|
...projectUsesAbsoluteSync(projectRoot) ? CAPACITOR_SYNC_PACKAGE_SPECS : []
|
|
22368
22791
|
];
|
|
22369
22792
|
const installed = await directProjectPackages(projectRoot);
|
|
@@ -22385,9 +22808,9 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22385
22808
|
}
|
|
22386
22809
|
return value;
|
|
22387
22810
|
}, capacitorExecutable = async (projectRoot) => {
|
|
22388
|
-
const executable =
|
|
22811
|
+
const executable = join58(projectRoot, "node_modules", ".bin", "cap");
|
|
22389
22812
|
try {
|
|
22390
|
-
await
|
|
22813
|
+
await access17(executable);
|
|
22391
22814
|
return executable;
|
|
22392
22815
|
} catch {
|
|
22393
22816
|
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
@@ -22405,9 +22828,9 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22405
22828
|
throw new TypeError(`Capacitor exited with status ${exitCode}.`);
|
|
22406
22829
|
}
|
|
22407
22830
|
}, runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform6) => pending.then(() => runCapacitor(projectRoot, [command, platform6])), Promise.resolve()), expoExecutable = async (project) => {
|
|
22408
|
-
const executable =
|
|
22831
|
+
const executable = join58(project, "node_modules", ".bin", "expo");
|
|
22409
22832
|
try {
|
|
22410
|
-
await
|
|
22833
|
+
await access17(executable);
|
|
22411
22834
|
return executable;
|
|
22412
22835
|
} catch {
|
|
22413
22836
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -22436,14 +22859,14 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22436
22859
|
throw new TypeError(`Expo exited with status ${exitCode}.`);
|
|
22437
22860
|
}, ensureExpoPackages = async (project, args) => {
|
|
22438
22861
|
try {
|
|
22439
|
-
const manifest = JSON.parse(await
|
|
22862
|
+
const manifest = JSON.parse(await readFile26(join58(project, "package.json"), "utf8"));
|
|
22440
22863
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
22441
22864
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
22442
22865
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
22443
22866
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
22444
22867
|
if (typeof expected !== "string")
|
|
22445
22868
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
22446
|
-
const installed = JSON.parse(await
|
|
22869
|
+
const installed = JSON.parse(await readFile26(join58(project, "node_modules", name, "package.json"), "utf8"));
|
|
22447
22870
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
22448
22871
|
if (typeof actual !== "string")
|
|
22449
22872
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -22579,7 +23002,7 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22579
23002
|
]);
|
|
22580
23003
|
return;
|
|
22581
23004
|
}
|
|
22582
|
-
await ensureCapacitorPackages(projectRoot, args);
|
|
23005
|
+
await ensureCapacitorPackages(projectRoot, args, mobile);
|
|
22583
23006
|
const generated = await writeAbsoluteCapacitorConfig(mobile, {
|
|
22584
23007
|
force: args.includes("--force"),
|
|
22585
23008
|
projectRoot
|
|
@@ -22591,6 +23014,7 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22591
23014
|
await applyAbsoluteNativeDeepLinks(mobile);
|
|
22592
23015
|
await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile);
|
|
22593
23016
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile);
|
|
23017
|
+
await applyAbsoluteNativeUpdates(mobile);
|
|
22594
23018
|
}, sync = async (args) => {
|
|
22595
23019
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
22596
23020
|
if (mobile.engine === "expo") {
|
|
@@ -22612,7 +23036,7 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22612
23036
|
]);
|
|
22613
23037
|
return;
|
|
22614
23038
|
}
|
|
22615
|
-
await ensureCapacitorPackages(projectRoot, args);
|
|
23039
|
+
await ensureCapacitorPackages(projectRoot, args, mobile);
|
|
22616
23040
|
const platform6 = args.find((value) => value === "android" || value === "ios");
|
|
22617
23041
|
const platforms = platform6 ? [platform6] : mobile.platforms;
|
|
22618
23042
|
if (platforms.includes("android"))
|
|
@@ -22623,9 +23047,10 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22623
23047
|
await applyAbsoluteNativeDeepLinks(mobile, platforms);
|
|
22624
23048
|
await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, platforms);
|
|
22625
23049
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
|
|
23050
|
+
await applyAbsoluteNativeUpdates(mobile, platforms);
|
|
22626
23051
|
}, associations = async (args) => {
|
|
22627
23052
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
22628
|
-
const outputDirectory =
|
|
23053
|
+
const outputDirectory = resolve46(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
22629
23054
|
if (args.includes("--verify")) {
|
|
22630
23055
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
22631
23056
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -22675,7 +23100,7 @@ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value
|
|
|
22675
23100
|
const publicResult = {
|
|
22676
23101
|
changed: result.changed,
|
|
22677
23102
|
format: result.format,
|
|
22678
|
-
path:
|
|
23103
|
+
path: relative33(projectRoot, result.path).replaceAll("\\", "/"),
|
|
22679
23104
|
platforms: result.platforms,
|
|
22680
23105
|
publishing: result.publishing,
|
|
22681
23106
|
requiredSecrets: result.requiredSecrets
|
|
@@ -22742,6 +23167,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
22742
23167
|
}, mobileBuildServerEntry = (args) => {
|
|
22743
23168
|
const valueFlags = new Set([
|
|
22744
23169
|
"--channel",
|
|
23170
|
+
"--classification",
|
|
22745
23171
|
"--config",
|
|
22746
23172
|
"--outdir",
|
|
22747
23173
|
"--play-name",
|
|
@@ -22751,6 +23177,8 @@ Mobile release security and compliance checks failed.`);
|
|
|
22751
23177
|
"--play-track",
|
|
22752
23178
|
"--play-update-priority",
|
|
22753
23179
|
"--registry",
|
|
23180
|
+
"--key-id",
|
|
23181
|
+
"--signing-key",
|
|
22754
23182
|
"--remote",
|
|
22755
23183
|
"--testflight-group",
|
|
22756
23184
|
"--testflight-notes",
|
|
@@ -22764,6 +23192,132 @@ Mobile release security and compliance checks failed.`);
|
|
|
22764
23192
|
}
|
|
22765
23193
|
});
|
|
22766
23194
|
return args.find((value, index) => !skipped.has(index) && value !== "--unsigned" && !value.startsWith("-")) ?? DEFAULT_SERVER_ENTRY;
|
|
23195
|
+
}, requireUpdateClassification = (value) => {
|
|
23196
|
+
if (value === "bug-fix" || value === "content" || value === "security")
|
|
23197
|
+
return value;
|
|
23198
|
+
throw new TypeError("mobile update build requires --classification bug-fix|content|security.");
|
|
23199
|
+
}, buildMobileUpdate = async (args) => {
|
|
23200
|
+
const configPath2 = valueAfter(args, "--config");
|
|
23201
|
+
const { mobile, projectRoot } = await loadMobile(configPath2);
|
|
23202
|
+
requireCapacitorEngine(mobile, "mobile update build");
|
|
23203
|
+
if (!mobile.updates)
|
|
23204
|
+
throw new TypeError("mobile update build requires mobile.updates.publicKeys in absolute.config.ts.");
|
|
23205
|
+
if (!args.includes("--within-submitted-purpose"))
|
|
23206
|
+
throw new TypeError("mobile update build requires --within-submitted-purpose to attest that the update does not change the submitted app purpose.");
|
|
23207
|
+
const classification = requireUpdateClassification(valueAfter(args, "--classification"));
|
|
23208
|
+
const keyId = valueAfter(args, "--key-id");
|
|
23209
|
+
if (!keyId || !mobile.updates.publicKeys[keyId])
|
|
23210
|
+
throw new TypeError("mobile update build requires --key-id matching mobile.updates.publicKeys.");
|
|
23211
|
+
const signingKeyPath = valueAfter(args, "--signing-key");
|
|
23212
|
+
if (!signingKeyPath)
|
|
23213
|
+
throw new TypeError("mobile update build requires --signing-key <private-key.pem>.");
|
|
23214
|
+
const startedAt = performance.now();
|
|
23215
|
+
let success = false;
|
|
23216
|
+
try {
|
|
23217
|
+
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath2, { prepareOnly: true });
|
|
23218
|
+
const embedded = JSON.parse(await readFile26(join58(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
23219
|
+
const runtimeFingerprint = isRecord17(embedded) ? embedded.nativeRuntime : undefined;
|
|
23220
|
+
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
23221
|
+
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
23222
|
+
const privateKey = await readFile26(resolve46(projectRoot, signingKeyPath));
|
|
23223
|
+
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
23224
|
+
const derivedPublicKey = createPublicKey3(privateKey).export({
|
|
23225
|
+
format: "der",
|
|
23226
|
+
type: "spki"
|
|
23227
|
+
});
|
|
23228
|
+
if (!configuredPublicKey.equals(derivedPublicKey))
|
|
23229
|
+
throw new TypeError(`The private key does not match mobile.updates.publicKeys.${keyId}.`);
|
|
23230
|
+
const result = await buildAbsoluteMobileUpdate({
|
|
23231
|
+
appId: mobile.appId,
|
|
23232
|
+
bundleDirectory: mobile.bundleDirectory,
|
|
23233
|
+
channel: mobile.updates.channel,
|
|
23234
|
+
classification,
|
|
23235
|
+
keyId,
|
|
23236
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join58(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
23237
|
+
privateKey,
|
|
23238
|
+
runtimeFingerprint
|
|
23239
|
+
});
|
|
23240
|
+
verifyAbsoluteMobileUpdateSignature(result.manifest, derivedPublicKey);
|
|
23241
|
+
success = true;
|
|
23242
|
+
console.log(`Built signed mobile update ${result.manifest.releaseId}.`);
|
|
23243
|
+
console.log(`Release: ${result.outputDirectory}`);
|
|
23244
|
+
console.log(`Manifest: ${result.manifestPath}`);
|
|
23245
|
+
return result;
|
|
23246
|
+
} finally {
|
|
23247
|
+
sendTelemetryEvent("mobile:update-build", {
|
|
23248
|
+
classification: classification ?? "invalid",
|
|
23249
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
23250
|
+
engine: mobile.engine,
|
|
23251
|
+
success
|
|
23252
|
+
});
|
|
23253
|
+
}
|
|
23254
|
+
}, updateRollout = (args, fallback) => {
|
|
23255
|
+
const value = valueAfter(args, "--rollout");
|
|
23256
|
+
if (value === undefined) {
|
|
23257
|
+
if (fallback !== undefined)
|
|
23258
|
+
return fallback;
|
|
23259
|
+
throw new TypeError("This command requires --rollout <fraction>.");
|
|
23260
|
+
}
|
|
23261
|
+
const rollout = Number(value);
|
|
23262
|
+
if (!Number.isFinite(rollout) || rollout <= 0 || rollout > 1)
|
|
23263
|
+
throw new TypeError("--rollout must be greater than 0 and at most 1.");
|
|
23264
|
+
return rollout;
|
|
23265
|
+
}, mobileUpdatePublisher = async (args) => {
|
|
23266
|
+
const { projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
23267
|
+
const modulePath = valueAfter(args, "--registry") ?? "mobile.release.ts";
|
|
23268
|
+
return {
|
|
23269
|
+
projectRoot,
|
|
23270
|
+
publisher: await loadAbsoluteMobileUpdatePublisher(projectRoot, modulePath)
|
|
23271
|
+
};
|
|
23272
|
+
}, publishMobileUpdate = async (args) => {
|
|
23273
|
+
const releaseDirectory = args.find((value, index) => {
|
|
23274
|
+
if (value.startsWith("-"))
|
|
23275
|
+
return false;
|
|
23276
|
+
const previous = args[index - 1];
|
|
23277
|
+
return !["--config", "--registry", "--rollout"].includes(previous ?? "");
|
|
23278
|
+
});
|
|
23279
|
+
if (!releaseDirectory)
|
|
23280
|
+
throw new TypeError("mobile update publish requires a release directory.");
|
|
23281
|
+
const { projectRoot, publisher } = await mobileUpdatePublisher(args);
|
|
23282
|
+
const result = await publishAbsoluteMobileUpdate({
|
|
23283
|
+
projectRoot,
|
|
23284
|
+
publisher,
|
|
23285
|
+
releaseDirectory,
|
|
23286
|
+
rollout: updateRollout(args, 0.05)
|
|
23287
|
+
});
|
|
23288
|
+
console.log(`${result.reused ? "Reused" : "Published"} mobile update ${result.releaseId} to ${result.channel} at ${Math.round(result.rollout * 100)}%.`);
|
|
23289
|
+
return result;
|
|
23290
|
+
}, promoteMobileUpdate = async (args) => {
|
|
23291
|
+
const { mobile } = await loadMobile(valueAfter(args, "--config"));
|
|
23292
|
+
if (!mobile.updates)
|
|
23293
|
+
throw new TypeError("mobile update promote requires mobile.updates config.");
|
|
23294
|
+
const releaseId = valueAfter(args, "--release");
|
|
23295
|
+
if (!releaseId)
|
|
23296
|
+
throw new TypeError("mobile update promote requires --release <release-id>.");
|
|
23297
|
+
const { publisher } = await mobileUpdatePublisher(args);
|
|
23298
|
+
const result = await promoteAbsoluteMobileUpdate({
|
|
23299
|
+
appId: mobile.appId,
|
|
23300
|
+
channel: mobile.updates.channel,
|
|
23301
|
+
publisher,
|
|
23302
|
+
releaseId,
|
|
23303
|
+
rollout: updateRollout(args)
|
|
23304
|
+
});
|
|
23305
|
+
console.log(`Promoted mobile update ${result.releaseId} to ${Math.round(result.rollout * 100)}%.`);
|
|
23306
|
+
return result;
|
|
23307
|
+
}, rollbackMobileUpdate = async (args) => {
|
|
23308
|
+
const { mobile } = await loadMobile(valueAfter(args, "--config"));
|
|
23309
|
+
if (!mobile.updates)
|
|
23310
|
+
throw new TypeError("mobile update rollback requires mobile.updates config.");
|
|
23311
|
+
const { publisher } = await mobileUpdatePublisher(args);
|
|
23312
|
+
const releaseId = valueAfter(args, "--release");
|
|
23313
|
+
const result = await rollbackAbsoluteMobileUpdate({
|
|
23314
|
+
appId: mobile.appId,
|
|
23315
|
+
channel: mobile.updates.channel,
|
|
23316
|
+
publisher,
|
|
23317
|
+
...releaseId ? { releaseId } : {}
|
|
23318
|
+
});
|
|
23319
|
+
console.log(result.releaseId ? `Rolled ${result.channel} back to ${result.releaseId}.` : `Rolled ${result.channel} back to the embedded store build.`);
|
|
23320
|
+
return result;
|
|
22767
23321
|
}, requireValueAfter = (args, flag) => {
|
|
22768
23322
|
const value = valueAfter(args, flag);
|
|
22769
23323
|
if (!value || value.startsWith("-")) {
|
|
@@ -22919,6 +23473,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
22919
23473
|
await applyAbsoluteNativeDeepLinks(mobile, ["ios"]);
|
|
22920
23474
|
await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, ["ios"]);
|
|
22921
23475
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["ios"]);
|
|
23476
|
+
await applyAbsoluteNativeUpdates(mobile, ["ios"]);
|
|
22922
23477
|
}, prepareIosReleaseProject = (mobile, projectRoot, args) => mobile.engine === "expo" ? prepareExpoIosReleaseProject(mobile, projectRoot, args) : prepareCapacitorIosReleaseProject(mobile, projectRoot), prepareCapacitorAndroidReleaseProject = async (mobile, projectRoot) => {
|
|
22923
23478
|
await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
|
|
22924
23479
|
await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
|
|
@@ -22954,7 +23509,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
22954
23509
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
22955
23510
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
22956
23511
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
22957
|
-
console.log(`Metadata: ${
|
|
23512
|
+
console.log(`Metadata: ${join58(release.releaseRoot, "release.json")}`);
|
|
22958
23513
|
return release;
|
|
22959
23514
|
} finally {
|
|
22960
23515
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -23102,7 +23657,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
23102
23657
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
23103
23658
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
|
|
23104
23659
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
23105
|
-
console.log(`Metadata: ${
|
|
23660
|
+
console.log(`Metadata: ${join58(release.releaseRoot, "release.json")}`);
|
|
23106
23661
|
return release;
|
|
23107
23662
|
} finally {
|
|
23108
23663
|
stopListeningForCancellation();
|
|
@@ -23211,7 +23766,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
23211
23766
|
checks.push({
|
|
23212
23767
|
id: "sync.storage-schema",
|
|
23213
23768
|
label: `Offline schema ${schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ")}`,
|
|
23214
|
-
path:
|
|
23769
|
+
path: join58(projectRoot, "package.json"),
|
|
23215
23770
|
platform: "host",
|
|
23216
23771
|
status: "pass"
|
|
23217
23772
|
});
|
|
@@ -23219,7 +23774,7 @@ Mobile release security and compliance checks failed.`);
|
|
|
23219
23774
|
checks.push({
|
|
23220
23775
|
id: "sync.storage-schema",
|
|
23221
23776
|
label: "Offline schema metadata is invalid",
|
|
23222
|
-
path:
|
|
23777
|
+
path: join58(projectRoot, "package.json"),
|
|
23223
23778
|
platform: "host",
|
|
23224
23779
|
remediation: error instanceof Error ? error.message : String(error),
|
|
23225
23780
|
status: "fail"
|
|
@@ -23304,7 +23859,7 @@ Emulator setup verification:`);
|
|
|
23304
23859
|
}
|
|
23305
23860
|
return { https: args.includes("--https"), port };
|
|
23306
23861
|
}
|
|
23307
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
23862
|
+
const instances = listLiveInstances().filter((instance2) => resolve46(instance2.cwd) === resolve46(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
23308
23863
|
if (instances.length !== 1) {
|
|
23309
23864
|
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>.");
|
|
23310
23865
|
}
|
|
@@ -23347,8 +23902,8 @@ Emulator setup verification:`);
|
|
|
23347
23902
|
}
|
|
23348
23903
|
return selected;
|
|
23349
23904
|
}, safeArtifactRoot = (projectRoot, value) => {
|
|
23350
|
-
const root =
|
|
23351
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
23905
|
+
const root = resolve46(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
23906
|
+
if (root !== projectRoot && !root.startsWith(`${resolve46(projectRoot)}/`)) {
|
|
23352
23907
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
23353
23908
|
}
|
|
23354
23909
|
return root;
|
|
@@ -23372,12 +23927,12 @@ Emulator setup verification:`);
|
|
|
23372
23927
|
timeoutMs
|
|
23373
23928
|
});
|
|
23374
23929
|
}, writeAndroidFailureArtifacts = async (options) => {
|
|
23375
|
-
await
|
|
23376
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
23930
|
+
await mkdir17(options.artifactRoot, { recursive: true });
|
|
23931
|
+
const screenshot = options.session ? await options.session.screenshot(join58(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
23377
23932
|
return;
|
|
23378
23933
|
}) : undefined;
|
|
23379
|
-
const diagnosticPath =
|
|
23380
|
-
await
|
|
23934
|
+
const diagnosticPath = join58(options.artifactRoot, "android-failure.json");
|
|
23935
|
+
await writeFile20(diagnosticPath, `${JSON.stringify({
|
|
23381
23936
|
diagnostics: options.session?.diagnostics ?? [],
|
|
23382
23937
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
23383
23938
|
platform: "android",
|
|
@@ -23441,7 +23996,7 @@ Emulator setup verification:`);
|
|
|
23441
23996
|
console.log(JSON.stringify(report, null, 2));
|
|
23442
23997
|
else
|
|
23443
23998
|
printAndroidTestReport(report);
|
|
23444
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
23999
|
+
const screenshot = reportRoot ? await session.screenshot(join58(artifactRoot, "android-emulator.png")) : undefined;
|
|
23445
24000
|
await writeRequestedAndroidReport({
|
|
23446
24001
|
adb,
|
|
23447
24002
|
args,
|
|
@@ -23509,14 +24064,14 @@ Emulator setup verification:`);
|
|
|
23509
24064
|
const port = Number(explicit);
|
|
23510
24065
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
23511
24066
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
23512
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
24067
|
+
const instance2 = listLiveInstances().find((candidate) => resolve46(candidate.cwd) === resolve46(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
23513
24068
|
return {
|
|
23514
24069
|
https: instance2?.https ?? args.includes("--https"),
|
|
23515
24070
|
instance: instance2,
|
|
23516
24071
|
port
|
|
23517
24072
|
};
|
|
23518
24073
|
}
|
|
23519
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
24074
|
+
const instances = listLiveInstances().filter((instance2) => resolve46(instance2.cwd) === resolve46(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
23520
24075
|
if (instances.length !== 1)
|
|
23521
24076
|
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>.");
|
|
23522
24077
|
const [instance] = instances;
|
|
@@ -23630,8 +24185,8 @@ Emulator setup verification:`);
|
|
|
23630
24185
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
23631
24186
|
return result;
|
|
23632
24187
|
}, writeIosFailureArtifacts = async (options) => {
|
|
23633
|
-
await
|
|
23634
|
-
const screenshot =
|
|
24188
|
+
await mkdir17(options.artifactRoot, { recursive: true });
|
|
24189
|
+
const screenshot = join58(options.artifactRoot, "ios-failure.png");
|
|
23635
24190
|
const screenshotResult = captureCommand4([
|
|
23636
24191
|
options.xcrun,
|
|
23637
24192
|
"simctl",
|
|
@@ -23640,8 +24195,8 @@ Emulator setup verification:`);
|
|
|
23640
24195
|
"screenshot",
|
|
23641
24196
|
screenshot
|
|
23642
24197
|
]);
|
|
23643
|
-
const diagnosticPath =
|
|
23644
|
-
await
|
|
24198
|
+
const diagnosticPath = join58(options.artifactRoot, "ios-failure.json");
|
|
24199
|
+
await writeFile20(diagnosticPath, `${JSON.stringify({
|
|
23645
24200
|
appId: options.appId,
|
|
23646
24201
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
23647
24202
|
platform: "ios",
|
|
@@ -23666,8 +24221,8 @@ Emulator setup verification:`);
|
|
|
23666
24221
|
}, absolutejsVersionForReport = async () => {
|
|
23667
24222
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
23668
24223
|
const versions = await Promise.all([
|
|
23669
|
-
|
|
23670
|
-
|
|
24224
|
+
resolve46(import.meta.dir, "..", "..", "package.json"),
|
|
24225
|
+
resolve46(import.meta.dir, "..", "..", "..", "package.json")
|
|
23671
24226
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
23672
24227
|
for (const version2 of versions) {
|
|
23673
24228
|
if (version2 === "unknown")
|
|
@@ -23886,8 +24441,8 @@ Emulator setup verification:`);
|
|
|
23886
24441
|
mobile.appId
|
|
23887
24442
|
], "iOS app launch");
|
|
23888
24443
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
23889
|
-
await
|
|
23890
|
-
const screenshot =
|
|
24444
|
+
await mkdir17(artifactRoot, { recursive: true });
|
|
24445
|
+
const screenshot = join58(artifactRoot, "ios-simulator.png");
|
|
23891
24446
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
23892
24447
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
23893
24448
|
const report = {
|
|
@@ -24024,6 +24579,22 @@ Emulator setup verification:`);
|
|
|
24024
24579
|
await buildIos(args.slice(2));
|
|
24025
24580
|
return;
|
|
24026
24581
|
}
|
|
24582
|
+
if (command === "update" && args[1] === "build") {
|
|
24583
|
+
await buildMobileUpdate(args.slice(2));
|
|
24584
|
+
return;
|
|
24585
|
+
}
|
|
24586
|
+
if (command === "update" && args[1] === "publish") {
|
|
24587
|
+
await publishMobileUpdate(args.slice(2));
|
|
24588
|
+
return;
|
|
24589
|
+
}
|
|
24590
|
+
if (command === "update" && args[1] === "promote") {
|
|
24591
|
+
await promoteMobileUpdate(args.slice(2));
|
|
24592
|
+
return;
|
|
24593
|
+
}
|
|
24594
|
+
if (command === "update" && args[1] === "rollback") {
|
|
24595
|
+
await rollbackMobileUpdate(args.slice(2));
|
|
24596
|
+
return;
|
|
24597
|
+
}
|
|
24027
24598
|
if (command === "publish" && args[1] === "android") {
|
|
24028
24599
|
await publishAndroid(args.slice(2));
|
|
24029
24600
|
return;
|
|
@@ -24032,7 +24603,7 @@ Emulator setup verification:`);
|
|
|
24032
24603
|
await publishIos(args.slice(2));
|
|
24033
24604
|
return;
|
|
24034
24605
|
}
|
|
24035
|
-
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--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] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--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]");
|
|
24606
|
+
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | 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] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--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]");
|
|
24036
24607
|
};
|
|
24037
24608
|
var init_mobile = __esm(() => {
|
|
24038
24609
|
init_dependencies();
|
|
@@ -24042,6 +24613,7 @@ var init_mobile = __esm(() => {
|
|
|
24042
24613
|
init_nativeDeepLinks();
|
|
24043
24614
|
init_nativeDeviceCapabilities();
|
|
24044
24615
|
init_nativeBackgroundSync();
|
|
24616
|
+
init_nativeUpdates();
|
|
24045
24617
|
init_emulatorDoctor();
|
|
24046
24618
|
init_emulatorInstaller();
|
|
24047
24619
|
init_associationFiles();
|
|
@@ -24069,6 +24641,8 @@ var init_mobile = __esm(() => {
|
|
|
24069
24641
|
init_deviceCapabilities();
|
|
24070
24642
|
init_mobileInspect();
|
|
24071
24643
|
init_ciWorkflow();
|
|
24644
|
+
init_updateSigning();
|
|
24645
|
+
init_updatePublisher();
|
|
24072
24646
|
CAPACITOR_PACKAGES = [
|
|
24073
24647
|
"@capacitor/core",
|
|
24074
24648
|
"@capacitor/app",
|
|
@@ -24104,10 +24678,10 @@ var exports_typecheck = {};
|
|
|
24104
24678
|
__export(exports_typecheck, {
|
|
24105
24679
|
typecheck: () => typecheck
|
|
24106
24680
|
});
|
|
24107
|
-
import { resolve as
|
|
24681
|
+
import { resolve as resolve47, join as join59 } from "path";
|
|
24108
24682
|
import { existsSync as existsSync44, readFileSync as readFileSync40 } from "fs";
|
|
24109
|
-
import { mkdir as
|
|
24110
|
-
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) =>
|
|
24683
|
+
import { mkdir as mkdir18, writeFile as writeFile21 } from "fs/promises";
|
|
24684
|
+
var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve47(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
|
|
24111
24685
|
if (!existsSync44(resolveConfigPath(configPath2))) {
|
|
24112
24686
|
const defaultService = {};
|
|
24113
24687
|
return [defaultService];
|
|
@@ -24129,7 +24703,7 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
|
|
|
24129
24703
|
const exitCode = await proc.exited;
|
|
24130
24704
|
return { exitCode, name, output: (stdout + stderr).trim() };
|
|
24131
24705
|
}, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
|
|
24132
|
-
const local =
|
|
24706
|
+
const local = resolve47("node_modules", ".bin", name);
|
|
24133
24707
|
return existsSync44(local) ? local : null;
|
|
24134
24708
|
}, ANSI_COLOR_REGEX, ANSI_PURPLE_REGEX, ANSI_CYAN_REGEX, ANSI_TOKEN_END_REGEX, stripAnsi4 = (str) => str.replace(ANSI_COLOR_REGEX, ""), formatSvelteOutput = (output) => {
|
|
24135
24709
|
const cwd = `${process.cwd()}/`;
|
|
@@ -24177,15 +24751,15 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24177
24751
|
return formatted;
|
|
24178
24752
|
}, ABSOLUTE_INTERNAL_EXCLUDES, resolveAbsoluteTypeFile = (fileName) => {
|
|
24179
24753
|
const candidates = [
|
|
24180
|
-
|
|
24181
|
-
|
|
24182
|
-
|
|
24183
|
-
|
|
24754
|
+
resolve47("node_modules/@absolutejs/absolute/dist/types", fileName),
|
|
24755
|
+
resolve47(import.meta.dir, "../types", fileName),
|
|
24756
|
+
resolve47(import.meta.dir, "../../types", fileName),
|
|
24757
|
+
resolve47(import.meta.dir, "../../../types", fileName)
|
|
24184
24758
|
];
|
|
24185
24759
|
return candidates.find((candidate) => existsSync44(candidate)) ?? candidates[0];
|
|
24186
24760
|
}, ABSOLUTE_TYPECHECK_FILES, readProjectTsconfig = () => {
|
|
24187
24761
|
try {
|
|
24188
|
-
return JSON.parse(readFileSync40(
|
|
24762
|
+
return JSON.parse(readFileSync40(resolve47("tsconfig.json"), "utf-8"));
|
|
24189
24763
|
} catch {
|
|
24190
24764
|
return {};
|
|
24191
24765
|
}
|
|
@@ -24213,27 +24787,27 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24213
24787
|
console.error("\x1B[31m\u2717\x1B[0m vue-tsc is required for Vue type checking. Install it: bun add -d vue-tsc");
|
|
24214
24788
|
process.exit(1);
|
|
24215
24789
|
}
|
|
24216
|
-
const vueTsconfigPath =
|
|
24217
|
-
await
|
|
24790
|
+
const vueTsconfigPath = join59(cacheDir, "tsconfig.vue-check.json");
|
|
24791
|
+
await writeFile21(vueTsconfigPath, JSON.stringify({
|
|
24218
24792
|
compilerOptions: {
|
|
24219
24793
|
rootDir: ".."
|
|
24220
24794
|
},
|
|
24221
24795
|
exclude: getProjectTypecheckExcludes(),
|
|
24222
|
-
extends:
|
|
24796
|
+
extends: resolve47("tsconfig.json"),
|
|
24223
24797
|
include: getProjectTypecheckIncludes()
|
|
24224
24798
|
}, null, "\t"));
|
|
24225
24799
|
const base = [
|
|
24226
24800
|
vueTscBin,
|
|
24227
24801
|
"--noEmit",
|
|
24228
24802
|
"--project",
|
|
24229
|
-
|
|
24803
|
+
resolve47(vueTsconfigPath),
|
|
24230
24804
|
"--pretty"
|
|
24231
24805
|
];
|
|
24232
24806
|
const cached = await run("vue-tsc", [
|
|
24233
24807
|
...base,
|
|
24234
24808
|
"--incremental",
|
|
24235
24809
|
"--tsBuildInfoFile",
|
|
24236
|
-
|
|
24810
|
+
join59(cacheDir, "vue-tsc.tsbuildinfo")
|
|
24237
24811
|
]);
|
|
24238
24812
|
if (cached.exitCode === 0 || cached.output.length > 0)
|
|
24239
24813
|
return cached;
|
|
@@ -24244,8 +24818,8 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24244
24818
|
console.error("\x1B[31m\u2717\x1B[0m @angular/compiler-cli is required for Angular type checking. Install it: bun add -d @angular/compiler-cli");
|
|
24245
24819
|
process.exit(1);
|
|
24246
24820
|
}
|
|
24247
|
-
const angularTsconfigPath =
|
|
24248
|
-
await
|
|
24821
|
+
const angularTsconfigPath = join59(cacheDir, "tsconfig.angular-check.json");
|
|
24822
|
+
await writeFile21(angularTsconfigPath, JSON.stringify({
|
|
24249
24823
|
angularCompilerOptions: {
|
|
24250
24824
|
strictTemplates: true
|
|
24251
24825
|
},
|
|
@@ -24254,32 +24828,32 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24254
24828
|
rootDir: ".."
|
|
24255
24829
|
},
|
|
24256
24830
|
exclude: ABSOLUTE_INTERNAL_EXCLUDES.map(toGeneratedConfigPath),
|
|
24257
|
-
extends:
|
|
24831
|
+
extends: resolve47("tsconfig.json"),
|
|
24258
24832
|
include: [`../${angularDir}/**/*`]
|
|
24259
24833
|
}, null, "\t"));
|
|
24260
|
-
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(
|
|
24834
|
+
return runShell("ngc", `${shellEscape(ngcBin)} -p ${shellEscape(resolve47(angularTsconfigPath))}`);
|
|
24261
24835
|
}, buildTscCheck = (cacheDir) => {
|
|
24262
24836
|
const tscBin = findBin("tsc");
|
|
24263
24837
|
if (!tscBin) {
|
|
24264
24838
|
console.error("\x1B[31m\u2717\x1B[0m typescript is required for type checking. Install it: bun add -d typescript");
|
|
24265
24839
|
process.exit(1);
|
|
24266
24840
|
}
|
|
24267
|
-
const tscConfigPath =
|
|
24268
|
-
return
|
|
24841
|
+
const tscConfigPath = join59(cacheDir, "tsconfig.typecheck.json");
|
|
24842
|
+
return writeFile21(tscConfigPath, JSON.stringify({
|
|
24269
24843
|
compilerOptions: {
|
|
24270
24844
|
rootDir: ".."
|
|
24271
24845
|
},
|
|
24272
24846
|
exclude: getProjectTypecheckExcludes(),
|
|
24273
|
-
extends:
|
|
24847
|
+
extends: resolve47("tsconfig.json"),
|
|
24274
24848
|
include: getProjectTypecheckIncludes()
|
|
24275
24849
|
}, null, "\t")).then(() => run("tsc", [
|
|
24276
24850
|
tscBin,
|
|
24277
24851
|
"--noEmit",
|
|
24278
24852
|
"--project",
|
|
24279
|
-
|
|
24853
|
+
resolve47(tscConfigPath),
|
|
24280
24854
|
"--incremental",
|
|
24281
24855
|
"--tsBuildInfoFile",
|
|
24282
|
-
|
|
24856
|
+
join59(cacheDir, "tsc.tsbuildinfo"),
|
|
24283
24857
|
"--pretty"
|
|
24284
24858
|
]));
|
|
24285
24859
|
}, buildSvelteCheck = async (cacheDir, svelteDir) => {
|
|
@@ -24288,16 +24862,16 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24288
24862
|
console.error("\x1B[31m\u2717\x1B[0m svelte-check is required for Svelte type checking. Install it: bun add -d svelte-check");
|
|
24289
24863
|
process.exit(1);
|
|
24290
24864
|
}
|
|
24291
|
-
const svelteTsconfigPath =
|
|
24292
|
-
await
|
|
24293
|
-
extends:
|
|
24865
|
+
const svelteTsconfigPath = join59(cacheDir, "tsconfig.svelte-check.json");
|
|
24866
|
+
await writeFile21(svelteTsconfigPath, JSON.stringify({
|
|
24867
|
+
extends: resolve47("tsconfig.json"),
|
|
24294
24868
|
files: ABSOLUTE_TYPECHECK_FILES,
|
|
24295
24869
|
include: [`../${svelteDir}/**/*`]
|
|
24296
24870
|
}, null, "\t"));
|
|
24297
24871
|
return run("svelte-check", [
|
|
24298
24872
|
svelteBin,
|
|
24299
24873
|
"--tsconfig",
|
|
24300
|
-
|
|
24874
|
+
resolve47(svelteTsconfigPath),
|
|
24301
24875
|
"--threshold",
|
|
24302
24876
|
"error",
|
|
24303
24877
|
"--compiler-warnings",
|
|
@@ -24318,7 +24892,7 @@ Found ${errorCount} error${suffix}.`;
|
|
|
24318
24892
|
...new Set(targets.map((config) => config.angularDirectory).filter((dir) => typeof dir === "string" && dir.length > 0))
|
|
24319
24893
|
];
|
|
24320
24894
|
const cacheDir = ".absolutejs";
|
|
24321
|
-
await
|
|
24895
|
+
await mkdir18(cacheDir, { recursive: true });
|
|
24322
24896
|
const checks = [];
|
|
24323
24897
|
checks.push(hasVue ? buildVueTscCheck(cacheDir) : buildTscCheck(cacheDir));
|
|
24324
24898
|
for (const svelteDir of hasSvelte ? svelteDirs : []) {
|
|
@@ -24491,11 +25065,11 @@ var DEFAULT_RELAY_PORT = 8787, DEFAULT_REQUEST_TIMEOUT_MS = 30000, headersToObje
|
|
|
24491
25065
|
url: url.pathname + url.search,
|
|
24492
25066
|
...bodyBytes && bodyBytes.length > 0 ? { bodyBase64: Buffer.from(bodyBytes).toString("base64") } : {}
|
|
24493
25067
|
};
|
|
24494
|
-
const responsePromise = new Promise((
|
|
24495
|
-
pending.set(id,
|
|
25068
|
+
const responsePromise = new Promise((resolve48) => {
|
|
25069
|
+
pending.set(id, resolve48);
|
|
24496
25070
|
});
|
|
24497
25071
|
client.send(encodeTunnelMessage(message));
|
|
24498
|
-
const timeout = new Promise((
|
|
25072
|
+
const timeout = new Promise((resolve48) => setTimeout(() => resolve48({ id, message: "timeout", type: "error" }), requestTimeoutMs));
|
|
24499
25073
|
const result = await Promise.race([responsePromise, timeout]);
|
|
24500
25074
|
pending.delete(id);
|
|
24501
25075
|
if (result.type === "error") {
|