@absolutejs/absolute 0.20.0-beta.2 → 0.20.0-beta.20
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/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 +1254 -598
- package/dist/build.js.map +16 -13
- package/dist/cli/index.js +4995 -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 +1628 -862
- package/dist/index.js.map +24 -21
- package/dist/mobile/browser.js +123 -1
- package/dist/mobile/browser.js.map +6 -4
- package/dist/mobile/index.js +3361 -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/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 +29 -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,269 @@ 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
|
+
"photo-library",
|
|
5076
|
+
"photo-library-add"
|
|
5077
|
+
]);
|
|
5078
|
+
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5079
|
+
var readJson = (path) => {
|
|
5080
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
5081
|
+
if (!object2(value))
|
|
5082
|
+
throw new TypeError(`${path} must contain an object.`);
|
|
5083
|
+
return value;
|
|
5084
|
+
};
|
|
5085
|
+
var text = (value, field) => {
|
|
5086
|
+
if (typeof value !== "string" || value.length === 0)
|
|
5087
|
+
throw new TypeError(`${field} must be a non-empty string.`);
|
|
5088
|
+
return value;
|
|
5089
|
+
};
|
|
5090
|
+
var androidPermissions = (value, field) => {
|
|
5091
|
+
if (value === undefined)
|
|
5092
|
+
return;
|
|
5093
|
+
if (!object2(value))
|
|
5094
|
+
throw new TypeError(`${field} must be an object.`);
|
|
5095
|
+
const { permissions } = value;
|
|
5096
|
+
if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
|
|
5097
|
+
throw new TypeError(`${field}.permissions must contain Android permission names.`);
|
|
5098
|
+
return [...permissions];
|
|
5099
|
+
};
|
|
5100
|
+
var iosUsageDescriptions = (value, field) => {
|
|
5101
|
+
if (value === undefined)
|
|
5102
|
+
return;
|
|
5103
|
+
if (!object2(value))
|
|
5104
|
+
throw new TypeError(`${field} must be an object.`);
|
|
5105
|
+
const { usageDescriptions } = value;
|
|
5106
|
+
if (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose)))
|
|
5107
|
+
throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
|
|
5108
|
+
return [...usageDescriptions];
|
|
5109
|
+
};
|
|
5110
|
+
var parseProvider = (name, value) => {
|
|
5111
|
+
if (!IDENTIFIER_PATTERN.test(name))
|
|
5112
|
+
throw new TypeError("Device capability names must be identifiers.");
|
|
5113
|
+
if (!object2(value))
|
|
5114
|
+
throw new TypeError(`Device capability ${name} must be an object.`);
|
|
5115
|
+
const factory = text(value.factory, `${name}.factory`);
|
|
5116
|
+
const module = text(value.module, `${name}.module`);
|
|
5117
|
+
if (!IDENTIFIER_PATTERN.test(factory))
|
|
5118
|
+
throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
|
|
5119
|
+
if (!CAPACITOR_MODULE_PATTERN.test(module))
|
|
5120
|
+
throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
|
|
5121
|
+
if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
|
|
5122
|
+
throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
|
|
5123
|
+
let native;
|
|
5124
|
+
const { native: nativeMetadata } = value;
|
|
5125
|
+
if (nativeMetadata !== undefined) {
|
|
5126
|
+
if (!object2(nativeMetadata))
|
|
5127
|
+
throw new TypeError(`${name}.native must be an object.`);
|
|
5128
|
+
const { android, ios } = nativeMetadata;
|
|
5129
|
+
const permissions = androidPermissions(android, `${name}.native.android`);
|
|
5130
|
+
const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
|
|
5131
|
+
native = {
|
|
5132
|
+
...permissions === undefined ? {} : { android: { permissions } },
|
|
5133
|
+
...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
return {
|
|
5137
|
+
factory,
|
|
5138
|
+
module,
|
|
5139
|
+
...native === undefined ? {} : { native },
|
|
5140
|
+
packages: [...value.packages]
|
|
5141
|
+
};
|
|
5142
|
+
};
|
|
5143
|
+
var absoluteDeviceNativeRequirements = (plan) => ({
|
|
5144
|
+
androidPermissions: [
|
|
5145
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
|
|
5146
|
+
].sort(),
|
|
5147
|
+
iosUsageDescriptions: [
|
|
5148
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
|
|
5149
|
+
].sort()
|
|
5150
|
+
});
|
|
5151
|
+
var loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
|
|
5152
|
+
const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
|
|
5153
|
+
const manifest = readJson(path);
|
|
5154
|
+
const { absolutejs } = manifest;
|
|
5155
|
+
const devices = object2(absolutejs) ? absolutejs.devices : undefined;
|
|
5156
|
+
if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
|
|
5157
|
+
throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
|
|
5158
|
+
const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
|
|
5159
|
+
name,
|
|
5160
|
+
provider: parseProvider(name, provider)
|
|
5161
|
+
}));
|
|
5162
|
+
return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
|
|
5163
|
+
};
|
|
5164
|
+
var isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
5165
|
+
var importedCapabilities = (source, file) => {
|
|
5166
|
+
const names = new Set;
|
|
5167
|
+
const namespaces = new Set;
|
|
5168
|
+
const visit = (node) => {
|
|
5169
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
|
|
5170
|
+
const bindings = node.importClause?.namedBindings;
|
|
5171
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
5172
|
+
for (const element of bindings.elements)
|
|
5173
|
+
if (!element.isTypeOnly)
|
|
5174
|
+
names.add((element.propertyName ?? element.name).text);
|
|
5175
|
+
}
|
|
5176
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
5177
|
+
namespaces.add(bindings.name.text);
|
|
5178
|
+
}
|
|
5179
|
+
if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
5180
|
+
for (const element of node.exportClause.elements)
|
|
5181
|
+
if (!element.isTypeOnly)
|
|
5182
|
+
names.add((element.propertyName ?? element.name).text);
|
|
5183
|
+
}
|
|
5184
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
|
|
5185
|
+
names.add(node.name.text);
|
|
5186
|
+
ts.forEachChild(node, visit);
|
|
5187
|
+
};
|
|
5188
|
+
const extension = extname3(file).toLowerCase();
|
|
5189
|
+
const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
|
|
5190
|
+
for (const [index, script] of sources.entries())
|
|
5191
|
+
visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
|
|
5192
|
+
return names;
|
|
5193
|
+
};
|
|
5194
|
+
var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
|
|
5195
|
+
const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
|
|
5196
|
+
const mismatched = plan.requiredPackages.filter((spec) => {
|
|
5197
|
+
const separator = spec.lastIndexOf("@");
|
|
5198
|
+
const packageName = spec.slice(0, separator);
|
|
5199
|
+
if (missing.includes(spec))
|
|
5200
|
+
return false;
|
|
5201
|
+
try {
|
|
5202
|
+
return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
|
|
5203
|
+
} catch {
|
|
5204
|
+
return true;
|
|
5205
|
+
}
|
|
5206
|
+
});
|
|
5207
|
+
const unmet = [...missing, ...mismatched];
|
|
5208
|
+
if (unmet.length > 0)
|
|
5209
|
+
throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
|
|
5210
|
+
};
|
|
5211
|
+
var directAbsoluteProjectPackages = (projectRoot) => {
|
|
5212
|
+
const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
|
|
5213
|
+
const packages = new Set;
|
|
5214
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
5215
|
+
const dependencies = manifest[field];
|
|
5216
|
+
if (object2(dependencies))
|
|
5217
|
+
for (const name of Object.keys(dependencies))
|
|
5218
|
+
packages.add(name);
|
|
5219
|
+
}
|
|
5220
|
+
return packages;
|
|
5221
|
+
};
|
|
5222
|
+
var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
|
|
5223
|
+
const root = resolve11(projectRoot);
|
|
5224
|
+
const known = new Set(Object.keys(providers));
|
|
5225
|
+
const capabilities = new Set;
|
|
5226
|
+
for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
|
|
5227
|
+
const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
|
|
5228
|
+
if (isIgnored(portable))
|
|
5229
|
+
continue;
|
|
5230
|
+
const source = readFileSync4(resolve11(root, portable), "utf8");
|
|
5231
|
+
for (const name of importedCapabilities(source, portable))
|
|
5232
|
+
if (known.has(name))
|
|
5233
|
+
capabilities.add(name);
|
|
5234
|
+
}
|
|
5235
|
+
return [...capabilities].sort();
|
|
5236
|
+
};
|
|
5237
|
+
var missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
|
|
5238
|
+
const packageName = spec.slice(0, spec.lastIndexOf("@"));
|
|
5239
|
+
return !directPackages.has(packageName);
|
|
5240
|
+
});
|
|
5241
|
+
var resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
|
|
5242
|
+
const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
|
|
5243
|
+
const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
|
|
5244
|
+
const providers = {};
|
|
5245
|
+
for (const name of capabilities) {
|
|
5246
|
+
const provider = allProviders[name];
|
|
5247
|
+
if (provider)
|
|
5248
|
+
providers[name] = provider;
|
|
5249
|
+
}
|
|
5250
|
+
return {
|
|
5251
|
+
capabilities,
|
|
5252
|
+
providers,
|
|
5253
|
+
requiredPackages: [
|
|
5254
|
+
...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
|
|
5255
|
+
].sort()
|
|
5256
|
+
};
|
|
5257
|
+
};
|
|
5258
|
+
|
|
2717
5259
|
// src/mobile/buildPipeline.ts
|
|
2718
5260
|
var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
|
|
2719
5261
|
var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
@@ -2724,12 +5266,11 @@ var serverExportName = (loaded, app) => {
|
|
|
2724
5266
|
return "app";
|
|
2725
5267
|
return "default";
|
|
2726
5268
|
};
|
|
2727
|
-
var
|
|
2728
|
-
if (previous !== undefined)
|
|
2729
|
-
process.env
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
delete process.env.ABSOLUTE_BUILD_DIR;
|
|
5269
|
+
var restoreEnvironmentVariable = (name, previous) => {
|
|
5270
|
+
if (previous !== undefined)
|
|
5271
|
+
process.env[name] = previous;
|
|
5272
|
+
else
|
|
5273
|
+
delete process.env[name];
|
|
2733
5274
|
};
|
|
2734
5275
|
var requireRelease = (releases, releaseId) => {
|
|
2735
5276
|
const release = releases.get(releaseId);
|
|
@@ -2749,11 +5290,11 @@ var loadServerApp = async (producerPath) => {
|
|
|
2749
5290
|
return { app, exportName };
|
|
2750
5291
|
};
|
|
2751
5292
|
var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
2752
|
-
const buildDirectory =
|
|
5293
|
+
const buildDirectory = resolve12(options.buildDirectory);
|
|
2753
5294
|
const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
|
|
2754
|
-
const root =
|
|
5295
|
+
const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
|
|
2755
5296
|
const [manifestSource, previous] = await Promise.all([
|
|
2756
|
-
|
|
5297
|
+
readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
|
|
2757
5298
|
readAbsoluteMobileMaterializedReleases(root)
|
|
2758
5299
|
]);
|
|
2759
5300
|
const manifest = JSON.parse(manifestSource);
|
|
@@ -2761,12 +5302,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2761
5302
|
throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
|
|
2762
5303
|
}
|
|
2763
5304
|
const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
|
|
5305
|
+
const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
|
|
5306
|
+
const previousConfigPath = process.env.ABSOLUTE_CONFIG;
|
|
2764
5307
|
process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
|
|
5308
|
+
process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
|
|
5309
|
+
if (options.configPath) {
|
|
5310
|
+
process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
|
|
5311
|
+
}
|
|
2765
5312
|
let loaded;
|
|
2766
5313
|
try {
|
|
2767
|
-
loaded = await loadServerApp(
|
|
5314
|
+
loaded = await loadServerApp(resolve12(options.producerPath));
|
|
2768
5315
|
} finally {
|
|
2769
|
-
|
|
5316
|
+
restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
|
|
5317
|
+
restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
|
|
5318
|
+
restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
|
|
2770
5319
|
}
|
|
2771
5320
|
const current = await buildAbsoluteMobileCompatibilityRelease({
|
|
2772
5321
|
app: loaded.app,
|
|
@@ -2775,9 +5324,17 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2775
5324
|
manifest,
|
|
2776
5325
|
previousArtifacts: previous.map(({ artifact }) => artifact),
|
|
2777
5326
|
producerExport: loaded.exportName,
|
|
2778
|
-
producerPath:
|
|
5327
|
+
producerPath: resolve12(options.producerPath),
|
|
2779
5328
|
runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
|
|
2780
5329
|
});
|
|
5330
|
+
const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
|
|
5331
|
+
const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
|
|
5332
|
+
const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
|
|
5333
|
+
const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
|
|
5334
|
+
assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
|
|
5335
|
+
if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
|
|
5336
|
+
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.");
|
|
5337
|
+
}
|
|
2781
5338
|
const releasesById = new Map([current, ...previous].map((release) => [
|
|
2782
5339
|
release.artifact.releaseId,
|
|
2783
5340
|
release
|
|
@@ -2790,11 +5347,127 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
2790
5347
|
});
|
|
2791
5348
|
await materializeAbsoluteCapacitorWebBundle({
|
|
2792
5349
|
artifact: current.artifact,
|
|
5350
|
+
...auth ? { auth } : {},
|
|
2793
5351
|
buildDirectory,
|
|
2794
|
-
config: mobile
|
|
5352
|
+
config: mobile,
|
|
5353
|
+
deviceCapabilities,
|
|
5354
|
+
projectRoot: options.projectRoot,
|
|
5355
|
+
...sync ? { sync: true } : {},
|
|
5356
|
+
...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
|
|
2795
5357
|
});
|
|
2796
5358
|
return current.artifact;
|
|
2797
5359
|
};
|
|
5360
|
+
|
|
5361
|
+
// src/mobile/index.ts
|
|
5362
|
+
init_syncSchema();
|
|
5363
|
+
|
|
5364
|
+
// node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
|
|
5365
|
+
var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
|
|
5366
|
+
var host2 = globalThis;
|
|
5367
|
+
var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
|
|
5368
|
+
var registry2 = (() => {
|
|
5369
|
+
const existing = host2[RUNTIME_TRANSPORT2];
|
|
5370
|
+
if (isRegistry2(existing))
|
|
5371
|
+
return existing;
|
|
5372
|
+
if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
|
|
5373
|
+
Reflect.set(existing, "clients", []);
|
|
5374
|
+
return existing;
|
|
5375
|
+
}
|
|
5376
|
+
const created = { clients: [], installations: [] };
|
|
5377
|
+
Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
|
|
5378
|
+
configurable: false,
|
|
5379
|
+
enumerable: false,
|
|
5380
|
+
value: created,
|
|
5381
|
+
writable: false
|
|
5382
|
+
});
|
|
5383
|
+
return created;
|
|
5384
|
+
})();
|
|
5385
|
+
var maximum = (values) => {
|
|
5386
|
+
const present = values.filter((value) => value !== undefined);
|
|
5387
|
+
return present.length === 0 ? undefined : Math.max(...present);
|
|
5388
|
+
};
|
|
5389
|
+
var minimum = (values) => {
|
|
5390
|
+
const present = values.filter((value) => value !== undefined);
|
|
5391
|
+
return present.length === 0 ? undefined : Math.min(...present);
|
|
5392
|
+
};
|
|
5393
|
+
var inspectSyncRuntime = async () => {
|
|
5394
|
+
const clients = [...registry2.clients];
|
|
5395
|
+
const statuses = clients.map((client) => client.status());
|
|
5396
|
+
const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
|
|
5397
|
+
attempts: record.attempts,
|
|
5398
|
+
...record.rejection?.code ? { code: record.rejection.code } : {},
|
|
5399
|
+
createdAt: record.createdAt,
|
|
5400
|
+
...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
|
|
5401
|
+
...record.rejection?.kind ? { kind: record.rejection.kind } : {},
|
|
5402
|
+
...record.rejection?.message ? { message: record.rejection.message } : {},
|
|
5403
|
+
name: record.name,
|
|
5404
|
+
operationId: record.operationId
|
|
5405
|
+
})).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
|
|
5406
|
+
const lastError = statuses.findLast((status) => status.lastError)?.lastError;
|
|
5407
|
+
return {
|
|
5408
|
+
automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
|
|
5409
|
+
clients: clients.length,
|
|
5410
|
+
conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
|
|
5411
|
+
deadLetters,
|
|
5412
|
+
...lastError ? { lastError } : {},
|
|
5413
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
|
|
5414
|
+
lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
|
|
5415
|
+
},
|
|
5416
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
|
|
5417
|
+
lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
|
|
5418
|
+
},
|
|
5419
|
+
...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
|
|
5420
|
+
oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
|
|
5421
|
+
},
|
|
5422
|
+
...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
|
|
5423
|
+
oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
|
|
5424
|
+
},
|
|
5425
|
+
pending: statuses.reduce((total, status) => total + status.pending, 0)
|
|
5426
|
+
};
|
|
5427
|
+
};
|
|
5428
|
+
var clientWithDeadLetter = async (operationId) => {
|
|
5429
|
+
for (const client of registry2.clients)
|
|
5430
|
+
if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
|
|
5431
|
+
return client;
|
|
5432
|
+
throw new Error(`Unknown Sync dead letter "${operationId}"`);
|
|
5433
|
+
};
|
|
5434
|
+
var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
|
|
5435
|
+
var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
|
|
5436
|
+
var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
|
|
5437
|
+
|
|
5438
|
+
// src/mobile/syncRemediation.ts
|
|
5439
|
+
var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
|
|
5440
|
+
var LAST_INSTALLATION_OFFSET = -1;
|
|
5441
|
+
var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
|
|
5442
|
+
var resolveRegistry = () => {
|
|
5443
|
+
const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
|
|
5444
|
+
if (isRegistry3(existing))
|
|
5445
|
+
return existing;
|
|
5446
|
+
const created = { installations: [] };
|
|
5447
|
+
Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
|
|
5448
|
+
configurable: false,
|
|
5449
|
+
enumerable: false,
|
|
5450
|
+
value: created,
|
|
5451
|
+
writable: false
|
|
5452
|
+
});
|
|
5453
|
+
return created;
|
|
5454
|
+
};
|
|
5455
|
+
var registry3 = resolveRegistry();
|
|
5456
|
+
var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
|
|
5457
|
+
var installAbsoluteMobileSyncRemediation = (bridge = {
|
|
5458
|
+
discard: discardSyncRuntimeDeadLetter,
|
|
5459
|
+
inspect: inspectSyncRuntime,
|
|
5460
|
+
rebase: rebaseSyncRuntimeDeadLetter,
|
|
5461
|
+
retry: retrySyncRuntimeDeadLetter
|
|
5462
|
+
}) => {
|
|
5463
|
+
const installation = { bridge };
|
|
5464
|
+
registry3.installations.push(installation);
|
|
5465
|
+
return () => {
|
|
5466
|
+
const index = registry3.installations.indexOf(installation);
|
|
5467
|
+
if (index >= 0)
|
|
5468
|
+
registry3.installations.splice(index, 1);
|
|
5469
|
+
};
|
|
5470
|
+
};
|
|
2798
5471
|
// src/mobile/compatibilityDispatcher.ts
|
|
2799
5472
|
import { Elysia as Elysia2 } from "elysia";
|
|
2800
5473
|
|
|
@@ -2816,6 +5489,64 @@ var ensureProducerStorage = () => {
|
|
|
2816
5489
|
var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
|
|
2817
5490
|
|
|
2818
5491
|
// src/mobile/compatibilityDispatcher.ts
|
|
5492
|
+
var MOBILE_WEBVIEW_ORIGINS = new Set([
|
|
5493
|
+
"capacitor://localhost",
|
|
5494
|
+
"http://localhost",
|
|
5495
|
+
"https://localhost"
|
|
5496
|
+
]);
|
|
5497
|
+
var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
|
|
5498
|
+
var MOBILE_CORS_ALLOW_HEADERS = [
|
|
5499
|
+
"accept",
|
|
5500
|
+
"content-type",
|
|
5501
|
+
"authorization",
|
|
5502
|
+
"hx-current-url",
|
|
5503
|
+
"hx-request",
|
|
5504
|
+
"hx-target",
|
|
5505
|
+
"hx-trigger",
|
|
5506
|
+
"hx-trigger-name",
|
|
5507
|
+
...MOBILE_REQUEST_HEADER_NAMES
|
|
5508
|
+
].join(", ");
|
|
5509
|
+
var MOBILE_CORS_METHODS = new Set([
|
|
5510
|
+
"DELETE",
|
|
5511
|
+
"GET",
|
|
5512
|
+
"HEAD",
|
|
5513
|
+
"OPTIONS",
|
|
5514
|
+
"PATCH",
|
|
5515
|
+
"POST",
|
|
5516
|
+
"PUT"
|
|
5517
|
+
]);
|
|
5518
|
+
var mobileWebViewOrigin = (request) => {
|
|
5519
|
+
const origin = request.headers.get("origin");
|
|
5520
|
+
return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
|
|
5521
|
+
};
|
|
5522
|
+
var applyMobileCorsHeaders = (response, origin) => {
|
|
5523
|
+
response.headers.set("access-control-allow-credentials", "true");
|
|
5524
|
+
response.headers.set("access-control-allow-origin", origin);
|
|
5525
|
+
response.headers.append("vary", "Origin");
|
|
5526
|
+
return response;
|
|
5527
|
+
};
|
|
5528
|
+
var mobilePreflightResponse = (request) => {
|
|
5529
|
+
if (request.method !== "OPTIONS")
|
|
5530
|
+
return;
|
|
5531
|
+
const origin = mobileWebViewOrigin(request);
|
|
5532
|
+
if (!origin)
|
|
5533
|
+
return;
|
|
5534
|
+
const requestedHeaders = request.headers.get("access-control-request-headers");
|
|
5535
|
+
const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
|
|
5536
|
+
if (!MOBILE_CORS_METHODS.has(requestedMethod))
|
|
5537
|
+
return;
|
|
5538
|
+
return new Response(null, {
|
|
5539
|
+
headers: {
|
|
5540
|
+
"access-control-allow-credentials": "true",
|
|
5541
|
+
"access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
|
|
5542
|
+
"access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
|
|
5543
|
+
"access-control-allow-origin": origin,
|
|
5544
|
+
"access-control-max-age": "600",
|
|
5545
|
+
vary: "Origin, Access-Control-Request-Headers"
|
|
5546
|
+
},
|
|
5547
|
+
status: 204
|
|
5548
|
+
});
|
|
5549
|
+
};
|
|
2819
5550
|
var artifactOwnsRequest = (artifact, pageId, request) => {
|
|
2820
5551
|
const { pathname } = new URL(request.url);
|
|
2821
5552
|
return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
|
|
@@ -2843,6 +5574,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
|
|
|
2843
5574
|
return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
|
|
2844
5575
|
if (getCurrentAbsoluteMobileProducerContext())
|
|
2845
5576
|
return;
|
|
5577
|
+
const preflight = mobilePreflightResponse(request);
|
|
5578
|
+
if (preflight)
|
|
5579
|
+
return preflight;
|
|
2846
5580
|
const parsed = parseAbsoluteMobilePageRequest(request);
|
|
2847
5581
|
if (parsed.kind !== "mobile")
|
|
2848
5582
|
return;
|
|
@@ -2866,23 +5600,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
|
|
|
2866
5600
|
console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
|
|
2867
5601
|
return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
|
|
2868
5602
|
}
|
|
5603
|
+
}).afterHandle("global", ({ request, responseValue }) => {
|
|
5604
|
+
const origin = mobileWebViewOrigin(request);
|
|
5605
|
+
if (!origin || !(responseValue instanceof Response))
|
|
5606
|
+
return;
|
|
5607
|
+
applyMobileCorsHeaders(responseValue, origin);
|
|
2869
5608
|
}).as("global");
|
|
2870
5609
|
};
|
|
2871
5610
|
// src/mobile/nativeDeepLinks.ts
|
|
2872
|
-
import { readFile as
|
|
2873
|
-
import { join as
|
|
5611
|
+
import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
|
|
5612
|
+
import { join as join15 } from "path";
|
|
2874
5613
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->";
|
|
2875
5614
|
var END_MARKER = "<!-- absolutejs:deep-links:end -->";
|
|
2876
5615
|
var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
|
|
2877
5616
|
var NOT_FOUND = -1;
|
|
2878
5617
|
var escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
2879
5618
|
var writeChangedFile = async (path, source) => {
|
|
2880
|
-
const current = await
|
|
5619
|
+
const current = await readFile14(path, "utf8");
|
|
2881
5620
|
if (current === source)
|
|
2882
5621
|
return false;
|
|
2883
5622
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
2884
|
-
await
|
|
2885
|
-
await
|
|
5623
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5624
|
+
await rename11(temporary, path);
|
|
2886
5625
|
return true;
|
|
2887
5626
|
};
|
|
2888
5627
|
var replaceManagedRegion = (source, region, insertAt) => {
|
|
@@ -2906,7 +5645,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
|
|
|
2906
5645
|
return `${source.slice(0, index)}${region}${source.slice(index)}`;
|
|
2907
5646
|
};
|
|
2908
5647
|
var androidRegion = (config) => {
|
|
2909
|
-
const hosts = config.deepLinkHosts.map((
|
|
5648
|
+
const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
|
|
2910
5649
|
`);
|
|
2911
5650
|
const customScheme = config.deepLinkScheme ? `
|
|
2912
5651
|
|
|
@@ -2927,8 +5666,8 @@ ${hosts}
|
|
|
2927
5666
|
`;
|
|
2928
5667
|
};
|
|
2929
5668
|
var configureAndroid = async (config) => {
|
|
2930
|
-
const path =
|
|
2931
|
-
const source = await
|
|
5669
|
+
const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
5670
|
+
const source = await readFile14(path, "utf8");
|
|
2932
5671
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
2933
5672
|
if (mainActivity === NOT_FOUND) {
|
|
2934
5673
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -2953,8 +5692,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
|
|
|
2953
5692
|
${END_MARKER}
|
|
2954
5693
|
`;
|
|
2955
5694
|
var configureIosInfo = async (config) => {
|
|
2956
|
-
const path =
|
|
2957
|
-
const source = await
|
|
5695
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
5696
|
+
const source = await readFile14(path, "utf8");
|
|
2958
5697
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
2959
5698
|
${END_MARKER}
|
|
2960
5699
|
`;
|
|
@@ -2962,7 +5701,7 @@ var configureIosInfo = async (config) => {
|
|
|
2962
5701
|
return writeChangedFile(path, updated);
|
|
2963
5702
|
};
|
|
2964
5703
|
var iosEntitlementsSource = (config) => {
|
|
2965
|
-
const domains = config.deepLinkHosts.map((
|
|
5704
|
+
const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
|
|
2966
5705
|
`);
|
|
2967
5706
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2968
5707
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -2977,10 +5716,10 @@ ${domains}
|
|
|
2977
5716
|
`;
|
|
2978
5717
|
};
|
|
2979
5718
|
var configureIosEntitlements = async (config) => {
|
|
2980
|
-
const path =
|
|
5719
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
2981
5720
|
let current = "";
|
|
2982
5721
|
try {
|
|
2983
|
-
current = await
|
|
5722
|
+
current = await readFile14(path, "utf8");
|
|
2984
5723
|
} catch (error) {
|
|
2985
5724
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
2986
5725
|
throw error;
|
|
@@ -2990,13 +5729,13 @@ var configureIosEntitlements = async (config) => {
|
|
|
2990
5729
|
if (current === source)
|
|
2991
5730
|
return false;
|
|
2992
5731
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
2993
|
-
await
|
|
2994
|
-
await
|
|
5732
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5733
|
+
await rename11(temporary, path);
|
|
2995
5734
|
return true;
|
|
2996
5735
|
};
|
|
2997
5736
|
var configureIosProject = async (config) => {
|
|
2998
|
-
const path =
|
|
2999
|
-
const source = await
|
|
5737
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
5738
|
+
const source = await readFile14(path, "utf8");
|
|
3000
5739
|
const declarations = [
|
|
3001
5740
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
3002
5741
|
].map((match) => match[1]);
|
|
@@ -3033,9 +5772,93 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
|
|
|
3033
5772
|
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
3034
5773
|
};
|
|
3035
5774
|
};
|
|
5775
|
+
// src/mobile/nativeDeviceCapabilities.ts
|
|
5776
|
+
import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
|
|
5777
|
+
import { join as join16 } from "path";
|
|
5778
|
+
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
|
|
5779
|
+
var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
|
|
5780
|
+
var NOT_FOUND2 = -1;
|
|
5781
|
+
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5782
|
+
var writeChangedFile2 = async (path, source) => {
|
|
5783
|
+
const current = await readFile15(path, "utf8");
|
|
5784
|
+
if (current === source)
|
|
5785
|
+
return false;
|
|
5786
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
5787
|
+
await writeFile13(temporary, source, { flag: "wx" });
|
|
5788
|
+
await rename12(temporary, path);
|
|
5789
|
+
return true;
|
|
5790
|
+
};
|
|
5791
|
+
var managed = (source, region, insertion) => {
|
|
5792
|
+
const start = source.indexOf(START_MARKER2);
|
|
5793
|
+
const end = source.indexOf(END_MARKER2);
|
|
5794
|
+
if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
|
|
5795
|
+
throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
|
|
5796
|
+
if (start !== NOT_FOUND2) {
|
|
5797
|
+
const lineStart = source.lastIndexOf(`
|
|
5798
|
+
`, start) + 1;
|
|
5799
|
+
const nextLine = source.indexOf(`
|
|
5800
|
+
`, end + END_MARKER2.length);
|
|
5801
|
+
const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
|
|
5802
|
+
return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
|
|
5803
|
+
}
|
|
5804
|
+
if (region.length === 0)
|
|
5805
|
+
return source;
|
|
5806
|
+
if (insertion === NOT_FOUND2)
|
|
5807
|
+
throw new TypeError("Could not find a safe native project location for device permissions.");
|
|
5808
|
+
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
5809
|
+
};
|
|
5810
|
+
var IOS_KEYS = {
|
|
5811
|
+
camera: "NSCameraUsageDescription",
|
|
5812
|
+
"photo-library": "NSPhotoLibraryUsageDescription",
|
|
5813
|
+
"photo-library-add": "NSPhotoLibraryAddUsageDescription"
|
|
5814
|
+
};
|
|
5815
|
+
var iosDescription = (appName, purpose) => {
|
|
5816
|
+
if (purpose === "camera")
|
|
5817
|
+
return `${appName} uses your camera when you choose to take a photo.`;
|
|
5818
|
+
if (purpose === "photo-library")
|
|
5819
|
+
return `${appName} accesses your photo library only for photo actions you choose.`;
|
|
5820
|
+
return `${appName} adds to your photo library only for photo actions you choose.`;
|
|
5821
|
+
};
|
|
5822
|
+
var configureIos2 = async (config, plan) => {
|
|
5823
|
+
const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
5824
|
+
const source = await readFile15(path, "utf8");
|
|
5825
|
+
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
5826
|
+
const content = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
|
|
5827
|
+
<string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
|
|
5828
|
+
`);
|
|
5829
|
+
const region = content ? ` ${START_MARKER2}
|
|
5830
|
+
${content}
|
|
5831
|
+
${END_MARKER2}
|
|
5832
|
+
` : "";
|
|
5833
|
+
return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
|
|
5834
|
+
};
|
|
5835
|
+
var configureAndroid2 = async (config, plan) => {
|
|
5836
|
+
const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
5837
|
+
const source = await readFile15(path, "utf8");
|
|
5838
|
+
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
5839
|
+
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
5840
|
+
`);
|
|
5841
|
+
const region = content ? ` ${START_MARKER2}
|
|
5842
|
+
${content}
|
|
5843
|
+
${END_MARKER2}
|
|
5844
|
+
` : "";
|
|
5845
|
+
const application = source.indexOf("<application");
|
|
5846
|
+
const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
|
|
5847
|
+
`, application) + 1;
|
|
5848
|
+
return writeChangedFile2(path, managed(source, region, insertion));
|
|
5849
|
+
};
|
|
5850
|
+
var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
|
|
5851
|
+
const results = await Promise.all(platforms.map(async (platform) => ({
|
|
5852
|
+
didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
|
|
5853
|
+
platform
|
|
5854
|
+
})));
|
|
5855
|
+
return {
|
|
5856
|
+
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
5857
|
+
};
|
|
5858
|
+
};
|
|
3036
5859
|
// src/mobile/releasePublisher.ts
|
|
3037
|
-
import { access as
|
|
3038
|
-
import { isAbsolute as
|
|
5860
|
+
import { access as access9 } from "fs/promises";
|
|
5861
|
+
import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
|
|
3039
5862
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
3040
5863
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
3041
5864
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -3058,24 +5881,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
3058
5881
|
}
|
|
3059
5882
|
return versionCode;
|
|
3060
5883
|
};
|
|
3061
|
-
var
|
|
3062
|
-
var isPublisher = (value) =>
|
|
5884
|
+
var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5885
|
+
var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
|
|
3063
5886
|
var publisherModulePath = (projectRoot, requested) => {
|
|
3064
|
-
const root =
|
|
3065
|
-
const path =
|
|
3066
|
-
const projectRelative =
|
|
3067
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
5887
|
+
const root = resolve13(projectRoot);
|
|
5888
|
+
const path = resolve13(root, requested);
|
|
5889
|
+
const projectRelative = relative10(root, path);
|
|
5890
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
|
|
3068
5891
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
3069
5892
|
}
|
|
3070
5893
|
return path;
|
|
3071
5894
|
};
|
|
3072
5895
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
3073
5896
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
3074
|
-
await
|
|
5897
|
+
await access9(modulePath).catch(() => {
|
|
3075
5898
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
3076
5899
|
});
|
|
3077
5900
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
3078
|
-
const publisher =
|
|
5901
|
+
const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
3079
5902
|
if (!isPublisher(publisher)) {
|
|
3080
5903
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
3081
5904
|
}
|
|
@@ -3132,67 +5955,112 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
3132
5955
|
return publication;
|
|
3133
5956
|
};
|
|
3134
5957
|
// src/mobile/routeMetadataTransform.ts
|
|
3135
|
-
import { existsSync as existsSync3, readFileSync as
|
|
3136
|
-
import { dirname as
|
|
3137
|
-
import
|
|
5958
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
5959
|
+
import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
|
|
5960
|
+
import ts2 from "typescript";
|
|
3138
5961
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
3139
5962
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
3140
|
-
var
|
|
5963
|
+
var PAGE_HANDLERS = new Map([
|
|
5964
|
+
[
|
|
5965
|
+
"handleHTMLPageRequest",
|
|
5966
|
+
{ framework: "html", inputKind: "static", propsProperty: "props" }
|
|
5967
|
+
],
|
|
5968
|
+
[
|
|
5969
|
+
"handleHTMXPageRequest",
|
|
5970
|
+
{ framework: "htmx", inputKind: "static", propsProperty: "props" }
|
|
5971
|
+
],
|
|
5972
|
+
[
|
|
5973
|
+
"handleAngularPageRequest",
|
|
5974
|
+
{
|
|
5975
|
+
bundleProperty: "indexPath",
|
|
5976
|
+
framework: "angular",
|
|
5977
|
+
propsProperty: "requestContext",
|
|
5978
|
+
sourceProperty: "pagePath"
|
|
5979
|
+
}
|
|
5980
|
+
],
|
|
5981
|
+
[
|
|
5982
|
+
"handleReactPageRequest",
|
|
5983
|
+
{
|
|
5984
|
+
bundleProperty: "index",
|
|
5985
|
+
framework: "react",
|
|
5986
|
+
pageProperty: "Page",
|
|
5987
|
+
propsProperty: "props"
|
|
5988
|
+
}
|
|
5989
|
+
],
|
|
5990
|
+
[
|
|
5991
|
+
"handleSveltePageRequest",
|
|
5992
|
+
{
|
|
5993
|
+
bundleProperty: "indexPath",
|
|
5994
|
+
framework: "svelte",
|
|
5995
|
+
propsProperty: "props",
|
|
5996
|
+
sourceProperty: "pagePath"
|
|
5997
|
+
}
|
|
5998
|
+
],
|
|
5999
|
+
[
|
|
6000
|
+
"handleVuePageRequest",
|
|
6001
|
+
{
|
|
6002
|
+
bundleProperty: "indexPath",
|
|
6003
|
+
framework: "vue",
|
|
6004
|
+
propsProperty: "props",
|
|
6005
|
+
sourceProperty: "pagePath"
|
|
6006
|
+
}
|
|
6007
|
+
]
|
|
6008
|
+
]);
|
|
3141
6009
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
3142
|
-
var findTsconfig = (entry, projectRoot) =>
|
|
6010
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
|
|
3143
6011
|
var createProgram = (entry, projectRoot) => {
|
|
3144
6012
|
const configPath = findTsconfig(entry, projectRoot);
|
|
3145
6013
|
if (!configPath) {
|
|
3146
|
-
return
|
|
6014
|
+
return ts2.createProgram([entry], {
|
|
3147
6015
|
allowJs: true,
|
|
3148
|
-
jsx:
|
|
3149
|
-
module:
|
|
3150
|
-
moduleResolution:
|
|
3151
|
-
target:
|
|
6016
|
+
jsx: ts2.JsxEmit.ReactJSX,
|
|
6017
|
+
module: ts2.ModuleKind.ESNext,
|
|
6018
|
+
moduleResolution: ts2.ModuleResolutionKind.Bundler,
|
|
6019
|
+
target: ts2.ScriptTarget.ESNext
|
|
3152
6020
|
});
|
|
3153
6021
|
}
|
|
3154
|
-
const parsed =
|
|
6022
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
|
|
3155
6023
|
if (!parsed.fileNames.includes(entry))
|
|
3156
6024
|
parsed.fileNames.push(entry);
|
|
3157
|
-
return
|
|
6025
|
+
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
3158
6026
|
};
|
|
3159
6027
|
var propertyName = (property) => {
|
|
3160
6028
|
if (!("name" in property) || !property.name)
|
|
3161
6029
|
return;
|
|
3162
|
-
if (
|
|
6030
|
+
if (ts2.isIdentifier(property.name))
|
|
3163
6031
|
return property.name.text;
|
|
3164
|
-
if (
|
|
6032
|
+
if (ts2.isStringLiteralLike(property.name))
|
|
3165
6033
|
return property.name.text;
|
|
3166
6034
|
return;
|
|
3167
6035
|
};
|
|
3168
|
-
var objectPropertyExpression = (
|
|
3169
|
-
const property =
|
|
3170
|
-
if (property &&
|
|
6036
|
+
var objectPropertyExpression = (object3, name) => {
|
|
6037
|
+
const property = object3.properties.find((candidate) => propertyName(candidate) === name);
|
|
6038
|
+
if (property && ts2.isPropertyAssignment(property)) {
|
|
3171
6039
|
return property.initializer;
|
|
3172
6040
|
}
|
|
3173
|
-
if (property &&
|
|
6041
|
+
if (property && ts2.isShorthandPropertyAssignment(property)) {
|
|
3174
6042
|
return property.name;
|
|
3175
6043
|
}
|
|
3176
6044
|
return;
|
|
3177
6045
|
};
|
|
3178
6046
|
var serializeType = (type, checker, ancestors = new Set) => {
|
|
3179
|
-
if (type.flags &
|
|
6047
|
+
if (type.flags & ts2.TypeFlags.Any)
|
|
3180
6048
|
return { type: "any" };
|
|
3181
|
-
if (type.flags &
|
|
6049
|
+
if (type.flags & ts2.TypeFlags.Unknown)
|
|
3182
6050
|
return { type: "unknown" };
|
|
3183
|
-
if (type.flags &
|
|
6051
|
+
if (type.flags & ts2.TypeFlags.Never)
|
|
3184
6052
|
return { type: "never" };
|
|
3185
|
-
if (type.flags &
|
|
6053
|
+
if (type.flags & ts2.TypeFlags.StringLike)
|
|
3186
6054
|
return { type: "string" };
|
|
3187
|
-
if (type.flags &
|
|
6055
|
+
if (type.flags & ts2.TypeFlags.NumberLike)
|
|
3188
6056
|
return { type: "number" };
|
|
3189
|
-
if (type.flags &
|
|
6057
|
+
if (type.flags & ts2.TypeFlags.BooleanLike)
|
|
3190
6058
|
return { type: "boolean" };
|
|
3191
|
-
if (type.flags &
|
|
6059
|
+
if (type.flags & ts2.TypeFlags.BigIntLike)
|
|
3192
6060
|
return { type: "bigint" };
|
|
3193
|
-
if (type.flags &
|
|
6061
|
+
if (type.flags & ts2.TypeFlags.Null)
|
|
3194
6062
|
return { type: "null" };
|
|
3195
|
-
if (type.flags &
|
|
6063
|
+
if (type.flags & ts2.TypeFlags.Undefined)
|
|
3196
6064
|
return { type: "undefined" };
|
|
3197
6065
|
if (type.isUnion()) {
|
|
3198
6066
|
return {
|
|
@@ -3206,11 +6074,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3206
6074
|
}
|
|
3207
6075
|
if (ancestors.has(type)) {
|
|
3208
6076
|
return {
|
|
3209
|
-
ref: checker.typeToString(type, undefined,
|
|
6077
|
+
ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
3210
6078
|
};
|
|
3211
6079
|
}
|
|
3212
6080
|
ancestors.add(type);
|
|
3213
|
-
const arrayElement = checker.getIndexTypeOfType(type,
|
|
6081
|
+
const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
|
|
3214
6082
|
const properties = checker.getPropertiesOfType(type);
|
|
3215
6083
|
let schema;
|
|
3216
6084
|
if (arrayElement && properties.some(({ name }) => name === "length")) {
|
|
@@ -3225,7 +6093,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3225
6093
|
return [
|
|
3226
6094
|
property.name,
|
|
3227
6095
|
{
|
|
3228
|
-
optional: Boolean(property.flags &
|
|
6096
|
+
optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
|
|
3229
6097
|
schema: serializeType(propertyType, checker, ancestors)
|
|
3230
6098
|
}
|
|
3231
6099
|
];
|
|
@@ -3233,7 +6101,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
3233
6101
|
schema = { properties: Object.fromEntries(entries), type: "object" };
|
|
3234
6102
|
} else {
|
|
3235
6103
|
schema = {
|
|
3236
|
-
type: checker.typeToString(type, undefined,
|
|
6104
|
+
type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
3237
6105
|
};
|
|
3238
6106
|
}
|
|
3239
6107
|
ancestors.delete(type);
|
|
@@ -3251,25 +6119,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
|
|
|
3251
6119
|
};
|
|
3252
6120
|
var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
|
|
3253
6121
|
let symbol = checker.getSymbolAtLocation(expression);
|
|
3254
|
-
if (symbol?.flags && symbol.flags &
|
|
6122
|
+
if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
|
|
3255
6123
|
symbol = checker.getAliasedSymbol(symbol);
|
|
3256
6124
|
}
|
|
3257
6125
|
const declaration = symbol?.declarations?.[0];
|
|
3258
6126
|
const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
|
|
3259
6127
|
const exportedName = symbol?.name ?? expression.getText(sourceFile);
|
|
3260
|
-
const source = posixPath(
|
|
6128
|
+
const source = posixPath(relative11(projectRoot, file));
|
|
3261
6129
|
return `${source}#${exportedName}`;
|
|
3262
6130
|
};
|
|
3263
6131
|
var resolveAlias = (symbol, checker) => {
|
|
3264
|
-
if (!(symbol.flags &
|
|
6132
|
+
if (!(symbol.flags & ts2.SymbolFlags.Alias))
|
|
3265
6133
|
return symbol;
|
|
3266
6134
|
return checker.getAliasedSymbol(symbol);
|
|
3267
6135
|
};
|
|
3268
6136
|
var assetKey = (expression, checker, seen = new Set) => {
|
|
3269
6137
|
if (!expression)
|
|
3270
6138
|
return;
|
|
3271
|
-
if (
|
|
3272
|
-
const unresolved =
|
|
6139
|
+
if (ts2.isIdentifier(expression)) {
|
|
6140
|
+
const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
|
|
3273
6141
|
if (!unresolved)
|
|
3274
6142
|
return;
|
|
3275
6143
|
const symbol = resolveAlias(unresolved, checker);
|
|
@@ -3277,28 +6145,123 @@ var assetKey = (expression, checker, seen = new Set) => {
|
|
|
3277
6145
|
return;
|
|
3278
6146
|
seen.add(symbol);
|
|
3279
6147
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
3280
|
-
if (!declaration || !
|
|
6148
|
+
if (!declaration || !ts2.isVariableDeclaration(declaration))
|
|
3281
6149
|
return;
|
|
3282
6150
|
return assetKey(declaration.initializer, checker, seen);
|
|
3283
6151
|
}
|
|
3284
|
-
if (!
|
|
6152
|
+
if (!ts2.isCallExpression(expression))
|
|
3285
6153
|
return;
|
|
3286
|
-
if (!
|
|
6154
|
+
if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
|
|
3287
6155
|
return;
|
|
3288
6156
|
}
|
|
3289
6157
|
const [, key] = expression.arguments;
|
|
3290
|
-
return key &&
|
|
6158
|
+
return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
|
|
6159
|
+
};
|
|
6160
|
+
var staticString = (expression, bindings) => {
|
|
6161
|
+
if (ts2.isStringLiteralLike(expression))
|
|
6162
|
+
return expression.text;
|
|
6163
|
+
if (ts2.isIdentifier(expression))
|
|
6164
|
+
return bindings.get(expression.text);
|
|
6165
|
+
if (ts2.isNoSubstitutionTemplateLiteral(expression))
|
|
6166
|
+
return expression.text;
|
|
6167
|
+
if (!ts2.isTemplateExpression(expression))
|
|
6168
|
+
return;
|
|
6169
|
+
let value = expression.head.text;
|
|
6170
|
+
for (const span of expression.templateSpans) {
|
|
6171
|
+
const substitution = staticString(span.expression, bindings);
|
|
6172
|
+
if (substitution === undefined)
|
|
6173
|
+
return;
|
|
6174
|
+
value += substitution + span.literal.text;
|
|
6175
|
+
}
|
|
6176
|
+
return value;
|
|
6177
|
+
};
|
|
6178
|
+
var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
|
|
6179
|
+
if (!expression)
|
|
6180
|
+
return;
|
|
6181
|
+
if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
|
|
6182
|
+
const [, key] = expression.arguments;
|
|
6183
|
+
return key ? staticString(key, bindings) : undefined;
|
|
6184
|
+
}
|
|
6185
|
+
return assetKey(expression, checker);
|
|
6186
|
+
};
|
|
6187
|
+
var callableObject = (call, checker) => {
|
|
6188
|
+
const symbol = checker.getSymbolAtLocation(call.expression);
|
|
6189
|
+
const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
|
|
6190
|
+
const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
|
|
6191
|
+
let callable;
|
|
6192
|
+
if (declaration && ts2.isFunctionDeclaration(declaration)) {
|
|
6193
|
+
callable = declaration;
|
|
6194
|
+
} else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
|
|
6195
|
+
callable = declaration.initializer;
|
|
6196
|
+
}
|
|
6197
|
+
if (!callable)
|
|
6198
|
+
return;
|
|
6199
|
+
const bindings = new Map;
|
|
6200
|
+
callable.parameters.forEach((parameter, index) => {
|
|
6201
|
+
if (!ts2.isIdentifier(parameter.name))
|
|
6202
|
+
return;
|
|
6203
|
+
const argument = call.arguments[index];
|
|
6204
|
+
if (!argument)
|
|
6205
|
+
return;
|
|
6206
|
+
const value = staticString(argument, new Map);
|
|
6207
|
+
if (value !== undefined)
|
|
6208
|
+
bindings.set(parameter.name.text, value);
|
|
6209
|
+
});
|
|
6210
|
+
const { body } = callable;
|
|
6211
|
+
if (!body)
|
|
6212
|
+
return;
|
|
6213
|
+
const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
|
|
6214
|
+
if (ts2.isObjectLiteralExpression(expressionBody)) {
|
|
6215
|
+
return { bindings, object: expressionBody };
|
|
6216
|
+
}
|
|
6217
|
+
if (ts2.isBlock(body)) {
|
|
6218
|
+
const returned = body.statements.find(ts2.isReturnStatement)?.expression;
|
|
6219
|
+
if (returned && ts2.isObjectLiteralExpression(returned)) {
|
|
6220
|
+
return { bindings, object: returned };
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
return;
|
|
6224
|
+
};
|
|
6225
|
+
var spreadObject = (expression, checker, bindings) => {
|
|
6226
|
+
if (ts2.isObjectLiteralExpression(expression)) {
|
|
6227
|
+
return { bindings, object: expression };
|
|
6228
|
+
}
|
|
6229
|
+
if (!ts2.isCallExpression(expression))
|
|
6230
|
+
return;
|
|
6231
|
+
return callableObject(expression, checker);
|
|
6232
|
+
};
|
|
6233
|
+
var objectAssetKey = (object3, name, checker, bindings = new Map) => {
|
|
6234
|
+
for (const property of [...object3.properties].reverse()) {
|
|
6235
|
+
if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
|
|
6236
|
+
return assetKeyWithBindings(property.name, checker, bindings);
|
|
6237
|
+
}
|
|
6238
|
+
if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
|
|
6239
|
+
return assetKeyWithBindings(property.initializer, checker, bindings);
|
|
6240
|
+
}
|
|
6241
|
+
if (!ts2.isSpreadAssignment(property))
|
|
6242
|
+
continue;
|
|
6243
|
+
const nestedObject = spreadObject(property.expression, checker, bindings);
|
|
6244
|
+
if (!nestedObject)
|
|
6245
|
+
continue;
|
|
6246
|
+
const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
|
|
6247
|
+
if (nested)
|
|
6248
|
+
return nested;
|
|
6249
|
+
}
|
|
6250
|
+
return;
|
|
3291
6251
|
};
|
|
3292
6252
|
var findPageCall = (nodes) => {
|
|
3293
6253
|
let found;
|
|
3294
6254
|
const visit = (candidate) => {
|
|
3295
6255
|
if (found)
|
|
3296
6256
|
return;
|
|
3297
|
-
if (
|
|
3298
|
-
|
|
6257
|
+
if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
|
|
6258
|
+
const definition = PAGE_HANDLERS.get(candidate.expression.text);
|
|
6259
|
+
if (!definition)
|
|
6260
|
+
return;
|
|
6261
|
+
found = { definition, node: candidate };
|
|
3299
6262
|
return;
|
|
3300
6263
|
}
|
|
3301
|
-
|
|
6264
|
+
ts2.forEachChild(candidate, visit);
|
|
3302
6265
|
};
|
|
3303
6266
|
for (const node of nodes)
|
|
3304
6267
|
visit(node);
|
|
@@ -3307,37 +6270,74 @@ var findPageCall = (nodes) => {
|
|
|
3307
6270
|
var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
|
|
3308
6271
|
var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
|
|
3309
6272
|
const callee = node.expression;
|
|
3310
|
-
if (!
|
|
6273
|
+
if (!ts2.isPropertyAccessExpression(callee))
|
|
3311
6274
|
return;
|
|
3312
6275
|
if (!ROUTE_METHODS.has(callee.name.text))
|
|
3313
6276
|
return;
|
|
3314
6277
|
const [routePath] = node.arguments;
|
|
3315
|
-
if (!routePath || !
|
|
6278
|
+
if (!routePath || !ts2.isStringLiteralLike(routePath))
|
|
3316
6279
|
return;
|
|
3317
|
-
const
|
|
6280
|
+
const foundPageCall = findPageCall(node.arguments.slice(1));
|
|
6281
|
+
const pageCall = foundPageCall?.node;
|
|
6282
|
+
const definition = foundPageCall?.definition;
|
|
3318
6283
|
const [input] = pageCall?.arguments ?? [];
|
|
3319
|
-
if (!pageCall || !input
|
|
6284
|
+
if (!pageCall || !input) {
|
|
3320
6285
|
return;
|
|
3321
6286
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
6287
|
+
if (!definition)
|
|
6288
|
+
return;
|
|
6289
|
+
if (definition.inputKind === "static") {
|
|
6290
|
+
const bundleKey2 = assetKey(input, checker);
|
|
6291
|
+
if (!bundleKey2)
|
|
6292
|
+
return;
|
|
6293
|
+
const pageId2 = `${definition.framework}:${bundleKey2}`;
|
|
6294
|
+
const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
|
|
6295
|
+
properties: {},
|
|
6296
|
+
type: "object"
|
|
6297
|
+
});
|
|
6298
|
+
return {
|
|
6299
|
+
inputKind: "static",
|
|
6300
|
+
metadata: {
|
|
6301
|
+
bundleKey: bundleKey2,
|
|
6302
|
+
contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
|
|
6303
|
+
framework: definition.framework,
|
|
6304
|
+
pageId: pageId2,
|
|
6305
|
+
propsSchemaHash: propsSchemaHash2
|
|
6306
|
+
},
|
|
6307
|
+
pageCallStart: pageCall.getStart(sourceFile),
|
|
6308
|
+
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
6309
|
+
};
|
|
6310
|
+
}
|
|
6311
|
+
if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
|
|
6312
|
+
return;
|
|
6313
|
+
}
|
|
6314
|
+
const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
|
|
6315
|
+
const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
|
|
6316
|
+
if (definition.pageProperty && !page)
|
|
6317
|
+
return;
|
|
6318
|
+
if (definition.sourceProperty && !source)
|
|
3324
6319
|
return;
|
|
3325
|
-
const props = objectPropertyExpression(input,
|
|
3326
|
-
const
|
|
3327
|
-
const bundleKey = assetKey(index, checker);
|
|
6320
|
+
const props = objectPropertyExpression(input, definition.propsProperty);
|
|
6321
|
+
const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
|
|
3328
6322
|
if (!bundleKey)
|
|
3329
6323
|
return;
|
|
3330
|
-
const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot)
|
|
3331
|
-
|
|
6324
|
+
const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
|
|
6325
|
+
let propsType;
|
|
6326
|
+
if (page)
|
|
6327
|
+
propsType = pagePropsType(page, props, checker);
|
|
6328
|
+
else if (props)
|
|
6329
|
+
propsType = checker.getTypeAtLocation(props);
|
|
6330
|
+
const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
|
|
3332
6331
|
const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
|
|
3333
6332
|
const metadata = {
|
|
3334
6333
|
bundleKey,
|
|
3335
|
-
contract:
|
|
3336
|
-
framework:
|
|
6334
|
+
contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
|
|
6335
|
+
framework: definition.framework,
|
|
3337
6336
|
pageId,
|
|
3338
6337
|
propsSchemaHash
|
|
3339
6338
|
};
|
|
3340
6339
|
const result = {
|
|
6340
|
+
inputKind: "object",
|
|
3341
6341
|
metadata,
|
|
3342
6342
|
pageCallStart: pageCall.getStart(sourceFile),
|
|
3343
6343
|
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
@@ -3350,21 +6350,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
|
|
|
3350
6350
|
byRouteCall: new Map
|
|
3351
6351
|
};
|
|
3352
6352
|
const visit = (node) => {
|
|
3353
|
-
const result =
|
|
6353
|
+
const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
|
|
3354
6354
|
if (result) {
|
|
3355
6355
|
analysis.byPageCall.set(result.pageCallStart, result);
|
|
3356
6356
|
analysis.byRouteCall.set(result.routeCallSpan, result);
|
|
3357
6357
|
}
|
|
3358
|
-
|
|
6358
|
+
ts2.forEachChild(node, visit);
|
|
3359
6359
|
};
|
|
3360
|
-
|
|
6360
|
+
ts2.forEachChild(sourceFile, visit);
|
|
3361
6361
|
return analysis;
|
|
3362
6362
|
};
|
|
3363
6363
|
var analyzeProgram = (program, projectRoot) => {
|
|
3364
6364
|
const checker = program.getTypeChecker();
|
|
3365
6365
|
const analyzed = new Map;
|
|
3366
6366
|
for (const sourceFile of program.getSourceFiles()) {
|
|
3367
|
-
const resolvedFile =
|
|
6367
|
+
const resolvedFile = resolve14(sourceFile.fileName);
|
|
3368
6368
|
if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
|
|
3369
6369
|
continue;
|
|
3370
6370
|
const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
|
|
@@ -3373,34 +6373,44 @@ var analyzeProgram = (program, projectRoot) => {
|
|
|
3373
6373
|
}
|
|
3374
6374
|
return analyzed;
|
|
3375
6375
|
};
|
|
3376
|
-
var metadataExpression = (metadata) =>
|
|
6376
|
+
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
6377
|
var routeOptions = (existing, metadata) => {
|
|
3378
|
-
const detail =
|
|
3379
|
-
|
|
6378
|
+
const detail = ts2.factory.createObjectLiteralExpression([
|
|
6379
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
3380
6380
|
]);
|
|
3381
6381
|
if (!existing) {
|
|
3382
|
-
return
|
|
3383
|
-
|
|
6382
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6383
|
+
ts2.factory.createPropertyAssignment("detail", detail)
|
|
3384
6384
|
]);
|
|
3385
6385
|
}
|
|
3386
|
-
return
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
6386
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6387
|
+
ts2.factory.createSpreadAssignment(existing),
|
|
6388
|
+
ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
|
|
6389
|
+
ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
|
|
6390
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
3391
6391
|
]))
|
|
3392
6392
|
]);
|
|
3393
6393
|
};
|
|
3394
6394
|
var transformPageCall = (node, page) => {
|
|
3395
6395
|
if (!page)
|
|
3396
6396
|
return;
|
|
6397
|
+
if (page.inputKind === "static") {
|
|
6398
|
+
const [pagePath, existingOptions, ...rest] = node.arguments;
|
|
6399
|
+
if (!pagePath)
|
|
6400
|
+
return;
|
|
6401
|
+
const options = ts2.factory.createObjectLiteralExpression([
|
|
6402
|
+
...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
|
|
6403
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
6404
|
+
]);
|
|
6405
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
|
|
6406
|
+
}
|
|
3397
6407
|
const [input] = node.arguments;
|
|
3398
|
-
if (!input || !
|
|
6408
|
+
if (!input || !ts2.isObjectLiteralExpression(input))
|
|
3399
6409
|
return;
|
|
3400
|
-
return
|
|
3401
|
-
|
|
6410
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
|
|
6411
|
+
ts2.factory.updateObjectLiteralExpression(input, [
|
|
3402
6412
|
...input.properties,
|
|
3403
|
-
|
|
6413
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
3404
6414
|
]),
|
|
3405
6415
|
...node.arguments.slice(1)
|
|
3406
6416
|
]);
|
|
@@ -3413,16 +6423,16 @@ var transformRouteCall = (node, route) => {
|
|
|
3413
6423
|
return;
|
|
3414
6424
|
const options = maybeHandler ? maybeOptions : undefined;
|
|
3415
6425
|
const handler = maybeHandler ?? maybeOptions;
|
|
3416
|
-
return
|
|
6426
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
|
|
3417
6427
|
};
|
|
3418
6428
|
var transformFile = (source, fileName, analysis) => {
|
|
3419
|
-
const sourceFile =
|
|
6429
|
+
const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
|
|
3420
6430
|
const transformer = (context) => {
|
|
3421
6431
|
const visit = (node) => {
|
|
3422
|
-
if (!
|
|
3423
|
-
return
|
|
6432
|
+
if (!ts2.isCallExpression(node)) {
|
|
6433
|
+
return ts2.visitEachChild(node, visit, context);
|
|
3424
6434
|
}
|
|
3425
|
-
const transformedChildren =
|
|
6435
|
+
const transformedChildren = ts2.visitEachChild(node, visit, context);
|
|
3426
6436
|
const page = analysis.byPageCall.get(node.getStart(sourceFile));
|
|
3427
6437
|
const transformedPage = transformPageCall(transformedChildren, page);
|
|
3428
6438
|
if (transformedPage)
|
|
@@ -3433,81 +6443,120 @@ var transformFile = (source, fileName, analysis) => {
|
|
|
3433
6443
|
return transformedRoute;
|
|
3434
6444
|
return transformedChildren;
|
|
3435
6445
|
};
|
|
3436
|
-
return (node) =>
|
|
6446
|
+
return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
|
|
3437
6447
|
};
|
|
3438
|
-
const result =
|
|
6448
|
+
const result = ts2.transform(sourceFile, [transformer]);
|
|
3439
6449
|
try {
|
|
3440
6450
|
const [transformed] = result.transformed;
|
|
3441
6451
|
if (!transformed)
|
|
3442
6452
|
throw new TypeError("Mobile route transform failed.");
|
|
3443
|
-
return
|
|
6453
|
+
return ts2.createPrinter().printFile(transformed);
|
|
3444
6454
|
} finally {
|
|
3445
6455
|
result.dispose();
|
|
3446
6456
|
}
|
|
3447
6457
|
};
|
|
3448
6458
|
var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
|
|
3449
6459
|
var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
3450
|
-
const projectRoot =
|
|
3451
|
-
const entry =
|
|
6460
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6461
|
+
const entry = resolve14(options.entry);
|
|
3452
6462
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
3453
6463
|
return {
|
|
3454
6464
|
name: "absolute-mobile-route-metadata",
|
|
3455
6465
|
setup(build) {
|
|
3456
6466
|
build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
|
|
3457
|
-
const analysis = analyzed.get(
|
|
6467
|
+
const analysis = analyzed.get(resolve14(path));
|
|
3458
6468
|
if (!analysis)
|
|
3459
6469
|
return;
|
|
3460
6470
|
const source = await Bun.file(path).text();
|
|
3461
6471
|
return {
|
|
3462
6472
|
contents: transformFile(source, path, analysis),
|
|
3463
|
-
loader:
|
|
6473
|
+
loader: extname4(path).endsWith("x") ? "tsx" : "ts"
|
|
3464
6474
|
};
|
|
3465
6475
|
});
|
|
3466
6476
|
}
|
|
3467
6477
|
};
|
|
3468
6478
|
};
|
|
3469
6479
|
var inspectAbsoluteMobileRouteMetadata = (options) => {
|
|
3470
|
-
const projectRoot =
|
|
3471
|
-
const entry =
|
|
6480
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6481
|
+
const entry = resolve14(options.entry);
|
|
3472
6482
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
3473
6483
|
return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
|
|
3474
|
-
file: posixPath(
|
|
6484
|
+
file: posixPath(relative11(projectRoot, file)),
|
|
3475
6485
|
metadata
|
|
3476
6486
|
})));
|
|
3477
6487
|
};
|
|
3478
6488
|
export {
|
|
3479
6489
|
writeAbsoluteCapacitorConfig,
|
|
6490
|
+
waitForAbsoluteIosHmrLog,
|
|
3480
6491
|
verifyAbsoluteMobileCompatibilityProducer,
|
|
3481
6492
|
verifyAbsoluteMobileAssociationFiles,
|
|
6493
|
+
validateAbsoluteSshDestination,
|
|
6494
|
+
validateAbsoluteRemoteMacProfileName,
|
|
6495
|
+
syncAbsoluteRemoteMacProject,
|
|
6496
|
+
startAbsoluteRemoteIosDevSession,
|
|
6497
|
+
startAbsoluteIosDevSession,
|
|
6498
|
+
serializeAbsoluteMobileAuthEnvironment,
|
|
3482
6499
|
runWithAbsoluteMobileProducer,
|
|
3483
6500
|
retainAbsoluteMobileCompatibilityArtifacts,
|
|
3484
6501
|
resolveAbsoluteMobileRoute,
|
|
6502
|
+
resolveAbsoluteMobileNavigation,
|
|
3485
6503
|
resolveAbsoluteMobileDeepLink,
|
|
3486
6504
|
resolveAbsoluteMobileCompatibilityRelease,
|
|
6505
|
+
resolveAbsoluteMobileAuthManifest,
|
|
6506
|
+
resolveAbsoluteDeviceCapabilityPlan,
|
|
6507
|
+
repairAbsoluteIosDevSession,
|
|
6508
|
+
removeAbsoluteRemoteMacProfile,
|
|
6509
|
+
redactAbsoluteIosLog,
|
|
3487
6510
|
readAbsoluteMobileMaterializedReleases,
|
|
3488
6511
|
publishAbsoluteIosRelease,
|
|
3489
6512
|
publishAbsoluteAndroidRelease,
|
|
6513
|
+
projectUsesAbsoluteSync,
|
|
6514
|
+
projectUsesAbsoluteAuth,
|
|
3490
6515
|
prepareAbsoluteIosRelease,
|
|
6516
|
+
prepareAbsoluteIosDevProject,
|
|
3491
6517
|
prepareAbsoluteAndroidRelease,
|
|
6518
|
+
parseIosSimulators,
|
|
6519
|
+
parseIosRuntimes,
|
|
6520
|
+
parseIosDeviceTypes,
|
|
3492
6521
|
parseAbsoluteMobilePageRequest,
|
|
3493
6522
|
parseAbsoluteMobilePageEnvelope,
|
|
3494
6523
|
parseAbsoluteMobileCompatibilityArtifact,
|
|
3495
6524
|
parseAbsoluteMobileBuildPageMetadata,
|
|
6525
|
+
parseAbsoluteIosLogLine,
|
|
6526
|
+
parseAbsoluteIosHmrLog,
|
|
6527
|
+
pairAbsoluteRemoteMac,
|
|
3496
6528
|
normalizeAbsoluteMobileConfig,
|
|
3497
6529
|
navigateAbsoluteMobilePage,
|
|
6530
|
+
missingAbsoluteDeviceCapabilityPackages,
|
|
6531
|
+
materializeAbsoluteRemoteMacAgent,
|
|
3498
6532
|
materializeAbsoluteMobileCompatibilityBundle,
|
|
3499
6533
|
materializeAbsoluteMobileAssociationFiles,
|
|
3500
6534
|
materializeAbsoluteCapacitorWebBundle,
|
|
3501
6535
|
matchesAbsoluteMobileRoutePattern,
|
|
3502
6536
|
loadAbsoluteNativeReleasePublisher,
|
|
3503
6537
|
loadAbsoluteMobileMaterializedBundle,
|
|
6538
|
+
loadAbsoluteDeviceCapabilityProviders,
|
|
6539
|
+
listAbsoluteRemoteMacProfiles,
|
|
6540
|
+
isAbsoluteIosNativeRootInput,
|
|
6541
|
+
installAbsoluteRemoteMacAgent,
|
|
6542
|
+
installAbsoluteMobileSyncRemediation,
|
|
6543
|
+
installAbsoluteMobileAuthEnvironment,
|
|
6544
|
+
inspectAbsoluteRemoteMac,
|
|
3504
6545
|
inspectAbsoluteMobileRouteMetadata,
|
|
3505
6546
|
hashAbsoluteMobilePropsSchema,
|
|
3506
6547
|
getCurrentAbsoluteMobileProducerContext,
|
|
6548
|
+
getAbsoluteRemoteMacProfile,
|
|
6549
|
+
getAbsoluteMobileSyncRemediation,
|
|
3507
6550
|
fingerprintAbsoluteIosNativeProject,
|
|
6551
|
+
fingerprintAbsoluteIosDevProject,
|
|
3508
6552
|
finalizeAbsoluteMobilePage,
|
|
3509
6553
|
finalizeAbsoluteMobileCompatibilityBuild,
|
|
3510
6554
|
fetchAbsoluteMobilePage,
|
|
6555
|
+
disposeAbsoluteMobilePage,
|
|
6556
|
+
discoverAbsoluteSyncSchema,
|
|
6557
|
+
discoverAbsoluteDeviceCapabilities,
|
|
6558
|
+
directAbsoluteProjectPackages,
|
|
6559
|
+
createAbsoluteRemoteIosDevProject,
|
|
3511
6560
|
createAbsoluteMobileUpgradeResponse,
|
|
3512
6561
|
createAbsoluteMobileRouteMetadataPlugin,
|
|
3513
6562
|
createAbsoluteMobilePageRequest,
|
|
@@ -3517,20 +6566,32 @@ export {
|
|
|
3517
6566
|
createAbsoluteMobileCompatibilityDispatcher,
|
|
3518
6567
|
createAbsoluteMobileCompatibilityArtifact,
|
|
3519
6568
|
createAbsoluteMobileBlobArtifactStore,
|
|
6569
|
+
createAbsoluteMobileAuthManifest,
|
|
3520
6570
|
createAbsoluteMobileAssociationPlugin,
|
|
3521
6571
|
createAbsoluteMobileAssociationDocuments,
|
|
6572
|
+
createAbsoluteIosNativeWatcher,
|
|
3522
6573
|
carryForwardAbsoluteMobileCompatibilityReleases,
|
|
3523
6574
|
captureAbsoluteMobileRouteGraph,
|
|
3524
6575
|
buildAbsoluteMobileCompatibilityRelease,
|
|
3525
6576
|
buildAbsoluteIosRelease,
|
|
3526
6577
|
buildAbsoluteAndroidRelease,
|
|
6578
|
+
assertAbsoluteDeviceCapabilityPackages,
|
|
6579
|
+
applyAbsoluteNativeDeviceCapabilities,
|
|
3527
6580
|
applyAbsoluteNativeDeepLinks,
|
|
3528
6581
|
activateAbsoluteMobilePage,
|
|
3529
6582
|
acceptsAbsoluteMobilePage,
|
|
6583
|
+
absoluteRemoteProjectSyncCommands,
|
|
6584
|
+
absoluteRemoteMacSshBase,
|
|
6585
|
+
absoluteDeviceNativeRequirements,
|
|
3530
6586
|
MOBILE_PAGE_REQUEST_HEADERS,
|
|
3531
6587
|
AbsoluteMobilePageProtocolError,
|
|
3532
6588
|
APPLE_ASSOCIATION_PATH,
|
|
3533
6589
|
ANDROID_ASSOCIATION_PATH,
|
|
6590
|
+
ABSOLUTE_SYNC_PACKAGE,
|
|
6591
|
+
ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
|
|
6592
|
+
ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
|
|
6593
|
+
ABSOLUTE_NATIVE_AUTH_SCOPES,
|
|
6594
|
+
ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
|
|
3534
6595
|
ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
|
|
3535
6596
|
ABSOLUTE_MOBILE_ROUTE_DETAIL,
|
|
3536
6597
|
ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
|
|
@@ -3539,9 +6600,11 @@ export {
|
|
|
3539
6600
|
ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
|
|
3540
6601
|
ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
|
|
3541
6602
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
6603
|
+
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
3542
6604
|
ABSOLUTE_IOS_RELEASE_FORMAT,
|
|
6605
|
+
ABSOLUTE_AUTH_PACKAGE,
|
|
3543
6606
|
ABSOLUTE_ANDROID_RELEASE_FORMAT
|
|
3544
6607
|
};
|
|
3545
6608
|
|
|
3546
|
-
//# debugId=
|
|
6609
|
+
//# debugId=8F3479AD17C2968F64756E2164756E21
|
|
3547
6610
|
//# sourceMappingURL=index.js.map
|