@absolutejs/absolute 0.20.0-beta.2 → 0.20.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/angular/browser.js +15 -1
- package/dist/angular/browser.js.map +3 -3
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/angular/index.js +341 -22
- package/dist/angular/index.js.map +8 -5
- package/dist/angular/server.js +341 -22
- package/dist/angular/server.js.map +8 -5
- package/dist/build.js +1268 -598
- package/dist/build.js.map +17 -14
- package/dist/cli/index.js +5005 -1614
- package/dist/dev/client/cssUtils.ts +16 -2
- package/dist/dev/client/handlers/rebuild.ts +11 -1
- package/dist/dev/client/hmrClient.ts +9 -3
- package/dist/dev/client/hmrTiming.ts +14 -7
- package/dist/dev/client/syncDevtools.ts +237 -0
- package/dist/index.js +1644 -863
- package/dist/index.js.map +26 -23
- package/dist/mobile/browser.js +123 -1
- package/dist/mobile/browser.js.map +6 -4
- package/dist/mobile/index.js +3369 -298
- package/dist/mobile/index.js.map +27 -13
- package/dist/mobile/remoteMacAgentEntry.js +29 -0
- package/dist/mobile/shellAuth.js +35 -0
- package/dist/mobile/shellBootstrap.js +585 -0
- package/dist/mobile/shellSync.js +123 -0
- package/dist/src/angular/pageHandler.d.ts +3 -0
- package/dist/src/build/pwa.d.ts +16 -0
- package/dist/src/cli/config/server.d.ts +1 -1
- package/dist/src/core/pageHandlers.d.ts +11 -2
- package/dist/src/core/prepare.d.ts +6 -0
- package/dist/src/dev/clientManager.d.ts +2 -0
- package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
- package/dist/src/mobile/browser.d.ts +1 -0
- package/dist/src/mobile/buildPipeline.d.ts +1 -0
- package/dist/src/mobile/capacitorBundle.d.ts +22 -1
- package/dist/src/mobile/client.d.ts +4 -0
- package/dist/src/mobile/deviceCapabilities.d.ts +33 -0
- package/dist/src/mobile/index.d.ts +9 -0
- package/dist/src/mobile/iosConformance.d.ts +15 -0
- package/dist/src/mobile/iosNativeWatcher.d.ts +19 -0
- package/dist/src/mobile/iosRelease.d.ts +2 -2
- package/dist/src/mobile/iosSimulatorController.d.ts +89 -0
- package/dist/src/mobile/nativeAuth.d.ts +17 -0
- package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
- package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
- package/dist/src/mobile/releaseArtifact.d.ts +2 -0
- package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
- package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
- package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
- package/dist/src/mobile/remoteMacWire.d.ts +2 -0
- package/dist/src/mobile/shellAuth.d.ts +13 -0
- package/dist/src/mobile/shellBootstrap.d.ts +18 -1
- package/dist/src/mobile/shellSync.d.ts +19 -0
- package/dist/src/mobile/staticDocument.d.ts +5 -0
- package/dist/src/mobile/syncRemediation.d.ts +10 -0
- package/dist/src/mobile/syncSchema.d.ts +9 -0
- package/dist/src/mobile/transport.d.ts +16 -1
- package/dist/src/plugins/hmr.d.ts +3 -0
- package/dist/src/plugins/imageOptimizer.d.ts +1 -1
- package/dist/src/svelte/pageHandler.d.ts +3 -0
- package/dist/src/utils/imageProcessing.d.ts +3 -0
- package/dist/src/utils/loadConfig.d.ts +1 -0
- package/dist/src/vue/pageHandler.d.ts +3 -0
- package/dist/svelte/index.js +312 -23
- package/dist/svelte/index.js.map +7 -4
- package/dist/svelte/server.js +307 -18
- package/dist/svelte/server.js.map +7 -4
- package/dist/types/build.d.ts +14 -0
- package/dist/vue/index.js +312 -23
- package/dist/vue/index.js.map +7 -4
- package/dist/vue/server.js +307 -18
- package/dist/vue/server.js.map +7 -4
- package/package.json +30 -9
package/dist/mobile/index.js
CHANGED
|
@@ -159,6 +159,436 @@ var init_startupBanner = __esm(() => {
|
|
|
159
159
|
];
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
+
// src/utils/stringModifiers.ts
|
|
163
|
+
var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-_]+/g, "").replace(/[-_]{2,}/g, "-"), toKebab = (str) => normalizeSlug(str).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), toPascal = (str) => {
|
|
164
|
+
if (!str.includes("-") && !str.includes("_")) {
|
|
165
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
166
|
+
}
|
|
167
|
+
return normalizeSlug(str).split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// node_modules/@absolutejs/sync/dist/client/index.js
|
|
171
|
+
var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
|
|
172
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
173
|
+
throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
|
|
174
|
+
return value;
|
|
175
|
+
}, isSchemaBundle = (schema) => ("components" in schema), validatePolicyMatch = (match, label) => {
|
|
176
|
+
if (match.length === 0 || match.trim() !== match || /^\*+$/.test(match) || match.includes("**"))
|
|
177
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.match must be an exact name or a non-empty glob without adjacent wildcards.`);
|
|
178
|
+
}, validateSyncLocalDataPolicy = (policy, label = "localData") => {
|
|
179
|
+
if (policy.maxBytesPerNamespace !== undefined && (!Number.isSafeInteger(policy.maxBytesPerNamespace) || policy.maxBytesPerNamespace < 1))
|
|
180
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.maxBytesPerNamespace must be a positive safe integer.`);
|
|
181
|
+
for (const [index, rule] of (policy.collections ?? []).entries()) {
|
|
182
|
+
validatePolicyMatch(rule.match, `${label}.collections[${index}]`);
|
|
183
|
+
if (rule.maxAgeMs !== undefined && (!Number.isSafeInteger(rule.maxAgeMs) || rule.maxAgeMs < 1))
|
|
184
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}].maxAgeMs must be a positive safe integer.`);
|
|
185
|
+
if (rule.persistence === "memory-only" && rule.protection === "required")
|
|
186
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] cannot require at-rest protection when it is memory-only.`);
|
|
187
|
+
if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
|
|
188
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] declares ${rule.sensitivity} data without required protection or memory-only persistence.`);
|
|
189
|
+
}
|
|
190
|
+
for (const [index, rule] of (policy.mutations ?? []).entries()) {
|
|
191
|
+
validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
|
|
192
|
+
if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
|
|
193
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
|
|
194
|
+
if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
|
|
195
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
|
|
196
|
+
if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
|
|
197
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
|
|
198
|
+
if (rule.persistence === "memory-only" && rule.protection === "required")
|
|
199
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
|
|
200
|
+
if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
|
|
201
|
+
throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] declares ${rule.sensitivity} arguments without required protection.`);
|
|
202
|
+
}
|
|
203
|
+
return policy;
|
|
204
|
+
}, normalizeSyncLocalSchemaComponents = (schema = { version: 1 }) => {
|
|
205
|
+
const components = isSchemaBundle(schema) ? [...schema.components] : [{ ...schema, id: "@absolutejs/app" }];
|
|
206
|
+
const ids = new Set;
|
|
207
|
+
for (const component of components) {
|
|
208
|
+
if (typeof component.id !== "string" || component.id.trim() !== component.id || component.id.length === 0)
|
|
209
|
+
throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Sync schema component id must be non-empty and trimmed");
|
|
210
|
+
if (ids.has(component.id))
|
|
211
|
+
throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync schema component "${component.id}" is declared more than once`);
|
|
212
|
+
ids.add(component.id);
|
|
213
|
+
if (component.localData)
|
|
214
|
+
validateSyncLocalDataPolicy(component.localData, `${component.id}.localData`);
|
|
215
|
+
}
|
|
216
|
+
return components.sort((a, b) => a.id.localeCompare(b.id));
|
|
217
|
+
}, resolveSyncLocalSchemaComponents = (storedVersions, schema = { version: 1 }) => {
|
|
218
|
+
const components = normalizeSyncLocalSchemaComponents(schema).map((component) => {
|
|
219
|
+
const current = resolveSyncLocalMigrations(component.version, component);
|
|
220
|
+
return {
|
|
221
|
+
id: component.id,
|
|
222
|
+
...resolveSyncLocalMigrations(storedVersions[component.id] ?? current.minimumCompatibleVersion, component)
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
const active = new Set(components.map((component) => component.id));
|
|
226
|
+
const orphanedComponents = Object.keys(storedVersions).filter((id) => !active.has(id)).sort();
|
|
227
|
+
return { components, orphanedComponents };
|
|
228
|
+
}, resolveSyncLocalMigrations = (storedVersion, schema = { version: 1 }) => {
|
|
229
|
+
positiveVersion(storedVersion, "Stored Sync schema version");
|
|
230
|
+
const targetVersion = positiveVersion(schema.version, "Target Sync schema version");
|
|
231
|
+
const migrations = [...schema.migrations ?? []].sort((a, b) => a.toVersion - b.toVersion);
|
|
232
|
+
const versions = new Set;
|
|
233
|
+
for (const migration of migrations) {
|
|
234
|
+
positiveVersion(migration.toVersion, "Sync migration toVersion");
|
|
235
|
+
if (versions.has(migration.toVersion))
|
|
236
|
+
throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync migration ${migration.toVersion} is declared more than once`);
|
|
237
|
+
versions.add(migration.toVersion);
|
|
238
|
+
}
|
|
239
|
+
const inferredMinimum = migrations[0] ? migrations[0].toVersion - 1 : targetVersion;
|
|
240
|
+
const minimumCompatibleVersion = positiveVersion(schema.minimumCompatibleVersion ?? inferredMinimum, "Minimum compatible Sync schema version");
|
|
241
|
+
if (minimumCompatibleVersion > targetVersion)
|
|
242
|
+
throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Minimum compatible Sync schema version cannot exceed its target");
|
|
243
|
+
if (storedVersion > targetVersion)
|
|
244
|
+
throw new SyncLocalStoreSchemaError("SCHEMA_TOO_NEW", `Stored Sync schema ${storedVersion} is newer than this runtime's schema ${targetVersion}`, { storedVersion, targetVersion });
|
|
245
|
+
if (storedVersion < minimumCompatibleVersion)
|
|
246
|
+
throw new SyncLocalStoreSchemaError("SCHEMA_TOO_OLD", `Stored Sync schema ${storedVersion} is older than the minimum compatible schema ${minimumCompatibleVersion}`, { storedVersion, targetVersion });
|
|
247
|
+
const steps = [];
|
|
248
|
+
for (let version = storedVersion + 1;version <= targetVersion; version++) {
|
|
249
|
+
const migration = migrations.find((candidate) => candidate.toVersion === version);
|
|
250
|
+
if (migration === undefined)
|
|
251
|
+
throw new SyncLocalStoreSchemaError("MIGRATION_MISSING", `Sync migration ${version - 1} -> ${version} is missing`, { storedVersion, targetVersion });
|
|
252
|
+
steps.push(migration);
|
|
253
|
+
}
|
|
254
|
+
return { minimumCompatibleVersion, steps, targetVersion };
|
|
255
|
+
};
|
|
256
|
+
var init_client = __esm(() => {
|
|
257
|
+
RUNTIME_TRANSPORT = Symbol.for("@absolutejs/sync/client-runtime-transport");
|
|
258
|
+
host = globalThis;
|
|
259
|
+
registry = (() => {
|
|
260
|
+
const existing = host[RUNTIME_TRANSPORT];
|
|
261
|
+
if (isRegistry(existing))
|
|
262
|
+
return existing;
|
|
263
|
+
if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
|
|
264
|
+
Reflect.set(existing, "clients", []);
|
|
265
|
+
return existing;
|
|
266
|
+
}
|
|
267
|
+
const created = { clients: [], installations: [] };
|
|
268
|
+
Object.defineProperty(host, RUNTIME_TRANSPORT, {
|
|
269
|
+
configurable: false,
|
|
270
|
+
enumerable: false,
|
|
271
|
+
value: created,
|
|
272
|
+
writable: false
|
|
273
|
+
});
|
|
274
|
+
return created;
|
|
275
|
+
})();
|
|
276
|
+
SyncLocalDataPolicyError = class SyncLocalDataPolicyError extends Error {
|
|
277
|
+
code;
|
|
278
|
+
constructor(code, message) {
|
|
279
|
+
super(message);
|
|
280
|
+
this.name = "SyncLocalDataPolicyError";
|
|
281
|
+
this.code = code;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
SyncLocalStoreSchemaError = class SyncLocalStoreSchemaError extends Error {
|
|
285
|
+
code;
|
|
286
|
+
storedVersion;
|
|
287
|
+
targetVersion;
|
|
288
|
+
constructor(code, message, versions = {}) {
|
|
289
|
+
super(message);
|
|
290
|
+
this.name = "SyncLocalStoreSchemaError";
|
|
291
|
+
this.code = code;
|
|
292
|
+
this.storedVersion = versions.storedVersion;
|
|
293
|
+
this.targetVersion = versions.targetVersion;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// src/mobile/syncSchema.ts
|
|
299
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
300
|
+
import { dirname as dirname9, join as join12, resolve as resolve10 } from "path";
|
|
301
|
+
var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value), manifestAt = (path) => {
|
|
302
|
+
try {
|
|
303
|
+
const value = JSON.parse(readFileSync3(path, "utf8"));
|
|
304
|
+
return object(value) ? value : undefined;
|
|
305
|
+
} catch {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}, localSchemaMetadata = (manifest) => {
|
|
309
|
+
const absolutejs = Reflect.get(manifest, "absolutejs");
|
|
310
|
+
if (!object(absolutejs))
|
|
311
|
+
return;
|
|
312
|
+
const sync = Reflect.get(absolutejs, "sync");
|
|
313
|
+
if (!object(sync))
|
|
314
|
+
return;
|
|
315
|
+
return Reflect.get(sync, "localSchema");
|
|
316
|
+
}, packageManifestPath = (projectRoot, packageName) => {
|
|
317
|
+
let directory = resolve10(projectRoot);
|
|
318
|
+
while (true) {
|
|
319
|
+
const candidate = join12(directory, "node_modules", packageName, "package.json");
|
|
320
|
+
const manifest = manifestAt(candidate);
|
|
321
|
+
if (manifest && Reflect.get(manifest, "name") === packageName)
|
|
322
|
+
return candidate;
|
|
323
|
+
const parent = dirname9(directory);
|
|
324
|
+
if (parent === directory)
|
|
325
|
+
return;
|
|
326
|
+
directory = parent;
|
|
327
|
+
}
|
|
328
|
+
}, metadataError = (id, detail) => new TypeError(`Invalid AbsoluteJS Sync schema metadata for ${id}: ${detail}`), positiveVersion2 = (value, id, field) => {
|
|
329
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
330
|
+
throw metadataError(id, `${field} must be a positive safe integer.`);
|
|
331
|
+
return value;
|
|
332
|
+
}, nonEmpty = (value, id, field) => {
|
|
333
|
+
if (typeof value !== "string" || value.trim() !== value || value.length === 0)
|
|
334
|
+
throw metadataError(id, `${field} must be a non-empty trimmed string.`);
|
|
335
|
+
return value;
|
|
336
|
+
}, requireObject = (value, id, detail) => {
|
|
337
|
+
if (!object(value))
|
|
338
|
+
throw metadataError(id, detail);
|
|
339
|
+
return value;
|
|
340
|
+
}, unknownField = (record, key) => record[key], normalizeJsonValue2 = (value, id, field) => {
|
|
341
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
342
|
+
return value;
|
|
343
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
344
|
+
return value;
|
|
345
|
+
if (Array.isArray(value))
|
|
346
|
+
return value.map((entry) => normalizeJsonValue2(entry, id, field));
|
|
347
|
+
if (object(value))
|
|
348
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
349
|
+
key,
|
|
350
|
+
normalizeJsonValue2(entry, id, field)
|
|
351
|
+
]));
|
|
352
|
+
throw metadataError(id, `${field} must be JSON-safe.`);
|
|
353
|
+
}, operation = (value, id, index) => {
|
|
354
|
+
const record = requireObject(value, id, `migration operation ${index} must be an object.`);
|
|
355
|
+
const type = Reflect.get(record, "type");
|
|
356
|
+
const collection = nonEmpty(Reflect.get(record, "collection"), id, `operation ${index}.collection`);
|
|
357
|
+
if (type === "delete-collection")
|
|
358
|
+
return { collection, type };
|
|
359
|
+
if (type === "rename-field")
|
|
360
|
+
return {
|
|
361
|
+
collection,
|
|
362
|
+
from: nonEmpty(Reflect.get(record, "from"), id, `operation ${index}.from`),
|
|
363
|
+
to: nonEmpty(Reflect.get(record, "to"), id, `operation ${index}.to`),
|
|
364
|
+
type
|
|
365
|
+
};
|
|
366
|
+
const field = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
|
|
367
|
+
if (type === "remove-field")
|
|
368
|
+
return { collection, field, type };
|
|
369
|
+
if (type === "set-default")
|
|
370
|
+
return {
|
|
371
|
+
collection,
|
|
372
|
+
field,
|
|
373
|
+
type,
|
|
374
|
+
value: normalizeJsonValue2(Reflect.get(record, "value"), id, `operation ${index}.value`)
|
|
375
|
+
};
|
|
376
|
+
throw metadataError(id, `operation ${index}.type is not supported.`);
|
|
377
|
+
}, migration = (value, id, index) => {
|
|
378
|
+
const record = requireObject(value, id, `migration ${index} must be an object.`);
|
|
379
|
+
const allowed = new Set(["operations", "toVersion"]);
|
|
380
|
+
const unsupported = Object.keys(record).find((key) => !allowed.has(key));
|
|
381
|
+
if (unsupported)
|
|
382
|
+
throw metadataError(id, `migration ${index}.${unsupported} is not declarative metadata.`);
|
|
383
|
+
const declaredOperations = Reflect.get(record, "operations");
|
|
384
|
+
if (declaredOperations !== undefined && !Array.isArray(declaredOperations))
|
|
385
|
+
throw metadataError(id, `migration ${index}.operations must be an array.`);
|
|
386
|
+
const operations = Array.isArray(declaredOperations) ? declaredOperations : [];
|
|
387
|
+
return {
|
|
388
|
+
operations: operations.map((entry, operationIndex) => operation(entry, id, operationIndex)),
|
|
389
|
+
toVersion: positiveVersion2(Reflect.get(record, "toVersion"), id, `migration ${index}.toVersion`)
|
|
390
|
+
};
|
|
391
|
+
}, localDataPolicy = (value, id) => {
|
|
392
|
+
const record = requireObject(value, id, "localData must be an object.");
|
|
393
|
+
const allowed = new Set([
|
|
394
|
+
"collections",
|
|
395
|
+
"maxBytesPerNamespace",
|
|
396
|
+
"mutations"
|
|
397
|
+
]);
|
|
398
|
+
const unsupported = Object.keys(record).find((key) => !allowed.has(key));
|
|
399
|
+
if (unsupported)
|
|
400
|
+
throw metadataError(id, `localData.${unsupported} is not supported.`);
|
|
401
|
+
const collectionRules = Reflect.get(record, "collections");
|
|
402
|
+
const mutationRules = Reflect.get(record, "mutations");
|
|
403
|
+
if (collectionRules !== undefined && !Array.isArray(collectionRules))
|
|
404
|
+
throw metadataError(id, "localData.collections must be an array.");
|
|
405
|
+
if (mutationRules !== undefined && !Array.isArray(mutationRules))
|
|
406
|
+
throw metadataError(id, "localData.mutations must be an array.");
|
|
407
|
+
const collections = Array.isArray(collectionRules) ? collectionRules.map((entry, index) => {
|
|
408
|
+
const rule = requireObject(entry, id, `localData.collections[${index}] must be an object.`);
|
|
409
|
+
const allowedRuleKeys = new Set([
|
|
410
|
+
"evictionPriority",
|
|
411
|
+
"match",
|
|
412
|
+
"maxAgeMs",
|
|
413
|
+
"onProtectionUnavailable",
|
|
414
|
+
"persistence",
|
|
415
|
+
"protection",
|
|
416
|
+
"sensitivity"
|
|
417
|
+
]);
|
|
418
|
+
const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
|
|
419
|
+
if (unsupportedRuleKey)
|
|
420
|
+
throw metadataError(id, `localData.collections[${index}].${unsupportedRuleKey} is not supported.`);
|
|
421
|
+
const match = nonEmpty(Reflect.get(rule, "match"), id, `localData.collections[${index}].match`);
|
|
422
|
+
const persistence = unknownField(rule, "persistence");
|
|
423
|
+
const sensitivity = unknownField(rule, "sensitivity");
|
|
424
|
+
const protection = unknownField(rule, "protection");
|
|
425
|
+
const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
|
|
426
|
+
const evictionPriority = unknownField(rule, "evictionPriority");
|
|
427
|
+
const maxAge = unknownField(rule, "maxAgeMs");
|
|
428
|
+
if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
|
|
429
|
+
throw metadataError(id, `localData.collections[${index}].persistence is invalid.`);
|
|
430
|
+
if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
|
|
431
|
+
throw metadataError(id, `localData.collections[${index}].sensitivity is invalid.`);
|
|
432
|
+
if (protection !== undefined && protection !== "none" && protection !== "required")
|
|
433
|
+
throw metadataError(id, `localData.collections[${index}].protection is invalid.`);
|
|
434
|
+
if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
|
|
435
|
+
throw metadataError(id, `localData.collections[${index}].onProtectionUnavailable is invalid.`);
|
|
436
|
+
if (evictionPriority !== undefined && evictionPriority !== "critical" && evictionPriority !== "normal" && evictionPriority !== "disposable")
|
|
437
|
+
throw metadataError(id, `localData.collections[${index}].evictionPriority is invalid.`);
|
|
438
|
+
return {
|
|
439
|
+
match,
|
|
440
|
+
...sensitivity ? { sensitivity } : {},
|
|
441
|
+
...persistence ? { persistence } : {},
|
|
442
|
+
...protection ? { protection } : {},
|
|
443
|
+
...onProtectionUnavailable ? {
|
|
444
|
+
onProtectionUnavailable
|
|
445
|
+
} : {},
|
|
446
|
+
...evictionPriority ? { evictionPriority } : {},
|
|
447
|
+
...maxAge === undefined ? {} : {
|
|
448
|
+
maxAgeMs: positiveVersion2(maxAge, id, `localData.collections[${index}].maxAgeMs`)
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}) : undefined;
|
|
452
|
+
const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
|
|
453
|
+
const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
|
|
454
|
+
const allowedRuleKeys = new Set([
|
|
455
|
+
"conflict",
|
|
456
|
+
"match",
|
|
457
|
+
"onProtectionUnavailable",
|
|
458
|
+
"persistence",
|
|
459
|
+
"protection",
|
|
460
|
+
"sensitivity"
|
|
461
|
+
]);
|
|
462
|
+
const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
|
|
463
|
+
if (unsupportedRuleKey)
|
|
464
|
+
throw metadataError(id, `localData.mutations[${index}].${unsupportedRuleKey} is not supported.`);
|
|
465
|
+
const protection = unknownField(rule, "protection");
|
|
466
|
+
const sensitivity = unknownField(rule, "sensitivity");
|
|
467
|
+
const persistence = unknownField(rule, "persistence");
|
|
468
|
+
const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
|
|
469
|
+
const declaredConflict = unknownField(rule, "conflict");
|
|
470
|
+
let conflict;
|
|
471
|
+
if (declaredConflict !== undefined) {
|
|
472
|
+
const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
|
|
473
|
+
const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
|
|
474
|
+
if (unsupportedConflictKey)
|
|
475
|
+
throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
|
|
476
|
+
const strategy = unknownField(conflictRecord, "strategy");
|
|
477
|
+
if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
|
|
478
|
+
throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
|
|
479
|
+
const maxAttempts = unknownField(conflictRecord, "maxAttempts");
|
|
480
|
+
if (maxAttempts !== undefined && strategy !== "client-wins")
|
|
481
|
+
throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
|
|
482
|
+
conflict = {
|
|
483
|
+
strategy,
|
|
484
|
+
...maxAttempts === undefined ? {} : {
|
|
485
|
+
maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (protection !== undefined && protection !== "none" && protection !== "required")
|
|
490
|
+
throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
|
|
491
|
+
if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
|
|
492
|
+
throw metadataError(id, `localData.mutations[${index}].sensitivity is invalid.`);
|
|
493
|
+
if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
|
|
494
|
+
throw metadataError(id, `localData.mutations[${index}].onProtectionUnavailable is invalid.`);
|
|
495
|
+
if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
|
|
496
|
+
throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
|
|
497
|
+
return {
|
|
498
|
+
match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
|
|
499
|
+
...conflict ? { conflict } : {},
|
|
500
|
+
...sensitivity ? { sensitivity } : {},
|
|
501
|
+
...onProtectionUnavailable ? { onProtectionUnavailable } : {},
|
|
502
|
+
...persistence ? {
|
|
503
|
+
persistence
|
|
504
|
+
} : {},
|
|
505
|
+
...protection ? { protection } : {}
|
|
506
|
+
};
|
|
507
|
+
}) : undefined;
|
|
508
|
+
const quota = Reflect.get(record, "maxBytesPerNamespace");
|
|
509
|
+
return {
|
|
510
|
+
...collections ? { collections } : {},
|
|
511
|
+
...mutations ? { mutations } : {},
|
|
512
|
+
...quota === undefined ? {} : {
|
|
513
|
+
maxBytesPerNamespace: positiveVersion2(quota, id, "localData.maxBytesPerNamespace")
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}, component = (id, value) => {
|
|
517
|
+
const record = requireObject(value, id, "localSchema must be an object.");
|
|
518
|
+
const allowed = new Set([
|
|
519
|
+
"localData",
|
|
520
|
+
"migrations",
|
|
521
|
+
"minimumCompatibleVersion",
|
|
522
|
+
"version"
|
|
523
|
+
]);
|
|
524
|
+
const unsupported = Object.keys(record).find((key) => !allowed.has(key));
|
|
525
|
+
if (unsupported)
|
|
526
|
+
throw metadataError(id, `${unsupported} is not supported.`);
|
|
527
|
+
const version = positiveVersion2(Reflect.get(record, "version"), id, "version");
|
|
528
|
+
const declaredMinimum = Reflect.get(record, "minimumCompatibleVersion");
|
|
529
|
+
const minimumCompatibleVersion = declaredMinimum === undefined ? Math.max(1, version - 2) : positiveVersion2(declaredMinimum, id, "minimumCompatibleVersion");
|
|
530
|
+
const declaredMigrations = Reflect.get(record, "migrations");
|
|
531
|
+
const declaredLocalData = Reflect.get(record, "localData");
|
|
532
|
+
if (declaredMigrations !== undefined && !Array.isArray(declaredMigrations))
|
|
533
|
+
throw metadataError(id, "migrations must be an array.");
|
|
534
|
+
const migrations = Array.isArray(declaredMigrations) ? declaredMigrations : undefined;
|
|
535
|
+
return {
|
|
536
|
+
id,
|
|
537
|
+
...declaredLocalData === undefined ? {} : { localData: localDataPolicy(declaredLocalData, id) },
|
|
538
|
+
minimumCompatibleVersion,
|
|
539
|
+
...Array.isArray(migrations) ? {
|
|
540
|
+
migrations: migrations.map((entry, index) => migration(entry, id, index))
|
|
541
|
+
} : {},
|
|
542
|
+
version
|
|
543
|
+
};
|
|
544
|
+
}, dependencyNames = (manifest) => [
|
|
545
|
+
Reflect.get(manifest, "dependencies"),
|
|
546
|
+
Reflect.get(manifest, "optionalDependencies"),
|
|
547
|
+
Reflect.get(manifest, "devDependencies"),
|
|
548
|
+
Reflect.get(manifest, "peerDependencies")
|
|
549
|
+
].flatMap((dependencies) => object(dependencies) ? Object.keys(dependencies) : []).filter((name, index, names) => names.indexOf(name) === index).sort(), discoverAbsoluteSyncSchema = (projectRoot) => {
|
|
550
|
+
const appManifestPath = join12(resolve10(projectRoot), "package.json");
|
|
551
|
+
const appManifest = manifestAt(appManifestPath);
|
|
552
|
+
if (!appManifest)
|
|
553
|
+
return {
|
|
554
|
+
components: [
|
|
555
|
+
{
|
|
556
|
+
id: "@absolutejs/app",
|
|
557
|
+
minimumCompatibleVersion: 1,
|
|
558
|
+
version: 1
|
|
559
|
+
}
|
|
560
|
+
],
|
|
561
|
+
sources: []
|
|
562
|
+
};
|
|
563
|
+
const appMetadata = localSchemaMetadata(appManifest);
|
|
564
|
+
const components = [
|
|
565
|
+
appMetadata === undefined ? { id: "@absolutejs/app", minimumCompatibleVersion: 1, version: 1 } : component("@absolutejs/app", appMetadata)
|
|
566
|
+
];
|
|
567
|
+
const sources = [
|
|
568
|
+
{ id: "@absolutejs/app", manifestPath: appManifestPath }
|
|
569
|
+
];
|
|
570
|
+
for (const name of dependencyNames(appManifest)) {
|
|
571
|
+
const manifestPath = packageManifestPath(projectRoot, name);
|
|
572
|
+
if (!manifestPath)
|
|
573
|
+
continue;
|
|
574
|
+
const manifest = manifestAt(manifestPath);
|
|
575
|
+
if (!manifest)
|
|
576
|
+
continue;
|
|
577
|
+
const metadata = localSchemaMetadata(manifest);
|
|
578
|
+
if (metadata === undefined)
|
|
579
|
+
continue;
|
|
580
|
+
components.push(component(name, metadata));
|
|
581
|
+
sources.push({ id: name, manifestPath });
|
|
582
|
+
}
|
|
583
|
+
components.sort((left, right) => left.id.localeCompare(right.id));
|
|
584
|
+
sources.sort((left, right) => left.id.localeCompare(right.id));
|
|
585
|
+
resolveSyncLocalSchemaComponents({}, { components });
|
|
586
|
+
return { components, sources };
|
|
587
|
+
};
|
|
588
|
+
var init_syncSchema = __esm(() => {
|
|
589
|
+
init_client();
|
|
590
|
+
});
|
|
591
|
+
|
|
162
592
|
// src/mobile/artifactStore.ts
|
|
163
593
|
import { createHash as createHash2 } from "crypto";
|
|
164
594
|
import {
|
|
@@ -249,13 +679,19 @@ var parseCompatibilityPage = (value) => {
|
|
|
249
679
|
if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
|
|
250
680
|
throw new TypeError("Compatibility artifact contains an invalid page.");
|
|
251
681
|
}
|
|
682
|
+
const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
|
|
683
|
+
const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
|
|
684
|
+
if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
|
|
685
|
+
throw new TypeError("Compatibility page style hash and path must be provided together.");
|
|
686
|
+
}
|
|
252
687
|
return {
|
|
253
688
|
bundleHash: readString(value.bundleHash, "page.bundleHash"),
|
|
254
689
|
bundlePath: readString(value.bundlePath, "page.bundlePath"),
|
|
255
690
|
contract: readString(value.contract, "page.contract"),
|
|
256
691
|
framework: value.framework,
|
|
257
692
|
pageId: readString(value.pageId, "page.pageId"),
|
|
258
|
-
propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
|
|
693
|
+
propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
|
|
694
|
+
...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
|
|
259
695
|
};
|
|
260
696
|
};
|
|
261
697
|
var parseCompatibilityRoute = (value) => {
|
|
@@ -287,14 +723,23 @@ var validateProducerModule = (module) => {
|
|
|
287
723
|
}
|
|
288
724
|
return module;
|
|
289
725
|
};
|
|
290
|
-
var normalizePage = (page) =>
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
726
|
+
var normalizePage = (page) => {
|
|
727
|
+
if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
|
|
728
|
+
throw new TypeError("Compatibility page style hash and path must be provided together.");
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
|
|
732
|
+
bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
|
|
733
|
+
contract: requireNonEmpty(page.contract, "page.contract"),
|
|
734
|
+
framework: page.framework,
|
|
735
|
+
pageId: requireNonEmpty(page.pageId, "page.pageId"),
|
|
736
|
+
propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
|
|
737
|
+
...page.styleBundleHash && page.styleBundlePath ? {
|
|
738
|
+
styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
|
|
739
|
+
styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
|
|
740
|
+
} : {}
|
|
741
|
+
};
|
|
742
|
+
};
|
|
298
743
|
var normalizeRoute = (route) => {
|
|
299
744
|
if (!route.pattern.startsWith("/")) {
|
|
300
745
|
throw new TypeError("route.pattern must start with /.");
|
|
@@ -617,7 +1062,7 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
|
|
|
617
1062
|
// src/mobile/androidRelease.ts
|
|
618
1063
|
import { createHash as createHash4 } from "crypto";
|
|
619
1064
|
import {
|
|
620
|
-
access as
|
|
1065
|
+
access as access4,
|
|
621
1066
|
copyFile as copyFile2,
|
|
622
1067
|
mkdir as mkdir3,
|
|
623
1068
|
mkdtemp as mkdtemp2,
|
|
@@ -630,6 +1075,7 @@ import {
|
|
|
630
1075
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
631
1076
|
|
|
632
1077
|
// src/mobile/emulatorDoctor.ts
|
|
1078
|
+
import { access } from "fs/promises";
|
|
633
1079
|
import { homedir } from "os";
|
|
634
1080
|
import { join as join2 } from "path";
|
|
635
1081
|
|
|
@@ -647,6 +1093,34 @@ var isWSLEnvironment = () => {
|
|
|
647
1093
|
};
|
|
648
1094
|
|
|
649
1095
|
// src/mobile/emulatorDoctor.ts
|
|
1096
|
+
var ABSOLUTE_ANDROID_AVD_NAME = "AbsoluteJS_API_36";
|
|
1097
|
+
var captureCommand = (command) => {
|
|
1098
|
+
try {
|
|
1099
|
+
const result = Bun.spawnSync(command, {
|
|
1100
|
+
stderr: "ignore",
|
|
1101
|
+
stdout: "pipe"
|
|
1102
|
+
});
|
|
1103
|
+
return {
|
|
1104
|
+
exitCode: result.exitCode,
|
|
1105
|
+
stdout: result.stdout.toString()
|
|
1106
|
+
};
|
|
1107
|
+
} catch {
|
|
1108
|
+
return { exitCode: 1, stdout: "" };
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
var hasAvailableIosRuntime = (output) => {
|
|
1112
|
+
try {
|
|
1113
|
+
const parsed = JSON.parse(output);
|
|
1114
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
1115
|
+
return false;
|
|
1116
|
+
const runtimes = Reflect.get(parsed, "runtimes");
|
|
1117
|
+
if (!Array.isArray(runtimes))
|
|
1118
|
+
return false;
|
|
1119
|
+
return runtimes.some((runtime) => typeof runtime === "object" && runtime !== null && Reflect.get(runtime, "isAvailable") === true && typeof Reflect.get(runtime, "identifier") === "string" && String(Reflect.get(runtime, "identifier")).includes("iOS"));
|
|
1120
|
+
} catch {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
650
1124
|
var windowsPathToWsl = (path) => {
|
|
651
1125
|
const match = /^([a-z]):[\\/](.*)$/i.exec(path.trim());
|
|
652
1126
|
if (!match)
|
|
@@ -678,6 +1152,14 @@ var absoluteManagedAndroidSdkRoot = (host, env = process.env) => {
|
|
|
678
1152
|
}
|
|
679
1153
|
return join2(homedir(), ".absolutejs", "android-sdk");
|
|
680
1154
|
};
|
|
1155
|
+
var pathExists = async (path) => {
|
|
1156
|
+
try {
|
|
1157
|
+
await access(path);
|
|
1158
|
+
return true;
|
|
1159
|
+
} catch {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
681
1163
|
var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
|
|
682
1164
|
if (platform === "darwin")
|
|
683
1165
|
return "macos";
|
|
@@ -687,10 +1169,151 @@ var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironm
|
|
|
687
1169
|
return "wsl";
|
|
688
1170
|
return "linux";
|
|
689
1171
|
};
|
|
1172
|
+
var executableNames = (host, name) => {
|
|
1173
|
+
if (host === "wsl")
|
|
1174
|
+
return [`${name}.exe`, `${name}.bat`, name];
|
|
1175
|
+
if (host === "windows")
|
|
1176
|
+
return [name, `${name}.exe`, `${name}.bat`];
|
|
1177
|
+
return [name];
|
|
1178
|
+
};
|
|
1179
|
+
var findExecutable = async (name, paths, options, host) => {
|
|
1180
|
+
const existing = await Promise.all(paths.map(async (path) => await options.exists(path) ? path : undefined));
|
|
1181
|
+
const configured = existing.find((path) => path !== undefined);
|
|
1182
|
+
if (configured)
|
|
1183
|
+
return configured;
|
|
1184
|
+
for (const candidate of executableNames(host, name)) {
|
|
1185
|
+
const path = options.which(candidate);
|
|
1186
|
+
if (path)
|
|
1187
|
+
return path;
|
|
1188
|
+
}
|
|
1189
|
+
return;
|
|
1190
|
+
};
|
|
1191
|
+
var toolCheck = (id, label, platform, path, remediation) => path ? {
|
|
1192
|
+
id,
|
|
1193
|
+
label,
|
|
1194
|
+
path,
|
|
1195
|
+
platform,
|
|
1196
|
+
status: "pass"
|
|
1197
|
+
} : {
|
|
1198
|
+
id,
|
|
1199
|
+
label,
|
|
1200
|
+
platform,
|
|
1201
|
+
remediation,
|
|
1202
|
+
status: "fail"
|
|
1203
|
+
};
|
|
1204
|
+
var inspectAbsoluteMobileToolchain = async (input = {}) => {
|
|
1205
|
+
const env = input.env ?? process.env;
|
|
1206
|
+
const host = input.host ?? detectAbsoluteMobileHost();
|
|
1207
|
+
const exists = input.exists ?? pathExists;
|
|
1208
|
+
const which = input.which ?? ((command) => Bun.which(command));
|
|
1209
|
+
const capture = input.capture ?? captureCommand;
|
|
1210
|
+
const androidRoot = input.androidRoot === null ? undefined : input.androidRoot ?? env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host, env);
|
|
1211
|
+
const windowsAndroidTools = host === "windows" || host === "wsl";
|
|
1212
|
+
const android = (segments) => androidRoot ? join2(androidRoot, ...segments) : undefined;
|
|
1213
|
+
const paths = (values) => values.filter((value) => Boolean(value));
|
|
1214
|
+
const adb = await findExecutable("adb", paths([
|
|
1215
|
+
android(["platform-tools", windowsAndroidTools ? "adb.exe" : "adb"])
|
|
1216
|
+
]), { exists, which }, host);
|
|
1217
|
+
const emulator = await findExecutable("emulator", paths([
|
|
1218
|
+
android([
|
|
1219
|
+
"emulator",
|
|
1220
|
+
windowsAndroidTools ? "emulator.exe" : "emulator"
|
|
1221
|
+
])
|
|
1222
|
+
]), { exists, which }, host);
|
|
1223
|
+
const sdkmanager = await findExecutable("sdkmanager", paths([
|
|
1224
|
+
android([
|
|
1225
|
+
"cmdline-tools",
|
|
1226
|
+
"latest",
|
|
1227
|
+
"bin",
|
|
1228
|
+
windowsAndroidTools ? "sdkmanager.bat" : "sdkmanager"
|
|
1229
|
+
])
|
|
1230
|
+
]), { exists, which }, host);
|
|
1231
|
+
const avdmanager = await findExecutable("avdmanager", paths([
|
|
1232
|
+
android([
|
|
1233
|
+
"cmdline-tools",
|
|
1234
|
+
"latest",
|
|
1235
|
+
"bin",
|
|
1236
|
+
windowsAndroidTools ? "avdmanager.bat" : "avdmanager"
|
|
1237
|
+
])
|
|
1238
|
+
]), { exists, which }, host);
|
|
1239
|
+
const java = await findExecutable("java", [], { exists, which }, host);
|
|
1240
|
+
const checks = [
|
|
1241
|
+
{
|
|
1242
|
+
id: "host",
|
|
1243
|
+
label: `Development host: ${host}`,
|
|
1244
|
+
platform: "host",
|
|
1245
|
+
status: "pass"
|
|
1246
|
+
},
|
|
1247
|
+
toolCheck("android.adb", "Android Debug Bridge", "android", adb, "Install Android SDK Platform Tools or expose adb on PATH."),
|
|
1248
|
+
toolCheck("android.emulator", "Android Emulator", "android", emulator, "Install the Android Emulator from Android Studio SDK Manager."),
|
|
1249
|
+
toolCheck("android.sdkmanager", "Android SDK Manager", "android", sdkmanager, "Install Android SDK Command-line Tools (latest)."),
|
|
1250
|
+
toolCheck("android.avdmanager", "Android Virtual Device Manager", "android", avdmanager, "Install Android SDK Command-line Tools (latest)."),
|
|
1251
|
+
toolCheck("android.java", "Java runtime", "android", java, "Install the JDK required by the configured Android Gradle plugin.")
|
|
1252
|
+
];
|
|
1253
|
+
if (emulator) {
|
|
1254
|
+
const avds = capture([emulator, "-list-avds"]);
|
|
1255
|
+
const hasManagedAvd = avds.exitCode === 0 && avds.stdout.split(/\r?\n/).includes(ABSOLUTE_ANDROID_AVD_NAME);
|
|
1256
|
+
checks.push({
|
|
1257
|
+
id: "android.avd",
|
|
1258
|
+
label: `AbsoluteJS Android emulator (${ABSOLUTE_ANDROID_AVD_NAME})`,
|
|
1259
|
+
platform: "android",
|
|
1260
|
+
remediation: hasManagedAvd ? undefined : "Run absolute mobile doctor android --fix to provision the managed emulator.",
|
|
1261
|
+
status: hasManagedAvd ? "pass" : "fail"
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
if (host === "wsl") {
|
|
1265
|
+
checks.push({
|
|
1266
|
+
id: "android.virtualization",
|
|
1267
|
+
label: adb?.endsWith(".exe") ? "Windows-host Android bridge available to WSL" : "WSL requires a Windows-host emulator bridge or Linux KVM",
|
|
1268
|
+
platform: "android",
|
|
1269
|
+
remediation: adb?.endsWith(".exe") ? undefined : "Expose the Windows Android SDK adb.exe to WSL, or enable /dev/kvm for a Linux SDK.",
|
|
1270
|
+
status: adb?.endsWith(".exe") ? "pass" : "warn"
|
|
1271
|
+
});
|
|
1272
|
+
} else if (host === "linux") {
|
|
1273
|
+
const hasKvm = await exists("/dev/kvm");
|
|
1274
|
+
checks.push({
|
|
1275
|
+
id: "android.virtualization",
|
|
1276
|
+
label: "Linux KVM acceleration",
|
|
1277
|
+
platform: "android",
|
|
1278
|
+
remediation: hasKvm ? undefined : "Enable KVM and grant the current user access to /dev/kvm.",
|
|
1279
|
+
status: hasKvm ? "pass" : "warn"
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
if (host !== "macos") {
|
|
1283
|
+
checks.push({
|
|
1284
|
+
id: "ios.simulator",
|
|
1285
|
+
label: "iOS Simulator requires macOS and Xcode",
|
|
1286
|
+
platform: "ios",
|
|
1287
|
+
status: "skip"
|
|
1288
|
+
});
|
|
1289
|
+
return checks;
|
|
1290
|
+
}
|
|
1291
|
+
const xcrun = await findExecutable("xcrun", [], { exists, which }, host);
|
|
1292
|
+
const xcodebuild = await findExecutable("xcodebuild", [], { exists, which }, host);
|
|
1293
|
+
checks.push(toolCheck("ios.xcrun", "Xcode command runner", "ios", xcrun, "Install Xcode and select it with xcode-select."), toolCheck("ios.xcodebuild", "Xcode build system", "ios", xcodebuild, "Install Xcode and select it with xcode-select."));
|
|
1294
|
+
if (xcrun) {
|
|
1295
|
+
const runtimes = capture([
|
|
1296
|
+
xcrun,
|
|
1297
|
+
"simctl",
|
|
1298
|
+
"list",
|
|
1299
|
+
"runtimes",
|
|
1300
|
+
"--json"
|
|
1301
|
+
]);
|
|
1302
|
+
const hasRuntime = runtimes.exitCode === 0 && hasAvailableIosRuntime(runtimes.stdout);
|
|
1303
|
+
checks.push({
|
|
1304
|
+
id: "ios.runtime",
|
|
1305
|
+
label: "iOS Simulator runtime",
|
|
1306
|
+
platform: "ios",
|
|
1307
|
+
remediation: hasRuntime ? undefined : "Run absolute mobile doctor ios --fix to download an iOS Simulator runtime.",
|
|
1308
|
+
status: hasRuntime ? "pass" : "fail"
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
return checks;
|
|
1312
|
+
};
|
|
690
1313
|
|
|
691
1314
|
// src/mobile/androidEmulatorController.ts
|
|
692
1315
|
import {
|
|
693
|
-
access as
|
|
1316
|
+
access as access3,
|
|
694
1317
|
copyFile,
|
|
695
1318
|
lstat,
|
|
696
1319
|
mkdir as mkdir2,
|
|
@@ -714,7 +1337,7 @@ import {
|
|
|
714
1337
|
} from "path";
|
|
715
1338
|
|
|
716
1339
|
// src/mobile/capacitorProject.ts
|
|
717
|
-
import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1340
|
+
import { access as access2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
718
1341
|
import { relative, resolve } from "path";
|
|
719
1342
|
var CONFIG_FILE = "capacitor.config.ts";
|
|
720
1343
|
var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
|
|
@@ -736,7 +1359,7 @@ export default config;
|
|
|
736
1359
|
`;
|
|
737
1360
|
var exists = async (path) => {
|
|
738
1361
|
try {
|
|
739
|
-
await
|
|
1362
|
+
await access2(path);
|
|
740
1363
|
return true;
|
|
741
1364
|
} catch {
|
|
742
1365
|
return false;
|
|
@@ -766,9 +1389,9 @@ var HASH_RADIX = 16;
|
|
|
766
1389
|
var EXECUTABLE_MODE_MASK = 73;
|
|
767
1390
|
var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
|
|
768
1391
|
var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
|
|
769
|
-
var
|
|
1392
|
+
var pathExists2 = async (path) => {
|
|
770
1393
|
try {
|
|
771
|
-
await
|
|
1394
|
+
await access3(path);
|
|
772
1395
|
return true;
|
|
773
1396
|
} catch {
|
|
774
1397
|
return false;
|
|
@@ -795,7 +1418,7 @@ var runCommand = async (command, options = {}) => {
|
|
|
795
1418
|
]);
|
|
796
1419
|
return exitCode;
|
|
797
1420
|
};
|
|
798
|
-
var
|
|
1421
|
+
var captureCommand2 = (command, options = {}) => {
|
|
799
1422
|
try {
|
|
800
1423
|
const result = Bun.spawnSync(command, {
|
|
801
1424
|
cwd: options.cwd,
|
|
@@ -881,11 +1504,11 @@ var hashNativeTree = async (root, label, ignorePublicBundle) => {
|
|
|
881
1504
|
const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
|
|
882
1505
|
return createHash3("sha256").update(records.join("")).digest("hex");
|
|
883
1506
|
};
|
|
884
|
-
var fingerprintAbsoluteAndroidNativeProject = async (project) => {
|
|
1507
|
+
var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
|
|
885
1508
|
const { dependencies } = await nativeDependencySources(project.nativeDirectory);
|
|
886
1509
|
const roots = [
|
|
887
1510
|
{
|
|
888
|
-
ignorePublicBundle: true,
|
|
1511
|
+
ignorePublicBundle: options.includePublicBundle !== true,
|
|
889
1512
|
label: "android",
|
|
890
1513
|
source: project.nativeDirectory
|
|
891
1514
|
},
|
|
@@ -965,14 +1588,14 @@ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
|
|
|
965
1588
|
};
|
|
966
1589
|
var resolveGradleArtifactPath = async (nativeDirectory, task) => {
|
|
967
1590
|
const primary = gradleArtifactPath(nativeDirectory, task);
|
|
968
|
-
if (task !== "assembleRelease" || await
|
|
1591
|
+
if (task !== "assembleRelease" || await pathExists2(primary)) {
|
|
969
1592
|
return primary;
|
|
970
1593
|
}
|
|
971
1594
|
return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
|
|
972
1595
|
};
|
|
973
1596
|
var buildAbsoluteAndroidGradleArtifact = async (options) => {
|
|
974
1597
|
const { project, task } = options;
|
|
975
|
-
const capture = options.capture ??
|
|
1598
|
+
const capture = options.capture ?? captureCommand2;
|
|
976
1599
|
const run = options.run ?? runCommand;
|
|
977
1600
|
const env = options.env ?? process.env;
|
|
978
1601
|
const gradleArguments = options.gradleArguments ?? [];
|
|
@@ -1015,9 +1638,9 @@ var requireManifest = (value) => {
|
|
|
1015
1638
|
runtime: value.runtime
|
|
1016
1639
|
};
|
|
1017
1640
|
};
|
|
1018
|
-
var
|
|
1641
|
+
var pathExists3 = async (path) => {
|
|
1019
1642
|
try {
|
|
1020
|
-
await
|
|
1643
|
+
await access4(path);
|
|
1021
1644
|
return true;
|
|
1022
1645
|
} catch {
|
|
1023
1646
|
return false;
|
|
@@ -1072,7 +1695,7 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
|
|
|
1072
1695
|
const releaseRoot = join4(outputRoot, metadata.releaseId);
|
|
1073
1696
|
const artifactName = "app-release.aab";
|
|
1074
1697
|
const destination = join4(releaseRoot, artifactName);
|
|
1075
|
-
if (await
|
|
1698
|
+
if (await pathExists3(releaseRoot)) {
|
|
1076
1699
|
const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
|
|
1077
1700
|
const [installedBytes, installedSha256] = await Promise.all([
|
|
1078
1701
|
stat(destination).then(({ size }) => size),
|
|
@@ -1148,7 +1771,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
1148
1771
|
run: options.run,
|
|
1149
1772
|
task: "bundleRelease"
|
|
1150
1773
|
});
|
|
1151
|
-
if (!await
|
|
1774
|
+
if (!await pathExists3(artifactPath)) {
|
|
1152
1775
|
throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
|
|
1153
1776
|
}
|
|
1154
1777
|
const capture = options.capture ?? defaultCapture;
|
|
@@ -1183,7 +1806,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
1183
1806
|
// src/mobile/iosRelease.ts
|
|
1184
1807
|
import { createHash as createHash5 } from "crypto";
|
|
1185
1808
|
import {
|
|
1186
|
-
access as
|
|
1809
|
+
access as access5,
|
|
1187
1810
|
copyFile as copyFile3,
|
|
1188
1811
|
mkdir as mkdir4,
|
|
1189
1812
|
mkdtemp as mkdtemp3,
|
|
@@ -1207,9 +1830,9 @@ var requireManifest2 = (value) => {
|
|
|
1207
1830
|
runtime: value.runtime
|
|
1208
1831
|
};
|
|
1209
1832
|
};
|
|
1210
|
-
var
|
|
1833
|
+
var pathExists4 = async (path) => {
|
|
1211
1834
|
try {
|
|
1212
|
-
await
|
|
1835
|
+
await access5(path);
|
|
1213
1836
|
return true;
|
|
1214
1837
|
} catch {
|
|
1215
1838
|
return false;
|
|
@@ -1290,7 +1913,7 @@ var safeOutputDirectory2 = (projectRoot, requested) => {
|
|
|
1290
1913
|
};
|
|
1291
1914
|
var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
|
|
1292
1915
|
var findByExtension = async (root, extension) => {
|
|
1293
|
-
if (!await
|
|
1916
|
+
if (!await pathExists4(root))
|
|
1294
1917
|
return;
|
|
1295
1918
|
const entries = await readdir3(root, { withFileTypes: true });
|
|
1296
1919
|
const matches = await Promise.all(entries.map(async (entry) => {
|
|
@@ -1322,7 +1945,7 @@ var requireBuildNumber = (value) => {
|
|
|
1322
1945
|
var installRelease2 = async (artifactPath, metadata, outputRoot) => {
|
|
1323
1946
|
const releaseRoot = join5(outputRoot, metadata.releaseId);
|
|
1324
1947
|
const destination = join5(releaseRoot, "App.ipa");
|
|
1325
|
-
if (await
|
|
1948
|
+
if (await pathExists4(releaseRoot)) {
|
|
1326
1949
|
const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
|
|
1327
1950
|
if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
|
|
1328
1951
|
throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
|
|
@@ -1459,30 +2082,1502 @@ var buildAbsoluteIosRelease = async (options) => {
|
|
|
1459
2082
|
await rm4(staging, { force: true, recursive: true }).catch(() => {
|
|
1460
2083
|
return;
|
|
1461
2084
|
});
|
|
1462
|
-
}
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
// src/mobile/iosConformance.ts
|
|
2088
|
+
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
2089
|
+
var HMR_LINE = new RegExp(String.raw`\[hmr:ios\]\s+([^\n]*?)\s+(applied in|falling back to reload after|failed after)\s+(\d+)ms(?:; server\s+(\d+)ms, client\s+(\d+)ms)?`, "u");
|
|
2090
|
+
var parseAbsoluteIosHmrLog = (line) => {
|
|
2091
|
+
const match = HMR_LINE.exec(line);
|
|
2092
|
+
if (!match)
|
|
2093
|
+
return null;
|
|
2094
|
+
const [, , action, durationValue, serverValue, clientValue] = match;
|
|
2095
|
+
let outcome = "reloaded";
|
|
2096
|
+
if (action === "applied in")
|
|
2097
|
+
outcome = "applied";
|
|
2098
|
+
if (action === "failed after")
|
|
2099
|
+
outcome = "failed";
|
|
2100
|
+
const serverMs = serverValue === undefined ? undefined : Number(serverValue);
|
|
2101
|
+
const clientMs = clientValue === undefined ? undefined : Number(clientValue);
|
|
2102
|
+
return {
|
|
2103
|
+
...clientMs === undefined ? {} : { clientMs },
|
|
2104
|
+
duration: Number(durationValue),
|
|
2105
|
+
line: match[0],
|
|
2106
|
+
outcome,
|
|
2107
|
+
...serverMs === undefined ? {} : { serverMs }
|
|
2108
|
+
};
|
|
2109
|
+
};
|
|
2110
|
+
var findHmrApply = (lines) => {
|
|
2111
|
+
const apply = lines.map((line) => parseAbsoluteIosHmrLog(line)).find((candidate) => candidate !== null);
|
|
2112
|
+
if (apply?.outcome === "failed")
|
|
2113
|
+
throw new Error(`iOS HMR client reported a failed apply: ${apply.line}`);
|
|
2114
|
+
return apply;
|
|
2115
|
+
};
|
|
2116
|
+
var waitForAbsoluteIosHmrLog = async (options) => {
|
|
2117
|
+
const sleep = options.sleep ?? Bun.sleep;
|
|
2118
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
2119
|
+
const deadline = Date.now() + timeoutMs;
|
|
2120
|
+
let offset = options.startOffset ?? await stat3(options.logPath).then(({ size }) => size).catch(() => 0);
|
|
2121
|
+
let buffered = "";
|
|
2122
|
+
const poll = async () => {
|
|
2123
|
+
if (Date.now() > deadline)
|
|
2124
|
+
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
2125
|
+
options.signal?.throwIfAborted();
|
|
2126
|
+
const contents = await readFile6(options.logPath).catch(() => Buffer.alloc(0));
|
|
2127
|
+
if (contents.byteLength < offset) {
|
|
2128
|
+
offset = 0;
|
|
2129
|
+
buffered = "";
|
|
2130
|
+
}
|
|
2131
|
+
if (contents.byteLength > offset) {
|
|
2132
|
+
buffered += contents.subarray(offset).toString("utf8");
|
|
2133
|
+
offset = contents.byteLength;
|
|
2134
|
+
const lines = buffered.split(/\r?\n/u);
|
|
2135
|
+
buffered = lines.pop() ?? "";
|
|
2136
|
+
const apply = findHmrApply(lines);
|
|
2137
|
+
if (apply)
|
|
2138
|
+
return apply;
|
|
2139
|
+
}
|
|
2140
|
+
await sleep(100);
|
|
2141
|
+
return poll();
|
|
2142
|
+
};
|
|
2143
|
+
return poll();
|
|
2144
|
+
};
|
|
2145
|
+
// src/mobile/iosSimulatorController.ts
|
|
2146
|
+
import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
|
|
2147
|
+
import {
|
|
2148
|
+
access as access6,
|
|
2149
|
+
copyFile as copyFile4,
|
|
2150
|
+
mkdir as mkdir5,
|
|
2151
|
+
readFile as readFile7,
|
|
2152
|
+
rename as rename6,
|
|
2153
|
+
rm as rm5,
|
|
2154
|
+
writeFile as writeFile6
|
|
2155
|
+
} from "fs/promises";
|
|
2156
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve5, sep as sep4 } from "path";
|
|
2157
|
+
init_getDurationString();
|
|
2158
|
+
var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone";
|
|
2159
|
+
var BOOT_TIMEOUT_MS = 180000;
|
|
2160
|
+
var BOOT_POLL_MS = 1000;
|
|
2161
|
+
var DEV_JOURNAL_FORMAT = 1;
|
|
2162
|
+
var NATIVE_CACHE_FORMAT = 1;
|
|
2163
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2164
|
+
var pathExists5 = async (path) => {
|
|
2165
|
+
try {
|
|
2166
|
+
await access6(path);
|
|
2167
|
+
return true;
|
|
2168
|
+
} catch {
|
|
2169
|
+
return false;
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
var throwIfAborted2 = (signal) => signal?.throwIfAborted();
|
|
2173
|
+
var defaultCapture3 = (command, options = {}) => {
|
|
2174
|
+
try {
|
|
2175
|
+
const result = Bun.spawnSync(command, {
|
|
2176
|
+
cwd: options.cwd,
|
|
2177
|
+
env: options.env,
|
|
2178
|
+
stderr: "pipe",
|
|
2179
|
+
stdin: "ignore",
|
|
2180
|
+
stdout: "pipe"
|
|
2181
|
+
});
|
|
2182
|
+
return {
|
|
2183
|
+
exitCode: result.exitCode,
|
|
2184
|
+
stderr: result.stderr.toString(),
|
|
2185
|
+
stdout: result.stdout.toString()
|
|
2186
|
+
};
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
return {
|
|
2189
|
+
exitCode: 1,
|
|
2190
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
2191
|
+
stdout: ""
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
var defaultRun2 = async (command, options = {}) => {
|
|
2196
|
+
const process2 = Bun.spawn(command, {
|
|
2197
|
+
cwd: options.cwd,
|
|
2198
|
+
env: options.env,
|
|
2199
|
+
signal: options.signal,
|
|
2200
|
+
stderr: "inherit",
|
|
2201
|
+
stdin: "inherit",
|
|
2202
|
+
stdout: "inherit"
|
|
2203
|
+
});
|
|
2204
|
+
return process2.exited;
|
|
2205
|
+
};
|
|
2206
|
+
var defaultSpawn = (command, options = {}) => {
|
|
2207
|
+
Bun.spawn(command, {
|
|
2208
|
+
cwd: options.cwd,
|
|
2209
|
+
env: options.env,
|
|
2210
|
+
signal: options.signal,
|
|
2211
|
+
stderr: "ignore",
|
|
2212
|
+
stdin: "ignore",
|
|
2213
|
+
stdout: "ignore"
|
|
2214
|
+
});
|
|
2215
|
+
};
|
|
2216
|
+
var consumeLines = async (stream, onLine) => {
|
|
2217
|
+
const reader = stream.getReader();
|
|
2218
|
+
const decoder = new TextDecoder;
|
|
2219
|
+
let buffered = "";
|
|
2220
|
+
const pump = async () => {
|
|
2221
|
+
const { done, value } = await reader.read();
|
|
2222
|
+
if (done)
|
|
2223
|
+
return;
|
|
2224
|
+
buffered += decoder.decode(value, { stream: true });
|
|
2225
|
+
const lines = buffered.split(/\r?\n/u);
|
|
2226
|
+
buffered = lines.pop() ?? "";
|
|
2227
|
+
lines.forEach(onLine);
|
|
2228
|
+
await pump();
|
|
2229
|
+
};
|
|
2230
|
+
try {
|
|
2231
|
+
await pump();
|
|
2232
|
+
buffered += decoder.decode();
|
|
2233
|
+
if (buffered)
|
|
2234
|
+
onLine(buffered);
|
|
2235
|
+
} finally {
|
|
2236
|
+
reader.releaseLock();
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
var defaultStartNativeLogs = (command, options, onLine) => {
|
|
2240
|
+
const process2 = Bun.spawn(command, {
|
|
2241
|
+
cwd: options.cwd,
|
|
2242
|
+
env: options.env,
|
|
2243
|
+
signal: options.signal,
|
|
2244
|
+
stderr: "pipe",
|
|
2245
|
+
stdin: "ignore",
|
|
2246
|
+
stdout: "pipe"
|
|
2247
|
+
});
|
|
2248
|
+
consumeLines(process2.stdout, onLine);
|
|
2249
|
+
consumeLines(process2.stderr, onLine);
|
|
2250
|
+
return {
|
|
2251
|
+
close: async () => {
|
|
2252
|
+
try {
|
|
2253
|
+
process2.kill();
|
|
2254
|
+
} catch {}
|
|
2255
|
+
await process2.exited.catch(() => {
|
|
2256
|
+
return;
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
};
|
|
2261
|
+
var requireSuccess2 = async (command, label, run, options) => {
|
|
2262
|
+
const exitCode = await run(command, options);
|
|
2263
|
+
if (exitCode !== 0)
|
|
2264
|
+
throw new Error(`${label} failed with status ${exitCode}.`);
|
|
2265
|
+
};
|
|
2266
|
+
var requireCapturedSuccess = (result, label) => {
|
|
2267
|
+
if (result.exitCode !== 0) {
|
|
2268
|
+
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
2269
|
+
}
|
|
2270
|
+
return result.stdout.trim();
|
|
2271
|
+
};
|
|
2272
|
+
var parseJson = (source, label) => {
|
|
2273
|
+
try {
|
|
2274
|
+
const parsed = JSON.parse(source);
|
|
2275
|
+
if (isRecord3(parsed))
|
|
2276
|
+
return parsed;
|
|
2277
|
+
} catch {}
|
|
2278
|
+
throw new Error(`Invalid ${label} JSON from simctl.`);
|
|
2279
|
+
};
|
|
2280
|
+
var parseIosDeviceTypes = (source) => {
|
|
2281
|
+
const parsed = parseJson(source, "device type");
|
|
2282
|
+
const types = parsed.devicetypes;
|
|
2283
|
+
if (!Array.isArray(types))
|
|
2284
|
+
return [];
|
|
2285
|
+
return types.flatMap((type) => {
|
|
2286
|
+
if (!isRecord3(type))
|
|
2287
|
+
return [];
|
|
2288
|
+
const { identifier } = type;
|
|
2289
|
+
const { name } = type;
|
|
2290
|
+
return typeof identifier === "string" && typeof name === "string" ? [{ identifier, name }] : [];
|
|
2291
|
+
});
|
|
2292
|
+
};
|
|
2293
|
+
var parseIosRuntimes = (source) => {
|
|
2294
|
+
const parsed = parseJson(source, "runtime");
|
|
2295
|
+
const { runtimes } = parsed;
|
|
2296
|
+
if (!Array.isArray(runtimes))
|
|
2297
|
+
return [];
|
|
2298
|
+
return runtimes.flatMap((runtime) => {
|
|
2299
|
+
if (!isRecord3(runtime))
|
|
2300
|
+
return [];
|
|
2301
|
+
const { identifier } = runtime;
|
|
2302
|
+
const { name } = runtime;
|
|
2303
|
+
const { version } = runtime;
|
|
2304
|
+
if (typeof identifier !== "string" || typeof name !== "string" || typeof version !== "string")
|
|
2305
|
+
return [];
|
|
2306
|
+
return [
|
|
2307
|
+
{
|
|
2308
|
+
identifier,
|
|
2309
|
+
isAvailable: runtime.isAvailable !== false,
|
|
2310
|
+
name,
|
|
2311
|
+
version
|
|
2312
|
+
}
|
|
2313
|
+
];
|
|
2314
|
+
});
|
|
2315
|
+
};
|
|
2316
|
+
var parseIosSimulators = (source) => {
|
|
2317
|
+
const parsed = parseJson(source, "device");
|
|
2318
|
+
const { devices } = parsed;
|
|
2319
|
+
if (!isRecord3(devices))
|
|
2320
|
+
return [];
|
|
2321
|
+
return Object.entries(devices).flatMap(([runtime, values]) => {
|
|
2322
|
+
if (!Array.isArray(values))
|
|
2323
|
+
return [];
|
|
2324
|
+
return values.flatMap((device) => {
|
|
2325
|
+
if (!isRecord3(device))
|
|
2326
|
+
return [];
|
|
2327
|
+
const { name } = device;
|
|
2328
|
+
const { state } = device;
|
|
2329
|
+
const { udid } = device;
|
|
2330
|
+
if (typeof name !== "string" || typeof state !== "string" || typeof udid !== "string")
|
|
2331
|
+
return [];
|
|
2332
|
+
return [
|
|
2333
|
+
{
|
|
2334
|
+
isAvailable: device.isAvailable !== false,
|
|
2335
|
+
name,
|
|
2336
|
+
runtime,
|
|
2337
|
+
state,
|
|
2338
|
+
udid
|
|
2339
|
+
}
|
|
2340
|
+
];
|
|
2341
|
+
});
|
|
2342
|
+
});
|
|
2343
|
+
};
|
|
2344
|
+
var versionParts = (version) => version.split(".").map((part) => Number(part));
|
|
2345
|
+
var compareVersions = (left, right) => {
|
|
2346
|
+
const leftParts = versionParts(left);
|
|
2347
|
+
const rightParts = versionParts(right);
|
|
2348
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
2349
|
+
for (let index = 0;index < length; index++) {
|
|
2350
|
+
const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
2351
|
+
if (difference !== 0)
|
|
2352
|
+
return difference;
|
|
2353
|
+
}
|
|
2354
|
+
return 0;
|
|
2355
|
+
};
|
|
2356
|
+
var latestIosRuntime = (runtimes) => runtimes.filter((runtime) => runtime.isAvailable && runtime.identifier.includes("SimRuntime.iOS-")).sort((left, right) => compareVersions(right.version, left.version))[0];
|
|
2357
|
+
var iphoneGeneration = (name) => Number(/iPhone\s+(\d+)/u.exec(name)?.[1] ?? 0);
|
|
2358
|
+
var preferredIphoneType = (types) => types.filter((type) => type.name.startsWith("iPhone")).sort((left, right) => {
|
|
2359
|
+
const generation = iphoneGeneration(right.name) - iphoneGeneration(left.name);
|
|
2360
|
+
if (generation !== 0)
|
|
2361
|
+
return generation;
|
|
2362
|
+
const rightPro = right.name.includes("Pro") ? 1 : 0;
|
|
2363
|
+
const leftPro = left.name.includes("Pro") ? 1 : 0;
|
|
2364
|
+
return rightPro - leftPro;
|
|
2365
|
+
})[0];
|
|
2366
|
+
var journalPaths = (projectRoot) => {
|
|
2367
|
+
const root = join6(projectRoot, ".absolutejs", "mobile", "ios-dev-session");
|
|
2368
|
+
return {
|
|
2369
|
+
configBackup: join6(root, "capacitor-config.backup"),
|
|
2370
|
+
infoBackup: join6(root, "Info.plist.backup"),
|
|
2371
|
+
journal: join6(root, "journal.json"),
|
|
2372
|
+
root
|
|
2373
|
+
};
|
|
2374
|
+
};
|
|
2375
|
+
var nativeCachePath = (projectRoot) => join6(projectRoot, ".absolutejs", "mobile", "ios-native-cache.json");
|
|
2376
|
+
var isInside = (root, path) => {
|
|
2377
|
+
const value = relative5(resolve5(root), resolve5(path));
|
|
2378
|
+
return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute4(value);
|
|
2379
|
+
};
|
|
2380
|
+
var parseJournal = (value) => {
|
|
2381
|
+
if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT)
|
|
2382
|
+
return null;
|
|
2383
|
+
const { configBackupPath } = value;
|
|
2384
|
+
const { infoBackupPath } = value;
|
|
2385
|
+
const { infoPath } = value;
|
|
2386
|
+
const { nativeConfigPath } = value;
|
|
2387
|
+
if (typeof configBackupPath !== "string" || typeof infoBackupPath !== "string" || typeof infoPath !== "string" || typeof nativeConfigPath !== "string")
|
|
2388
|
+
return null;
|
|
2389
|
+
return {
|
|
2390
|
+
configBackupPath,
|
|
2391
|
+
format: DEV_JOURNAL_FORMAT,
|
|
2392
|
+
infoBackupPath,
|
|
2393
|
+
infoPath,
|
|
2394
|
+
nativeConfigPath
|
|
2395
|
+
};
|
|
2396
|
+
};
|
|
2397
|
+
var repairAbsoluteIosDevSession = async (projectRoot) => {
|
|
2398
|
+
const paths = journalPaths(projectRoot);
|
|
2399
|
+
if (!await pathExists5(paths.journal)) {
|
|
2400
|
+
await rm5(paths.root, { force: true, recursive: true });
|
|
2401
|
+
return false;
|
|
2402
|
+
}
|
|
2403
|
+
const journal = await readFile7(paths.journal, "utf8").then((source) => parseJournal(JSON.parse(source))).catch(() => null);
|
|
2404
|
+
if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(projectRoot, journal.infoPath) || !isInside(paths.root, journal.configBackupPath) || !isInside(paths.root, journal.infoBackupPath)) {
|
|
2405
|
+
throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
|
|
2406
|
+
}
|
|
2407
|
+
if (await pathExists5(journal.configBackupPath))
|
|
2408
|
+
await copyFile4(journal.configBackupPath, journal.nativeConfigPath);
|
|
2409
|
+
if (await pathExists5(journal.infoBackupPath))
|
|
2410
|
+
await copyFile4(journal.infoBackupPath, journal.infoPath);
|
|
2411
|
+
await rm5(paths.root, { force: true, recursive: true });
|
|
2412
|
+
return true;
|
|
2413
|
+
};
|
|
2414
|
+
var iosDevelopmentInfoPlist = (source, cleartext) => {
|
|
2415
|
+
if (!cleartext)
|
|
2416
|
+
return source;
|
|
2417
|
+
const arbitraryLoads = /(<key>NSAllowsArbitraryLoads<\/key>\s*)<false\s*\/>/u;
|
|
2418
|
+
if (arbitraryLoads.test(source))
|
|
2419
|
+
return source.replace(arbitraryLoads, "$1<true/>");
|
|
2420
|
+
if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(source))
|
|
2421
|
+
return source;
|
|
2422
|
+
const transport = /(<key>NSAppTransportSecurity<\/key>\s*<dict>)/u;
|
|
2423
|
+
if (transport.test(source))
|
|
2424
|
+
return source.replace(transport, `$1
|
|
2425
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
2426
|
+
<true/>`);
|
|
2427
|
+
return source.replace(/<dict>/u, `<dict>
|
|
2428
|
+
<key>NSAppTransportSecurity</key>
|
|
2429
|
+
<dict>
|
|
2430
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
2431
|
+
<true/>
|
|
2432
|
+
</dict>`);
|
|
2433
|
+
};
|
|
2434
|
+
var writeDevProjection = async (project, port, https) => {
|
|
2435
|
+
const paths = journalPaths(project.projectRoot);
|
|
2436
|
+
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
2437
|
+
const nativeConfigPath = join6(project.nativeDirectory, "App", "App", "capacitor.config.json");
|
|
2438
|
+
const infoPath = join6(project.nativeDirectory, "App", "App", "Info.plist");
|
|
2439
|
+
const [configSource, infoSource] = await Promise.all([
|
|
2440
|
+
readFile7(nativeConfigPath, "utf8"),
|
|
2441
|
+
readFile7(infoPath, "utf8")
|
|
2442
|
+
]);
|
|
2443
|
+
const parsed = JSON.parse(configSource);
|
|
2444
|
+
if (!isRecord3(parsed))
|
|
2445
|
+
throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
|
|
2446
|
+
await mkdir5(paths.root, { recursive: true });
|
|
2447
|
+
await Promise.all([
|
|
2448
|
+
writeFile6(paths.configBackup, configSource, { flag: "wx" }),
|
|
2449
|
+
writeFile6(paths.infoBackup, infoSource, { flag: "wx" })
|
|
2450
|
+
]);
|
|
2451
|
+
const journal = {
|
|
2452
|
+
configBackupPath: paths.configBackup,
|
|
2453
|
+
format: DEV_JOURNAL_FORMAT,
|
|
2454
|
+
infoBackupPath: paths.infoBackup,
|
|
2455
|
+
infoPath,
|
|
2456
|
+
nativeConfigPath
|
|
2457
|
+
};
|
|
2458
|
+
await writeFile6(paths.journal, `${JSON.stringify(journal, null, "\t")}
|
|
2459
|
+
`, {
|
|
2460
|
+
flag: "wx"
|
|
2461
|
+
});
|
|
2462
|
+
const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
|
|
2463
|
+
developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
|
|
2464
|
+
const existingServer = parsed.server;
|
|
2465
|
+
parsed.server = {
|
|
2466
|
+
...isRecord3(existingServer) ? existingServer : {},
|
|
2467
|
+
cleartext: !https,
|
|
2468
|
+
url: developmentUrl.href
|
|
2469
|
+
};
|
|
2470
|
+
await Promise.all([
|
|
2471
|
+
writeFile6(nativeConfigPath, `${JSON.stringify(parsed, null, "\t")}
|
|
2472
|
+
`),
|
|
2473
|
+
writeFile6(infoPath, iosDevelopmentInfoPlist(infoSource, !https))
|
|
2474
|
+
]);
|
|
2475
|
+
};
|
|
2476
|
+
var parseNativeCache = (value) => {
|
|
2477
|
+
if (!isRecord3(value))
|
|
2478
|
+
return null;
|
|
2479
|
+
const { appId, fingerprint, format, installations } = value;
|
|
2480
|
+
if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord3(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
|
|
2481
|
+
return null;
|
|
2482
|
+
return {
|
|
2483
|
+
appId,
|
|
2484
|
+
fingerprint,
|
|
2485
|
+
format,
|
|
2486
|
+
installations: Object.fromEntries(Object.entries(installations).map(([udid, identity]) => [
|
|
2487
|
+
udid,
|
|
2488
|
+
String(identity)
|
|
2489
|
+
]))
|
|
2490
|
+
};
|
|
2491
|
+
};
|
|
2492
|
+
var readNativeCache = (projectRoot) => readFile7(nativeCachePath(projectRoot), "utf8").then((source) => parseNativeCache(JSON.parse(source))).catch(() => null);
|
|
2493
|
+
var writeNativeCache = async (projectRoot, cache) => {
|
|
2494
|
+
const destination = nativeCachePath(projectRoot);
|
|
2495
|
+
const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
|
|
2496
|
+
await mkdir5(dirname4(destination), { recursive: true });
|
|
2497
|
+
try {
|
|
2498
|
+
await writeFile6(temporary, `${JSON.stringify(cache, null, "\t")}
|
|
2499
|
+
`, {
|
|
2500
|
+
flag: "wx"
|
|
2501
|
+
});
|
|
2502
|
+
await rename6(temporary, destination);
|
|
2503
|
+
} finally {
|
|
2504
|
+
await rm5(temporary, { force: true }).catch(() => {
|
|
2505
|
+
return;
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
var fingerprintAbsoluteIosDevProject = async (project) => fingerprintAbsoluteIosNativeProject(project.nativeDirectory);
|
|
2510
|
+
var simulatorInventory = (xcrun, capture) => {
|
|
2511
|
+
const result = capture([
|
|
2512
|
+
xcrun,
|
|
2513
|
+
"simctl",
|
|
2514
|
+
"list",
|
|
2515
|
+
"devices",
|
|
2516
|
+
"available",
|
|
2517
|
+
"-j"
|
|
2518
|
+
]);
|
|
2519
|
+
return parseIosSimulators(requireCapturedSuccess(result, "iOS simulator discovery"));
|
|
2520
|
+
};
|
|
2521
|
+
var ensureManagedSimulator = async (project, capture) => {
|
|
2522
|
+
const runtimes = parseIosRuntimes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "runtimes", "-j"]), "iOS runtime discovery"));
|
|
2523
|
+
const runtime = latestIosRuntime(runtimes);
|
|
2524
|
+
if (!runtime)
|
|
2525
|
+
throw new Error("No available iOS Simulator runtime. Run absolute mobile doctor ios --fix.");
|
|
2526
|
+
const [existing] = simulatorInventory(project.xcrun, capture).filter((device) => device.isAvailable && device.name === ABSOLUTE_IOS_SIMULATOR_NAME && device.runtime === runtime.identifier).sort((left, right) => Number(right.state === "Booted") - Number(left.state === "Booted"));
|
|
2527
|
+
if (existing)
|
|
2528
|
+
return { created: false, device: existing };
|
|
2529
|
+
const types = parseIosDeviceTypes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "devicetypes", "-j"]), "iOS device-type discovery"));
|
|
2530
|
+
const type = preferredIphoneType(types);
|
|
2531
|
+
if (!type)
|
|
2532
|
+
throw new Error("Xcode did not report an iPhone simulator type.");
|
|
2533
|
+
const [udid] = requireCapturedSuccess(capture([
|
|
2534
|
+
project.xcrun,
|
|
2535
|
+
"simctl",
|
|
2536
|
+
"create",
|
|
2537
|
+
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
2538
|
+
type.identifier,
|
|
2539
|
+
runtime.identifier
|
|
2540
|
+
]), "iOS simulator creation").split(/\s/u);
|
|
2541
|
+
if (!udid)
|
|
2542
|
+
throw new Error("simctl did not return the created simulator UDID.");
|
|
2543
|
+
return {
|
|
2544
|
+
created: true,
|
|
2545
|
+
device: {
|
|
2546
|
+
isAvailable: true,
|
|
2547
|
+
name: ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
2548
|
+
runtime: runtime.identifier,
|
|
2549
|
+
state: "Shutdown",
|
|
2550
|
+
udid
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
};
|
|
2554
|
+
var waitForBootedSimulator = async (project, udid, capture, sleep, signal) => {
|
|
2555
|
+
const deadline = Date.now() + BOOT_TIMEOUT_MS;
|
|
2556
|
+
const poll = async () => {
|
|
2557
|
+
throwIfAborted2(signal);
|
|
2558
|
+
const device = simulatorInventory(project.xcrun, capture).find((candidate) => candidate.udid === udid);
|
|
2559
|
+
if (device?.state === "Booted")
|
|
2560
|
+
return;
|
|
2561
|
+
if (Date.now() > deadline)
|
|
2562
|
+
throw new Error(`iOS simulator ${udid} did not finish booting within ${BOOT_TIMEOUT_MS / 1000}s.`);
|
|
2563
|
+
await sleep(BOOT_POLL_MS);
|
|
2564
|
+
await poll();
|
|
2565
|
+
};
|
|
2566
|
+
return poll();
|
|
2567
|
+
};
|
|
2568
|
+
var bootSimulator = (project, device, capture) => {
|
|
2569
|
+
if (device.state === "Booted")
|
|
2570
|
+
return;
|
|
2571
|
+
requireCapturedSuccess(capture([project.xcrun, "simctl", "boot", device.udid]), "iOS simulator boot");
|
|
2572
|
+
};
|
|
2573
|
+
var installedAppIdentity = (project, udid, capture) => {
|
|
2574
|
+
const result = capture([
|
|
2575
|
+
project.xcrun,
|
|
2576
|
+
"simctl",
|
|
2577
|
+
"get_app_container",
|
|
2578
|
+
udid,
|
|
2579
|
+
project.config.appId,
|
|
2580
|
+
"app"
|
|
2581
|
+
]);
|
|
2582
|
+
return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
|
|
2583
|
+
};
|
|
2584
|
+
var buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
|
|
2585
|
+
const derivedDataPath = join6(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash6("sha256").update(project.config.appId).digest("hex").slice(0, 16));
|
|
2586
|
+
await mkdir5(derivedDataPath, { recursive: true });
|
|
2587
|
+
await requireSuccess2([
|
|
2588
|
+
project.xcodebuild,
|
|
2589
|
+
"-workspace",
|
|
2590
|
+
join6(project.nativeDirectory, "App", "App.xcworkspace"),
|
|
2591
|
+
"-scheme",
|
|
2592
|
+
"App",
|
|
2593
|
+
"-configuration",
|
|
2594
|
+
"Debug",
|
|
2595
|
+
"-destination",
|
|
2596
|
+
`platform=iOS Simulator,id=${udid}`,
|
|
2597
|
+
"-derivedDataPath",
|
|
2598
|
+
derivedDataPath,
|
|
2599
|
+
"build"
|
|
2600
|
+
], "iOS simulator build", run, { cwd: project.nativeDirectory, signal });
|
|
2601
|
+
const appPath = join6(derivedDataPath, "Build", "Products", "Debug-iphonesimulator", "App.app");
|
|
2602
|
+
if (!await pathExists5(appPath))
|
|
2603
|
+
throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
|
|
2604
|
+
return appPath;
|
|
2605
|
+
};
|
|
2606
|
+
var ensureIosDebugApp = async (options) => {
|
|
2607
|
+
const installed = installedAppIdentity(options.project, options.udid, options.capture);
|
|
2608
|
+
const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
|
|
2609
|
+
if (cacheHit) {
|
|
2610
|
+
options.log(`iOS native app is unchanged on ${options.udid}; skipped Xcode build and install.`);
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
options.log("iOS native inputs changed or the installed app is stale; rebuilding.");
|
|
2614
|
+
options.transition("building");
|
|
2615
|
+
const appPath = await buildIosDebugApp(options.project, options.udid, options.fingerprint, options.run, options.signal);
|
|
2616
|
+
throwIfAborted2(options.signal);
|
|
2617
|
+
options.transition("installing");
|
|
2618
|
+
await requireSuccess2([options.project.xcrun, "simctl", "install", options.udid, appPath], "iOS simulator app installation", options.run, { signal: options.signal });
|
|
2619
|
+
const updated = installedAppIdentity(options.project, options.udid, options.capture);
|
|
2620
|
+
if (updated) {
|
|
2621
|
+
await writeNativeCache(options.project.projectRoot, {
|
|
2622
|
+
appId: options.project.config.appId,
|
|
2623
|
+
fingerprint: options.fingerprint,
|
|
2624
|
+
format: NATIVE_CACHE_FORMAT,
|
|
2625
|
+
installations: { [options.udid]: updated }
|
|
2626
|
+
}).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
|
|
2627
|
+
}
|
|
2628
|
+
return false;
|
|
2629
|
+
};
|
|
2630
|
+
var SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
|
|
2631
|
+
var BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
|
|
2632
|
+
var JWT_VALUE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu;
|
|
2633
|
+
var IOS_LOG_PATTERN = new RegExp(String.raw`\s(Debug|Info|Notice|Error|Fault)\s+.*?\[[^\]]+\]\s+\[([^\]]+)\]\s*(.*)$`, "iu");
|
|
2634
|
+
var parseAbsoluteIosLogLine = (line) => {
|
|
2635
|
+
const sanitized = redactAbsoluteIosLog(line).trim();
|
|
2636
|
+
if (!sanitized)
|
|
2637
|
+
return null;
|
|
2638
|
+
const match = IOS_LOG_PATTERN.exec(sanitized);
|
|
2639
|
+
const candidate = match?.[1]?.toLowerCase();
|
|
2640
|
+
const level = candidate === "debug" || candidate === "error" || candidate === "fault" || candidate === "notice" ? candidate : "info";
|
|
2641
|
+
return {
|
|
2642
|
+
level,
|
|
2643
|
+
message: match?.[3]?.trim() || sanitized,
|
|
2644
|
+
tag: match?.[2]?.trim() || "App"
|
|
2645
|
+
};
|
|
2646
|
+
};
|
|
2647
|
+
var redactAbsoluteIosLog = (value) => value.replaceAll(BEARER_VALUE, "Bearer [REDACTED]").replaceAll(JWT_VALUE, "[REDACTED_JWT]").replaceAll(SECRET_VALUE, "$1[REDACTED]").replaceAll(/\p{C}/gu, "");
|
|
2648
|
+
var attachNativeLogs = (project, udid, options) => {
|
|
2649
|
+
if (!options.nativeLog)
|
|
2650
|
+
return null;
|
|
2651
|
+
const start = options.startNativeLogs ?? defaultStartNativeLogs;
|
|
2652
|
+
return start([
|
|
2653
|
+
project.xcrun,
|
|
2654
|
+
"simctl",
|
|
2655
|
+
"spawn",
|
|
2656
|
+
udid,
|
|
2657
|
+
"log",
|
|
2658
|
+
"stream",
|
|
2659
|
+
"--style",
|
|
2660
|
+
"compact",
|
|
2661
|
+
"--level",
|
|
2662
|
+
"debug",
|
|
2663
|
+
"--predicate",
|
|
2664
|
+
'process == "App"'
|
|
2665
|
+
], { signal: options.signal }, (line) => {
|
|
2666
|
+
const entry = parseAbsoluteIosLogLine(line);
|
|
2667
|
+
if (entry)
|
|
2668
|
+
options.nativeLog?.(entry);
|
|
2669
|
+
});
|
|
2670
|
+
};
|
|
2671
|
+
var IOS_TIMING_PHASES = [
|
|
2672
|
+
["syncing", "Capacitor sync"],
|
|
2673
|
+
["configuring", "dev config"],
|
|
2674
|
+
["fingerprinting", "fingerprint"],
|
|
2675
|
+
["booting", "simulator"],
|
|
2676
|
+
["connecting", "device ready"],
|
|
2677
|
+
["checking-native", "app check"],
|
|
2678
|
+
["building", "Xcode"],
|
|
2679
|
+
["installing", "install"],
|
|
2680
|
+
["launching", "launch"],
|
|
2681
|
+
["streaming-logs", "logs"]
|
|
2682
|
+
];
|
|
2683
|
+
var timingSummary = (timings) => IOS_TIMING_PHASES.map(([phase, label]) => {
|
|
2684
|
+
const duration = timings[phase];
|
|
2685
|
+
return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
|
|
2686
|
+
}).filter((value) => value !== null).join(", ");
|
|
2687
|
+
var prepareAbsoluteIosDevProject = async (config, options) => {
|
|
2688
|
+
if (detectAbsoluteMobileHost() !== "macos")
|
|
2689
|
+
throw new Error("iOS simulation requires macOS and Xcode.");
|
|
2690
|
+
const projectRoot = resolve5(options.projectRoot);
|
|
2691
|
+
const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
|
|
2692
|
+
const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
|
|
2693
|
+
if (failed.length > 0)
|
|
2694
|
+
throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
|
|
2695
|
+
const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
|
|
2696
|
+
const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
|
|
2697
|
+
if (!xcrun || !xcodebuild)
|
|
2698
|
+
throw new Error("Xcode tools disappeared after readiness checks.");
|
|
2699
|
+
const cap = join6(projectRoot, "node_modules", ".bin", "cap");
|
|
2700
|
+
if (!await pathExists5(cap))
|
|
2701
|
+
throw new Error("Capacitor CLI is not installed. Run absolute mobile init first.");
|
|
2702
|
+
await writeAbsoluteCapacitorConfig(config, { projectRoot });
|
|
2703
|
+
await mkdir5(config.bundleDirectory, { recursive: true });
|
|
2704
|
+
const placeholder = join6(config.bundleDirectory, "index.html");
|
|
2705
|
+
if (!await pathExists5(placeholder))
|
|
2706
|
+
await writeFile6(placeholder, `<!doctype html><title>AbsoluteJS mobile development</title>
|
|
2707
|
+
`);
|
|
2708
|
+
const nativeDirectory = join6(config.nativeProjectDirectory, "ios");
|
|
2709
|
+
if (!await pathExists5(nativeDirectory)) {
|
|
2710
|
+
if (!options.createNativeProject)
|
|
2711
|
+
throw new Error("iOS native project has not been created.");
|
|
2712
|
+
const run = options.run ?? defaultRun2;
|
|
2713
|
+
if (await run([cap, "add", "ios"], { cwd: projectRoot }) !== 0)
|
|
2714
|
+
throw new Error("Capacitor iOS project creation failed.");
|
|
2715
|
+
}
|
|
2716
|
+
return {
|
|
2717
|
+
cap,
|
|
2718
|
+
config,
|
|
2719
|
+
nativeDirectory,
|
|
2720
|
+
projectRoot,
|
|
2721
|
+
xcodebuild,
|
|
2722
|
+
xcrun
|
|
2723
|
+
};
|
|
2724
|
+
};
|
|
2725
|
+
var startAbsoluteIosDevSession = async (options) => {
|
|
2726
|
+
const { project } = options;
|
|
2727
|
+
const capture = options.capture ?? defaultCapture3;
|
|
2728
|
+
const run = options.run ?? defaultRun2;
|
|
2729
|
+
const sleep = options.sleep ?? Bun.sleep;
|
|
2730
|
+
const spawn = options.spawn ?? defaultSpawn;
|
|
2731
|
+
const log = options.log ?? console.log;
|
|
2732
|
+
const startedAt = performance.now();
|
|
2733
|
+
let phaseStartedAt = performance.now();
|
|
2734
|
+
const timings = {};
|
|
2735
|
+
let state = "syncing";
|
|
2736
|
+
const transition = (next) => {
|
|
2737
|
+
if (next === state) {
|
|
2738
|
+
options.onStateChange?.(next);
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
const now = performance.now();
|
|
2742
|
+
const durationMs = now - phaseStartedAt;
|
|
2743
|
+
timings[state] = (timings[state] ?? 0) + durationMs;
|
|
2744
|
+
options.onPhaseTiming?.({
|
|
2745
|
+
durationMs,
|
|
2746
|
+
phase: state,
|
|
2747
|
+
totalMs: now - startedAt
|
|
2748
|
+
});
|
|
2749
|
+
state = next;
|
|
2750
|
+
phaseStartedAt = now;
|
|
2751
|
+
options.onStateChange?.(next);
|
|
2752
|
+
};
|
|
2753
|
+
let nativeLogs = null;
|
|
2754
|
+
const closeLogs = async () => {
|
|
2755
|
+
const stream = nativeLogs;
|
|
2756
|
+
nativeLogs = null;
|
|
2757
|
+
await stream?.close().catch(() => {
|
|
2758
|
+
return;
|
|
2759
|
+
});
|
|
2760
|
+
};
|
|
2761
|
+
try {
|
|
2762
|
+
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
2763
|
+
throwIfAborted2(options.signal);
|
|
2764
|
+
transition("syncing");
|
|
2765
|
+
await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
|
|
2766
|
+
transition("configuring");
|
|
2767
|
+
await writeDevProjection(project, options.port, options.https === true);
|
|
2768
|
+
throwIfAborted2(options.signal);
|
|
2769
|
+
const fingerprintStartedAt = performance.now();
|
|
2770
|
+
const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
|
|
2771
|
+
timings.fingerprinting = performance.now() - fingerprintStartedAt;
|
|
2772
|
+
return fingerprint2;
|
|
2773
|
+
});
|
|
2774
|
+
transition("booting");
|
|
2775
|
+
const { created, device } = await ensureManagedSimulator(project, capture);
|
|
2776
|
+
const startedSimulator = created || device.state !== "Booted";
|
|
2777
|
+
bootSimulator(project, device, capture);
|
|
2778
|
+
spawn([
|
|
2779
|
+
"open",
|
|
2780
|
+
"-a",
|
|
2781
|
+
"Simulator",
|
|
2782
|
+
"--args",
|
|
2783
|
+
"-CurrentDeviceUDID",
|
|
2784
|
+
device.udid
|
|
2785
|
+
]);
|
|
2786
|
+
transition("connecting");
|
|
2787
|
+
await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
|
|
2788
|
+
await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
|
|
2789
|
+
const fingerprint = await fingerprintPromise;
|
|
2790
|
+
transition("checking-native");
|
|
2791
|
+
const nativeCacheHit = await ensureIosDebugApp({
|
|
2792
|
+
cache: await readNativeCache(project.projectRoot),
|
|
2793
|
+
capture,
|
|
2794
|
+
fingerprint,
|
|
2795
|
+
log,
|
|
2796
|
+
project,
|
|
2797
|
+
run,
|
|
2798
|
+
signal: options.signal,
|
|
2799
|
+
transition,
|
|
2800
|
+
udid: device.udid
|
|
2801
|
+
});
|
|
2802
|
+
throwIfAborted2(options.signal);
|
|
2803
|
+
if (options.nativeLog)
|
|
2804
|
+
transition("streaming-logs");
|
|
2805
|
+
nativeLogs = attachNativeLogs(project, device.udid, options);
|
|
2806
|
+
transition("launching");
|
|
2807
|
+
await requireSuccess2([
|
|
2808
|
+
project.xcrun,
|
|
2809
|
+
"simctl",
|
|
2810
|
+
"launch",
|
|
2811
|
+
"--terminate-running-process",
|
|
2812
|
+
device.udid,
|
|
2813
|
+
project.config.appId
|
|
2814
|
+
], "iOS app launch", run, { signal: options.signal });
|
|
2815
|
+
transition("ready");
|
|
2816
|
+
timings.total = performance.now() - startedAt;
|
|
2817
|
+
log(`iOS simulator connected (${device.udid}) with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
|
|
2818
|
+
log(`iOS startup: ${timingSummary(timings)}.`);
|
|
2819
|
+
let closed = false;
|
|
2820
|
+
const close = async () => {
|
|
2821
|
+
if (closed)
|
|
2822
|
+
return;
|
|
2823
|
+
closed = true;
|
|
2824
|
+
transition("closing");
|
|
2825
|
+
await closeLogs();
|
|
2826
|
+
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
2827
|
+
transition("closed");
|
|
2828
|
+
};
|
|
2829
|
+
return {
|
|
2830
|
+
close,
|
|
2831
|
+
nativeCacheHit,
|
|
2832
|
+
startedSimulator,
|
|
2833
|
+
timings: { ...timings },
|
|
2834
|
+
udid: device.udid,
|
|
2835
|
+
rebuild: async () => {
|
|
2836
|
+
if (closed)
|
|
2837
|
+
throw new Error("iOS development session is closed.");
|
|
2838
|
+
log("iOS native inputs changed; rebuilding without restarting the dev server.");
|
|
2839
|
+
await close();
|
|
2840
|
+
return startAbsoluteIosDevSession(options);
|
|
2841
|
+
},
|
|
2842
|
+
relaunch: async () => {
|
|
2843
|
+
if (closed)
|
|
2844
|
+
throw new Error("iOS development session is closed.");
|
|
2845
|
+
transition("launching");
|
|
2846
|
+
try {
|
|
2847
|
+
await requireSuccess2([
|
|
2848
|
+
project.xcrun,
|
|
2849
|
+
"simctl",
|
|
2850
|
+
"launch",
|
|
2851
|
+
"--terminate-running-process",
|
|
2852
|
+
device.udid,
|
|
2853
|
+
project.config.appId
|
|
2854
|
+
], "iOS app relaunch", run, { signal: options.signal });
|
|
2855
|
+
transition("ready");
|
|
2856
|
+
log(`iOS app relaunched on ${device.udid}.`);
|
|
2857
|
+
} catch (error) {
|
|
2858
|
+
transition("failed");
|
|
2859
|
+
throw error;
|
|
2860
|
+
}
|
|
2861
|
+
},
|
|
2862
|
+
screenshot: async (destination) => {
|
|
2863
|
+
const resolved = resolve5(project.projectRoot, destination);
|
|
2864
|
+
if (!isInside(project.projectRoot, resolved))
|
|
2865
|
+
throw new Error("iOS screenshot destination must remain inside the project.");
|
|
2866
|
+
await mkdir5(dirname4(resolved), { recursive: true });
|
|
2867
|
+
await requireSuccess2([
|
|
2868
|
+
project.xcrun,
|
|
2869
|
+
"simctl",
|
|
2870
|
+
"io",
|
|
2871
|
+
device.udid,
|
|
2872
|
+
"screenshot",
|
|
2873
|
+
resolved
|
|
2874
|
+
], "iOS simulator screenshot", run, { signal: options.signal });
|
|
2875
|
+
return resolved;
|
|
2876
|
+
},
|
|
2877
|
+
get state() {
|
|
2878
|
+
return state;
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
transition("failed");
|
|
2883
|
+
await closeLogs();
|
|
2884
|
+
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
2885
|
+
throw error;
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
// src/mobile/iosNativeWatcher.ts
|
|
2889
|
+
import { watch } from "fs";
|
|
2890
|
+
import { basename } from "path";
|
|
2891
|
+
var NATIVE_CHANGE_DEBOUNCE_MS = 500;
|
|
2892
|
+
var ROOT_NATIVE_INPUTS = new Set([
|
|
2893
|
+
"absolute.config.js",
|
|
2894
|
+
"absolute.config.mjs",
|
|
2895
|
+
"absolute.config.ts",
|
|
2896
|
+
"absolutejs.config.js",
|
|
2897
|
+
"absolutejs.config.mjs",
|
|
2898
|
+
"absolutejs.config.ts",
|
|
2899
|
+
"bun.lock",
|
|
2900
|
+
"bun.lockb",
|
|
2901
|
+
"capacitor.config.js",
|
|
2902
|
+
"capacitor.config.ts",
|
|
2903
|
+
"package.json"
|
|
2904
|
+
]);
|
|
2905
|
+
var createAbsoluteIosNativeWatcher = async (options) => {
|
|
2906
|
+
let fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
|
|
2907
|
+
let closed = false;
|
|
2908
|
+
let running = false;
|
|
2909
|
+
let timer;
|
|
2910
|
+
let rootInputChanged = false;
|
|
2911
|
+
const changedPaths = new Set;
|
|
2912
|
+
const watchers = [];
|
|
2913
|
+
const debounceMs = options.debounceMs ?? NATIVE_CHANGE_DEBOUNCE_MS;
|
|
2914
|
+
const close = () => {
|
|
2915
|
+
if (closed)
|
|
2916
|
+
return;
|
|
2917
|
+
closed = true;
|
|
2918
|
+
if (timer)
|
|
2919
|
+
clearTimeout(timer);
|
|
2920
|
+
watchers.forEach((watcher) => watcher.close());
|
|
2921
|
+
options.signal?.removeEventListener("abort", close);
|
|
2922
|
+
};
|
|
2923
|
+
const schedule = () => {
|
|
2924
|
+
if (closed || running)
|
|
2925
|
+
return;
|
|
2926
|
+
if (timer)
|
|
2927
|
+
clearTimeout(timer);
|
|
2928
|
+
timer = setTimeout(() => void flush(), debounceMs);
|
|
2929
|
+
};
|
|
2930
|
+
const flush = async () => {
|
|
2931
|
+
timer = undefined;
|
|
2932
|
+
if (closed || running || changedPaths.size === 0)
|
|
2933
|
+
return;
|
|
2934
|
+
running = true;
|
|
2935
|
+
const paths = [...changedPaths].sort();
|
|
2936
|
+
const forced = rootInputChanged;
|
|
2937
|
+
changedPaths.clear();
|
|
2938
|
+
rootInputChanged = false;
|
|
2939
|
+
try {
|
|
2940
|
+
const next = await fingerprintAbsoluteIosDevProject(options.project);
|
|
2941
|
+
if (!forced && next === fingerprint)
|
|
2942
|
+
return;
|
|
2943
|
+
await options.onChange({
|
|
2944
|
+
afterFingerprint: next,
|
|
2945
|
+
beforeFingerprint: fingerprint,
|
|
2946
|
+
paths,
|
|
2947
|
+
rootInputChanged: forced
|
|
2948
|
+
});
|
|
2949
|
+
fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
options.onError?.(error);
|
|
2952
|
+
} finally {
|
|
2953
|
+
running = false;
|
|
2954
|
+
if (changedPaths.size > 0)
|
|
2955
|
+
schedule();
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
const record = (path, force) => {
|
|
2959
|
+
if (closed)
|
|
2960
|
+
return;
|
|
2961
|
+
changedPaths.add(path);
|
|
2962
|
+
rootInputChanged ||= force;
|
|
2963
|
+
schedule();
|
|
2964
|
+
};
|
|
2965
|
+
watchers.push(watch(options.project.nativeDirectory, { recursive: true }, (_event, filename) => {
|
|
2966
|
+
if (filename)
|
|
2967
|
+
record(String(filename), false);
|
|
2968
|
+
}));
|
|
2969
|
+
watchers.push(watch(options.project.projectRoot, (_event, filename) => {
|
|
2970
|
+
if (!filename)
|
|
2971
|
+
return;
|
|
2972
|
+
const path = String(filename);
|
|
2973
|
+
if (isAbsoluteIosNativeRootInput(path))
|
|
2974
|
+
record(path, true);
|
|
2975
|
+
}));
|
|
2976
|
+
watchers.forEach((watcher) => watcher.on("error", (error) => options.onError?.(error)));
|
|
2977
|
+
options.signal?.addEventListener("abort", close, { once: true });
|
|
2978
|
+
return { close };
|
|
2979
|
+
};
|
|
2980
|
+
var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
|
|
2981
|
+
// src/mobile/remoteMacProtocol.ts
|
|
2982
|
+
import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
|
|
2983
|
+
import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
|
|
2984
|
+
import { homedir as homedir2 } from "os";
|
|
2985
|
+
import {
|
|
2986
|
+
dirname as dirname5,
|
|
2987
|
+
isAbsolute as isAbsolute5,
|
|
2988
|
+
join as join7,
|
|
2989
|
+
posix,
|
|
2990
|
+
relative as relative6,
|
|
2991
|
+
resolve as resolvePath2,
|
|
2992
|
+
sep as sep5
|
|
2993
|
+
} from "path";
|
|
2994
|
+
|
|
2995
|
+
// src/mobile/remoteMacWire.ts
|
|
2996
|
+
var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
|
|
2997
|
+
var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
|
|
2998
|
+
|
|
2999
|
+
// src/mobile/remoteMacProtocol.ts
|
|
3000
|
+
var PROFILE_FORMAT = 1;
|
|
3001
|
+
var REMOTE_STDIN_FLUSH_ATTEMPTS = 3;
|
|
3002
|
+
var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
|
|
3003
|
+
var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
|
|
3004
|
+
var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
|
|
3005
|
+
var emptyStore = () => ({
|
|
3006
|
+
format: PROFILE_FORMAT,
|
|
3007
|
+
profiles: {}
|
|
3008
|
+
});
|
|
3009
|
+
var loadStore = async (path = defaultProfilePath()) => {
|
|
3010
|
+
try {
|
|
3011
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
3012
|
+
if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
|
|
3013
|
+
throw new Error("Unsupported remote Mac profile format.");
|
|
3014
|
+
for (const [key, profile] of Object.entries(parsed.profiles)) {
|
|
3015
|
+
if (typeof profile !== "object" || profile === null || validateAbsoluteRemoteMacProfileName(key) !== key || profile.name !== key || validateAbsoluteSshDestination(profile.destination) !== profile.destination || validatePort(profile.port) !== profile.port || typeof profile.createdAt !== "string" || !profile.createdAt || typeof profile.bunPath !== "string" || !profile.bunPath.startsWith("/") || /[\r\n\0]/u.test(profile.bunPath) || typeof profile.workspaceRoot !== "string" || !profile.workspaceRoot.startsWith("/") || profile.workspaceRoot === "/" || /[\r\n\0]/u.test(profile.workspaceRoot) || typeof profile.xcodeVersion !== "string" || !profile.xcodeVersion.startsWith("Xcode "))
|
|
3016
|
+
throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
|
|
3017
|
+
}
|
|
3018
|
+
if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
|
|
3019
|
+
throw new Error("The default remote Mac profile does not exist.");
|
|
3020
|
+
return parsed;
|
|
3021
|
+
} catch (error) {
|
|
3022
|
+
if (error.code === "ENOENT")
|
|
3023
|
+
return emptyStore();
|
|
3024
|
+
throw error;
|
|
3025
|
+
}
|
|
3026
|
+
};
|
|
3027
|
+
var saveStore = async (store, path = defaultProfilePath()) => {
|
|
3028
|
+
await mkdir6(dirname5(path), { recursive: true });
|
|
3029
|
+
const temporary = `${path}.${randomUUID3()}.tmp`;
|
|
3030
|
+
await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
|
|
3031
|
+
`, {
|
|
3032
|
+
mode: 384
|
|
3033
|
+
});
|
|
3034
|
+
await rename7(temporary, path);
|
|
3035
|
+
await chmod(path, 384);
|
|
3036
|
+
};
|
|
3037
|
+
var validateAbsoluteRemoteMacProfileName = (name) => {
|
|
3038
|
+
const normalized = name.trim().toLowerCase();
|
|
3039
|
+
if (!PROFILE_NAME.test(normalized))
|
|
3040
|
+
throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
|
|
3041
|
+
return normalized;
|
|
3042
|
+
};
|
|
3043
|
+
var validateAbsoluteSshDestination = (destination) => {
|
|
3044
|
+
const normalized = destination.trim();
|
|
3045
|
+
if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
|
|
3046
|
+
throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
|
|
3047
|
+
return normalized;
|
|
3048
|
+
};
|
|
3049
|
+
var validatePort = (port) => {
|
|
3050
|
+
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
|
|
3051
|
+
throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
|
|
3052
|
+
return port;
|
|
3053
|
+
};
|
|
3054
|
+
var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
3055
|
+
var absoluteRemoteMacSshBase = (profile, options = {}) => [
|
|
3056
|
+
"ssh",
|
|
3057
|
+
"-o",
|
|
3058
|
+
"BatchMode=yes",
|
|
3059
|
+
"-o",
|
|
3060
|
+
"ConnectTimeout=10",
|
|
3061
|
+
"-o",
|
|
3062
|
+
"ServerAliveInterval=15",
|
|
3063
|
+
"-o",
|
|
3064
|
+
"ServerAliveCountMax=3",
|
|
3065
|
+
"-o",
|
|
3066
|
+
`StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
|
|
3067
|
+
...profile.port ? ["-p", String(profile.port)] : [],
|
|
3068
|
+
profile.destination
|
|
3069
|
+
];
|
|
3070
|
+
var localCapture = async (command) => {
|
|
3071
|
+
const process2 = Bun.spawn(command, {
|
|
3072
|
+
stderr: "pipe",
|
|
3073
|
+
stdin: "ignore",
|
|
3074
|
+
stdout: "pipe"
|
|
3075
|
+
});
|
|
3076
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
3077
|
+
process2.exited,
|
|
3078
|
+
new Response(process2.stdout).text(),
|
|
3079
|
+
new Response(process2.stderr).text()
|
|
3080
|
+
]);
|
|
3081
|
+
return { exitCode, stderr, stdout };
|
|
3082
|
+
};
|
|
3083
|
+
var defaultTransport = {
|
|
3084
|
+
capture: localCapture,
|
|
3085
|
+
spawn: (command, options) => Bun.spawn(command, {
|
|
3086
|
+
signal: options.signal,
|
|
3087
|
+
stderr: "pipe",
|
|
3088
|
+
stdin: "pipe",
|
|
3089
|
+
stdout: "pipe"
|
|
3090
|
+
})
|
|
3091
|
+
};
|
|
3092
|
+
var requireRemoteSuccess = (result, label) => {
|
|
3093
|
+
if (result.exitCode !== 0)
|
|
3094
|
+
throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
|
|
3095
|
+
return result.stdout.trim();
|
|
3096
|
+
};
|
|
3097
|
+
var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
|
|
3098
|
+
const store = await loadStore(profilePath);
|
|
3099
|
+
const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
|
|
3100
|
+
if (!selected)
|
|
3101
|
+
return;
|
|
3102
|
+
const profile = store.profiles[selected];
|
|
3103
|
+
if (!profile)
|
|
3104
|
+
throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
|
|
3105
|
+
return profile;
|
|
3106
|
+
};
|
|
3107
|
+
var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
|
|
3108
|
+
const profile = {
|
|
3109
|
+
destination: validateAbsoluteSshDestination(destination),
|
|
3110
|
+
port: validatePort(options.port)
|
|
3111
|
+
};
|
|
3112
|
+
const capture = options.transport?.capture ?? defaultTransport.capture;
|
|
3113
|
+
const command = [
|
|
3114
|
+
...absoluteRemoteMacSshBase(profile, {
|
|
3115
|
+
acceptNew: options.acceptNew === true
|
|
3116
|
+
}),
|
|
3117
|
+
"/bin/sh -lc",
|
|
3118
|
+
shellQuote(`bun_path="$(command -v bun || true)"; if [ -z "$bun_path" ] && [ -x "$HOME/.bun/bin/bun" ]; then bun_path="$HOME/.bun/bin/bun"; fi; printf '%s\\n' "$(uname -s)" "$HOME" "$bun_path" "$(/usr/bin/xcodebuild -version 2>/dev/null | tr '\\n' ' ' || true)"`)
|
|
3119
|
+
];
|
|
3120
|
+
const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
|
|
3121
|
+
const [operatingSystem, home, bunPath, xcodeVersion] = lines;
|
|
3122
|
+
if (operatingSystem !== "Darwin")
|
|
3123
|
+
throw new Error("The SSH target is not a Mac.");
|
|
3124
|
+
if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
|
|
3125
|
+
throw new Error("The remote Mac must have Bun installed and available to SSH.");
|
|
3126
|
+
if (!xcodeVersion?.startsWith("Xcode "))
|
|
3127
|
+
throw new Error("The remote Mac must have full Xcode installed and selected.");
|
|
3128
|
+
return { bunPath, home, os: operatingSystem, xcodeVersion };
|
|
3129
|
+
};
|
|
3130
|
+
var listAbsoluteRemoteMacProfiles = async (profilePath) => {
|
|
3131
|
+
const store = await loadStore(profilePath);
|
|
3132
|
+
return {
|
|
3133
|
+
defaultProfile: store.defaultProfile,
|
|
3134
|
+
profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
|
|
3135
|
+
};
|
|
3136
|
+
};
|
|
3137
|
+
var pairAbsoluteRemoteMac = async (options) => {
|
|
3138
|
+
const name = validateAbsoluteRemoteMacProfileName(options.name);
|
|
3139
|
+
const destination = validateAbsoluteSshDestination(options.destination);
|
|
3140
|
+
const port = validatePort(options.port);
|
|
3141
|
+
const inspection = await inspectAbsoluteRemoteMac(destination, {
|
|
3142
|
+
acceptNew: true,
|
|
3143
|
+
port,
|
|
3144
|
+
transport: options.transport
|
|
3145
|
+
});
|
|
3146
|
+
const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
|
|
3147
|
+
if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
|
|
3148
|
+
throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
|
|
3149
|
+
const profile = {
|
|
3150
|
+
bunPath: inspection.bunPath,
|
|
3151
|
+
createdAt: new Date().toISOString(),
|
|
3152
|
+
destination,
|
|
3153
|
+
name,
|
|
3154
|
+
...port ? { port } : {},
|
|
3155
|
+
workspaceRoot,
|
|
3156
|
+
xcodeVersion: inspection.xcodeVersion
|
|
3157
|
+
};
|
|
3158
|
+
const store = await loadStore(options.profilePath);
|
|
3159
|
+
store.profiles[name] = profile;
|
|
3160
|
+
store.defaultProfile = name;
|
|
3161
|
+
await saveStore(store, options.profilePath);
|
|
3162
|
+
return profile;
|
|
3163
|
+
};
|
|
3164
|
+
var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
|
|
3165
|
+
const normalized = validateAbsoluteRemoteMacProfileName(name);
|
|
3166
|
+
const store = await loadStore(profilePath);
|
|
3167
|
+
if (!store.profiles[normalized])
|
|
3168
|
+
return false;
|
|
3169
|
+
delete store.profiles[normalized];
|
|
3170
|
+
if (store.defaultProfile === normalized) {
|
|
3171
|
+
const [nextDefault] = Object.keys(store.profiles).sort();
|
|
3172
|
+
store.defaultProfile = nextDefault;
|
|
3173
|
+
}
|
|
3174
|
+
await saveStore(store, profilePath);
|
|
3175
|
+
return true;
|
|
3176
|
+
};
|
|
3177
|
+
var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
|
|
3178
|
+
var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
|
|
3179
|
+
cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
|
|
3180
|
+
config,
|
|
3181
|
+
nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
|
|
3182
|
+
profile,
|
|
3183
|
+
projectRoot: resolvePath2(projectRoot),
|
|
3184
|
+
remote: true,
|
|
3185
|
+
remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
|
|
3186
|
+
xcodebuild: "remote:xcodebuild",
|
|
3187
|
+
xcrun: "remote:xcrun"
|
|
3188
|
+
});
|
|
3189
|
+
var installAbsoluteRemoteMacAgent = async (project) => {
|
|
3190
|
+
const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
|
|
3191
|
+
const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
|
|
3192
|
+
const remotePath = posix.join(directory, "agent.js");
|
|
3193
|
+
const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
|
|
3194
|
+
const verified = await defaultTransport.capture([
|
|
3195
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3196
|
+
"/bin/sh -lc",
|
|
3197
|
+
shellQuote(verifyScript)
|
|
3198
|
+
]);
|
|
3199
|
+
if (verified.exitCode === 0)
|
|
3200
|
+
return { ...artifact, remotePath, uploaded: false };
|
|
3201
|
+
const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
|
|
3202
|
+
const installScript = [
|
|
3203
|
+
"set -eu",
|
|
3204
|
+
"umask 077",
|
|
3205
|
+
`mkdir -p ${shellQuote(directory)}`,
|
|
3206
|
+
`cat > ${shellQuote(temporary)}`,
|
|
3207
|
+
`test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
|
|
3208
|
+
`chmod 600 ${shellQuote(temporary)}`,
|
|
3209
|
+
`mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
|
|
3210
|
+
].join("; ");
|
|
3211
|
+
const upload = Bun.spawn([
|
|
3212
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3213
|
+
"/bin/sh -lc",
|
|
3214
|
+
shellQuote(installScript)
|
|
3215
|
+
], {
|
|
3216
|
+
stderr: "pipe",
|
|
3217
|
+
stdin: Bun.file(artifact.path),
|
|
3218
|
+
stdout: "pipe"
|
|
3219
|
+
});
|
|
3220
|
+
const [exitCode, stderr] = await Promise.all([
|
|
3221
|
+
upload.exited,
|
|
3222
|
+
new Response(upload.stderr).text()
|
|
3223
|
+
]);
|
|
3224
|
+
if (exitCode !== 0)
|
|
3225
|
+
throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
3226
|
+
return { ...artifact, remotePath, uploaded: true };
|
|
3227
|
+
};
|
|
3228
|
+
var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
|
|
3229
|
+
const shippedCandidates = [
|
|
3230
|
+
join7(import.meta.dir, "remoteMacAgentEntry.js"),
|
|
3231
|
+
join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
|
|
3232
|
+
];
|
|
3233
|
+
let path;
|
|
3234
|
+
for (const candidate of shippedCandidates) {
|
|
3235
|
+
if (await Bun.file(candidate).exists()) {
|
|
3236
|
+
path = candidate;
|
|
3237
|
+
break;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
if (!path) {
|
|
3241
|
+
const sourceCandidates = [
|
|
3242
|
+
join7(import.meta.dir, "remoteMacAgentEntry.ts"),
|
|
3243
|
+
join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
|
|
3244
|
+
];
|
|
3245
|
+
const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
|
|
3246
|
+
if (!source)
|
|
3247
|
+
throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
|
|
3248
|
+
const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
|
|
3249
|
+
await mkdir6(outdir, { recursive: true });
|
|
3250
|
+
const result = await Bun.build({
|
|
3251
|
+
entrypoints: [source],
|
|
3252
|
+
minify: true,
|
|
3253
|
+
outdir,
|
|
3254
|
+
target: "bun"
|
|
3255
|
+
});
|
|
3256
|
+
if (!result.success)
|
|
3257
|
+
throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
|
|
3258
|
+
path = join7(outdir, "remoteMacAgentEntry.js");
|
|
3259
|
+
}
|
|
3260
|
+
const bytes = await Bun.file(path).arrayBuffer();
|
|
3261
|
+
const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
|
|
3262
|
+
return { bytes: bytes.byteLength, path, sha256 };
|
|
3263
|
+
};
|
|
3264
|
+
var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
|
|
3265
|
+
var portableMobileConfig = (project) => ({
|
|
3266
|
+
appId: project.config.appId,
|
|
3267
|
+
appName: project.config.appName,
|
|
3268
|
+
bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
|
|
3269
|
+
...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
|
|
3270
|
+
deepLinks: {
|
|
3271
|
+
...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
|
|
3272
|
+
hosts: project.config.deepLinkHosts,
|
|
3273
|
+
...project.config.appleAppIdPrefix ? {
|
|
3274
|
+
apple: {
|
|
3275
|
+
appIdPrefix: project.config.appleAppIdPrefix
|
|
3276
|
+
}
|
|
3277
|
+
} : {}
|
|
3278
|
+
}
|
|
3279
|
+
} : {},
|
|
3280
|
+
entry: project.config.entry,
|
|
3281
|
+
...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
|
|
3282
|
+
nativeProject: {
|
|
3283
|
+
directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
|
|
3284
|
+
mode: "source"
|
|
3285
|
+
},
|
|
3286
|
+
platforms: ["ios"],
|
|
3287
|
+
server: { productionOrigin: project.config.productionOrigin }
|
|
3288
|
+
});
|
|
3289
|
+
var absoluteRemoteProjectSyncCommands = (project) => {
|
|
3290
|
+
const current = project.remoteProjectRoot;
|
|
3291
|
+
const parent = posix.dirname(current);
|
|
3292
|
+
const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
|
|
3293
|
+
const previous = posix.join(parent, ".previous");
|
|
3294
|
+
const script = [
|
|
3295
|
+
"set -eu",
|
|
3296
|
+
`mkdir -p ${shellQuote(staging)}`,
|
|
3297
|
+
`tar -xf - -C ${shellQuote(staging)}`,
|
|
3298
|
+
`if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
|
|
3299
|
+
`if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
|
|
3300
|
+
`rm -rf ${shellQuote(previous)}`,
|
|
3301
|
+
`if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
|
|
3302
|
+
`mv ${shellQuote(staging)} ${shellQuote(current)}`,
|
|
3303
|
+
`rm -rf ${shellQuote(previous)}`
|
|
3304
|
+
].join("; ");
|
|
3305
|
+
return {
|
|
3306
|
+
remote: [
|
|
3307
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3308
|
+
"/bin/sh -lc",
|
|
3309
|
+
shellQuote(script)
|
|
3310
|
+
],
|
|
3311
|
+
tar: [
|
|
3312
|
+
"tar",
|
|
3313
|
+
"--exclude=.git",
|
|
3314
|
+
"--exclude=node_modules",
|
|
3315
|
+
"--exclude=build",
|
|
3316
|
+
"--exclude=.absolutejs",
|
|
3317
|
+
"-cf",
|
|
3318
|
+
"-",
|
|
3319
|
+
"-C",
|
|
3320
|
+
project.projectRoot,
|
|
3321
|
+
"."
|
|
3322
|
+
]
|
|
3323
|
+
};
|
|
3324
|
+
};
|
|
3325
|
+
var syncAbsoluteRemoteMacProject = async (project) => {
|
|
3326
|
+
const commands = absoluteRemoteProjectSyncCommands(project);
|
|
3327
|
+
const archive = Bun.spawn(commands.tar, {
|
|
3328
|
+
stderr: "pipe",
|
|
3329
|
+
stdout: "pipe"
|
|
3330
|
+
});
|
|
3331
|
+
const upload = Bun.spawn(commands.remote, {
|
|
3332
|
+
stderr: "pipe",
|
|
3333
|
+
stdin: archive.stdout,
|
|
3334
|
+
stdout: "pipe"
|
|
3335
|
+
});
|
|
3336
|
+
const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
|
|
3337
|
+
archive.exited,
|
|
3338
|
+
upload.exited,
|
|
3339
|
+
new Response(archive.stderr).text(),
|
|
3340
|
+
new Response(upload.stderr).text()
|
|
3341
|
+
]);
|
|
3342
|
+
if (archiveExit !== 0 || uploadExit !== 0)
|
|
3343
|
+
throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
|
|
3344
|
+
const install = await defaultTransport.capture([
|
|
3345
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3346
|
+
"/bin/sh -lc",
|
|
3347
|
+
shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
|
|
3348
|
+
]);
|
|
3349
|
+
requireRemoteSuccess(install, "Remote Mac dependency installation");
|
|
3350
|
+
};
|
|
3351
|
+
var consumeLines2 = async (stream, onLine) => {
|
|
3352
|
+
const reader = stream.getReader();
|
|
3353
|
+
const decoder = new TextDecoder;
|
|
3354
|
+
let buffered = "";
|
|
3355
|
+
try {
|
|
3356
|
+
while (true) {
|
|
3357
|
+
const { done, value } = await reader.read();
|
|
3358
|
+
if (done)
|
|
3359
|
+
break;
|
|
3360
|
+
buffered += decoder.decode(value, { stream: true });
|
|
3361
|
+
const lines = buffered.split(/\r?\n/u);
|
|
3362
|
+
buffered = lines.pop() ?? "";
|
|
3363
|
+
lines.forEach(onLine);
|
|
3364
|
+
}
|
|
3365
|
+
buffered += decoder.decode();
|
|
3366
|
+
if (buffered)
|
|
3367
|
+
onLine(buffered);
|
|
3368
|
+
} finally {
|
|
3369
|
+
reader.releaseLock();
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
var startAbsoluteRemoteIosDevSession = async (options) => {
|
|
3373
|
+
const startedAt = performance.now();
|
|
3374
|
+
const transport = options.transport ?? defaultTransport;
|
|
3375
|
+
const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
|
|
3376
|
+
const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
|
|
3377
|
+
const agentStartedAt = performance.now();
|
|
3378
|
+
const agent = await installAgent(options.project);
|
|
3379
|
+
const agentDuration = performance.now() - agentStartedAt;
|
|
3380
|
+
const syncStartedAt = performance.now();
|
|
3381
|
+
await syncProject(options.project);
|
|
3382
|
+
const syncDuration = performance.now() - syncStartedAt;
|
|
3383
|
+
const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
|
|
3384
|
+
const remoteCommand = [
|
|
3385
|
+
`cd ${shellQuote(options.project.remoteProjectRoot)}`,
|
|
3386
|
+
"&&",
|
|
3387
|
+
"exec",
|
|
3388
|
+
shellQuote(options.project.profile.bunPath),
|
|
3389
|
+
shellQuote(agent.remotePath),
|
|
3390
|
+
"--port",
|
|
3391
|
+
String(options.port),
|
|
3392
|
+
"--mobile-config",
|
|
3393
|
+
shellQuote(encodedConfig),
|
|
3394
|
+
...options.https ? ["--https"] : []
|
|
3395
|
+
].join(" ");
|
|
3396
|
+
const command = [
|
|
3397
|
+
...absoluteRemoteMacSshBase(options.project.profile),
|
|
3398
|
+
"-o",
|
|
3399
|
+
"ExitOnForwardFailure=yes",
|
|
3400
|
+
"-R",
|
|
3401
|
+
`${options.port}:127.0.0.1:${options.port}`,
|
|
3402
|
+
"/bin/sh -lc",
|
|
3403
|
+
shellQuote(remoteCommand)
|
|
3404
|
+
];
|
|
3405
|
+
const connectStartedAt = performance.now();
|
|
3406
|
+
const process2 = transport.spawn(command, { signal: options.signal });
|
|
3407
|
+
let state = "syncing";
|
|
3408
|
+
let ready;
|
|
3409
|
+
let fatal;
|
|
3410
|
+
const pending = new Map;
|
|
3411
|
+
let resolveReady;
|
|
3412
|
+
let rejectReady;
|
|
3413
|
+
const readyPromise = new Promise((resolve6, reject) => {
|
|
3414
|
+
resolveReady = resolve6;
|
|
3415
|
+
rejectReady = reject;
|
|
3416
|
+
});
|
|
3417
|
+
const handleEvent = (event) => {
|
|
3418
|
+
if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
|
|
3419
|
+
rejectReady(new Error("Remote Mac protocol version mismatch."));
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
if (event.type === "log")
|
|
3423
|
+
options.log?.(event.message);
|
|
3424
|
+
if (event.type === "native-log")
|
|
3425
|
+
options.nativeLog?.(event.entry);
|
|
3426
|
+
if (event.type === "state") {
|
|
3427
|
+
({ state } = event);
|
|
3428
|
+
options.onStateChange?.(state);
|
|
3429
|
+
}
|
|
3430
|
+
if (event.type === "timing")
|
|
3431
|
+
options.onPhaseTiming?.(event);
|
|
3432
|
+
if (event.type === "ready") {
|
|
3433
|
+
ready = event;
|
|
3434
|
+
resolveReady();
|
|
3435
|
+
}
|
|
3436
|
+
if (event.type === "fatal") {
|
|
3437
|
+
fatal = new Error(event.error);
|
|
3438
|
+
rejectReady(fatal);
|
|
3439
|
+
}
|
|
3440
|
+
if (event.type === "response") {
|
|
3441
|
+
const request2 = pending.get(event.id);
|
|
3442
|
+
if (!request2)
|
|
3443
|
+
return;
|
|
3444
|
+
pending.delete(event.id);
|
|
3445
|
+
if (event.ok)
|
|
3446
|
+
request2.resolve(event.result);
|
|
3447
|
+
else
|
|
3448
|
+
request2.reject(new Error(event.error ?? "Remote command failed."));
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
const stdoutDone = consumeLines2(process2.stdout, (line) => {
|
|
3452
|
+
if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
|
|
3453
|
+
return;
|
|
3454
|
+
try {
|
|
3455
|
+
handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
|
|
3456
|
+
} catch {
|
|
3457
|
+
options.log?.(`Remote Mac emitted an invalid protocol event.`);
|
|
3458
|
+
}
|
|
3459
|
+
}).catch((error) => {
|
|
3460
|
+
fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
|
|
3461
|
+
rejectReady(fatal);
|
|
3462
|
+
});
|
|
3463
|
+
const stderrDone = consumeLines2(process2.stderr, (line) => options.log?.(`[remote] ${line}`)).catch((error) => options.log?.(`[remote] ${error instanceof Error ? error.message : "Failed to read SSH stderr."}`));
|
|
3464
|
+
process2.exited.then(async (exitCode) => {
|
|
3465
|
+
await Promise.all([stdoutDone, stderrDone]);
|
|
3466
|
+
const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
|
|
3467
|
+
if (!ready)
|
|
3468
|
+
rejectReady(error);
|
|
3469
|
+
pending.forEach(({ reject }) => reject(error));
|
|
3470
|
+
pending.clear();
|
|
3471
|
+
return;
|
|
3472
|
+
});
|
|
3473
|
+
await readyPromise;
|
|
3474
|
+
if (!ready)
|
|
3475
|
+
throw fatal ?? new Error("Remote Mac did not become ready.");
|
|
3476
|
+
const totalDuration = performance.now() - startedAt;
|
|
3477
|
+
let currentReady = {
|
|
3478
|
+
...ready,
|
|
3479
|
+
timings: {
|
|
3480
|
+
...ready.timings,
|
|
3481
|
+
"remote-agent": agentDuration,
|
|
3482
|
+
"remote-connect": performance.now() - connectStartedAt,
|
|
3483
|
+
"remote-sync": syncDuration,
|
|
3484
|
+
total: totalDuration
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
options.log?.(`Remote Mac connected (${options.project.profile.name}); agent ${agent.uploaded ? "uploaded" : "cache hit"}, project synced, and iOS ready in ${totalDuration.toFixed(2)}ms.`);
|
|
3488
|
+
const request = (commandName) => {
|
|
3489
|
+
const id = randomUUID3();
|
|
3490
|
+
const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
|
|
3491
|
+
process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
|
|
3492
|
+
`);
|
|
3493
|
+
const flush = async () => {
|
|
3494
|
+
for (let attempt = 0;attempt < REMOTE_STDIN_FLUSH_ATTEMPTS; attempt++) {
|
|
3495
|
+
try {
|
|
3496
|
+
await process2.stdin.flush();
|
|
3497
|
+
return;
|
|
3498
|
+
} catch {
|
|
3499
|
+
await Promise.resolve();
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
};
|
|
3503
|
+
return flush().then(() => response);
|
|
3504
|
+
};
|
|
3505
|
+
let closed = false;
|
|
3506
|
+
const close = async () => {
|
|
3507
|
+
if (closed)
|
|
3508
|
+
return;
|
|
3509
|
+
closed = true;
|
|
3510
|
+
await request("close").catch(() => {
|
|
3511
|
+
return;
|
|
3512
|
+
});
|
|
3513
|
+
process2.stdin.end();
|
|
3514
|
+
await process2.exited.catch(() => {
|
|
3515
|
+
return;
|
|
3516
|
+
});
|
|
3517
|
+
};
|
|
3518
|
+
const makeSession = () => ({
|
|
3519
|
+
close,
|
|
3520
|
+
nativeCacheHit: currentReady.nativeCacheHit,
|
|
3521
|
+
startedSimulator: currentReady.startedSimulator,
|
|
3522
|
+
timings: currentReady.timings,
|
|
3523
|
+
udid: currentReady.udid,
|
|
3524
|
+
rebuild: async () => {
|
|
3525
|
+
const rebuildStartedAt = performance.now();
|
|
3526
|
+
const rebuildSyncStartedAt = performance.now();
|
|
3527
|
+
await syncProject(options.project);
|
|
3528
|
+
const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
|
|
3529
|
+
const result = await request("rebuild");
|
|
3530
|
+
currentReady = {
|
|
3531
|
+
...result,
|
|
3532
|
+
timings: {
|
|
3533
|
+
...result.timings,
|
|
3534
|
+
"remote-sync": rebuildSyncDuration,
|
|
3535
|
+
total: performance.now() - rebuildStartedAt
|
|
3536
|
+
}
|
|
3537
|
+
};
|
|
3538
|
+
return makeSession();
|
|
3539
|
+
},
|
|
3540
|
+
relaunch: async () => {
|
|
3541
|
+
await request("relaunch");
|
|
3542
|
+
},
|
|
3543
|
+
screenshot: async (destination) => {
|
|
3544
|
+
const result = await request("screenshot");
|
|
3545
|
+
const target = resolvePath2(options.project.projectRoot, destination);
|
|
3546
|
+
const targetRelative = relative6(options.project.projectRoot, target);
|
|
3547
|
+
if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
|
|
3548
|
+
throw new Error("iOS screenshot must remain inside the project.");
|
|
3549
|
+
await mkdir6(dirname5(target), { recursive: true });
|
|
3550
|
+
await writeFile7(target, Buffer.from(result.data, "base64"));
|
|
3551
|
+
return target;
|
|
3552
|
+
},
|
|
3553
|
+
get state() {
|
|
3554
|
+
return state;
|
|
3555
|
+
}
|
|
3556
|
+
});
|
|
3557
|
+
return makeSession();
|
|
1463
3558
|
};
|
|
1464
3559
|
// src/mobile/associationFiles.ts
|
|
1465
3560
|
import {
|
|
1466
|
-
access as
|
|
1467
|
-
mkdir as
|
|
1468
|
-
readFile as
|
|
1469
|
-
rename as
|
|
1470
|
-
rm as
|
|
1471
|
-
writeFile as
|
|
3561
|
+
access as access7,
|
|
3562
|
+
mkdir as mkdir7,
|
|
3563
|
+
readFile as readFile9,
|
|
3564
|
+
rename as rename8,
|
|
3565
|
+
rm as rm6,
|
|
3566
|
+
writeFile as writeFile8
|
|
1472
3567
|
} from "fs/promises";
|
|
1473
|
-
import { resolve as
|
|
3568
|
+
import { resolve as resolve7 } from "path";
|
|
1474
3569
|
import { Elysia } from "elysia";
|
|
1475
3570
|
|
|
1476
3571
|
// src/mobile/config.ts
|
|
1477
|
-
import { resolve as
|
|
3572
|
+
import { resolve as resolve6 } from "path";
|
|
1478
3573
|
var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
|
|
1479
3574
|
var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
|
|
1480
3575
|
var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
|
|
1481
3576
|
var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
|
|
1482
3577
|
var 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])?))*$/;
|
|
1483
3578
|
var resolveProjectPath = (projectRoot, value, field) => {
|
|
1484
|
-
const root =
|
|
1485
|
-
const path =
|
|
3579
|
+
const root = resolve6(projectRoot);
|
|
3580
|
+
const path = resolve6(root, value);
|
|
1486
3581
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
1487
3582
|
throw new TypeError(`${field} must remain inside the project root.`);
|
|
1488
3583
|
}
|
|
@@ -1503,8 +3598,9 @@ var normalizeEntry = (entry) => {
|
|
|
1503
3598
|
};
|
|
1504
3599
|
var normalizeProductionOrigin = (value) => {
|
|
1505
3600
|
const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
|
|
1506
|
-
|
|
1507
|
-
|
|
3601
|
+
const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
|
|
3602
|
+
if (parsed.protocol !== "https:" && !isLoopbackHttp) {
|
|
3603
|
+
throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
|
|
1508
3604
|
}
|
|
1509
3605
|
if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
1510
3606
|
throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
|
|
@@ -1528,9 +3624,8 @@ var normalizeHosts = (hosts, productionOrigin) => {
|
|
|
1528
3624
|
}
|
|
1529
3625
|
return value;
|
|
1530
3626
|
};
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
]);
|
|
3627
|
+
const productionHostname = new URL(productionOrigin).hostname;
|
|
3628
|
+
const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
|
|
1534
3629
|
for (const host of hosts ?? []) {
|
|
1535
3630
|
normalized.add(normalizeHostname(host));
|
|
1536
3631
|
}
|
|
@@ -1568,7 +3663,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
|
|
|
1568
3663
|
throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
|
|
1569
3664
|
}
|
|
1570
3665
|
const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
|
|
1571
|
-
const deepLinkScheme = config.deepLinks?.scheme
|
|
3666
|
+
const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
|
|
1572
3667
|
if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
|
|
1573
3668
|
throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
|
|
1574
3669
|
}
|
|
@@ -1670,7 +3765,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
|
|
|
1670
3765
|
var writeAtomic = async (path, source) => {
|
|
1671
3766
|
let current;
|
|
1672
3767
|
try {
|
|
1673
|
-
current = await
|
|
3768
|
+
current = await readFile9(path, "utf8");
|
|
1674
3769
|
} catch (error) {
|
|
1675
3770
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
1676
3771
|
throw error;
|
|
@@ -1679,23 +3774,23 @@ var writeAtomic = async (path, source) => {
|
|
|
1679
3774
|
if (current === source)
|
|
1680
3775
|
return false;
|
|
1681
3776
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
1682
|
-
await
|
|
1683
|
-
await
|
|
3777
|
+
await writeFile8(temporary, source, { flag: "wx" });
|
|
3778
|
+
await rename8(temporary, path);
|
|
1684
3779
|
return true;
|
|
1685
3780
|
};
|
|
1686
3781
|
var exists2 = async (path) => {
|
|
1687
3782
|
try {
|
|
1688
|
-
await
|
|
3783
|
+
await access7(path);
|
|
1689
3784
|
return true;
|
|
1690
3785
|
} catch {
|
|
1691
3786
|
return false;
|
|
1692
3787
|
}
|
|
1693
3788
|
};
|
|
1694
3789
|
var assertOwnedOutput = async (root) => {
|
|
1695
|
-
const path =
|
|
3790
|
+
const path = resolve7(root, OWNERSHIP_FILE);
|
|
1696
3791
|
let ownership;
|
|
1697
3792
|
try {
|
|
1698
|
-
ownership = JSON.parse(await
|
|
3793
|
+
ownership = JSON.parse(await readFile9(path, "utf8"));
|
|
1699
3794
|
} catch {
|
|
1700
3795
|
throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
|
|
1701
3796
|
}
|
|
@@ -1709,22 +3804,22 @@ var publishGeneratedDirectory = async (temporary, root) => {
|
|
|
1709
3804
|
await assertOwnedOutput(root);
|
|
1710
3805
|
const backup = `${root}.${crypto.randomUUID()}.previous`;
|
|
1711
3806
|
if (hasCurrent)
|
|
1712
|
-
await
|
|
3807
|
+
await rename8(root, backup);
|
|
1713
3808
|
try {
|
|
1714
|
-
await
|
|
3809
|
+
await rename8(temporary, root);
|
|
1715
3810
|
} catch (error) {
|
|
1716
3811
|
if (hasCurrent)
|
|
1717
|
-
await
|
|
3812
|
+
await rename8(backup, root);
|
|
1718
3813
|
throw error;
|
|
1719
3814
|
}
|
|
1720
3815
|
if (hasCurrent)
|
|
1721
|
-
await
|
|
3816
|
+
await rm6(backup, { force: true, recursive: true });
|
|
1722
3817
|
};
|
|
1723
3818
|
var materializeHost = async (root, host, files) => {
|
|
1724
|
-
const directory =
|
|
1725
|
-
await
|
|
3819
|
+
const directory = resolve7(root, host, ".well-known");
|
|
3820
|
+
await mkdir7(directory, { recursive: true });
|
|
1726
3821
|
return Promise.all(files.map(async ([name, document]) => {
|
|
1727
|
-
const path =
|
|
3822
|
+
const path = resolve7(directory, name);
|
|
1728
3823
|
await writeAtomic(path, `${JSON.stringify(document, null, 2)}
|
|
1729
3824
|
`);
|
|
1730
3825
|
return path;
|
|
@@ -1749,7 +3844,7 @@ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((
|
|
|
1749
3844
|
return endpoints;
|
|
1750
3845
|
});
|
|
1751
3846
|
var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
|
|
1752
|
-
const root =
|
|
3847
|
+
const root = resolve7(outputDirectory);
|
|
1753
3848
|
const temporary = `${root}.${crypto.randomUUID()}.tmp`;
|
|
1754
3849
|
const documents = createAbsoluteMobileAssociationDocuments(config, {
|
|
1755
3850
|
requireAll: true
|
|
@@ -1760,16 +3855,16 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
|
|
|
1760
3855
|
if (documents.apple) {
|
|
1761
3856
|
files.push(["apple-app-site-association", documents.apple]);
|
|
1762
3857
|
}
|
|
1763
|
-
await
|
|
3858
|
+
await mkdir7(temporary, { recursive: true });
|
|
1764
3859
|
try {
|
|
1765
3860
|
const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
|
|
1766
|
-
await writeAtomic(
|
|
3861
|
+
await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
|
|
1767
3862
|
`);
|
|
1768
3863
|
await publishGeneratedDirectory(temporary, root);
|
|
1769
|
-
const written = temporaryPaths.map((path) =>
|
|
3864
|
+
const written = temporaryPaths.map((path) => resolve7(root, path.slice(temporary.length + 1)));
|
|
1770
3865
|
return { root, written };
|
|
1771
3866
|
} catch (error) {
|
|
1772
|
-
await
|
|
3867
|
+
await rm6(temporary, { force: true, recursive: true });
|
|
1773
3868
|
throw error;
|
|
1774
3869
|
}
|
|
1775
3870
|
};
|
|
@@ -1814,10 +3909,10 @@ var frameworks2 = new Set([
|
|
|
1814
3909
|
"svelte",
|
|
1815
3910
|
"vue"
|
|
1816
3911
|
]);
|
|
1817
|
-
var
|
|
3912
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1818
3913
|
var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
|
|
1819
3914
|
var parseAbsoluteMobileBuildPageMetadata = (value) => {
|
|
1820
|
-
if (!
|
|
3915
|
+
if (!isRecord4(value))
|
|
1821
3916
|
return;
|
|
1822
3917
|
if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
|
|
1823
3918
|
return;
|
|
@@ -1831,45 +3926,75 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
|
|
|
1831
3926
|
};
|
|
1832
3927
|
};
|
|
1833
3928
|
// src/mobile/buildPipeline.ts
|
|
1834
|
-
import { readFile as
|
|
1835
|
-
import { join as
|
|
3929
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
3930
|
+
import { join as join14, resolve as resolve12 } from "path";
|
|
1836
3931
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1837
3932
|
|
|
1838
3933
|
// src/mobile/buildRelease.ts
|
|
1839
|
-
import { createHash as
|
|
1840
|
-
import { readFile as
|
|
1841
|
-
import { join as
|
|
1842
|
-
var sha256 = (bytes) =>
|
|
3934
|
+
import { createHash as createHash8 } from "crypto";
|
|
3935
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
3936
|
+
import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
|
|
3937
|
+
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
|
|
3938
|
+
var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
|
|
3939
|
+
var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
|
|
3940
|
+
if (path.endsWith("/htmx.min.js"))
|
|
3941
|
+
return match;
|
|
3942
|
+
const key = toPascal(basename2(path, extname(path)));
|
|
3943
|
+
const builtPath = manifest[key];
|
|
3944
|
+
return builtPath ? `${prefix}${builtPath}${suffix}` : match;
|
|
3945
|
+
});
|
|
1843
3946
|
var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
|
|
1844
3947
|
var resolveAssetPath = (buildDirectory, assetPath) => {
|
|
1845
|
-
const resolvedBuildDirectory =
|
|
1846
|
-
const resolvedAsset =
|
|
3948
|
+
const resolvedBuildDirectory = resolve8(buildDirectory);
|
|
3949
|
+
const resolvedAsset = resolve8(assetPath);
|
|
1847
3950
|
if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
|
|
1848
3951
|
return resolvedAsset;
|
|
1849
3952
|
}
|
|
1850
|
-
return
|
|
3953
|
+
return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
|
|
1851
3954
|
};
|
|
1852
3955
|
var pageFor = async (metadata, manifest, buildDirectory) => {
|
|
1853
3956
|
const assetPath = manifest[metadata.bundleKey];
|
|
1854
3957
|
if (!assetPath) {
|
|
1855
3958
|
throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
|
|
1856
3959
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
3960
|
+
let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
|
|
3961
|
+
if (metadata.framework === "html" || metadata.framework === "htmx") {
|
|
3962
|
+
const source = await readFile10(resolvedAssetPath, "utf8");
|
|
3963
|
+
const rewritten = rewriteStaticScriptPaths(source, manifest);
|
|
3964
|
+
const documentHash = sha256(new TextEncoder().encode(rewritten));
|
|
3965
|
+
resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
|
|
3966
|
+
await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
|
|
3967
|
+
await writeFile9(resolvedAssetPath, rewritten);
|
|
3968
|
+
}
|
|
3969
|
+
const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
|
|
3970
|
+
const styleAssetPath = [
|
|
3971
|
+
`${pageAssetKey}BundledCSS`,
|
|
3972
|
+
`${pageAssetKey}CompiledCSS`
|
|
3973
|
+
].map((key) => manifest[key]).find((path) => typeof path === "string");
|
|
3974
|
+
const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
|
|
3975
|
+
const [bytes, styleBytes] = await Promise.all([
|
|
3976
|
+
readFile10(resolvedAssetPath),
|
|
3977
|
+
resolvedStylePath ? readFile10(resolvedStylePath) : undefined
|
|
3978
|
+
]);
|
|
3979
|
+
const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
|
|
3980
|
+
const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
|
|
1860
3981
|
return {
|
|
1861
3982
|
bundleHash: sha256(bytes),
|
|
1862
3983
|
bundlePath,
|
|
1863
3984
|
contract: metadata.contract,
|
|
1864
3985
|
framework: metadata.framework,
|
|
1865
3986
|
pageId: metadata.pageId,
|
|
1866
|
-
propsSchemaHash: metadata.propsSchemaHash
|
|
3987
|
+
propsSchemaHash: metadata.propsSchemaHash,
|
|
3988
|
+
...styleBytes && styleBundlePath ? {
|
|
3989
|
+
styleBundleHash: sha256(styleBytes),
|
|
3990
|
+
styleBundlePath
|
|
3991
|
+
} : {}
|
|
1867
3992
|
};
|
|
1868
3993
|
};
|
|
1869
3994
|
var buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
1870
3995
|
const [captured, producerBytes] = await Promise.all([
|
|
1871
3996
|
captureAbsoluteMobileRouteGraph(options.app),
|
|
1872
|
-
|
|
3997
|
+
readFile10(options.producerPath)
|
|
1873
3998
|
]);
|
|
1874
3999
|
if (captured.length === 0) {
|
|
1875
4000
|
throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
|
|
@@ -1885,11 +4010,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
|
1885
4010
|
const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
|
|
1886
4011
|
const producerHash = sha256(producerBytes);
|
|
1887
4012
|
const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
|
|
1888
|
-
pages: pages.map(({
|
|
4013
|
+
pages: pages.map(({
|
|
4014
|
+
bundleHash,
|
|
4015
|
+
bundlePath,
|
|
4016
|
+
contract,
|
|
4017
|
+
pageId,
|
|
4018
|
+
styleBundleHash,
|
|
4019
|
+
styleBundlePath
|
|
4020
|
+
}) => ({
|
|
1889
4021
|
bundleHash,
|
|
1890
4022
|
bundlePath,
|
|
1891
4023
|
contract,
|
|
1892
|
-
pageId
|
|
4024
|
+
pageId,
|
|
4025
|
+
...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
|
|
1893
4026
|
})),
|
|
1894
4027
|
producerHash,
|
|
1895
4028
|
runtime: options.runtime
|
|
@@ -1949,16 +4082,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
|
|
|
1949
4082
|
|
|
1950
4083
|
// src/mobile/capacitorBundle.ts
|
|
1951
4084
|
import {
|
|
1952
|
-
|
|
1953
|
-
|
|
4085
|
+
cp,
|
|
4086
|
+
copyFile as copyFile5,
|
|
4087
|
+
mkdir as mkdir9,
|
|
1954
4088
|
mkdtemp as mkdtemp4,
|
|
1955
|
-
readFile as
|
|
1956
|
-
rename as
|
|
1957
|
-
rm as
|
|
1958
|
-
writeFile as
|
|
4089
|
+
readFile as readFile11,
|
|
4090
|
+
rename as rename9,
|
|
4091
|
+
rm as rm7,
|
|
4092
|
+
writeFile as writeFile10
|
|
1959
4093
|
} from "fs/promises";
|
|
1960
4094
|
import { existsSync as existsSync2 } from "fs";
|
|
1961
|
-
import { basename, dirname as
|
|
4095
|
+
import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
|
|
1962
4096
|
|
|
1963
4097
|
// src/mobile/routeMatcher.ts
|
|
1964
4098
|
var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
|
|
@@ -2114,14 +4248,14 @@ var envelopeResponse = (response, status) => new Response(JSON.stringify({
|
|
|
2114
4248
|
protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
|
|
2115
4249
|
response
|
|
2116
4250
|
}), { headers: responseHeaders(), status });
|
|
2117
|
-
var
|
|
4251
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2118
4252
|
var normalizeJsonValue = (value) => {
|
|
2119
4253
|
const serialized = JSON.stringify(value);
|
|
2120
4254
|
if (serialized === undefined) {
|
|
2121
4255
|
throw new TypeError("Mobile page props must be JSON-serializable.");
|
|
2122
4256
|
}
|
|
2123
4257
|
const parsed = JSON.parse(serialized);
|
|
2124
|
-
if (!
|
|
4258
|
+
if (!isRecord5(parsed)) {
|
|
2125
4259
|
throw new TypeError("Mobile page props must serialize to an object.");
|
|
2126
4260
|
}
|
|
2127
4261
|
return parsed;
|
|
@@ -2231,6 +4365,13 @@ class AbsoluteMobilePageProtocolError extends Error {
|
|
|
2231
4365
|
this.code = code;
|
|
2232
4366
|
}
|
|
2233
4367
|
}
|
|
4368
|
+
var disposeAbsoluteMobilePage = async (target = window) => {
|
|
4369
|
+
const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
|
|
4370
|
+
target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
|
|
4371
|
+
target.__ABSOLUTE_PAGE_READY__ = undefined;
|
|
4372
|
+
if (dispose)
|
|
4373
|
+
await dispose();
|
|
4374
|
+
};
|
|
2234
4375
|
var frameworks4 = new Set([
|
|
2235
4376
|
"angular",
|
|
2236
4377
|
"ember",
|
|
@@ -2246,14 +4387,14 @@ var upgradeReasons = new Set([
|
|
|
2246
4387
|
"protocol",
|
|
2247
4388
|
"runtime"
|
|
2248
4389
|
]);
|
|
2249
|
-
var
|
|
4390
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2250
4391
|
var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
|
|
2251
4392
|
var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
|
|
2252
4393
|
var parsePageResult = (value) => {
|
|
2253
4394
|
if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
|
|
2254
4395
|
throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
|
|
2255
4396
|
}
|
|
2256
|
-
if (!
|
|
4397
|
+
if (!isRecord6(value.props)) {
|
|
2257
4398
|
throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
|
|
2258
4399
|
}
|
|
2259
4400
|
return {
|
|
@@ -2286,12 +4427,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
|
|
|
2286
4427
|
throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
|
|
2287
4428
|
}
|
|
2288
4429
|
const target = options.target ?? window;
|
|
4430
|
+
await disposeAbsoluteMobilePage(target);
|
|
2289
4431
|
target.__INITIAL_PROPS__ = envelope.response.props;
|
|
4432
|
+
target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
|
|
2290
4433
|
target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
|
|
2291
4434
|
await options.loadPage({
|
|
2292
4435
|
contract: envelope.response.contract,
|
|
2293
4436
|
pageId: envelope.response.pageId
|
|
2294
4437
|
});
|
|
4438
|
+
if (target.__ABSOLUTE_PAGE_READY__) {
|
|
4439
|
+
await target.__ABSOLUTE_PAGE_READY__;
|
|
4440
|
+
}
|
|
2295
4441
|
return {
|
|
2296
4442
|
contract: envelope.response.contract,
|
|
2297
4443
|
kind: "rendered",
|
|
@@ -2299,7 +4445,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
|
|
|
2299
4445
|
};
|
|
2300
4446
|
};
|
|
2301
4447
|
var parseAbsoluteMobilePageEnvelope = (value) => {
|
|
2302
|
-
if (!
|
|
4448
|
+
if (!isRecord6(value) || !isRecord6(value.response)) {
|
|
2303
4449
|
throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
|
|
2304
4450
|
}
|
|
2305
4451
|
if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
|
|
@@ -2384,19 +4530,51 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
|
|
|
2384
4530
|
}
|
|
2385
4531
|
return `${url.pathname || "/"}${url.search}${url.hash}`;
|
|
2386
4532
|
};
|
|
4533
|
+
var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
|
|
4534
|
+
const url = new URL(value, `${localOrigin}/`);
|
|
4535
|
+
const local = new URL(localOrigin);
|
|
4536
|
+
const production = new URL(manifest.productionOrigin);
|
|
4537
|
+
const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
|
|
4538
|
+
if (!matches(url, local) && !matches(url, production)) {
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
4542
|
+
};
|
|
2387
4543
|
|
|
2388
4544
|
// src/mobile/capacitorBundle.ts
|
|
2389
4545
|
var MANIFEST_FILE = "absolute-mobile-manifest.json";
|
|
2390
4546
|
var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
|
|
2391
4547
|
var INDEX_FILE = "index.html";
|
|
2392
|
-
var
|
|
4548
|
+
var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
|
|
4549
|
+
var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
|
|
4550
|
+
var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
|
|
4551
|
+
"angular",
|
|
4552
|
+
"html",
|
|
4553
|
+
"htmx",
|
|
4554
|
+
"react",
|
|
4555
|
+
"svelte",
|
|
4556
|
+
"vue"
|
|
4557
|
+
]);
|
|
4558
|
+
var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
|
|
2393
4559
|
var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
|
|
2394
4560
|
var shellBootstrapModule = () => {
|
|
2395
|
-
const candidate = ["js", "ts"].map((extension) =>
|
|
4561
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
|
|
2396
4562
|
if (candidate)
|
|
2397
4563
|
return candidate;
|
|
2398
4564
|
throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
|
|
2399
4565
|
};
|
|
4566
|
+
var shellAuthModule = () => {
|
|
4567
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
|
|
4568
|
+
if (candidate)
|
|
4569
|
+
return candidate;
|
|
4570
|
+
throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
|
|
4571
|
+
};
|
|
4572
|
+
var shellSyncModule = () => {
|
|
4573
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
|
|
4574
|
+
if (candidate)
|
|
4575
|
+
return candidate;
|
|
4576
|
+
throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
|
|
4577
|
+
};
|
|
2400
4578
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
2401
4579
|
var indexHtml = (appName) => `<!doctype html>
|
|
2402
4580
|
<html>
|
|
@@ -2413,18 +4591,58 @@ var indexHtml = (appName) => `<!doctype html>
|
|
|
2413
4591
|
</html>
|
|
2414
4592
|
`;
|
|
2415
4593
|
var sourceAssetPath = (buildDirectory, bundlePath) => {
|
|
2416
|
-
const root =
|
|
2417
|
-
const asset =
|
|
4594
|
+
const root = resolve9(buildDirectory);
|
|
4595
|
+
const asset = resolve9(root, bundlePath.replace(/^\/+/, ""));
|
|
2418
4596
|
if (!asset.startsWith(`${root}/`)) {
|
|
2419
4597
|
throw new TypeError("Mobile page bundle escaped the build directory.");
|
|
2420
4598
|
}
|
|
2421
4599
|
return asset;
|
|
2422
4600
|
};
|
|
2423
|
-
var
|
|
4601
|
+
var importEntryTarget = (entry) => {
|
|
4602
|
+
if (typeof entry === "string")
|
|
4603
|
+
return entry;
|
|
4604
|
+
if (typeof entry === "object" && entry !== null)
|
|
4605
|
+
return Reflect.get(entry, "import");
|
|
4606
|
+
return;
|
|
4607
|
+
};
|
|
4608
|
+
var resolveProjectImport = async (projectRoot, specifier) => {
|
|
4609
|
+
const segments = specifier.split("/");
|
|
4610
|
+
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
|
|
4611
|
+
const subpath = specifier.slice(packageName.length);
|
|
4612
|
+
const packageDirectory = join9(resolve9(projectRoot), "node_modules", packageName);
|
|
4613
|
+
const manifest = JSON.parse(await readFile11(join9(packageDirectory, "package.json"), "utf8"));
|
|
4614
|
+
const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
|
|
4615
|
+
const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
|
|
4616
|
+
const target = importEntryTarget(entry);
|
|
4617
|
+
if (typeof target !== "string" || !target.startsWith("./"))
|
|
4618
|
+
throw new TypeError(`${specifier} does not publish an import entry.`);
|
|
4619
|
+
const resolved = resolve9(packageDirectory, target);
|
|
4620
|
+
if (!resolved.startsWith(`${resolve9(packageDirectory)}/`))
|
|
4621
|
+
throw new TypeError(`${specifier} has an unsafe import entry.`);
|
|
4622
|
+
return resolved;
|
|
4623
|
+
};
|
|
4624
|
+
var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapabilities, projectRoot) => {
|
|
2424
4625
|
const modulePath = shellBootstrapModule();
|
|
2425
|
-
const
|
|
2426
|
-
|
|
2427
|
-
|
|
4626
|
+
const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
|
|
4627
|
+
` : "";
|
|
4628
|
+
const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
|
|
4629
|
+
const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
|
|
4630
|
+
` : "";
|
|
4631
|
+
const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
|
|
4632
|
+
const provider = deviceCapabilities.providers[name];
|
|
4633
|
+
if (!provider)
|
|
4634
|
+
throw new TypeError(`Missing device capability provider ${name}.`);
|
|
4635
|
+
return `import { ${provider.factory} as absoluteDeviceCapability${index} } from ${JSON.stringify(await resolveProjectImport(projectRoot, provider.module))};`;
|
|
4636
|
+
}))).join(`
|
|
4637
|
+
`);
|
|
4638
|
+
const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: absoluteDeviceCapability${index}()`).join(", ");
|
|
4639
|
+
const entryPath = join9(staging, ".absolute-mobile-entry.ts");
|
|
4640
|
+
const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
|
|
4641
|
+
await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
|
|
4642
|
+
import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
|
|
4643
|
+
${authImport}${syncImport}${capabilityImports}
|
|
4644
|
+
installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
|
|
4645
|
+
void startAbsoluteMobileShell(${options});
|
|
2428
4646
|
`);
|
|
2429
4647
|
const build = await Bun.build({
|
|
2430
4648
|
entrypoints: [entryPath],
|
|
@@ -2435,31 +4653,31 @@ void startAbsoluteMobileShell();
|
|
|
2435
4653
|
if (!build.success || build.outputs.length !== 1) {
|
|
2436
4654
|
throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
|
|
2437
4655
|
}
|
|
2438
|
-
await
|
|
2439
|
-
await
|
|
4656
|
+
await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
|
|
4657
|
+
await rm7(entryPath, { force: true });
|
|
2440
4658
|
};
|
|
2441
4659
|
var removePreviousBundle = async (backup, moved) => {
|
|
2442
4660
|
if (!moved)
|
|
2443
4661
|
return;
|
|
2444
|
-
await
|
|
4662
|
+
await rm7(backup, { force: true, recursive: true });
|
|
2445
4663
|
};
|
|
2446
4664
|
var restorePreviousBundle = async (backup, destination, moved) => {
|
|
2447
4665
|
if (!moved)
|
|
2448
4666
|
return;
|
|
2449
|
-
await
|
|
4667
|
+
await rename9(backup, destination);
|
|
2450
4668
|
};
|
|
2451
4669
|
var installBundle = async (staging, destination) => {
|
|
2452
4670
|
const backup = `${destination}.previous-${crypto.randomUUID()}`;
|
|
2453
4671
|
let movedPrevious = false;
|
|
2454
4672
|
try {
|
|
2455
|
-
await
|
|
4673
|
+
await rename9(destination, backup);
|
|
2456
4674
|
movedPrevious = true;
|
|
2457
4675
|
} catch (error) {
|
|
2458
4676
|
if (!errorHasCode2(error, "ENOENT"))
|
|
2459
4677
|
throw error;
|
|
2460
4678
|
}
|
|
2461
4679
|
try {
|
|
2462
|
-
await
|
|
4680
|
+
await rename9(staging, destination);
|
|
2463
4681
|
await removePreviousBundle(backup, movedPrevious);
|
|
2464
4682
|
} catch (error) {
|
|
2465
4683
|
await restorePreviousBundle(backup, destination, movedPrevious);
|
|
@@ -2467,21 +4685,62 @@ var installBundle = async (staging, destination) => {
|
|
|
2467
4685
|
}
|
|
2468
4686
|
};
|
|
2469
4687
|
var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
|
|
2470
|
-
if (page.framework
|
|
2471
|
-
throw new TypeError(`Capacitor
|
|
4688
|
+
if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
|
|
4689
|
+
throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
|
|
2472
4690
|
}
|
|
2473
|
-
const extension =
|
|
4691
|
+
const extension = extname2(page.bundlePath) || ".js";
|
|
2474
4692
|
const localBundlePath = `./pages/${page.bundleHash}${extension}`;
|
|
2475
4693
|
const source = sourceAssetPath(buildDirectory, page.bundlePath);
|
|
2476
|
-
await
|
|
4694
|
+
await copyFile5(source, join9(staging, localBundlePath));
|
|
2477
4695
|
await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
|
|
2478
|
-
|
|
4696
|
+
let localStylePath;
|
|
4697
|
+
if (page.styleBundlePath && page.styleBundleHash) {
|
|
4698
|
+
const styleExtension = extname2(page.styleBundlePath) || ".css";
|
|
4699
|
+
localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
|
|
4700
|
+
const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
|
|
4701
|
+
await mkdir9(dirname7(join9(staging, localStylePath)), {
|
|
4702
|
+
recursive: true
|
|
4703
|
+
});
|
|
4704
|
+
await copyFile5(styleSource, join9(staging, localStylePath));
|
|
4705
|
+
await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
|
|
4706
|
+
}
|
|
4707
|
+
return {
|
|
4708
|
+
...page,
|
|
4709
|
+
localBundlePath,
|
|
4710
|
+
...localStylePath ? { localStylePath } : {}
|
|
4711
|
+
};
|
|
2479
4712
|
};
|
|
2480
|
-
var absoluteClientImports = async (sourcePath) => {
|
|
2481
|
-
const source = await
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
4713
|
+
var absoluteClientImports = async (sourcePath, buildDirectory) => {
|
|
4714
|
+
const source = await readFile11(sourcePath, "utf8");
|
|
4715
|
+
const extension = extname2(sourcePath).toLowerCase();
|
|
4716
|
+
let scriptLoader;
|
|
4717
|
+
if (extension === ".tsx")
|
|
4718
|
+
scriptLoader = "tsx";
|
|
4719
|
+
else if (extension === ".ts")
|
|
4720
|
+
scriptLoader = "ts";
|
|
4721
|
+
else if (extension === ".jsx")
|
|
4722
|
+
scriptLoader = "jsx";
|
|
4723
|
+
else if ([".js", ".mjs", ".cjs"].includes(extension))
|
|
4724
|
+
scriptLoader = "js";
|
|
4725
|
+
const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
|
|
4726
|
+
const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
|
|
4727
|
+
const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
|
|
4728
|
+
return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
|
|
4729
|
+
if (!specifier)
|
|
4730
|
+
return [];
|
|
4731
|
+
if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
|
|
4732
|
+
return [];
|
|
4733
|
+
}
|
|
4734
|
+
const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
|
|
4735
|
+
if (clean.startsWith("/"))
|
|
4736
|
+
return [clean];
|
|
4737
|
+
const resolved = resolve9(dirname7(sourcePath), clean);
|
|
4738
|
+
const root = resolve9(buildDirectory);
|
|
4739
|
+
const relativePath = relative8(root, resolved).replaceAll("\\", "/");
|
|
4740
|
+
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
4741
|
+
throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
|
|
4742
|
+
}
|
|
4743
|
+
return [`/${relativePath}`];
|
|
2485
4744
|
});
|
|
2486
4745
|
};
|
|
2487
4746
|
var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
|
|
@@ -2489,13 +4748,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
|
|
|
2489
4748
|
return;
|
|
2490
4749
|
copied.add(specifier);
|
|
2491
4750
|
const source = sourceAssetPath(buildDirectory, specifier);
|
|
2492
|
-
const destination =
|
|
2493
|
-
await
|
|
2494
|
-
await
|
|
4751
|
+
const destination = join9(staging, specifier.replace(/^\/+/, ""));
|
|
4752
|
+
await mkdir9(dirname7(destination), { recursive: true });
|
|
4753
|
+
await copyFile5(source, destination);
|
|
2495
4754
|
await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
|
|
2496
4755
|
};
|
|
2497
4756
|
var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
|
|
2498
|
-
const dependencies = await absoluteClientImports(sourcePath);
|
|
4757
|
+
const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
|
|
2499
4758
|
await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
|
|
2500
4759
|
};
|
|
2501
4760
|
var materializeAbsoluteCapacitorWebBundle = async (options) => {
|
|
@@ -2503,69 +4762,89 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
|
|
|
2503
4762
|
throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
|
|
2504
4763
|
}
|
|
2505
4764
|
const destination = options.config.bundleDirectory;
|
|
2506
|
-
await
|
|
2507
|
-
const staging = await mkdtemp4(
|
|
4765
|
+
await mkdir9(dirname7(destination), { recursive: true });
|
|
4766
|
+
const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
|
|
2508
4767
|
try {
|
|
2509
|
-
const pageDirectory =
|
|
2510
|
-
await
|
|
4768
|
+
const pageDirectory = join9(staging, "pages");
|
|
4769
|
+
await mkdir9(pageDirectory, { recursive: true });
|
|
4770
|
+
await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
|
|
4771
|
+
destination: join9(staging, directory),
|
|
4772
|
+
source: join9(options.buildDirectory, directory)
|
|
4773
|
+
})).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
|
|
2511
4774
|
const copiedDependencies = new Set;
|
|
2512
4775
|
const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
|
|
2513
4776
|
const manifest = {
|
|
2514
4777
|
appBuild: options.artifact.appBuild,
|
|
4778
|
+
...options.auth ? { auth: options.auth } : {},
|
|
2515
4779
|
appId: options.config.appId,
|
|
2516
4780
|
appName: options.config.appName,
|
|
2517
4781
|
deepLinkHosts: options.config.deepLinkHosts,
|
|
2518
4782
|
deepLinkScheme: options.config.deepLinkScheme,
|
|
4783
|
+
deviceCapabilities: options.deviceCapabilities.capabilities,
|
|
2519
4784
|
entry: options.config.entry,
|
|
2520
4785
|
format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
2521
4786
|
pages,
|
|
2522
4787
|
productionOrigin: options.config.productionOrigin,
|
|
2523
4788
|
routes: options.artifact.routes,
|
|
2524
|
-
runtime: options.artifact.runtime
|
|
4789
|
+
runtime: options.artifact.runtime,
|
|
4790
|
+
...options.sync ? {
|
|
4791
|
+
sync: {
|
|
4792
|
+
background: {
|
|
4793
|
+
endpoint: new URL("/__absolute/sync/background", options.config.productionOrigin).href,
|
|
4794
|
+
intervalMinutes: 15
|
|
4795
|
+
},
|
|
4796
|
+
socketTickets: true,
|
|
4797
|
+
storageSchema: options.syncSchema ?? {
|
|
4798
|
+
components: [
|
|
4799
|
+
{ id: "@absolutejs/app", version: 1 }
|
|
4800
|
+
]
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
} : {}
|
|
2525
4804
|
};
|
|
2526
4805
|
await Promise.all([
|
|
2527
|
-
|
|
4806
|
+
writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
2528
4807
|
`),
|
|
2529
|
-
|
|
2530
|
-
buildShellBootstrap(staging)
|
|
4808
|
+
writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
|
|
4809
|
+
buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
|
|
2531
4810
|
]);
|
|
2532
4811
|
await installBundle(staging, destination);
|
|
2533
4812
|
return manifest;
|
|
2534
4813
|
} catch (error) {
|
|
2535
|
-
await
|
|
4814
|
+
await rm7(staging, { force: true, recursive: true });
|
|
2536
4815
|
throw error;
|
|
2537
4816
|
}
|
|
2538
4817
|
};
|
|
2539
4818
|
|
|
2540
4819
|
// src/mobile/materializedBundle.ts
|
|
2541
|
-
import { createHash as
|
|
4820
|
+
import { createHash as createHash9 } from "crypto";
|
|
2542
4821
|
import {
|
|
2543
|
-
access as
|
|
2544
|
-
mkdir as
|
|
4822
|
+
access as access8,
|
|
4823
|
+
mkdir as mkdir10,
|
|
2545
4824
|
mkdtemp as mkdtemp5,
|
|
2546
|
-
readFile as
|
|
2547
|
-
rename as
|
|
2548
|
-
rm as
|
|
2549
|
-
writeFile as
|
|
4825
|
+
readFile as readFile12,
|
|
4826
|
+
rename as rename10,
|
|
4827
|
+
rm as rm8,
|
|
4828
|
+
writeFile as writeFile11
|
|
2550
4829
|
} from "fs/promises";
|
|
2551
|
-
import { dirname as
|
|
4830
|
+
import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
|
|
2552
4831
|
import { pathToFileURL } from "url";
|
|
2553
4832
|
var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
|
|
2554
4833
|
var CURRENT_BUNDLE_FILE = "current.json";
|
|
2555
4834
|
var BUNDLES_DIRECTORY = "bundles";
|
|
2556
4835
|
var ARTIFACT_FILE2 = "artifact.json";
|
|
2557
4836
|
var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
|
|
2558
|
-
var
|
|
4837
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2559
4838
|
var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
|
|
2560
4839
|
var bundleIdFor = (currentReleaseId, releases) => {
|
|
2561
4840
|
const identity = JSON.stringify({
|
|
2562
4841
|
currentReleaseId,
|
|
2563
4842
|
releases: releases.map(({ releaseId }) => releaseId)
|
|
2564
4843
|
});
|
|
2565
|
-
return `amb_${
|
|
4844
|
+
return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
|
|
2566
4845
|
};
|
|
2567
4846
|
var parseBundleIndex = (value) => {
|
|
2568
|
-
if (!
|
|
4847
|
+
if (!isRecord7(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
|
|
2569
4848
|
throw new TypeError("Invalid materialized mobile compatibility bundle.");
|
|
2570
4849
|
}
|
|
2571
4850
|
const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
|
|
@@ -2588,30 +4867,30 @@ var parseBundleIndex = (value) => {
|
|
|
2588
4867
|
};
|
|
2589
4868
|
};
|
|
2590
4869
|
var writeRelease = async (root, release) => {
|
|
2591
|
-
const directory =
|
|
2592
|
-
const producerPath =
|
|
2593
|
-
await
|
|
4870
|
+
const directory = join10(root, release.artifact.releaseId);
|
|
4871
|
+
const producerPath = join10(directory, release.artifact.producer.module);
|
|
4872
|
+
await mkdir10(dirname8(producerPath), { recursive: true });
|
|
2594
4873
|
await Promise.all([
|
|
2595
|
-
|
|
4874
|
+
writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
|
|
2596
4875
|
`),
|
|
2597
|
-
|
|
4876
|
+
writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
|
|
2598
4877
|
]);
|
|
2599
4878
|
};
|
|
2600
4879
|
var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
|
|
2601
|
-
const destination =
|
|
4880
|
+
const destination = join10(bundlesRoot, bundleId);
|
|
2602
4881
|
try {
|
|
2603
|
-
await
|
|
4882
|
+
await access8(destination);
|
|
2604
4883
|
return destination;
|
|
2605
4884
|
} catch (error) {
|
|
2606
4885
|
if (!errorHasCode3(error, "ENOENT"))
|
|
2607
4886
|
throw error;
|
|
2608
4887
|
}
|
|
2609
|
-
const staging = await mkdtemp5(
|
|
4888
|
+
const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
|
|
2610
4889
|
try {
|
|
2611
4890
|
await Promise.all(releases.map((release) => writeRelease(staging, release)));
|
|
2612
|
-
await
|
|
4891
|
+
await rename10(staging, destination);
|
|
2613
4892
|
} catch (error) {
|
|
2614
|
-
await
|
|
4893
|
+
await rm8(staging, { force: true, recursive: true });
|
|
2615
4894
|
if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
|
|
2616
4895
|
return destination;
|
|
2617
4896
|
}
|
|
@@ -2622,7 +4901,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
|
|
|
2622
4901
|
var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
|
|
2623
4902
|
var resolveProducerHandler = (loaded, exportName) => {
|
|
2624
4903
|
const value = loaded[exportName];
|
|
2625
|
-
if (!
|
|
4904
|
+
if (!isRecord7(value) || typeof value.handle !== "function") {
|
|
2626
4905
|
throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
|
|
2627
4906
|
}
|
|
2628
4907
|
const { handle } = value;
|
|
@@ -2639,16 +4918,16 @@ var resolveProducerHandler = (loaded, exportName) => {
|
|
|
2639
4918
|
};
|
|
2640
4919
|
};
|
|
2641
4920
|
var loadAbsoluteMobileMaterializedBundle = async (root) => {
|
|
2642
|
-
const resolvedRoot =
|
|
2643
|
-
const serialized = await
|
|
4921
|
+
const resolvedRoot = resolvePath3(root);
|
|
4922
|
+
const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
|
|
2644
4923
|
const parsed = JSON.parse(serialized);
|
|
2645
4924
|
const index = parseBundleIndex(parsed);
|
|
2646
|
-
const bundleRoot =
|
|
4925
|
+
const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
|
|
2647
4926
|
return {
|
|
2648
4927
|
artifacts: index.releases,
|
|
2649
4928
|
currentReleaseId: index.currentReleaseId,
|
|
2650
4929
|
loadProducer: async (artifact) => {
|
|
2651
|
-
const modulePath =
|
|
4930
|
+
const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
|
|
2652
4931
|
await verifyAbsoluteMobileCompatibilityProducer({
|
|
2653
4932
|
artifact,
|
|
2654
4933
|
producer: Bun.file(modulePath)
|
|
@@ -2674,9 +4953,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
|
|
|
2674
4953
|
}
|
|
2675
4954
|
return release;
|
|
2676
4955
|
});
|
|
2677
|
-
const root =
|
|
2678
|
-
const bundlesRoot =
|
|
2679
|
-
await
|
|
4956
|
+
const root = resolvePath3(input.root);
|
|
4957
|
+
const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
|
|
4958
|
+
await mkdir10(bundlesRoot, { recursive: true });
|
|
2680
4959
|
const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
|
|
2681
4960
|
await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
|
|
2682
4961
|
const index = {
|
|
@@ -2685,22 +4964,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
|
|
|
2685
4964
|
format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
|
|
2686
4965
|
releases: artifacts
|
|
2687
4966
|
};
|
|
2688
|
-
const pointerPath =
|
|
2689
|
-
const temporaryPointerPath =
|
|
2690
|
-
await
|
|
4967
|
+
const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
|
|
4968
|
+
const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
|
|
4969
|
+
await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
|
|
2691
4970
|
`, { flag: "wx" });
|
|
2692
|
-
await
|
|
4971
|
+
await rename10(temporaryPointerPath, pointerPath);
|
|
2693
4972
|
return index;
|
|
2694
4973
|
};
|
|
2695
4974
|
var readAbsoluteMobileMaterializedReleases = async (root) => {
|
|
2696
|
-
const resolvedRoot =
|
|
4975
|
+
const resolvedRoot = resolvePath3(root);
|
|
2697
4976
|
try {
|
|
2698
|
-
const serialized = await
|
|
4977
|
+
const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
|
|
2699
4978
|
const parsed = JSON.parse(serialized);
|
|
2700
4979
|
const index = parseBundleIndex(parsed);
|
|
2701
|
-
const bundleRoot =
|
|
4980
|
+
const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
|
|
2702
4981
|
return Promise.all(index.releases.map(async (artifact) => {
|
|
2703
|
-
const producer = Bun.file(
|
|
4982
|
+
const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
|
|
2704
4983
|
await verifyAbsoluteMobileCompatibilityProducer({
|
|
2705
4984
|
artifact,
|
|
2706
4985
|
producer
|
|
@@ -2714,6 +4993,271 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
|
|
|
2714
4993
|
}
|
|
2715
4994
|
};
|
|
2716
4995
|
|
|
4996
|
+
// src/mobile/nativeAuth.ts
|
|
4997
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
4998
|
+
import { join as join11 } from "path";
|
|
4999
|
+
var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
|
|
5000
|
+
var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
|
|
5001
|
+
var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
|
|
5002
|
+
var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
|
|
5003
|
+
var readPackageManifest = (projectRoot) => {
|
|
5004
|
+
try {
|
|
5005
|
+
return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
|
|
5006
|
+
} catch {
|
|
5007
|
+
return;
|
|
5008
|
+
}
|
|
5009
|
+
};
|
|
5010
|
+
var packageManifestHas = (manifest, packageName) => {
|
|
5011
|
+
if (typeof manifest !== "object" || manifest === null)
|
|
5012
|
+
return false;
|
|
5013
|
+
return [
|
|
5014
|
+
Reflect.get(manifest, "dependencies"),
|
|
5015
|
+
Reflect.get(manifest, "devDependencies"),
|
|
5016
|
+
Reflect.get(manifest, "optionalDependencies"),
|
|
5017
|
+
Reflect.get(manifest, "peerDependencies")
|
|
5018
|
+
].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
|
|
5019
|
+
};
|
|
5020
|
+
var createAbsoluteMobileAuthManifest = (config) => {
|
|
5021
|
+
const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
|
|
5022
|
+
return {
|
|
5023
|
+
clientId: `absolutejs-native:${config.appId}`,
|
|
5024
|
+
issuer: config.productionOrigin,
|
|
5025
|
+
redirectUri: `${scheme}://auth/callback`,
|
|
5026
|
+
scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
|
|
5027
|
+
};
|
|
5028
|
+
};
|
|
5029
|
+
var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
|
|
5030
|
+
const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
|
|
5031
|
+
const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
|
|
5032
|
+
if (serialized === undefined)
|
|
5033
|
+
delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
|
|
5034
|
+
else
|
|
5035
|
+
process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
|
|
5036
|
+
return auth;
|
|
5037
|
+
};
|
|
5038
|
+
var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
|
|
5039
|
+
var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
|
|
5040
|
+
var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
|
|
5041
|
+
var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
|
|
5042
|
+
{
|
|
5043
|
+
...auth,
|
|
5044
|
+
name: `${config.appName} native app`
|
|
5045
|
+
}
|
|
5046
|
+
]);
|
|
5047
|
+
|
|
5048
|
+
// src/mobile/buildPipeline.ts
|
|
5049
|
+
init_syncSchema();
|
|
5050
|
+
|
|
5051
|
+
// src/mobile/deviceCapabilities.ts
|
|
5052
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5053
|
+
import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
|
|
5054
|
+
import ts from "typescript";
|
|
5055
|
+
var DEVICES_PACKAGE = "@absolutejs/devices";
|
|
5056
|
+
var CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor";
|
|
5057
|
+
var SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
|
|
5058
|
+
var IGNORED_DIRECTORIES = new Set([
|
|
5059
|
+
".absolutejs",
|
|
5060
|
+
".git",
|
|
5061
|
+
".test-builds",
|
|
5062
|
+
".test-shards",
|
|
5063
|
+
"build",
|
|
5064
|
+
"dist",
|
|
5065
|
+
"node_modules",
|
|
5066
|
+
"test",
|
|
5067
|
+
"tests"
|
|
5068
|
+
]);
|
|
5069
|
+
var IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
|
|
5070
|
+
var CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
|
|
5071
|
+
var CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
|
|
5072
|
+
var ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
|
|
5073
|
+
var IOS_USAGE_DESCRIPTIONS = new Set([
|
|
5074
|
+
"camera",
|
|
5075
|
+
"location-always",
|
|
5076
|
+
"location-when-in-use",
|
|
5077
|
+
"photo-library",
|
|
5078
|
+
"photo-library-add"
|
|
5079
|
+
]);
|
|
5080
|
+
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5081
|
+
var readJson = (path) => {
|
|
5082
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
5083
|
+
if (!object2(value))
|
|
5084
|
+
throw new TypeError(`${path} must contain an object.`);
|
|
5085
|
+
return value;
|
|
5086
|
+
};
|
|
5087
|
+
var text = (value, field) => {
|
|
5088
|
+
if (typeof value !== "string" || value.length === 0)
|
|
5089
|
+
throw new TypeError(`${field} must be a non-empty string.`);
|
|
5090
|
+
return value;
|
|
5091
|
+
};
|
|
5092
|
+
var androidPermissions = (value, field) => {
|
|
5093
|
+
if (value === undefined)
|
|
5094
|
+
return;
|
|
5095
|
+
if (!object2(value))
|
|
5096
|
+
throw new TypeError(`${field} must be an object.`);
|
|
5097
|
+
const { permissions } = value;
|
|
5098
|
+
if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
|
|
5099
|
+
throw new TypeError(`${field}.permissions must contain Android permission names.`);
|
|
5100
|
+
return [...permissions];
|
|
5101
|
+
};
|
|
5102
|
+
var iosUsageDescriptions = (value, field) => {
|
|
5103
|
+
if (value === undefined)
|
|
5104
|
+
return;
|
|
5105
|
+
if (!object2(value))
|
|
5106
|
+
throw new TypeError(`${field} must be an object.`);
|
|
5107
|
+
const { usageDescriptions } = value;
|
|
5108
|
+
if (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose)))
|
|
5109
|
+
throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
|
|
5110
|
+
return [...usageDescriptions];
|
|
5111
|
+
};
|
|
5112
|
+
var parseProvider = (name, value) => {
|
|
5113
|
+
if (!IDENTIFIER_PATTERN.test(name))
|
|
5114
|
+
throw new TypeError("Device capability names must be identifiers.");
|
|
5115
|
+
if (!object2(value))
|
|
5116
|
+
throw new TypeError(`Device capability ${name} must be an object.`);
|
|
5117
|
+
const factory = text(value.factory, `${name}.factory`);
|
|
5118
|
+
const module = text(value.module, `${name}.module`);
|
|
5119
|
+
if (!IDENTIFIER_PATTERN.test(factory))
|
|
5120
|
+
throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
|
|
5121
|
+
if (!CAPACITOR_MODULE_PATTERN.test(module))
|
|
5122
|
+
throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
|
|
5123
|
+
if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
|
|
5124
|
+
throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
|
|
5125
|
+
let native;
|
|
5126
|
+
const { native: nativeMetadata } = value;
|
|
5127
|
+
if (nativeMetadata !== undefined) {
|
|
5128
|
+
if (!object2(nativeMetadata))
|
|
5129
|
+
throw new TypeError(`${name}.native must be an object.`);
|
|
5130
|
+
const { android, ios } = nativeMetadata;
|
|
5131
|
+
const permissions = androidPermissions(android, `${name}.native.android`);
|
|
5132
|
+
const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
|
|
5133
|
+
native = {
|
|
5134
|
+
...permissions === undefined ? {} : { android: { permissions } },
|
|
5135
|
+
...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5138
|
+
return {
|
|
5139
|
+
factory,
|
|
5140
|
+
module,
|
|
5141
|
+
...native === undefined ? {} : { native },
|
|
5142
|
+
packages: [...value.packages]
|
|
5143
|
+
};
|
|
5144
|
+
};
|
|
5145
|
+
var absoluteDeviceNativeRequirements = (plan) => ({
|
|
5146
|
+
androidPermissions: [
|
|
5147
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
|
|
5148
|
+
].sort(),
|
|
5149
|
+
iosUsageDescriptions: [
|
|
5150
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
|
|
5151
|
+
].sort()
|
|
5152
|
+
});
|
|
5153
|
+
var loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
|
|
5154
|
+
const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
|
|
5155
|
+
const manifest = readJson(path);
|
|
5156
|
+
const { absolutejs } = manifest;
|
|
5157
|
+
const devices = object2(absolutejs) ? absolutejs.devices : undefined;
|
|
5158
|
+
if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
|
|
5159
|
+
throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
|
|
5160
|
+
const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
|
|
5161
|
+
name,
|
|
5162
|
+
provider: parseProvider(name, provider)
|
|
5163
|
+
}));
|
|
5164
|
+
return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
|
|
5165
|
+
};
|
|
5166
|
+
var isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
5167
|
+
var importedCapabilities = (source, file) => {
|
|
5168
|
+
const names = new Set;
|
|
5169
|
+
const namespaces = new Set;
|
|
5170
|
+
const visit = (node) => {
|
|
5171
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
|
|
5172
|
+
const bindings = node.importClause?.namedBindings;
|
|
5173
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
5174
|
+
for (const element of bindings.elements)
|
|
5175
|
+
if (!element.isTypeOnly)
|
|
5176
|
+
names.add((element.propertyName ?? element.name).text);
|
|
5177
|
+
}
|
|
5178
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
5179
|
+
namespaces.add(bindings.name.text);
|
|
5180
|
+
}
|
|
5181
|
+
if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
5182
|
+
for (const element of node.exportClause.elements)
|
|
5183
|
+
if (!element.isTypeOnly)
|
|
5184
|
+
names.add((element.propertyName ?? element.name).text);
|
|
5185
|
+
}
|
|
5186
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
|
|
5187
|
+
names.add(node.name.text);
|
|
5188
|
+
ts.forEachChild(node, visit);
|
|
5189
|
+
};
|
|
5190
|
+
const extension = extname3(file).toLowerCase();
|
|
5191
|
+
const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
|
|
5192
|
+
for (const [index, script] of sources.entries())
|
|
5193
|
+
visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
|
|
5194
|
+
return names;
|
|
5195
|
+
};
|
|
5196
|
+
var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
|
|
5197
|
+
const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
|
|
5198
|
+
const mismatched = plan.requiredPackages.filter((spec) => {
|
|
5199
|
+
const separator = spec.lastIndexOf("@");
|
|
5200
|
+
const packageName = spec.slice(0, separator);
|
|
5201
|
+
if (missing.includes(spec))
|
|
5202
|
+
return false;
|
|
5203
|
+
try {
|
|
5204
|
+
return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
|
|
5205
|
+
} catch {
|
|
5206
|
+
return true;
|
|
5207
|
+
}
|
|
5208
|
+
});
|
|
5209
|
+
const unmet = [...missing, ...mismatched];
|
|
5210
|
+
if (unmet.length > 0)
|
|
5211
|
+
throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
|
|
5212
|
+
};
|
|
5213
|
+
var directAbsoluteProjectPackages = (projectRoot) => {
|
|
5214
|
+
const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
|
|
5215
|
+
const packages = new Set;
|
|
5216
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
5217
|
+
const dependencies = manifest[field];
|
|
5218
|
+
if (object2(dependencies))
|
|
5219
|
+
for (const name of Object.keys(dependencies))
|
|
5220
|
+
packages.add(name);
|
|
5221
|
+
}
|
|
5222
|
+
return packages;
|
|
5223
|
+
};
|
|
5224
|
+
var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
|
|
5225
|
+
const root = resolve11(projectRoot);
|
|
5226
|
+
const known = new Set(Object.keys(providers));
|
|
5227
|
+
const capabilities = new Set;
|
|
5228
|
+
for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
|
|
5229
|
+
const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
|
|
5230
|
+
if (isIgnored(portable))
|
|
5231
|
+
continue;
|
|
5232
|
+
const source = readFileSync4(resolve11(root, portable), "utf8");
|
|
5233
|
+
for (const name of importedCapabilities(source, portable))
|
|
5234
|
+
if (known.has(name))
|
|
5235
|
+
capabilities.add(name);
|
|
5236
|
+
}
|
|
5237
|
+
return [...capabilities].sort();
|
|
5238
|
+
};
|
|
5239
|
+
var missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
|
|
5240
|
+
const packageName = spec.slice(0, spec.lastIndexOf("@"));
|
|
5241
|
+
return !directPackages.has(packageName);
|
|
5242
|
+
});
|
|
5243
|
+
var resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
|
|
5244
|
+
const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
|
|
5245
|
+
const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
|
|
5246
|
+
const providers = {};
|
|
5247
|
+
for (const name of capabilities) {
|
|
5248
|
+
const provider = allProviders[name];
|
|
5249
|
+
if (provider)
|
|
5250
|
+
providers[name] = provider;
|
|
5251
|
+
}
|
|
5252
|
+
return {
|
|
5253
|
+
capabilities,
|
|
5254
|
+
providers,
|
|
5255
|
+
requiredPackages: [
|
|
5256
|
+
...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
|
|
5257
|
+
].sort()
|
|
5258
|
+
};
|
|
5259
|
+
};
|
|
5260
|
+
|
|
2717
5261
|
// src/mobile/buildPipeline.ts
|
|
2718
5262
|
var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
|
|
2719
5263
|
var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
@@ -2724,12 +5268,11 @@ var serverExportName = (loaded, app) => {
|
|
|
2724
5268
|
return "app";
|
|
2725
5269
|
return "default";
|
|
2726
5270
|
};
|
|
2727
|
-
var
|
|
2728
|
-
if (previous !== undefined)
|
|
2729
|
-
process.env
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
delete process.env.ABSOLUTE_BUILD_DIR;
|
|
5271
|
+
var restoreEnvironmentVariable = (name, previous) => {
|
|
5272
|
+
if (previous !== undefined)
|
|
5273
|
+
process.env[name] = previous;
|
|
5274
|
+
else
|
|
5275
|
+
delete process.env[name];
|
|
2733
5276
|
};
|
|
2734
5277
|
var requireRelease = (releases, releaseId) => {
|
|
2735
5278
|
const release = releases.get(releaseId);
|
|
@@ -2749,11 +5292,11 @@ var loadServerApp = async (producerPath) => {
|
|
|
2749
5292
|
return { app, exportName };
|
|
2750
5293
|
};
|
|
2751
5294
|
var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
2752
|
-
const buildDirectory =
|
|
5295
|
+
const buildDirectory = resolve12(options.buildDirectory);
|
|
2753
5296
|
const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
|
|
2754
|
-
const root =
|
|
5297
|
+
const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
|
|
2755
5298
|
const [manifestSource, previous] = await Promise.all([
|
|
2756
|
-
|
|
5299
|
+
readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
|
|
2757
5300
|
readAbsoluteMobileMaterializedReleases(root)
|
|
2758
5301
|
]);
|
|
2759
5302
|
const manifest = JSON.parse(manifestSource);
|
|
@@ -2761,12 +5304,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2761
5304
|
throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
|
|
2762
5305
|
}
|
|
2763
5306
|
const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
|
|
5307
|
+
const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
|
|
5308
|
+
const previousConfigPath = process.env.ABSOLUTE_CONFIG;
|
|
2764
5309
|
process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
|
|
5310
|
+
process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
|
|
5311
|
+
if (options.configPath) {
|
|
5312
|
+
process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
|
|
5313
|
+
}
|
|
2765
5314
|
let loaded;
|
|
2766
5315
|
try {
|
|
2767
|
-
loaded = await loadServerApp(
|
|
5316
|
+
loaded = await loadServerApp(resolve12(options.producerPath));
|
|
2768
5317
|
} finally {
|
|
2769
|
-
|
|
5318
|
+
restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
|
|
5319
|
+
restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
|
|
5320
|
+
restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
|
|
2770
5321
|
}
|
|
2771
5322
|
const current = await buildAbsoluteMobileCompatibilityRelease({
|
|
2772
5323
|
app: loaded.app,
|
|
@@ -2775,9 +5326,17 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2775
5326
|
manifest,
|
|
2776
5327
|
previousArtifacts: previous.map(({ artifact }) => artifact),
|
|
2777
5328
|
producerExport: loaded.exportName,
|
|
2778
|
-
producerPath:
|
|
5329
|
+
producerPath: resolve12(options.producerPath),
|
|
2779
5330
|
runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
|
|
2780
5331
|
});
|
|
5332
|
+
const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
|
|
5333
|
+
const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
|
|
5334
|
+
const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
|
|
5335
|
+
const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
|
|
5336
|
+
assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
|
|
5337
|
+
if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
|
|
5338
|
+
throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
|
|
5339
|
+
}
|
|
2781
5340
|
const releasesById = new Map([current, ...previous].map((release) => [
|
|
2782
5341
|
release.artifact.releaseId,
|
|
2783
5342
|
release
|
|
@@ -2790,11 +5349,127 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2790
5349
|
});
|
|
2791
5350
|
await materializeAbsoluteCapacitorWebBundle({
|
|
2792
5351
|
artifact: current.artifact,
|
|
5352
|
+
...auth ? { auth } : {},
|
|
2793
5353
|
buildDirectory,
|
|
2794
|
-
config: mobile
|
|
5354
|
+
config: mobile,
|
|
5355
|
+
deviceCapabilities,
|
|
5356
|
+
projectRoot: options.projectRoot,
|
|
5357
|
+
...sync ? { sync: true } : {},
|
|
5358
|
+
...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
|
|
2795
5359
|
});
|
|
2796
5360
|
return current.artifact;
|
|
2797
5361
|
};
|
|
5362
|
+
|
|
5363
|
+
// src/mobile/index.ts
|
|
5364
|
+
init_syncSchema();
|
|
5365
|
+
|
|
5366
|
+
// node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
|
|
5367
|
+
var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
|
|
5368
|
+
var host2 = globalThis;
|
|
5369
|
+
var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
|
|
5370
|
+
var registry2 = (() => {
|
|
5371
|
+
const existing = host2[RUNTIME_TRANSPORT2];
|
|
5372
|
+
if (isRegistry2(existing))
|
|
5373
|
+
return existing;
|
|
5374
|
+
if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
|
|
5375
|
+
Reflect.set(existing, "clients", []);
|
|
5376
|
+
return existing;
|
|
5377
|
+
}
|
|
5378
|
+
const created = { clients: [], installations: [] };
|
|
5379
|
+
Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
|
|
5380
|
+
configurable: false,
|
|
5381
|
+
enumerable: false,
|
|
5382
|
+
value: created,
|
|
5383
|
+
writable: false
|
|
5384
|
+
});
|
|
5385
|
+
return created;
|
|
5386
|
+
})();
|
|
5387
|
+
var maximum = (values) => {
|
|
5388
|
+
const present = values.filter((value) => value !== undefined);
|
|
5389
|
+
return present.length === 0 ? undefined : Math.max(...present);
|
|
5390
|
+
};
|
|
5391
|
+
var minimum = (values) => {
|
|
5392
|
+
const present = values.filter((value) => value !== undefined);
|
|
5393
|
+
return present.length === 0 ? undefined : Math.min(...present);
|
|
5394
|
+
};
|
|
5395
|
+
var inspectSyncRuntime = async () => {
|
|
5396
|
+
const clients = [...registry2.clients];
|
|
5397
|
+
const statuses = clients.map((client) => client.status());
|
|
5398
|
+
const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
|
|
5399
|
+
attempts: record.attempts,
|
|
5400
|
+
...record.rejection?.code ? { code: record.rejection.code } : {},
|
|
5401
|
+
createdAt: record.createdAt,
|
|
5402
|
+
...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
|
|
5403
|
+
...record.rejection?.kind ? { kind: record.rejection.kind } : {},
|
|
5404
|
+
...record.rejection?.message ? { message: record.rejection.message } : {},
|
|
5405
|
+
name: record.name,
|
|
5406
|
+
operationId: record.operationId
|
|
5407
|
+
})).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
|
|
5408
|
+
const lastError = statuses.findLast((status) => status.lastError)?.lastError;
|
|
5409
|
+
return {
|
|
5410
|
+
automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
|
|
5411
|
+
clients: clients.length,
|
|
5412
|
+
conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
|
|
5413
|
+
deadLetters,
|
|
5414
|
+
...lastError ? { lastError } : {},
|
|
5415
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
|
|
5416
|
+
lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
|
|
5417
|
+
},
|
|
5418
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
|
|
5419
|
+
lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
|
|
5420
|
+
},
|
|
5421
|
+
...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
|
|
5422
|
+
oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
|
|
5423
|
+
},
|
|
5424
|
+
...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
|
|
5425
|
+
oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
|
|
5426
|
+
},
|
|
5427
|
+
pending: statuses.reduce((total, status) => total + status.pending, 0)
|
|
5428
|
+
};
|
|
5429
|
+
};
|
|
5430
|
+
var clientWithDeadLetter = async (operationId) => {
|
|
5431
|
+
for (const client of registry2.clients)
|
|
5432
|
+
if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
|
|
5433
|
+
return client;
|
|
5434
|
+
throw new Error(`Unknown Sync dead letter "${operationId}"`);
|
|
5435
|
+
};
|
|
5436
|
+
var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
|
|
5437
|
+
var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
|
|
5438
|
+
var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
|
|
5439
|
+
|
|
5440
|
+
// src/mobile/syncRemediation.ts
|
|
5441
|
+
var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
|
|
5442
|
+
var LAST_INSTALLATION_OFFSET = -1;
|
|
5443
|
+
var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
|
|
5444
|
+
var resolveRegistry = () => {
|
|
5445
|
+
const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
|
|
5446
|
+
if (isRegistry3(existing))
|
|
5447
|
+
return existing;
|
|
5448
|
+
const created = { installations: [] };
|
|
5449
|
+
Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
|
|
5450
|
+
configurable: false,
|
|
5451
|
+
enumerable: false,
|
|
5452
|
+
value: created,
|
|
5453
|
+
writable: false
|
|
5454
|
+
});
|
|
5455
|
+
return created;
|
|
5456
|
+
};
|
|
5457
|
+
var registry3 = resolveRegistry();
|
|
5458
|
+
var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
|
|
5459
|
+
var installAbsoluteMobileSyncRemediation = (bridge = {
|
|
5460
|
+
discard: discardSyncRuntimeDeadLetter,
|
|
5461
|
+
inspect: inspectSyncRuntime,
|
|
5462
|
+
rebase: rebaseSyncRuntimeDeadLetter,
|
|
5463
|
+
retry: retrySyncRuntimeDeadLetter
|
|
5464
|
+
}) => {
|
|
5465
|
+
const installation = { bridge };
|
|
5466
|
+
registry3.installations.push(installation);
|
|
5467
|
+
return () => {
|
|
5468
|
+
const index = registry3.installations.indexOf(installation);
|
|
5469
|
+
if (index >= 0)
|
|
5470
|
+
registry3.installations.splice(index, 1);
|
|
5471
|
+
};
|
|
5472
|
+
};
|
|
2798
5473
|
// src/mobile/compatibilityDispatcher.ts
|
|
2799
5474
|
import { Elysia as Elysia2 } from "elysia";
|
|
2800
5475
|
|
|
@@ -2816,6 +5491,64 @@ var ensureProducerStorage = () => {
|
|
|
2816
5491
|
var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
|
|
2817
5492
|
|
|
2818
5493
|
// src/mobile/compatibilityDispatcher.ts
|
|
5494
|
+
var MOBILE_WEBVIEW_ORIGINS = new Set([
|
|
5495
|
+
"capacitor://localhost",
|
|
5496
|
+
"http://localhost",
|
|
5497
|
+
"https://localhost"
|
|
5498
|
+
]);
|
|
5499
|
+
var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
|
|
5500
|
+
var MOBILE_CORS_ALLOW_HEADERS = [
|
|
5501
|
+
"accept",
|
|
5502
|
+
"content-type",
|
|
5503
|
+
"authorization",
|
|
5504
|
+
"hx-current-url",
|
|
5505
|
+
"hx-request",
|
|
5506
|
+
"hx-target",
|
|
5507
|
+
"hx-trigger",
|
|
5508
|
+
"hx-trigger-name",
|
|
5509
|
+
...MOBILE_REQUEST_HEADER_NAMES
|
|
5510
|
+
].join(", ");
|
|
5511
|
+
var MOBILE_CORS_METHODS = new Set([
|
|
5512
|
+
"DELETE",
|
|
5513
|
+
"GET",
|
|
5514
|
+
"HEAD",
|
|
5515
|
+
"OPTIONS",
|
|
5516
|
+
"PATCH",
|
|
5517
|
+
"POST",
|
|
5518
|
+
"PUT"
|
|
5519
|
+
]);
|
|
5520
|
+
var mobileWebViewOrigin = (request) => {
|
|
5521
|
+
const origin = request.headers.get("origin");
|
|
5522
|
+
return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
|
|
5523
|
+
};
|
|
5524
|
+
var applyMobileCorsHeaders = (response, origin) => {
|
|
5525
|
+
response.headers.set("access-control-allow-credentials", "true");
|
|
5526
|
+
response.headers.set("access-control-allow-origin", origin);
|
|
5527
|
+
response.headers.append("vary", "Origin");
|
|
5528
|
+
return response;
|
|
5529
|
+
};
|
|
5530
|
+
var mobilePreflightResponse = (request) => {
|
|
5531
|
+
if (request.method !== "OPTIONS")
|
|
5532
|
+
return;
|
|
5533
|
+
const origin = mobileWebViewOrigin(request);
|
|
5534
|
+
if (!origin)
|
|
5535
|
+
return;
|
|
5536
|
+
const requestedHeaders = request.headers.get("access-control-request-headers");
|
|
5537
|
+
const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
|
|
5538
|
+
if (!MOBILE_CORS_METHODS.has(requestedMethod))
|
|
5539
|
+
return;
|
|
5540
|
+
return new Response(null, {
|
|
5541
|
+
headers: {
|
|
5542
|
+
"access-control-allow-credentials": "true",
|
|
5543
|
+
"access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
|
|
5544
|
+
"access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
|
|
5545
|
+
"access-control-allow-origin": origin,
|
|
5546
|
+
"access-control-max-age": "600",
|
|
5547
|
+
vary: "Origin, Access-Control-Request-Headers"
|
|
5548
|
+
},
|
|
5549
|
+
status: 204
|
|
5550
|
+
});
|
|
5551
|
+
};
|
|
2819
5552
|
var artifactOwnsRequest = (artifact, pageId, request) => {
|
|
2820
5553
|
const { pathname } = new URL(request.url);
|
|
2821
5554
|
return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
|
|
@@ -2843,6 +5576,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
|
|
|
2843
5576
|
return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
|
|
2844
5577
|
if (getCurrentAbsoluteMobileProducerContext())
|
|
2845
5578
|
return;
|
|
5579
|
+
const preflight = mobilePreflightResponse(request);
|
|
5580
|
+
if (preflight)
|
|
5581
|
+
return preflight;
|
|
2846
5582
|
const parsed = parseAbsoluteMobilePageRequest(request);
|
|
2847
5583
|
if (parsed.kind !== "mobile")
|
|
2848
5584
|
return;
|
|
@@ -2866,23 +5602,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
|
|
|
2866
5602
|
console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
|
|
2867
5603
|
return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
|
|
2868
5604
|
}
|
|
5605
|
+
}).afterHandle("global", ({ request, responseValue }) => {
|
|
5606
|
+
const origin = mobileWebViewOrigin(request);
|
|
5607
|
+
if (!origin || !(responseValue instanceof Response))
|
|
5608
|
+
return;
|
|
5609
|
+
applyMobileCorsHeaders(responseValue, origin);
|
|
2869
5610
|
}).as("global");
|
|
2870
5611
|
};
|
|
2871
5612
|
// src/mobile/nativeDeepLinks.ts
|
|
2872
|
-
import { readFile as
|
|
2873
|
-
import { join as
|
|
5613
|
+
import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
|
|
5614
|
+
import { join as join15 } from "path";
|
|
2874
5615
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->";
|
|
2875
5616
|
var END_MARKER = "<!-- absolutejs:deep-links:end -->";
|
|
2876
5617
|
var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
|
|
2877
5618
|
var NOT_FOUND = -1;
|
|
2878
5619
|
var escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
2879
5620
|
var writeChangedFile = async (path, source) => {
|
|
2880
|
-
const current = await
|
|
5621
|
+
const current = await readFile14(path, "utf8");
|
|
2881
5622
|
if (current === source)
|
|
2882
5623
|
return false;
|
|
2883
5624
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
2884
|
-
await
|
|
2885
|
-
await
|
|
5625
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5626
|
+
await rename11(temporary, path);
|
|
2886
5627
|
return true;
|
|
2887
5628
|
};
|
|
2888
5629
|
var replaceManagedRegion = (source, region, insertAt) => {
|
|
@@ -2906,7 +5647,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
|
|
|
2906
5647
|
return `${source.slice(0, index)}${region}${source.slice(index)}`;
|
|
2907
5648
|
};
|
|
2908
5649
|
var androidRegion = (config) => {
|
|
2909
|
-
const hosts = config.deepLinkHosts.map((
|
|
5650
|
+
const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
|
|
2910
5651
|
`);
|
|
2911
5652
|
const customScheme = config.deepLinkScheme ? `
|
|
2912
5653
|
|
|
@@ -2927,8 +5668,8 @@ ${hosts}
|
|
|
2927
5668
|
`;
|
|
2928
5669
|
};
|
|
2929
5670
|
var configureAndroid = async (config) => {
|
|
2930
|
-
const path =
|
|
2931
|
-
const source = await
|
|
5671
|
+
const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
5672
|
+
const source = await readFile14(path, "utf8");
|
|
2932
5673
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
2933
5674
|
if (mainActivity === NOT_FOUND) {
|
|
2934
5675
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -2953,8 +5694,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
|
|
|
2953
5694
|
${END_MARKER}
|
|
2954
5695
|
`;
|
|
2955
5696
|
var configureIosInfo = async (config) => {
|
|
2956
|
-
const path =
|
|
2957
|
-
const source = await
|
|
5697
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
5698
|
+
const source = await readFile14(path, "utf8");
|
|
2958
5699
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
2959
5700
|
${END_MARKER}
|
|
2960
5701
|
`;
|
|
@@ -2962,7 +5703,7 @@ var configureIosInfo = async (config) => {
|
|
|
2962
5703
|
return writeChangedFile(path, updated);
|
|
2963
5704
|
};
|
|
2964
5705
|
var iosEntitlementsSource = (config) => {
|
|
2965
|
-
const domains = config.deepLinkHosts.map((
|
|
5706
|
+
const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
|
|
2966
5707
|
`);
|
|
2967
5708
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2968
5709
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2977,10 +5718,10 @@ ${domains}
|
|
|
2977
5718
|
`;
|
|
2978
5719
|
};
|
|
2979
5720
|
var configureIosEntitlements = async (config) => {
|
|
2980
|
-
const path =
|
|
5721
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
2981
5722
|
let current = "";
|
|
2982
5723
|
try {
|
|
2983
|
-
current = await
|
|
5724
|
+
current = await readFile14(path, "utf8");
|
|
2984
5725
|
} catch (error) {
|
|
2985
5726
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
2986
5727
|
throw error;
|
|
@@ -2990,13 +5731,13 @@ var configureIosEntitlements = async (config) => {
|
|
|
2990
5731
|
if (current === source)
|
|
2991
5732
|
return false;
|
|
2992
5733
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
2993
|
-
await
|
|
2994
|
-
await
|
|
5734
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5735
|
+
await rename11(temporary, path);
|
|
2995
5736
|
return true;
|
|
2996
5737
|
};
|
|
2997
5738
|
var configureIosProject = async (config) => {
|
|
2998
|
-
const path =
|
|
2999
|
-
const source = await
|
|
5739
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
5740
|
+
const source = await readFile14(path, "utf8");
|
|
3000
5741
|
const declarations = [
|
|
3001
5742
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
3002
5743
|
].map((match) => match[1]);
|
|
@@ -3033,9 +5774,99 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
|
|
|
3033
5774
|
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
3034
5775
|
};
|
|
3035
5776
|
};
|
|
5777
|
+
// src/mobile/nativeDeviceCapabilities.ts
|
|
5778
|
+
import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
|
|
5779
|
+
import { join as join16 } from "path";
|
|
5780
|
+
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
|
|
5781
|
+
var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
|
|
5782
|
+
var NOT_FOUND2 = -1;
|
|
5783
|
+
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5784
|
+
var writeChangedFile2 = async (path, source) => {
|
|
5785
|
+
const current = await readFile15(path, "utf8");
|
|
5786
|
+
if (current === source)
|
|
5787
|
+
return false;
|
|
5788
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
5789
|
+
await writeFile13(temporary, source, { flag: "wx" });
|
|
5790
|
+
await rename12(temporary, path);
|
|
5791
|
+
return true;
|
|
5792
|
+
};
|
|
5793
|
+
var managed = (source, region, insertion) => {
|
|
5794
|
+
const start = source.indexOf(START_MARKER2);
|
|
5795
|
+
const end = source.indexOf(END_MARKER2);
|
|
5796
|
+
if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
|
|
5797
|
+
throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
|
|
5798
|
+
if (start !== NOT_FOUND2) {
|
|
5799
|
+
const lineStart = source.lastIndexOf(`
|
|
5800
|
+
`, start) + 1;
|
|
5801
|
+
const nextLine = source.indexOf(`
|
|
5802
|
+
`, end + END_MARKER2.length);
|
|
5803
|
+
const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
|
|
5804
|
+
return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
|
|
5805
|
+
}
|
|
5806
|
+
if (region.length === 0)
|
|
5807
|
+
return source;
|
|
5808
|
+
if (insertion === NOT_FOUND2)
|
|
5809
|
+
throw new TypeError("Could not find a safe native project location for device permissions.");
|
|
5810
|
+
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
5811
|
+
};
|
|
5812
|
+
var IOS_KEYS = {
|
|
5813
|
+
camera: "NSCameraUsageDescription",
|
|
5814
|
+
"location-always": "NSLocationAlwaysAndWhenInUseUsageDescription",
|
|
5815
|
+
"location-when-in-use": "NSLocationWhenInUseUsageDescription",
|
|
5816
|
+
"photo-library": "NSPhotoLibraryUsageDescription",
|
|
5817
|
+
"photo-library-add": "NSPhotoLibraryAddUsageDescription"
|
|
5818
|
+
};
|
|
5819
|
+
var iosDescription = (appName, purpose) => {
|
|
5820
|
+
if (purpose === "camera")
|
|
5821
|
+
return `${appName} uses your camera when you choose to take a photo.`;
|
|
5822
|
+
if (purpose === "photo-library")
|
|
5823
|
+
return `${appName} accesses your photo library only for photo actions you choose.`;
|
|
5824
|
+
if (purpose === "location-when-in-use")
|
|
5825
|
+
return `${appName} uses your location only while you are using the app and request a location-based action.`;
|
|
5826
|
+
if (purpose === "location-always")
|
|
5827
|
+
return `${appName} does not track location in the background; this description supports the foreground location provider required by the native runtime.`;
|
|
5828
|
+
return `${appName} adds to your photo library only for photo actions you choose.`;
|
|
5829
|
+
};
|
|
5830
|
+
var configureIos2 = async (config, plan) => {
|
|
5831
|
+
const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
5832
|
+
const source = await readFile15(path, "utf8");
|
|
5833
|
+
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
5834
|
+
const content = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
|
|
5835
|
+
<string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
|
|
5836
|
+
`);
|
|
5837
|
+
const region = content ? ` ${START_MARKER2}
|
|
5838
|
+
${content}
|
|
5839
|
+
${END_MARKER2}
|
|
5840
|
+
` : "";
|
|
5841
|
+
return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
|
|
5842
|
+
};
|
|
5843
|
+
var configureAndroid2 = async (config, plan) => {
|
|
5844
|
+
const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
5845
|
+
const source = await readFile15(path, "utf8");
|
|
5846
|
+
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
5847
|
+
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
5848
|
+
`);
|
|
5849
|
+
const region = content ? ` ${START_MARKER2}
|
|
5850
|
+
${content}
|
|
5851
|
+
${END_MARKER2}
|
|
5852
|
+
` : "";
|
|
5853
|
+
const application = source.indexOf("<application");
|
|
5854
|
+
const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
|
|
5855
|
+
`, application) + 1;
|
|
5856
|
+
return writeChangedFile2(path, managed(source, region, insertion));
|
|
5857
|
+
};
|
|
5858
|
+
var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
|
|
5859
|
+
const results = await Promise.all(platforms.map(async (platform) => ({
|
|
5860
|
+
didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
|
|
5861
|
+
platform
|
|
5862
|
+
})));
|
|
5863
|
+
return {
|
|
5864
|
+
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
5865
|
+
};
|
|
5866
|
+
};
|
|
3036
5867
|
// src/mobile/releasePublisher.ts
|
|
3037
|
-
import { access as
|
|
3038
|
-
import { isAbsolute as
|
|
5868
|
+
import { access as access9 } from "fs/promises";
|
|
5869
|
+
import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
|
|
3039
5870
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
3040
5871
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
3041
5872
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -3058,24 +5889,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
3058
5889
|
}
|
|
3059
5890
|
return versionCode;
|
|
3060
5891
|
};
|
|
3061
|
-
var
|
|
3062
|
-
var isPublisher = (value) =>
|
|
5892
|
+
var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5893
|
+
var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
|
|
3063
5894
|
var publisherModulePath = (projectRoot, requested) => {
|
|
3064
|
-
const root =
|
|
3065
|
-
const path =
|
|
3066
|
-
const projectRelative =
|
|
3067
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
5895
|
+
const root = resolve13(projectRoot);
|
|
5896
|
+
const path = resolve13(root, requested);
|
|
5897
|
+
const projectRelative = relative10(root, path);
|
|
5898
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
|
|
3068
5899
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
3069
5900
|
}
|
|
3070
5901
|
return path;
|
|
3071
5902
|
};
|
|
3072
5903
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
3073
5904
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
3074
|
-
await
|
|
5905
|
+
await access9(modulePath).catch(() => {
|
|
3075
5906
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
3076
5907
|
});
|
|
3077
5908
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
3078
|
-
const publisher =
|
|
5909
|
+
const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
3079
5910
|
if (!isPublisher(publisher)) {
|
|
3080
5911
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
3081
5912
|
}
|
|
@@ -3132,67 +5963,112 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
3132
5963
|
return publication;
|
|
3133
5964
|
};
|
|
3134
5965
|
// src/mobile/routeMetadataTransform.ts
|
|
3135
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3136
|
-
import { dirname as
|
|
3137
|
-
import
|
|
5966
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
5967
|
+
import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
|
|
5968
|
+
import ts2 from "typescript";
|
|
3138
5969
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
3139
5970
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
3140
|
-
var
|
|
5971
|
+
var PAGE_HANDLERS = new Map([
|
|
5972
|
+
[
|
|
5973
|
+
"handleHTMLPageRequest",
|
|
5974
|
+
{ framework: "html", inputKind: "static", propsProperty: "props" }
|
|
5975
|
+
],
|
|
5976
|
+
[
|
|
5977
|
+
"handleHTMXPageRequest",
|
|
5978
|
+
{ framework: "htmx", inputKind: "static", propsProperty: "props" }
|
|
5979
|
+
],
|
|
5980
|
+
[
|
|
5981
|
+
"handleAngularPageRequest",
|
|
5982
|
+
{
|
|
5983
|
+
bundleProperty: "indexPath",
|
|
5984
|
+
framework: "angular",
|
|
5985
|
+
propsProperty: "requestContext",
|
|
5986
|
+
sourceProperty: "pagePath"
|
|
5987
|
+
}
|
|
5988
|
+
],
|
|
5989
|
+
[
|
|
5990
|
+
"handleReactPageRequest",
|
|
5991
|
+
{
|
|
5992
|
+
bundleProperty: "index",
|
|
5993
|
+
framework: "react",
|
|
5994
|
+
pageProperty: "Page",
|
|
5995
|
+
propsProperty: "props"
|
|
5996
|
+
}
|
|
5997
|
+
],
|
|
5998
|
+
[
|
|
5999
|
+
"handleSveltePageRequest",
|
|
6000
|
+
{
|
|
6001
|
+
bundleProperty: "indexPath",
|
|
6002
|
+
framework: "svelte",
|
|
6003
|
+
propsProperty: "props",
|
|
6004
|
+
sourceProperty: "pagePath"
|
|
6005
|
+
}
|
|
6006
|
+
],
|
|
6007
|
+
[
|
|
6008
|
+
"handleVuePageRequest",
|
|
6009
|
+
{
|
|
6010
|
+
bundleProperty: "indexPath",
|
|
6011
|
+
framework: "vue",
|
|
6012
|
+
propsProperty: "props",
|
|
6013
|
+
sourceProperty: "pagePath"
|
|
6014
|
+
}
|
|
6015
|
+
]
|
|
6016
|
+
]);
|
|
3141
6017
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
3142
|
-
var findTsconfig = (entry, projectRoot) =>
|
|
6018
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
|
|
3143
6019
|
var createProgram = (entry, projectRoot) => {
|
|
3144
6020
|
const configPath = findTsconfig(entry, projectRoot);
|
|
3145
6021
|
if (!configPath) {
|
|
3146
|
-
return
|
|
6022
|
+
return ts2.createProgram([entry], {
|
|
3147
6023
|
allowJs: true,
|
|
3148
|
-
jsx:
|
|
3149
|
-
module:
|
|
3150
|
-
moduleResolution:
|
|
3151
|
-
target:
|
|
6024
|
+
jsx: ts2.JsxEmit.ReactJSX,
|
|
6025
|
+
module: ts2.ModuleKind.ESNext,
|
|
6026
|
+
moduleResolution: ts2.ModuleResolutionKind.Bundler,
|
|
6027
|
+
target: ts2.ScriptTarget.ESNext
|
|
3152
6028
|
});
|
|
3153
6029
|
}
|
|
3154
|
-
const parsed =
|
|
6030
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
|
|
3155
6031
|
if (!parsed.fileNames.includes(entry))
|
|
3156
6032
|
parsed.fileNames.push(entry);
|
|
3157
|
-
return
|
|
6033
|
+
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
3158
6034
|
};
|
|
3159
6035
|
var propertyName = (property) => {
|
|
3160
6036
|
if (!("name" in property) || !property.name)
|
|
3161
6037
|
return;
|
|
3162
|
-
if (
|
|
6038
|
+
if (ts2.isIdentifier(property.name))
|
|
3163
6039
|
return property.name.text;
|
|
3164
|
-
if (
|
|
6040
|
+
if (ts2.isStringLiteralLike(property.name))
|
|
3165
6041
|
return property.name.text;
|
|
3166
6042
|
return;
|
|
3167
6043
|
};
|
|
3168
|
-
var objectPropertyExpression = (
|
|
3169
|
-
const property =
|
|
3170
|
-
if (property &&
|
|
6044
|
+
var objectPropertyExpression = (object3, name) => {
|
|
6045
|
+
const property = object3.properties.find((candidate) => propertyName(candidate) === name);
|
|
6046
|
+
if (property && ts2.isPropertyAssignment(property)) {
|
|
3171
6047
|
return property.initializer;
|
|
3172
6048
|
}
|
|
3173
|
-
if (property &&
|
|
6049
|
+
if (property && ts2.isShorthandPropertyAssignment(property)) {
|
|
3174
6050
|
return property.name;
|
|
3175
6051
|
}
|
|
3176
6052
|
return;
|
|
3177
6053
|
};
|
|
3178
6054
|
var serializeType = (type, checker, ancestors = new Set) => {
|
|
3179
|
-
if (type.flags &
|
|
6055
|
+
if (type.flags & ts2.TypeFlags.Any)
|
|
3180
6056
|
return { type: "any" };
|
|
3181
|
-
if (type.flags &
|
|
6057
|
+
if (type.flags & ts2.TypeFlags.Unknown)
|
|
3182
6058
|
return { type: "unknown" };
|
|
3183
|
-
if (type.flags &
|
|
6059
|
+
if (type.flags & ts2.TypeFlags.Never)
|
|
3184
6060
|
return { type: "never" };
|
|
3185
|
-
if (type.flags &
|
|
6061
|
+
if (type.flags & ts2.TypeFlags.StringLike)
|
|
3186
6062
|
return { type: "string" };
|
|
3187
|
-
if (type.flags &
|
|
6063
|
+
if (type.flags & ts2.TypeFlags.NumberLike)
|
|
3188
6064
|
return { type: "number" };
|
|
3189
|
-
if (type.flags &
|
|
6065
|
+
if (type.flags & ts2.TypeFlags.BooleanLike)
|
|
3190
6066
|
return { type: "boolean" };
|
|
3191
|
-
if (type.flags &
|
|
6067
|
+
if (type.flags & ts2.TypeFlags.BigIntLike)
|
|
3192
6068
|
return { type: "bigint" };
|
|
3193
|
-
if (type.flags &
|
|
6069
|
+
if (type.flags & ts2.TypeFlags.Null)
|
|
3194
6070
|
return { type: "null" };
|
|
3195
|
-
if (type.flags &
|
|
6071
|
+
if (type.flags & ts2.TypeFlags.Undefined)
|
|
3196
6072
|
return { type: "undefined" };
|
|
3197
6073
|
if (type.isUnion()) {
|
|
3198
6074
|
return {
|
|
@@ -3206,11 +6082,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3206
6082
|
}
|
|
3207
6083
|
if (ancestors.has(type)) {
|
|
3208
6084
|
return {
|
|
3209
|
-
ref: checker.typeToString(type, undefined,
|
|
6085
|
+
ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
3210
6086
|
};
|
|
3211
6087
|
}
|
|
3212
6088
|
ancestors.add(type);
|
|
3213
|
-
const arrayElement = checker.getIndexTypeOfType(type,
|
|
6089
|
+
const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
|
|
3214
6090
|
const properties = checker.getPropertiesOfType(type);
|
|
3215
6091
|
let schema;
|
|
3216
6092
|
if (arrayElement && properties.some(({ name }) => name === "length")) {
|
|
@@ -3225,7 +6101,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3225
6101
|
return [
|
|
3226
6102
|
property.name,
|
|
3227
6103
|
{
|
|
3228
|
-
optional: Boolean(property.flags &
|
|
6104
|
+
optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
|
|
3229
6105
|
schema: serializeType(propertyType, checker, ancestors)
|
|
3230
6106
|
}
|
|
3231
6107
|
];
|
|
@@ -3233,7 +6109,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3233
6109
|
schema = { properties: Object.fromEntries(entries), type: "object" };
|
|
3234
6110
|
} else {
|
|
3235
6111
|
schema = {
|
|
3236
|
-
type: checker.typeToString(type, undefined,
|
|
6112
|
+
type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
3237
6113
|
};
|
|
3238
6114
|
}
|
|
3239
6115
|
ancestors.delete(type);
|
|
@@ -3251,25 +6127,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
|
|
|
3251
6127
|
};
|
|
3252
6128
|
var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
|
|
3253
6129
|
let symbol = checker.getSymbolAtLocation(expression);
|
|
3254
|
-
if (symbol?.flags && symbol.flags &
|
|
6130
|
+
if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
|
|
3255
6131
|
symbol = checker.getAliasedSymbol(symbol);
|
|
3256
6132
|
}
|
|
3257
6133
|
const declaration = symbol?.declarations?.[0];
|
|
3258
6134
|
const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
|
|
3259
6135
|
const exportedName = symbol?.name ?? expression.getText(sourceFile);
|
|
3260
|
-
const source = posixPath(
|
|
6136
|
+
const source = posixPath(relative11(projectRoot, file));
|
|
3261
6137
|
return `${source}#${exportedName}`;
|
|
3262
6138
|
};
|
|
3263
6139
|
var resolveAlias = (symbol, checker) => {
|
|
3264
|
-
if (!(symbol.flags &
|
|
6140
|
+
if (!(symbol.flags & ts2.SymbolFlags.Alias))
|
|
3265
6141
|
return symbol;
|
|
3266
6142
|
return checker.getAliasedSymbol(symbol);
|
|
3267
6143
|
};
|
|
3268
6144
|
var assetKey = (expression, checker, seen = new Set) => {
|
|
3269
6145
|
if (!expression)
|
|
3270
6146
|
return;
|
|
3271
|
-
if (
|
|
3272
|
-
const unresolved =
|
|
6147
|
+
if (ts2.isIdentifier(expression)) {
|
|
6148
|
+
const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
|
|
3273
6149
|
if (!unresolved)
|
|
3274
6150
|
return;
|
|
3275
6151
|
const symbol = resolveAlias(unresolved, checker);
|
|
@@ -3277,28 +6153,123 @@ var assetKey = (expression, checker, seen = new Set) => {
|
|
|
3277
6153
|
return;
|
|
3278
6154
|
seen.add(symbol);
|
|
3279
6155
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
3280
|
-
if (!declaration || !
|
|
6156
|
+
if (!declaration || !ts2.isVariableDeclaration(declaration))
|
|
3281
6157
|
return;
|
|
3282
6158
|
return assetKey(declaration.initializer, checker, seen);
|
|
3283
6159
|
}
|
|
3284
|
-
if (!
|
|
6160
|
+
if (!ts2.isCallExpression(expression))
|
|
3285
6161
|
return;
|
|
3286
|
-
if (!
|
|
6162
|
+
if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
|
|
3287
6163
|
return;
|
|
3288
6164
|
}
|
|
3289
6165
|
const [, key] = expression.arguments;
|
|
3290
|
-
return key &&
|
|
6166
|
+
return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
|
|
6167
|
+
};
|
|
6168
|
+
var staticString = (expression, bindings) => {
|
|
6169
|
+
if (ts2.isStringLiteralLike(expression))
|
|
6170
|
+
return expression.text;
|
|
6171
|
+
if (ts2.isIdentifier(expression))
|
|
6172
|
+
return bindings.get(expression.text);
|
|
6173
|
+
if (ts2.isNoSubstitutionTemplateLiteral(expression))
|
|
6174
|
+
return expression.text;
|
|
6175
|
+
if (!ts2.isTemplateExpression(expression))
|
|
6176
|
+
return;
|
|
6177
|
+
let value = expression.head.text;
|
|
6178
|
+
for (const span of expression.templateSpans) {
|
|
6179
|
+
const substitution = staticString(span.expression, bindings);
|
|
6180
|
+
if (substitution === undefined)
|
|
6181
|
+
return;
|
|
6182
|
+
value += substitution + span.literal.text;
|
|
6183
|
+
}
|
|
6184
|
+
return value;
|
|
6185
|
+
};
|
|
6186
|
+
var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
|
|
6187
|
+
if (!expression)
|
|
6188
|
+
return;
|
|
6189
|
+
if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
|
|
6190
|
+
const [, key] = expression.arguments;
|
|
6191
|
+
return key ? staticString(key, bindings) : undefined;
|
|
6192
|
+
}
|
|
6193
|
+
return assetKey(expression, checker);
|
|
6194
|
+
};
|
|
6195
|
+
var callableObject = (call, checker) => {
|
|
6196
|
+
const symbol = checker.getSymbolAtLocation(call.expression);
|
|
6197
|
+
const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
|
|
6198
|
+
const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
|
|
6199
|
+
let callable;
|
|
6200
|
+
if (declaration && ts2.isFunctionDeclaration(declaration)) {
|
|
6201
|
+
callable = declaration;
|
|
6202
|
+
} else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
|
|
6203
|
+
callable = declaration.initializer;
|
|
6204
|
+
}
|
|
6205
|
+
if (!callable)
|
|
6206
|
+
return;
|
|
6207
|
+
const bindings = new Map;
|
|
6208
|
+
callable.parameters.forEach((parameter, index) => {
|
|
6209
|
+
if (!ts2.isIdentifier(parameter.name))
|
|
6210
|
+
return;
|
|
6211
|
+
const argument = call.arguments[index];
|
|
6212
|
+
if (!argument)
|
|
6213
|
+
return;
|
|
6214
|
+
const value = staticString(argument, new Map);
|
|
6215
|
+
if (value !== undefined)
|
|
6216
|
+
bindings.set(parameter.name.text, value);
|
|
6217
|
+
});
|
|
6218
|
+
const { body } = callable;
|
|
6219
|
+
if (!body)
|
|
6220
|
+
return;
|
|
6221
|
+
const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
|
|
6222
|
+
if (ts2.isObjectLiteralExpression(expressionBody)) {
|
|
6223
|
+
return { bindings, object: expressionBody };
|
|
6224
|
+
}
|
|
6225
|
+
if (ts2.isBlock(body)) {
|
|
6226
|
+
const returned = body.statements.find(ts2.isReturnStatement)?.expression;
|
|
6227
|
+
if (returned && ts2.isObjectLiteralExpression(returned)) {
|
|
6228
|
+
return { bindings, object: returned };
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
return;
|
|
6232
|
+
};
|
|
6233
|
+
var spreadObject = (expression, checker, bindings) => {
|
|
6234
|
+
if (ts2.isObjectLiteralExpression(expression)) {
|
|
6235
|
+
return { bindings, object: expression };
|
|
6236
|
+
}
|
|
6237
|
+
if (!ts2.isCallExpression(expression))
|
|
6238
|
+
return;
|
|
6239
|
+
return callableObject(expression, checker);
|
|
6240
|
+
};
|
|
6241
|
+
var objectAssetKey = (object3, name, checker, bindings = new Map) => {
|
|
6242
|
+
for (const property of [...object3.properties].reverse()) {
|
|
6243
|
+
if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
|
|
6244
|
+
return assetKeyWithBindings(property.name, checker, bindings);
|
|
6245
|
+
}
|
|
6246
|
+
if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
|
|
6247
|
+
return assetKeyWithBindings(property.initializer, checker, bindings);
|
|
6248
|
+
}
|
|
6249
|
+
if (!ts2.isSpreadAssignment(property))
|
|
6250
|
+
continue;
|
|
6251
|
+
const nestedObject = spreadObject(property.expression, checker, bindings);
|
|
6252
|
+
if (!nestedObject)
|
|
6253
|
+
continue;
|
|
6254
|
+
const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
|
|
6255
|
+
if (nested)
|
|
6256
|
+
return nested;
|
|
6257
|
+
}
|
|
6258
|
+
return;
|
|
3291
6259
|
};
|
|
3292
6260
|
var findPageCall = (nodes) => {
|
|
3293
6261
|
let found;
|
|
3294
6262
|
const visit = (candidate) => {
|
|
3295
6263
|
if (found)
|
|
3296
6264
|
return;
|
|
3297
|
-
if (
|
|
3298
|
-
|
|
6265
|
+
if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
|
|
6266
|
+
const definition = PAGE_HANDLERS.get(candidate.expression.text);
|
|
6267
|
+
if (!definition)
|
|
6268
|
+
return;
|
|
6269
|
+
found = { definition, node: candidate };
|
|
3299
6270
|
return;
|
|
3300
6271
|
}
|
|
3301
|
-
|
|
6272
|
+
ts2.forEachChild(candidate, visit);
|
|
3302
6273
|
};
|
|
3303
6274
|
for (const node of nodes)
|
|
3304
6275
|
visit(node);
|
|
@@ -3307,37 +6278,74 @@ var findPageCall = (nodes) => {
|
|
|
3307
6278
|
var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
|
|
3308
6279
|
var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
|
|
3309
6280
|
const callee = node.expression;
|
|
3310
|
-
if (!
|
|
6281
|
+
if (!ts2.isPropertyAccessExpression(callee))
|
|
3311
6282
|
return;
|
|
3312
6283
|
if (!ROUTE_METHODS.has(callee.name.text))
|
|
3313
6284
|
return;
|
|
3314
6285
|
const [routePath] = node.arguments;
|
|
3315
|
-
if (!routePath || !
|
|
6286
|
+
if (!routePath || !ts2.isStringLiteralLike(routePath))
|
|
3316
6287
|
return;
|
|
3317
|
-
const
|
|
6288
|
+
const foundPageCall = findPageCall(node.arguments.slice(1));
|
|
6289
|
+
const pageCall = foundPageCall?.node;
|
|
6290
|
+
const definition = foundPageCall?.definition;
|
|
3318
6291
|
const [input] = pageCall?.arguments ?? [];
|
|
3319
|
-
if (!pageCall || !input
|
|
6292
|
+
if (!pageCall || !input) {
|
|
3320
6293
|
return;
|
|
3321
6294
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
6295
|
+
if (!definition)
|
|
6296
|
+
return;
|
|
6297
|
+
if (definition.inputKind === "static") {
|
|
6298
|
+
const bundleKey2 = assetKey(input, checker);
|
|
6299
|
+
if (!bundleKey2)
|
|
6300
|
+
return;
|
|
6301
|
+
const pageId2 = `${definition.framework}:${bundleKey2}`;
|
|
6302
|
+
const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
|
|
6303
|
+
properties: {},
|
|
6304
|
+
type: "object"
|
|
6305
|
+
});
|
|
6306
|
+
return {
|
|
6307
|
+
inputKind: "static",
|
|
6308
|
+
metadata: {
|
|
6309
|
+
bundleKey: bundleKey2,
|
|
6310
|
+
contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
|
|
6311
|
+
framework: definition.framework,
|
|
6312
|
+
pageId: pageId2,
|
|
6313
|
+
propsSchemaHash: propsSchemaHash2
|
|
6314
|
+
},
|
|
6315
|
+
pageCallStart: pageCall.getStart(sourceFile),
|
|
6316
|
+
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
|
|
6320
|
+
return;
|
|
6321
|
+
}
|
|
6322
|
+
const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
|
|
6323
|
+
const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
|
|
6324
|
+
if (definition.pageProperty && !page)
|
|
6325
|
+
return;
|
|
6326
|
+
if (definition.sourceProperty && !source)
|
|
3324
6327
|
return;
|
|
3325
|
-
const props = objectPropertyExpression(input,
|
|
3326
|
-
const
|
|
3327
|
-
const bundleKey = assetKey(index, checker);
|
|
6328
|
+
const props = objectPropertyExpression(input, definition.propsProperty);
|
|
6329
|
+
const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
|
|
3328
6330
|
if (!bundleKey)
|
|
3329
6331
|
return;
|
|
3330
|
-
const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot)
|
|
3331
|
-
|
|
6332
|
+
const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
|
|
6333
|
+
let propsType;
|
|
6334
|
+
if (page)
|
|
6335
|
+
propsType = pagePropsType(page, props, checker);
|
|
6336
|
+
else if (props)
|
|
6337
|
+
propsType = checker.getTypeAtLocation(props);
|
|
6338
|
+
const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
|
|
3332
6339
|
const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
|
|
3333
6340
|
const metadata = {
|
|
3334
6341
|
bundleKey,
|
|
3335
|
-
contract:
|
|
3336
|
-
framework:
|
|
6342
|
+
contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
|
|
6343
|
+
framework: definition.framework,
|
|
3337
6344
|
pageId,
|
|
3338
6345
|
propsSchemaHash
|
|
3339
6346
|
};
|
|
3340
6347
|
const result = {
|
|
6348
|
+
inputKind: "object",
|
|
3341
6349
|
metadata,
|
|
3342
6350
|
pageCallStart: pageCall.getStart(sourceFile),
|
|
3343
6351
|
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
@@ -3350,21 +6358,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
|
|
|
3350
6358
|
byRouteCall: new Map
|
|
3351
6359
|
};
|
|
3352
6360
|
const visit = (node) => {
|
|
3353
|
-
const result =
|
|
6361
|
+
const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
|
|
3354
6362
|
if (result) {
|
|
3355
6363
|
analysis.byPageCall.set(result.pageCallStart, result);
|
|
3356
6364
|
analysis.byRouteCall.set(result.routeCallSpan, result);
|
|
3357
6365
|
}
|
|
3358
|
-
|
|
6366
|
+
ts2.forEachChild(node, visit);
|
|
3359
6367
|
};
|
|
3360
|
-
|
|
6368
|
+
ts2.forEachChild(sourceFile, visit);
|
|
3361
6369
|
return analysis;
|
|
3362
6370
|
};
|
|
3363
6371
|
var analyzeProgram = (program, projectRoot) => {
|
|
3364
6372
|
const checker = program.getTypeChecker();
|
|
3365
6373
|
const analyzed = new Map;
|
|
3366
6374
|
for (const sourceFile of program.getSourceFiles()) {
|
|
3367
|
-
const resolvedFile =
|
|
6375
|
+
const resolvedFile = resolve14(sourceFile.fileName);
|
|
3368
6376
|
if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
|
|
3369
6377
|
continue;
|
|
3370
6378
|
const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
|
|
@@ -3373,34 +6381,44 @@ var analyzeProgram = (program, projectRoot) => {
|
|
|
3373
6381
|
}
|
|
3374
6382
|
return analyzed;
|
|
3375
6383
|
};
|
|
3376
|
-
var metadataExpression = (metadata) =>
|
|
6384
|
+
var metadataExpression = (metadata) => ts2.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(key), ts2.factory.createStringLiteral(item))), false);
|
|
3377
6385
|
var routeOptions = (existing, metadata) => {
|
|
3378
|
-
const detail =
|
|
3379
|
-
|
|
6386
|
+
const detail = ts2.factory.createObjectLiteralExpression([
|
|
6387
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
3380
6388
|
]);
|
|
3381
6389
|
if (!existing) {
|
|
3382
|
-
return
|
|
3383
|
-
|
|
6390
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6391
|
+
ts2.factory.createPropertyAssignment("detail", detail)
|
|
3384
6392
|
]);
|
|
3385
6393
|
}
|
|
3386
|
-
return
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
6394
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6395
|
+
ts2.factory.createSpreadAssignment(existing),
|
|
6396
|
+
ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
|
|
6397
|
+
ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
|
|
6398
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
3391
6399
|
]))
|
|
3392
6400
|
]);
|
|
3393
6401
|
};
|
|
3394
6402
|
var transformPageCall = (node, page) => {
|
|
3395
6403
|
if (!page)
|
|
3396
6404
|
return;
|
|
6405
|
+
if (page.inputKind === "static") {
|
|
6406
|
+
const [pagePath, existingOptions, ...rest] = node.arguments;
|
|
6407
|
+
if (!pagePath)
|
|
6408
|
+
return;
|
|
6409
|
+
const options = ts2.factory.createObjectLiteralExpression([
|
|
6410
|
+
...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
|
|
6411
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
6412
|
+
]);
|
|
6413
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
|
|
6414
|
+
}
|
|
3397
6415
|
const [input] = node.arguments;
|
|
3398
|
-
if (!input || !
|
|
6416
|
+
if (!input || !ts2.isObjectLiteralExpression(input))
|
|
3399
6417
|
return;
|
|
3400
|
-
return
|
|
3401
|
-
|
|
6418
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
|
|
6419
|
+
ts2.factory.updateObjectLiteralExpression(input, [
|
|
3402
6420
|
...input.properties,
|
|
3403
|
-
|
|
6421
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
3404
6422
|
]),
|
|
3405
6423
|
...node.arguments.slice(1)
|
|
3406
6424
|
]);
|
|
@@ -3413,16 +6431,16 @@ var transformRouteCall = (node, route) => {
|
|
|
3413
6431
|
return;
|
|
3414
6432
|
const options = maybeHandler ? maybeOptions : undefined;
|
|
3415
6433
|
const handler = maybeHandler ?? maybeOptions;
|
|
3416
|
-
return
|
|
6434
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
|
|
3417
6435
|
};
|
|
3418
6436
|
var transformFile = (source, fileName, analysis) => {
|
|
3419
|
-
const sourceFile =
|
|
6437
|
+
const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
|
|
3420
6438
|
const transformer = (context) => {
|
|
3421
6439
|
const visit = (node) => {
|
|
3422
|
-
if (!
|
|
3423
|
-
return
|
|
6440
|
+
if (!ts2.isCallExpression(node)) {
|
|
6441
|
+
return ts2.visitEachChild(node, visit, context);
|
|
3424
6442
|
}
|
|
3425
|
-
const transformedChildren =
|
|
6443
|
+
const transformedChildren = ts2.visitEachChild(node, visit, context);
|
|
3426
6444
|
const page = analysis.byPageCall.get(node.getStart(sourceFile));
|
|
3427
6445
|
const transformedPage = transformPageCall(transformedChildren, page);
|
|
3428
6446
|
if (transformedPage)
|
|
@@ -3433,81 +6451,120 @@ var transformFile = (source, fileName, analysis) => {
|
|
|
3433
6451
|
return transformedRoute;
|
|
3434
6452
|
return transformedChildren;
|
|
3435
6453
|
};
|
|
3436
|
-
return (node) =>
|
|
6454
|
+
return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
|
|
3437
6455
|
};
|
|
3438
|
-
const result =
|
|
6456
|
+
const result = ts2.transform(sourceFile, [transformer]);
|
|
3439
6457
|
try {
|
|
3440
6458
|
const [transformed] = result.transformed;
|
|
3441
6459
|
if (!transformed)
|
|
3442
6460
|
throw new TypeError("Mobile route transform failed.");
|
|
3443
|
-
return
|
|
6461
|
+
return ts2.createPrinter().printFile(transformed);
|
|
3444
6462
|
} finally {
|
|
3445
6463
|
result.dispose();
|
|
3446
6464
|
}
|
|
3447
6465
|
};
|
|
3448
6466
|
var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
|
|
3449
6467
|
var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
3450
|
-
const projectRoot =
|
|
3451
|
-
const entry =
|
|
6468
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6469
|
+
const entry = resolve14(options.entry);
|
|
3452
6470
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
3453
6471
|
return {
|
|
3454
6472
|
name: "absolute-mobile-route-metadata",
|
|
3455
6473
|
setup(build) {
|
|
3456
6474
|
build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
|
|
3457
|
-
const analysis = analyzed.get(
|
|
6475
|
+
const analysis = analyzed.get(resolve14(path));
|
|
3458
6476
|
if (!analysis)
|
|
3459
6477
|
return;
|
|
3460
6478
|
const source = await Bun.file(path).text();
|
|
3461
6479
|
return {
|
|
3462
6480
|
contents: transformFile(source, path, analysis),
|
|
3463
|
-
loader:
|
|
6481
|
+
loader: extname4(path).endsWith("x") ? "tsx" : "ts"
|
|
3464
6482
|
};
|
|
3465
6483
|
});
|
|
3466
6484
|
}
|
|
3467
6485
|
};
|
|
3468
6486
|
};
|
|
3469
6487
|
var inspectAbsoluteMobileRouteMetadata = (options) => {
|
|
3470
|
-
const projectRoot =
|
|
3471
|
-
const entry =
|
|
6488
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6489
|
+
const entry = resolve14(options.entry);
|
|
3472
6490
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
3473
6491
|
return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
|
|
3474
|
-
file: posixPath(
|
|
6492
|
+
file: posixPath(relative11(projectRoot, file)),
|
|
3475
6493
|
metadata
|
|
3476
6494
|
})));
|
|
3477
6495
|
};
|
|
3478
6496
|
export {
|
|
3479
6497
|
writeAbsoluteCapacitorConfig,
|
|
6498
|
+
waitForAbsoluteIosHmrLog,
|
|
3480
6499
|
verifyAbsoluteMobileCompatibilityProducer,
|
|
3481
6500
|
verifyAbsoluteMobileAssociationFiles,
|
|
6501
|
+
validateAbsoluteSshDestination,
|
|
6502
|
+
validateAbsoluteRemoteMacProfileName,
|
|
6503
|
+
syncAbsoluteRemoteMacProject,
|
|
6504
|
+
startAbsoluteRemoteIosDevSession,
|
|
6505
|
+
startAbsoluteIosDevSession,
|
|
6506
|
+
serializeAbsoluteMobileAuthEnvironment,
|
|
3482
6507
|
runWithAbsoluteMobileProducer,
|
|
3483
6508
|
retainAbsoluteMobileCompatibilityArtifacts,
|
|
3484
6509
|
resolveAbsoluteMobileRoute,
|
|
6510
|
+
resolveAbsoluteMobileNavigation,
|
|
3485
6511
|
resolveAbsoluteMobileDeepLink,
|
|
3486
6512
|
resolveAbsoluteMobileCompatibilityRelease,
|
|
6513
|
+
resolveAbsoluteMobileAuthManifest,
|
|
6514
|
+
resolveAbsoluteDeviceCapabilityPlan,
|
|
6515
|
+
repairAbsoluteIosDevSession,
|
|
6516
|
+
removeAbsoluteRemoteMacProfile,
|
|
6517
|
+
redactAbsoluteIosLog,
|
|
3487
6518
|
readAbsoluteMobileMaterializedReleases,
|
|
3488
6519
|
publishAbsoluteIosRelease,
|
|
3489
6520
|
publishAbsoluteAndroidRelease,
|
|
6521
|
+
projectUsesAbsoluteSync,
|
|
6522
|
+
projectUsesAbsoluteAuth,
|
|
3490
6523
|
prepareAbsoluteIosRelease,
|
|
6524
|
+
prepareAbsoluteIosDevProject,
|
|
3491
6525
|
prepareAbsoluteAndroidRelease,
|
|
6526
|
+
parseIosSimulators,
|
|
6527
|
+
parseIosRuntimes,
|
|
6528
|
+
parseIosDeviceTypes,
|
|
3492
6529
|
parseAbsoluteMobilePageRequest,
|
|
3493
6530
|
parseAbsoluteMobilePageEnvelope,
|
|
3494
6531
|
parseAbsoluteMobileCompatibilityArtifact,
|
|
3495
6532
|
parseAbsoluteMobileBuildPageMetadata,
|
|
6533
|
+
parseAbsoluteIosLogLine,
|
|
6534
|
+
parseAbsoluteIosHmrLog,
|
|
6535
|
+
pairAbsoluteRemoteMac,
|
|
3496
6536
|
normalizeAbsoluteMobileConfig,
|
|
3497
6537
|
navigateAbsoluteMobilePage,
|
|
6538
|
+
missingAbsoluteDeviceCapabilityPackages,
|
|
6539
|
+
materializeAbsoluteRemoteMacAgent,
|
|
3498
6540
|
materializeAbsoluteMobileCompatibilityBundle,
|
|
3499
6541
|
materializeAbsoluteMobileAssociationFiles,
|
|
3500
6542
|
materializeAbsoluteCapacitorWebBundle,
|
|
3501
6543
|
matchesAbsoluteMobileRoutePattern,
|
|
3502
6544
|
loadAbsoluteNativeReleasePublisher,
|
|
3503
6545
|
loadAbsoluteMobileMaterializedBundle,
|
|
6546
|
+
loadAbsoluteDeviceCapabilityProviders,
|
|
6547
|
+
listAbsoluteRemoteMacProfiles,
|
|
6548
|
+
isAbsoluteIosNativeRootInput,
|
|
6549
|
+
installAbsoluteRemoteMacAgent,
|
|
6550
|
+
installAbsoluteMobileSyncRemediation,
|
|
6551
|
+
installAbsoluteMobileAuthEnvironment,
|
|
6552
|
+
inspectAbsoluteRemoteMac,
|
|
3504
6553
|
inspectAbsoluteMobileRouteMetadata,
|
|
3505
6554
|
hashAbsoluteMobilePropsSchema,
|
|
3506
6555
|
getCurrentAbsoluteMobileProducerContext,
|
|
6556
|
+
getAbsoluteRemoteMacProfile,
|
|
6557
|
+
getAbsoluteMobileSyncRemediation,
|
|
3507
6558
|
fingerprintAbsoluteIosNativeProject,
|
|
6559
|
+
fingerprintAbsoluteIosDevProject,
|
|
3508
6560
|
finalizeAbsoluteMobilePage,
|
|
3509
6561
|
finalizeAbsoluteMobileCompatibilityBuild,
|
|
3510
6562
|
fetchAbsoluteMobilePage,
|
|
6563
|
+
disposeAbsoluteMobilePage,
|
|
6564
|
+
discoverAbsoluteSyncSchema,
|
|
6565
|
+
discoverAbsoluteDeviceCapabilities,
|
|
6566
|
+
directAbsoluteProjectPackages,
|
|
6567
|
+
createAbsoluteRemoteIosDevProject,
|
|
3511
6568
|
createAbsoluteMobileUpgradeResponse,
|
|
3512
6569
|
createAbsoluteMobileRouteMetadataPlugin,
|
|
3513
6570
|
createAbsoluteMobilePageRequest,
|
|
@@ -3517,20 +6574,32 @@ export {
|
|
|
3517
6574
|
createAbsoluteMobileCompatibilityDispatcher,
|
|
3518
6575
|
createAbsoluteMobileCompatibilityArtifact,
|
|
3519
6576
|
createAbsoluteMobileBlobArtifactStore,
|
|
6577
|
+
createAbsoluteMobileAuthManifest,
|
|
3520
6578
|
createAbsoluteMobileAssociationPlugin,
|
|
3521
6579
|
createAbsoluteMobileAssociationDocuments,
|
|
6580
|
+
createAbsoluteIosNativeWatcher,
|
|
3522
6581
|
carryForwardAbsoluteMobileCompatibilityReleases,
|
|
3523
6582
|
captureAbsoluteMobileRouteGraph,
|
|
3524
6583
|
buildAbsoluteMobileCompatibilityRelease,
|
|
3525
6584
|
buildAbsoluteIosRelease,
|
|
3526
6585
|
buildAbsoluteAndroidRelease,
|
|
6586
|
+
assertAbsoluteDeviceCapabilityPackages,
|
|
6587
|
+
applyAbsoluteNativeDeviceCapabilities,
|
|
3527
6588
|
applyAbsoluteNativeDeepLinks,
|
|
3528
6589
|
activateAbsoluteMobilePage,
|
|
3529
6590
|
acceptsAbsoluteMobilePage,
|
|
6591
|
+
absoluteRemoteProjectSyncCommands,
|
|
6592
|
+
absoluteRemoteMacSshBase,
|
|
6593
|
+
absoluteDeviceNativeRequirements,
|
|
3530
6594
|
MOBILE_PAGE_REQUEST_HEADERS,
|
|
3531
6595
|
AbsoluteMobilePageProtocolError,
|
|
3532
6596
|
APPLE_ASSOCIATION_PATH,
|
|
3533
6597
|
ANDROID_ASSOCIATION_PATH,
|
|
6598
|
+
ABSOLUTE_SYNC_PACKAGE,
|
|
6599
|
+
ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
|
|
6600
|
+
ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
|
|
6601
|
+
ABSOLUTE_NATIVE_AUTH_SCOPES,
|
|
6602
|
+
ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
|
|
3534
6603
|
ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
|
|
3535
6604
|
ABSOLUTE_MOBILE_ROUTE_DETAIL,
|
|
3536
6605
|
ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
|
|
@@ -3539,9 +6608,11 @@ export {
|
|
|
3539
6608
|
ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
|
|
3540
6609
|
ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
|
|
3541
6610
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
6611
|
+
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
3542
6612
|
ABSOLUTE_IOS_RELEASE_FORMAT,
|
|
6613
|
+
ABSOLUTE_AUTH_PACKAGE,
|
|
3543
6614
|
ABSOLUTE_ANDROID_RELEASE_FORMAT
|
|
3544
6615
|
};
|
|
3545
6616
|
|
|
3546
|
-
//# debugId=
|
|
6617
|
+
//# debugId=1579CD5E4A79E32964756E2164756E21
|
|
3547
6618
|
//# sourceMappingURL=index.js.map
|