@absolutejs/absolute 0.20.0-beta.3 → 0.20.0-beta.30
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 +167 -0
- package/dist/angular/browser.js +15 -1
- package/dist/angular/browser.js.map +3 -3
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/angular/index.js +341 -22
- package/dist/angular/index.js.map +8 -5
- package/dist/angular/server.js +341 -22
- package/dist/angular/server.js.map +8 -5
- package/dist/build.js +2058 -1105
- package/dist/build.js.map +17 -13
- package/dist/cli/index.js +4077 -1139
- package/dist/dev/client/cssUtils.ts +16 -2
- package/dist/dev/client/handlers/rebuild.ts +11 -1
- package/dist/dev/client/hmrClient.ts +32 -3
- package/dist/dev/client/hmrTiming.ts +14 -7
- package/dist/dev/client/syncDevtools.ts +237 -0
- package/dist/index.js +2483 -1367
- package/dist/index.js.map +27 -22
- package/dist/mobile/browser.js +123 -1
- package/dist/mobile/browser.js.map +6 -4
- package/dist/mobile/index.js +2663 -247
- package/dist/mobile/index.js.map +25 -13
- package/dist/mobile/remoteMacAgentEntry.js +29 -0
- package/dist/mobile/shellAuth.js +36 -0
- package/dist/mobile/shellBootstrap.js +601 -0
- package/dist/mobile/shellSync.js +173 -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/devBuild.d.ts +1 -0
- package/dist/src/core/pageHandlers.d.ts +11 -2
- package/dist/src/core/prepare.d.ts +24 -0
- package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
- package/dist/src/mobile/androidTestReport.d.ts +14 -0
- package/dist/src/mobile/androidUpgradeConformance.d.ts +47 -0
- package/dist/src/mobile/androidWebView.d.ts +1 -0
- 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/config.d.ts +1 -0
- package/dist/src/mobile/devDeviceAdapter.d.ts +3 -0
- package/dist/src/mobile/deviceCapabilities.d.ts +46 -0
- package/dist/src/mobile/index.d.ts +7 -0
- package/dist/src/mobile/iosTestReport.d.ts +23 -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/nativeTestReport.d.ts +85 -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 +15 -0
- package/dist/src/mobile/shellBootstrap.d.ts +22 -1
- package/dist/src/mobile/shellPush.d.ts +11 -0
- package/dist/src/mobile/shellSync.d.ts +47 -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/imageOptimizer.d.ts +1 -1
- package/dist/src/svelte/pageHandler.d.ts +3 -0
- package/dist/src/utils/imageProcessing.d.ts +3 -0
- package/dist/src/utils/loadConfig.d.ts +1 -0
- package/dist/src/vue/pageHandler.d.ts +3 -0
- package/dist/svelte/index.js +312 -23
- package/dist/svelte/index.js.map +7 -4
- package/dist/svelte/server.js +307 -18
- package/dist/svelte/server.js.map +7 -4
- package/dist/types/build.d.ts +29 -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 +37 -10
package/dist/mobile/index.js
CHANGED
|
@@ -159,6 +159,702 @@ 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, field2) => {
|
|
329
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
330
|
+
throw metadataError(id, `${field2} must be a positive safe integer.`);
|
|
331
|
+
return value;
|
|
332
|
+
}, nonEmpty = (value, id, field2) => {
|
|
333
|
+
if (typeof value !== "string" || value.trim() !== value || value.length === 0)
|
|
334
|
+
throw metadataError(id, `${field2} 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, field2) => {
|
|
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, field2));
|
|
347
|
+
if (object(value))
|
|
348
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
349
|
+
key,
|
|
350
|
+
normalizeJsonValue2(entry, id, field2)
|
|
351
|
+
]));
|
|
352
|
+
throw metadataError(id, `${field2} 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 field2 = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
|
|
367
|
+
if (type === "remove-field")
|
|
368
|
+
return { collection, field: field2, type };
|
|
369
|
+
if (type === "set-default")
|
|
370
|
+
return {
|
|
371
|
+
collection,
|
|
372
|
+
field: field2,
|
|
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
|
+
|
|
592
|
+
// src/mobile/deviceCapabilities.ts
|
|
593
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
594
|
+
import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
|
|
595
|
+
import ts from "typescript";
|
|
596
|
+
var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor", SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, CAPACITOR_MODULE_PATTERN, CAPACITOR_PACKAGE_PATTERN, ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
|
|
597
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
598
|
+
if (!object2(value))
|
|
599
|
+
throw new TypeError(`${path} must contain an object.`);
|
|
600
|
+
return value;
|
|
601
|
+
}, text = (value, field2) => {
|
|
602
|
+
if (typeof value !== "string" || value.length === 0)
|
|
603
|
+
throw new TypeError(`${field2} must be a non-empty string.`);
|
|
604
|
+
return value;
|
|
605
|
+
}, androidPermissions = (value, field2) => {
|
|
606
|
+
if (value === undefined)
|
|
607
|
+
return;
|
|
608
|
+
if (!object2(value))
|
|
609
|
+
throw new TypeError(`${field2} must be an object.`);
|
|
610
|
+
const { permissions } = value;
|
|
611
|
+
if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
|
|
612
|
+
throw new TypeError(`${field2}.permissions must contain Android permission names.`);
|
|
613
|
+
return [...permissions];
|
|
614
|
+
}, iosPrivacyAccessedApis = (value, field2) => {
|
|
615
|
+
if (value === undefined)
|
|
616
|
+
return;
|
|
617
|
+
if (!object2(value))
|
|
618
|
+
throw new TypeError(`${field2} must be an object.`);
|
|
619
|
+
const privacy = {};
|
|
620
|
+
for (const api of IOS_PRIVACY_ACCESSED_APIS) {
|
|
621
|
+
const reasons = value[api];
|
|
622
|
+
if (reasons === undefined)
|
|
623
|
+
continue;
|
|
624
|
+
const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
|
|
625
|
+
if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
|
|
626
|
+
throw new TypeError(`${field2} contains an unsupported API or reason.`);
|
|
627
|
+
privacy[api] = [...reasons];
|
|
628
|
+
}
|
|
629
|
+
if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
|
|
630
|
+
throw new TypeError(`${field2} contains an unsupported API or reason.`);
|
|
631
|
+
return privacy;
|
|
632
|
+
}, iosNativeRequirements = (value, field2) => {
|
|
633
|
+
if (value === undefined)
|
|
634
|
+
return;
|
|
635
|
+
if (!object2(value))
|
|
636
|
+
throw new TypeError(`${field2} must be an object.`);
|
|
637
|
+
const {
|
|
638
|
+
privacyAccessedApis,
|
|
639
|
+
pushNotifications,
|
|
640
|
+
systemBars,
|
|
641
|
+
usageDescriptions
|
|
642
|
+
} = value;
|
|
643
|
+
if (pushNotifications !== undefined && pushNotifications !== true)
|
|
644
|
+
throw new TypeError(`${field2}.pushNotifications must be true.`);
|
|
645
|
+
if (systemBars !== undefined && systemBars !== true)
|
|
646
|
+
throw new TypeError(`${field2}.systemBars must be true.`);
|
|
647
|
+
if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
|
|
648
|
+
throw new TypeError(`${field2}.usageDescriptions contains an unsupported purpose.`);
|
|
649
|
+
const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field2}.privacyAccessedApis`);
|
|
650
|
+
return {
|
|
651
|
+
...privacy === undefined ? {} : { privacyAccessedApis: privacy },
|
|
652
|
+
...pushNotifications === true ? { pushNotifications: true } : {},
|
|
653
|
+
...systemBars === true ? { systemBars: true } : {},
|
|
654
|
+
...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
|
|
655
|
+
};
|
|
656
|
+
}, parseProvider = (name, value) => {
|
|
657
|
+
if (!IDENTIFIER_PATTERN.test(name))
|
|
658
|
+
throw new TypeError("Device capability names must be identifiers.");
|
|
659
|
+
if (!object2(value))
|
|
660
|
+
throw new TypeError(`Device capability ${name} must be an object.`);
|
|
661
|
+
const factory = text(value.factory, `${name}.factory`);
|
|
662
|
+
const module = text(value.module, `${name}.module`);
|
|
663
|
+
if (!IDENTIFIER_PATTERN.test(factory))
|
|
664
|
+
throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
|
|
665
|
+
if (!CAPACITOR_MODULE_PATTERN.test(module))
|
|
666
|
+
throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
|
|
667
|
+
if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
|
|
668
|
+
throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
|
|
669
|
+
let native;
|
|
670
|
+
const { native: nativeMetadata } = value;
|
|
671
|
+
if (nativeMetadata !== undefined) {
|
|
672
|
+
if (!object2(nativeMetadata))
|
|
673
|
+
throw new TypeError(`${name}.native must be an object.`);
|
|
674
|
+
const { android, ios } = nativeMetadata;
|
|
675
|
+
const permissions = androidPermissions(android, `${name}.native.android`);
|
|
676
|
+
const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
|
|
677
|
+
native = {
|
|
678
|
+
...permissions === undefined ? {} : { android: { permissions } },
|
|
679
|
+
...iosRequirements === undefined ? {} : { ios: iosRequirements }
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
factory,
|
|
684
|
+
module,
|
|
685
|
+
...native === undefined ? {} : { native },
|
|
686
|
+
packages: [...value.packages]
|
|
687
|
+
};
|
|
688
|
+
}, absoluteDeviceNativeRequirements = (plan) => {
|
|
689
|
+
const privacy = plan.capabilities.reduce((requirements, name) => {
|
|
690
|
+
for (const api of IOS_PRIVACY_ACCESSED_APIS) {
|
|
691
|
+
const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
|
|
692
|
+
if (reasons.length === 0)
|
|
693
|
+
continue;
|
|
694
|
+
const current = requirements[api] ?? new Set;
|
|
695
|
+
for (const reason of reasons)
|
|
696
|
+
current.add(reason);
|
|
697
|
+
requirements[api] = current;
|
|
698
|
+
}
|
|
699
|
+
return requirements;
|
|
700
|
+
}, {});
|
|
701
|
+
return {
|
|
702
|
+
androidPermissions: [
|
|
703
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
|
|
704
|
+
].sort(),
|
|
705
|
+
iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
|
|
706
|
+
const reasons = privacy[api];
|
|
707
|
+
return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
|
|
708
|
+
}),
|
|
709
|
+
iosPushNotifications: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.pushNotifications === true),
|
|
710
|
+
iosSystemBars: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.systemBars === true),
|
|
711
|
+
iosUsageDescriptions: [
|
|
712
|
+
...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
|
|
713
|
+
].sort()
|
|
714
|
+
};
|
|
715
|
+
}, loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
|
|
716
|
+
const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
|
|
717
|
+
const manifest = readJson(path);
|
|
718
|
+
const { absolutejs } = manifest;
|
|
719
|
+
const devices = object2(absolutejs) ? absolutejs.devices : undefined;
|
|
720
|
+
if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
|
|
721
|
+
throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
|
|
722
|
+
const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
|
|
723
|
+
name,
|
|
724
|
+
provider: parseProvider(name, provider)
|
|
725
|
+
}));
|
|
726
|
+
return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
|
|
727
|
+
}, isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment)), importedCapabilities = (source, file) => {
|
|
728
|
+
const names = new Set;
|
|
729
|
+
const namespaces = new Set;
|
|
730
|
+
const visit = (node) => {
|
|
731
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
|
|
732
|
+
const bindings = node.importClause?.namedBindings;
|
|
733
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
734
|
+
for (const element of bindings.elements)
|
|
735
|
+
if (!element.isTypeOnly)
|
|
736
|
+
names.add((element.propertyName ?? element.name).text);
|
|
737
|
+
}
|
|
738
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
739
|
+
namespaces.add(bindings.name.text);
|
|
740
|
+
}
|
|
741
|
+
if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
742
|
+
for (const element of node.exportClause.elements)
|
|
743
|
+
if (!element.isTypeOnly)
|
|
744
|
+
names.add((element.propertyName ?? element.name).text);
|
|
745
|
+
}
|
|
746
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
|
|
747
|
+
names.add(node.name.text);
|
|
748
|
+
ts.forEachChild(node, visit);
|
|
749
|
+
};
|
|
750
|
+
const extension = extname3(file).toLowerCase();
|
|
751
|
+
const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
|
|
752
|
+
for (const [index, script] of sources.entries())
|
|
753
|
+
visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
|
|
754
|
+
return names;
|
|
755
|
+
}, assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
|
|
756
|
+
const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
|
|
757
|
+
const mismatched = plan.requiredPackages.filter((spec) => {
|
|
758
|
+
const separator = spec.lastIndexOf("@");
|
|
759
|
+
const packageName = spec.slice(0, separator);
|
|
760
|
+
if (missing.includes(spec))
|
|
761
|
+
return false;
|
|
762
|
+
try {
|
|
763
|
+
return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
|
|
764
|
+
} catch {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
const unmet = [...missing, ...mismatched];
|
|
769
|
+
if (unmet.length > 0)
|
|
770
|
+
throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
|
|
771
|
+
}, directAbsoluteProjectPackages = (projectRoot) => {
|
|
772
|
+
const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
|
|
773
|
+
const packages = new Set;
|
|
774
|
+
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
775
|
+
const dependencies = manifest[field2];
|
|
776
|
+
if (object2(dependencies))
|
|
777
|
+
for (const name of Object.keys(dependencies))
|
|
778
|
+
packages.add(name);
|
|
779
|
+
}
|
|
780
|
+
return packages;
|
|
781
|
+
}, discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
|
|
782
|
+
const root = resolve11(projectRoot);
|
|
783
|
+
const known = new Set(Object.keys(providers));
|
|
784
|
+
const capabilities = new Set;
|
|
785
|
+
for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
|
|
786
|
+
const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
|
|
787
|
+
if (isIgnored(portable))
|
|
788
|
+
continue;
|
|
789
|
+
const source = readFileSync4(resolve11(root, portable), "utf8");
|
|
790
|
+
for (const name of importedCapabilities(source, portable))
|
|
791
|
+
if (known.has(name))
|
|
792
|
+
capabilities.add(name);
|
|
793
|
+
}
|
|
794
|
+
return [...capabilities].sort();
|
|
795
|
+
}, missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
|
|
796
|
+
const packageName = spec.slice(0, spec.lastIndexOf("@"));
|
|
797
|
+
return !directPackages.has(packageName);
|
|
798
|
+
}), projectImportsAbsoluteDeviceCapability = (projectRoot, capability) => {
|
|
799
|
+
const root = resolve11(projectRoot);
|
|
800
|
+
for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
|
|
801
|
+
const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
|
|
802
|
+
if (isIgnored(portable))
|
|
803
|
+
continue;
|
|
804
|
+
const source = readFileSync4(resolve11(root, portable), "utf8");
|
|
805
|
+
if (importedCapabilities(source, portable).has(capability))
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
return false;
|
|
809
|
+
}, resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
|
|
810
|
+
const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
|
|
811
|
+
const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
|
|
812
|
+
const providers = {};
|
|
813
|
+
for (const name of capabilities) {
|
|
814
|
+
const provider = allProviders[name];
|
|
815
|
+
if (provider)
|
|
816
|
+
providers[name] = provider;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
capabilities,
|
|
820
|
+
providers,
|
|
821
|
+
requiredPackages: [
|
|
822
|
+
...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
|
|
823
|
+
].sort()
|
|
824
|
+
};
|
|
825
|
+
};
|
|
826
|
+
var init_deviceCapabilities = __esm(() => {
|
|
827
|
+
SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
|
|
828
|
+
IGNORED_DIRECTORIES = new Set([
|
|
829
|
+
".absolutejs",
|
|
830
|
+
".git",
|
|
831
|
+
".test-builds",
|
|
832
|
+
".test-shards",
|
|
833
|
+
"build",
|
|
834
|
+
"dist",
|
|
835
|
+
"node_modules",
|
|
836
|
+
"test",
|
|
837
|
+
"tests"
|
|
838
|
+
]);
|
|
839
|
+
IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
|
|
840
|
+
CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
|
|
841
|
+
CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
|
|
842
|
+
ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
|
|
843
|
+
IOS_USAGE_DESCRIPTIONS = new Set([
|
|
844
|
+
"camera",
|
|
845
|
+
"location-always",
|
|
846
|
+
"location-when-in-use",
|
|
847
|
+
"photo-library",
|
|
848
|
+
"photo-library-add"
|
|
849
|
+
]);
|
|
850
|
+
IOS_PRIVACY_ACCESSED_API_REASONS = {
|
|
851
|
+
NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
|
|
852
|
+
};
|
|
853
|
+
IOS_PRIVACY_ACCESSED_APIS = [
|
|
854
|
+
"NSPrivacyAccessedAPICategoryFileTimestamp"
|
|
855
|
+
];
|
|
856
|
+
});
|
|
857
|
+
|
|
162
858
|
// src/mobile/artifactStore.ts
|
|
163
859
|
import { createHash as createHash2 } from "crypto";
|
|
164
860
|
import {
|
|
@@ -249,13 +945,19 @@ var parseCompatibilityPage = (value) => {
|
|
|
249
945
|
if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
|
|
250
946
|
throw new TypeError("Compatibility artifact contains an invalid page.");
|
|
251
947
|
}
|
|
948
|
+
const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
|
|
949
|
+
const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
|
|
950
|
+
if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
|
|
951
|
+
throw new TypeError("Compatibility page style hash and path must be provided together.");
|
|
952
|
+
}
|
|
252
953
|
return {
|
|
253
954
|
bundleHash: readString(value.bundleHash, "page.bundleHash"),
|
|
254
955
|
bundlePath: readString(value.bundlePath, "page.bundlePath"),
|
|
255
956
|
contract: readString(value.contract, "page.contract"),
|
|
256
957
|
framework: value.framework,
|
|
257
958
|
pageId: readString(value.pageId, "page.pageId"),
|
|
258
|
-
propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
|
|
959
|
+
propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
|
|
960
|
+
...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
|
|
259
961
|
};
|
|
260
962
|
};
|
|
261
963
|
var parseCompatibilityRoute = (value) => {
|
|
@@ -287,14 +989,23 @@ var validateProducerModule = (module) => {
|
|
|
287
989
|
}
|
|
288
990
|
return module;
|
|
289
991
|
};
|
|
290
|
-
var normalizePage = (page) =>
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
992
|
+
var normalizePage = (page) => {
|
|
993
|
+
if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
|
|
994
|
+
throw new TypeError("Compatibility page style hash and path must be provided together.");
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
|
|
998
|
+
bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
|
|
999
|
+
contract: requireNonEmpty(page.contract, "page.contract"),
|
|
1000
|
+
framework: page.framework,
|
|
1001
|
+
pageId: requireNonEmpty(page.pageId, "page.pageId"),
|
|
1002
|
+
propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
|
|
1003
|
+
...page.styleBundleHash && page.styleBundlePath ? {
|
|
1004
|
+
styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
|
|
1005
|
+
styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
|
|
1006
|
+
} : {}
|
|
1007
|
+
};
|
|
1008
|
+
};
|
|
298
1009
|
var normalizeRoute = (route) => {
|
|
299
1010
|
if (!route.pattern.startsWith("/")) {
|
|
300
1011
|
throw new TypeError("route.pattern must start with /.");
|
|
@@ -1059,11 +1770,11 @@ var hashNativeTree = async (root, label, ignorePublicBundle) => {
|
|
|
1059
1770
|
const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
|
|
1060
1771
|
return createHash3("sha256").update(records.join("")).digest("hex");
|
|
1061
1772
|
};
|
|
1062
|
-
var fingerprintAbsoluteAndroidNativeProject = async (project) => {
|
|
1773
|
+
var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
|
|
1063
1774
|
const { dependencies } = await nativeDependencySources(project.nativeDirectory);
|
|
1064
1775
|
const roots = [
|
|
1065
1776
|
{
|
|
1066
|
-
ignorePublicBundle: true,
|
|
1777
|
+
ignorePublicBundle: options.includePublicBundle !== true,
|
|
1067
1778
|
label: "android",
|
|
1068
1779
|
source: project.nativeDirectory
|
|
1069
1780
|
},
|
|
@@ -1358,6 +2069,92 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
1358
2069
|
};
|
|
1359
2070
|
return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
|
|
1360
2071
|
};
|
|
2072
|
+
// src/mobile/androidUpgradeConformance.ts
|
|
2073
|
+
import { performance as performance2 } from "perf_hooks";
|
|
2074
|
+
var captureCommand3 = async (command) => {
|
|
2075
|
+
const process2 = Bun.spawn(command, { stderr: "pipe", stdout: "pipe" });
|
|
2076
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
2077
|
+
process2.exited,
|
|
2078
|
+
new Response(process2.stderr).text(),
|
|
2079
|
+
new Response(process2.stdout).text()
|
|
2080
|
+
]);
|
|
2081
|
+
return { exitCode, stderr, stdout };
|
|
2082
|
+
};
|
|
2083
|
+
var field = (output, name) => new RegExp(`^\\s*${name}=([^\\r\\n]+)$`, "mu").exec(output)?.[1]?.trim();
|
|
2084
|
+
var inspectAbsoluteAndroidInstalledApp = async (adb, serial, appId, run = captureCommand3) => {
|
|
2085
|
+
const result = await run([
|
|
2086
|
+
adb,
|
|
2087
|
+
"-s",
|
|
2088
|
+
serial,
|
|
2089
|
+
"shell",
|
|
2090
|
+
"dumpsys",
|
|
2091
|
+
"package",
|
|
2092
|
+
appId
|
|
2093
|
+
]);
|
|
2094
|
+
if (result.exitCode !== 0)
|
|
2095
|
+
throw new Error(`Could not inspect installed Android app ${appId}: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
2096
|
+
const installed = parseAbsoluteAndroidInstalledApp(appId, result.stdout);
|
|
2097
|
+
if (!installed)
|
|
2098
|
+
throw new Error(`Android app ${appId} is not installed on ${serial}.`);
|
|
2099
|
+
return installed;
|
|
2100
|
+
};
|
|
2101
|
+
var parseAbsoluteAndroidInstalledApp = (appId, output) => {
|
|
2102
|
+
const packageName = /^\s*Package \[([^\]]+)\]/mu.exec(output)?.[1];
|
|
2103
|
+
if (packageName !== appId && !output.includes(`package:${appId}`))
|
|
2104
|
+
return null;
|
|
2105
|
+
const versionCodeText = /^\s*versionCode=(\d+)/mu.exec(output)?.[1];
|
|
2106
|
+
return {
|
|
2107
|
+
appId,
|
|
2108
|
+
...field(output, "dataDir") ? { dataDirectory: field(output, "dataDir") } : {},
|
|
2109
|
+
...field(output, "firstInstallTime") ? { firstInstallTime: field(output, "firstInstallTime") } : {},
|
|
2110
|
+
...field(output, "lastUpdateTime") ? { lastUpdateTime: field(output, "lastUpdateTime") } : {},
|
|
2111
|
+
...field(output, "userId") || field(output, "appId") ? { uid: field(output, "userId") ?? field(output, "appId") } : {},
|
|
2112
|
+
...versionCodeText ? { versionCode: Number(versionCodeText) } : {},
|
|
2113
|
+
...field(output, "versionName") ? { versionName: field(output, "versionName") } : {}
|
|
2114
|
+
};
|
|
2115
|
+
};
|
|
2116
|
+
var unchanged = (name, before, after) => {
|
|
2117
|
+
if (!before || !after)
|
|
2118
|
+
throw new Error(`Android did not report ${name} for the upgrade proof.`);
|
|
2119
|
+
if (before !== after)
|
|
2120
|
+
throw new Error(`Android ${name} changed during the in-place upgrade.`);
|
|
2121
|
+
};
|
|
2122
|
+
var runAbsoluteAndroidUpgradeConformance = async (options) => {
|
|
2123
|
+
const run = options.run ?? captureCommand3;
|
|
2124
|
+
const startedAt = performance2.now();
|
|
2125
|
+
const before = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.appId, run);
|
|
2126
|
+
const installStartedAt = performance2.now();
|
|
2127
|
+
const installed = await run([
|
|
2128
|
+
options.adb,
|
|
2129
|
+
"-s",
|
|
2130
|
+
options.serial,
|
|
2131
|
+
"install",
|
|
2132
|
+
"-r",
|
|
2133
|
+
options.apkPath
|
|
2134
|
+
]);
|
|
2135
|
+
const installMs = Math.round(performance2.now() - installStartedAt);
|
|
2136
|
+
if (installed.exitCode !== 0 || !installed.stdout.includes("Success"))
|
|
2137
|
+
throw new Error(`Android in-place upgrade failed: ${installed.stderr.trim() || installed.stdout.trim()}`);
|
|
2138
|
+
const after = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.appId, run);
|
|
2139
|
+
unchanged("application UID", before.uid, after.uid);
|
|
2140
|
+
unchanged("data directory", before.dataDirectory, after.dataDirectory);
|
|
2141
|
+
unchanged("first install timestamp", before.firstInstallTime, after.firstInstallTime);
|
|
2142
|
+
if (before.versionCode !== undefined && after.versionCode !== undefined && after.versionCode <= before.versionCode)
|
|
2143
|
+
throw new Error(`Android versionCode did not increase (${before.versionCode} -> ${after.versionCode}).`);
|
|
2144
|
+
await options.afterInstall?.(after);
|
|
2145
|
+
const state = await options.verifyState();
|
|
2146
|
+
const compatibilityPass = !options.compatibility || options.compatibility.nPlusOne === "compatible" && options.compatibility.nPlusTwo === "compatible" && options.compatibility.nPlusThree === "upgrade-required" && options.compatibility.rollback === "compatible";
|
|
2147
|
+
const outcome = Object.values(state).every(Boolean) && compatibilityPass ? "pass" : "fail";
|
|
2148
|
+
return {
|
|
2149
|
+
after,
|
|
2150
|
+
before,
|
|
2151
|
+
...options.compatibility ? { compatibility: options.compatibility } : {},
|
|
2152
|
+
durationMs: Math.round(performance2.now() - startedAt),
|
|
2153
|
+
installMs,
|
|
2154
|
+
outcome,
|
|
2155
|
+
state
|
|
2156
|
+
};
|
|
2157
|
+
};
|
|
1361
2158
|
// src/mobile/iosRelease.ts
|
|
1362
2159
|
import { createHash as createHash5 } from "crypto";
|
|
1363
2160
|
import {
|
|
@@ -2533,14 +3330,592 @@ var createAbsoluteIosNativeWatcher = async (options) => {
|
|
|
2533
3330
|
return { close };
|
|
2534
3331
|
};
|
|
2535
3332
|
var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
|
|
3333
|
+
// src/mobile/remoteMacProtocol.ts
|
|
3334
|
+
import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
|
|
3335
|
+
import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
|
|
3336
|
+
import { homedir as homedir2 } from "os";
|
|
3337
|
+
import {
|
|
3338
|
+
dirname as dirname5,
|
|
3339
|
+
isAbsolute as isAbsolute5,
|
|
3340
|
+
join as join7,
|
|
3341
|
+
posix,
|
|
3342
|
+
relative as relative6,
|
|
3343
|
+
resolve as resolvePath2,
|
|
3344
|
+
sep as sep5
|
|
3345
|
+
} from "path";
|
|
3346
|
+
|
|
3347
|
+
// src/mobile/remoteMacWire.ts
|
|
3348
|
+
var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
|
|
3349
|
+
var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
|
|
3350
|
+
|
|
3351
|
+
// src/mobile/remoteMacProtocol.ts
|
|
3352
|
+
var PROFILE_FORMAT = 1;
|
|
3353
|
+
var REMOTE_STDIN_FLUSH_ATTEMPTS = 3;
|
|
3354
|
+
var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
|
|
3355
|
+
var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
|
|
3356
|
+
var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
|
|
3357
|
+
var emptyStore = () => ({
|
|
3358
|
+
format: PROFILE_FORMAT,
|
|
3359
|
+
profiles: {}
|
|
3360
|
+
});
|
|
3361
|
+
var loadStore = async (path = defaultProfilePath()) => {
|
|
3362
|
+
try {
|
|
3363
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
3364
|
+
if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
|
|
3365
|
+
throw new Error("Unsupported remote Mac profile format.");
|
|
3366
|
+
for (const [key, profile] of Object.entries(parsed.profiles)) {
|
|
3367
|
+
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 "))
|
|
3368
|
+
throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
|
|
3369
|
+
}
|
|
3370
|
+
if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
|
|
3371
|
+
throw new Error("The default remote Mac profile does not exist.");
|
|
3372
|
+
return parsed;
|
|
3373
|
+
} catch (error) {
|
|
3374
|
+
if (error.code === "ENOENT")
|
|
3375
|
+
return emptyStore();
|
|
3376
|
+
throw error;
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
var saveStore = async (store, path = defaultProfilePath()) => {
|
|
3380
|
+
await mkdir6(dirname5(path), { recursive: true });
|
|
3381
|
+
const temporary = `${path}.${randomUUID3()}.tmp`;
|
|
3382
|
+
await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
|
|
3383
|
+
`, {
|
|
3384
|
+
mode: 384
|
|
3385
|
+
});
|
|
3386
|
+
await rename7(temporary, path);
|
|
3387
|
+
await chmod(path, 384);
|
|
3388
|
+
};
|
|
3389
|
+
var validateAbsoluteRemoteMacProfileName = (name) => {
|
|
3390
|
+
const normalized = name.trim().toLowerCase();
|
|
3391
|
+
if (!PROFILE_NAME.test(normalized))
|
|
3392
|
+
throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
|
|
3393
|
+
return normalized;
|
|
3394
|
+
};
|
|
3395
|
+
var validateAbsoluteSshDestination = (destination) => {
|
|
3396
|
+
const normalized = destination.trim();
|
|
3397
|
+
if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
|
|
3398
|
+
throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
|
|
3399
|
+
return normalized;
|
|
3400
|
+
};
|
|
3401
|
+
var validatePort = (port) => {
|
|
3402
|
+
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
|
|
3403
|
+
throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
|
|
3404
|
+
return port;
|
|
3405
|
+
};
|
|
3406
|
+
var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
3407
|
+
var absoluteRemoteMacSshBase = (profile, options = {}) => [
|
|
3408
|
+
"ssh",
|
|
3409
|
+
"-o",
|
|
3410
|
+
"BatchMode=yes",
|
|
3411
|
+
"-o",
|
|
3412
|
+
"ConnectTimeout=10",
|
|
3413
|
+
"-o",
|
|
3414
|
+
"ServerAliveInterval=15",
|
|
3415
|
+
"-o",
|
|
3416
|
+
"ServerAliveCountMax=3",
|
|
3417
|
+
"-o",
|
|
3418
|
+
`StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
|
|
3419
|
+
...profile.port ? ["-p", String(profile.port)] : [],
|
|
3420
|
+
profile.destination
|
|
3421
|
+
];
|
|
3422
|
+
var localCapture = async (command) => {
|
|
3423
|
+
const process2 = Bun.spawn(command, {
|
|
3424
|
+
stderr: "pipe",
|
|
3425
|
+
stdin: "ignore",
|
|
3426
|
+
stdout: "pipe"
|
|
3427
|
+
});
|
|
3428
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
3429
|
+
process2.exited,
|
|
3430
|
+
new Response(process2.stdout).text(),
|
|
3431
|
+
new Response(process2.stderr).text()
|
|
3432
|
+
]);
|
|
3433
|
+
return { exitCode, stderr, stdout };
|
|
3434
|
+
};
|
|
3435
|
+
var defaultTransport = {
|
|
3436
|
+
capture: localCapture,
|
|
3437
|
+
spawn: (command, options) => Bun.spawn(command, {
|
|
3438
|
+
signal: options.signal,
|
|
3439
|
+
stderr: "pipe",
|
|
3440
|
+
stdin: "pipe",
|
|
3441
|
+
stdout: "pipe"
|
|
3442
|
+
})
|
|
3443
|
+
};
|
|
3444
|
+
var requireRemoteSuccess = (result, label) => {
|
|
3445
|
+
if (result.exitCode !== 0)
|
|
3446
|
+
throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
|
|
3447
|
+
return result.stdout.trim();
|
|
3448
|
+
};
|
|
3449
|
+
var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
|
|
3450
|
+
const store = await loadStore(profilePath);
|
|
3451
|
+
const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
|
|
3452
|
+
if (!selected)
|
|
3453
|
+
return;
|
|
3454
|
+
const profile = store.profiles[selected];
|
|
3455
|
+
if (!profile)
|
|
3456
|
+
throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
|
|
3457
|
+
return profile;
|
|
3458
|
+
};
|
|
3459
|
+
var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
|
|
3460
|
+
const profile = {
|
|
3461
|
+
destination: validateAbsoluteSshDestination(destination),
|
|
3462
|
+
port: validatePort(options.port)
|
|
3463
|
+
};
|
|
3464
|
+
const capture = options.transport?.capture ?? defaultTransport.capture;
|
|
3465
|
+
const command = [
|
|
3466
|
+
...absoluteRemoteMacSshBase(profile, {
|
|
3467
|
+
acceptNew: options.acceptNew === true
|
|
3468
|
+
}),
|
|
3469
|
+
"/bin/sh -lc",
|
|
3470
|
+
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)"`)
|
|
3471
|
+
];
|
|
3472
|
+
const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
|
|
3473
|
+
const [operatingSystem, home, bunPath, xcodeVersion] = lines;
|
|
3474
|
+
if (operatingSystem !== "Darwin")
|
|
3475
|
+
throw new Error("The SSH target is not a Mac.");
|
|
3476
|
+
if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
|
|
3477
|
+
throw new Error("The remote Mac must have Bun installed and available to SSH.");
|
|
3478
|
+
if (!xcodeVersion?.startsWith("Xcode "))
|
|
3479
|
+
throw new Error("The remote Mac must have full Xcode installed and selected.");
|
|
3480
|
+
return { bunPath, home, os: operatingSystem, xcodeVersion };
|
|
3481
|
+
};
|
|
3482
|
+
var listAbsoluteRemoteMacProfiles = async (profilePath) => {
|
|
3483
|
+
const store = await loadStore(profilePath);
|
|
3484
|
+
return {
|
|
3485
|
+
defaultProfile: store.defaultProfile,
|
|
3486
|
+
profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
|
|
3487
|
+
};
|
|
3488
|
+
};
|
|
3489
|
+
var pairAbsoluteRemoteMac = async (options) => {
|
|
3490
|
+
const name = validateAbsoluteRemoteMacProfileName(options.name);
|
|
3491
|
+
const destination = validateAbsoluteSshDestination(options.destination);
|
|
3492
|
+
const port = validatePort(options.port);
|
|
3493
|
+
const inspection = await inspectAbsoluteRemoteMac(destination, {
|
|
3494
|
+
acceptNew: true,
|
|
3495
|
+
port,
|
|
3496
|
+
transport: options.transport
|
|
3497
|
+
});
|
|
3498
|
+
const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
|
|
3499
|
+
if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
|
|
3500
|
+
throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
|
|
3501
|
+
const profile = {
|
|
3502
|
+
bunPath: inspection.bunPath,
|
|
3503
|
+
createdAt: new Date().toISOString(),
|
|
3504
|
+
destination,
|
|
3505
|
+
name,
|
|
3506
|
+
...port ? { port } : {},
|
|
3507
|
+
workspaceRoot,
|
|
3508
|
+
xcodeVersion: inspection.xcodeVersion
|
|
3509
|
+
};
|
|
3510
|
+
const store = await loadStore(options.profilePath);
|
|
3511
|
+
store.profiles[name] = profile;
|
|
3512
|
+
store.defaultProfile = name;
|
|
3513
|
+
await saveStore(store, options.profilePath);
|
|
3514
|
+
return profile;
|
|
3515
|
+
};
|
|
3516
|
+
var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
|
|
3517
|
+
const normalized = validateAbsoluteRemoteMacProfileName(name);
|
|
3518
|
+
const store = await loadStore(profilePath);
|
|
3519
|
+
if (!store.profiles[normalized])
|
|
3520
|
+
return false;
|
|
3521
|
+
delete store.profiles[normalized];
|
|
3522
|
+
if (store.defaultProfile === normalized) {
|
|
3523
|
+
const [nextDefault] = Object.keys(store.profiles).sort();
|
|
3524
|
+
store.defaultProfile = nextDefault;
|
|
3525
|
+
}
|
|
3526
|
+
await saveStore(store, profilePath);
|
|
3527
|
+
return true;
|
|
3528
|
+
};
|
|
3529
|
+
var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
|
|
3530
|
+
var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
|
|
3531
|
+
cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
|
|
3532
|
+
config,
|
|
3533
|
+
nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
|
|
3534
|
+
profile,
|
|
3535
|
+
projectRoot: resolvePath2(projectRoot),
|
|
3536
|
+
remote: true,
|
|
3537
|
+
remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
|
|
3538
|
+
xcodebuild: "remote:xcodebuild",
|
|
3539
|
+
xcrun: "remote:xcrun"
|
|
3540
|
+
});
|
|
3541
|
+
var installAbsoluteRemoteMacAgent = async (project) => {
|
|
3542
|
+
const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
|
|
3543
|
+
const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
|
|
3544
|
+
const remotePath = posix.join(directory, "agent.js");
|
|
3545
|
+
const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
|
|
3546
|
+
const verified = await defaultTransport.capture([
|
|
3547
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3548
|
+
"/bin/sh -lc",
|
|
3549
|
+
shellQuote(verifyScript)
|
|
3550
|
+
]);
|
|
3551
|
+
if (verified.exitCode === 0)
|
|
3552
|
+
return { ...artifact, remotePath, uploaded: false };
|
|
3553
|
+
const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
|
|
3554
|
+
const installScript = [
|
|
3555
|
+
"set -eu",
|
|
3556
|
+
"umask 077",
|
|
3557
|
+
`mkdir -p ${shellQuote(directory)}`,
|
|
3558
|
+
`cat > ${shellQuote(temporary)}`,
|
|
3559
|
+
`test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
|
|
3560
|
+
`chmod 600 ${shellQuote(temporary)}`,
|
|
3561
|
+
`mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
|
|
3562
|
+
].join("; ");
|
|
3563
|
+
const upload = Bun.spawn([
|
|
3564
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3565
|
+
"/bin/sh -lc",
|
|
3566
|
+
shellQuote(installScript)
|
|
3567
|
+
], {
|
|
3568
|
+
stderr: "pipe",
|
|
3569
|
+
stdin: Bun.file(artifact.path),
|
|
3570
|
+
stdout: "pipe"
|
|
3571
|
+
});
|
|
3572
|
+
const [exitCode, stderr] = await Promise.all([
|
|
3573
|
+
upload.exited,
|
|
3574
|
+
new Response(upload.stderr).text()
|
|
3575
|
+
]);
|
|
3576
|
+
if (exitCode !== 0)
|
|
3577
|
+
throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
3578
|
+
return { ...artifact, remotePath, uploaded: true };
|
|
3579
|
+
};
|
|
3580
|
+
var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
|
|
3581
|
+
const shippedCandidates = [
|
|
3582
|
+
join7(import.meta.dir, "remoteMacAgentEntry.js"),
|
|
3583
|
+
join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
|
|
3584
|
+
];
|
|
3585
|
+
let path;
|
|
3586
|
+
for (const candidate of shippedCandidates) {
|
|
3587
|
+
if (await Bun.file(candidate).exists()) {
|
|
3588
|
+
path = candidate;
|
|
3589
|
+
break;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
if (!path) {
|
|
3593
|
+
const sourceCandidates = [
|
|
3594
|
+
join7(import.meta.dir, "remoteMacAgentEntry.ts"),
|
|
3595
|
+
join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
|
|
3596
|
+
];
|
|
3597
|
+
const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
|
|
3598
|
+
if (!source)
|
|
3599
|
+
throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
|
|
3600
|
+
const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
|
|
3601
|
+
await mkdir6(outdir, { recursive: true });
|
|
3602
|
+
const result = await Bun.build({
|
|
3603
|
+
entrypoints: [source],
|
|
3604
|
+
minify: true,
|
|
3605
|
+
outdir,
|
|
3606
|
+
target: "bun"
|
|
3607
|
+
});
|
|
3608
|
+
if (!result.success)
|
|
3609
|
+
throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
|
|
3610
|
+
path = join7(outdir, "remoteMacAgentEntry.js");
|
|
3611
|
+
}
|
|
3612
|
+
const bytes = await Bun.file(path).arrayBuffer();
|
|
3613
|
+
const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
|
|
3614
|
+
return { bytes: bytes.byteLength, path, sha256 };
|
|
3615
|
+
};
|
|
3616
|
+
var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
|
|
3617
|
+
var portableMobileConfig = (project) => ({
|
|
3618
|
+
appId: project.config.appId,
|
|
3619
|
+
appName: project.config.appName,
|
|
3620
|
+
bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
|
|
3621
|
+
...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
|
|
3622
|
+
deepLinks: {
|
|
3623
|
+
...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
|
|
3624
|
+
hosts: project.config.deepLinkHosts,
|
|
3625
|
+
...project.config.appleAppIdPrefix ? {
|
|
3626
|
+
apple: {
|
|
3627
|
+
appIdPrefix: project.config.appleAppIdPrefix
|
|
3628
|
+
}
|
|
3629
|
+
} : {}
|
|
3630
|
+
}
|
|
3631
|
+
} : {},
|
|
3632
|
+
entry: project.config.entry,
|
|
3633
|
+
...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
|
|
3634
|
+
nativeProject: {
|
|
3635
|
+
directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
|
|
3636
|
+
mode: "source"
|
|
3637
|
+
},
|
|
3638
|
+
platforms: ["ios"],
|
|
3639
|
+
server: { productionOrigin: project.config.productionOrigin }
|
|
3640
|
+
});
|
|
3641
|
+
var absoluteRemoteProjectSyncCommands = (project) => {
|
|
3642
|
+
const current = project.remoteProjectRoot;
|
|
3643
|
+
const parent = posix.dirname(current);
|
|
3644
|
+
const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
|
|
3645
|
+
const previous = posix.join(parent, ".previous");
|
|
3646
|
+
const script = [
|
|
3647
|
+
"set -eu",
|
|
3648
|
+
`mkdir -p ${shellQuote(staging)}`,
|
|
3649
|
+
`tar -xf - -C ${shellQuote(staging)}`,
|
|
3650
|
+
`if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
|
|
3651
|
+
`if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
|
|
3652
|
+
`rm -rf ${shellQuote(previous)}`,
|
|
3653
|
+
`if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
|
|
3654
|
+
`mv ${shellQuote(staging)} ${shellQuote(current)}`,
|
|
3655
|
+
`rm -rf ${shellQuote(previous)}`
|
|
3656
|
+
].join("; ");
|
|
3657
|
+
return {
|
|
3658
|
+
remote: [
|
|
3659
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3660
|
+
"/bin/sh -lc",
|
|
3661
|
+
shellQuote(script)
|
|
3662
|
+
],
|
|
3663
|
+
tar: [
|
|
3664
|
+
"tar",
|
|
3665
|
+
"--exclude=.git",
|
|
3666
|
+
"--exclude=node_modules",
|
|
3667
|
+
"--exclude=build",
|
|
3668
|
+
"--exclude=.absolutejs",
|
|
3669
|
+
"-cf",
|
|
3670
|
+
"-",
|
|
3671
|
+
"-C",
|
|
3672
|
+
project.projectRoot,
|
|
3673
|
+
"."
|
|
3674
|
+
]
|
|
3675
|
+
};
|
|
3676
|
+
};
|
|
3677
|
+
var syncAbsoluteRemoteMacProject = async (project) => {
|
|
3678
|
+
const commands = absoluteRemoteProjectSyncCommands(project);
|
|
3679
|
+
const archive = Bun.spawn(commands.tar, {
|
|
3680
|
+
stderr: "pipe",
|
|
3681
|
+
stdout: "pipe"
|
|
3682
|
+
});
|
|
3683
|
+
const upload = Bun.spawn(commands.remote, {
|
|
3684
|
+
stderr: "pipe",
|
|
3685
|
+
stdin: archive.stdout,
|
|
3686
|
+
stdout: "pipe"
|
|
3687
|
+
});
|
|
3688
|
+
const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
|
|
3689
|
+
archive.exited,
|
|
3690
|
+
upload.exited,
|
|
3691
|
+
new Response(archive.stderr).text(),
|
|
3692
|
+
new Response(upload.stderr).text()
|
|
3693
|
+
]);
|
|
3694
|
+
if (archiveExit !== 0 || uploadExit !== 0)
|
|
3695
|
+
throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
|
|
3696
|
+
const install = await defaultTransport.capture([
|
|
3697
|
+
...absoluteRemoteMacSshBase(project.profile),
|
|
3698
|
+
"/bin/sh -lc",
|
|
3699
|
+
shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
|
|
3700
|
+
]);
|
|
3701
|
+
requireRemoteSuccess(install, "Remote Mac dependency installation");
|
|
3702
|
+
};
|
|
3703
|
+
var consumeLines2 = async (stream, onLine) => {
|
|
3704
|
+
const reader = stream.getReader();
|
|
3705
|
+
const decoder = new TextDecoder;
|
|
3706
|
+
let buffered = "";
|
|
3707
|
+
try {
|
|
3708
|
+
while (true) {
|
|
3709
|
+
const { done, value } = await reader.read();
|
|
3710
|
+
if (done)
|
|
3711
|
+
break;
|
|
3712
|
+
buffered += decoder.decode(value, { stream: true });
|
|
3713
|
+
const lines = buffered.split(/\r?\n/u);
|
|
3714
|
+
buffered = lines.pop() ?? "";
|
|
3715
|
+
lines.forEach(onLine);
|
|
3716
|
+
}
|
|
3717
|
+
buffered += decoder.decode();
|
|
3718
|
+
if (buffered)
|
|
3719
|
+
onLine(buffered);
|
|
3720
|
+
} finally {
|
|
3721
|
+
reader.releaseLock();
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3724
|
+
var startAbsoluteRemoteIosDevSession = async (options) => {
|
|
3725
|
+
const startedAt = performance.now();
|
|
3726
|
+
const transport = options.transport ?? defaultTransport;
|
|
3727
|
+
const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
|
|
3728
|
+
const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
|
|
3729
|
+
const agentStartedAt = performance.now();
|
|
3730
|
+
const agent = await installAgent(options.project);
|
|
3731
|
+
const agentDuration = performance.now() - agentStartedAt;
|
|
3732
|
+
const syncStartedAt = performance.now();
|
|
3733
|
+
await syncProject(options.project);
|
|
3734
|
+
const syncDuration = performance.now() - syncStartedAt;
|
|
3735
|
+
const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
|
|
3736
|
+
const remoteCommand = [
|
|
3737
|
+
`cd ${shellQuote(options.project.remoteProjectRoot)}`,
|
|
3738
|
+
"&&",
|
|
3739
|
+
"exec",
|
|
3740
|
+
shellQuote(options.project.profile.bunPath),
|
|
3741
|
+
shellQuote(agent.remotePath),
|
|
3742
|
+
"--port",
|
|
3743
|
+
String(options.port),
|
|
3744
|
+
"--mobile-config",
|
|
3745
|
+
shellQuote(encodedConfig),
|
|
3746
|
+
...options.https ? ["--https"] : []
|
|
3747
|
+
].join(" ");
|
|
3748
|
+
const command = [
|
|
3749
|
+
...absoluteRemoteMacSshBase(options.project.profile),
|
|
3750
|
+
"-o",
|
|
3751
|
+
"ExitOnForwardFailure=yes",
|
|
3752
|
+
"-R",
|
|
3753
|
+
`${options.port}:127.0.0.1:${options.port}`,
|
|
3754
|
+
"/bin/sh -lc",
|
|
3755
|
+
shellQuote(remoteCommand)
|
|
3756
|
+
];
|
|
3757
|
+
const connectStartedAt = performance.now();
|
|
3758
|
+
const process2 = transport.spawn(command, { signal: options.signal });
|
|
3759
|
+
let state = "syncing";
|
|
3760
|
+
let ready;
|
|
3761
|
+
let fatal;
|
|
3762
|
+
const pending = new Map;
|
|
3763
|
+
let resolveReady;
|
|
3764
|
+
let rejectReady;
|
|
3765
|
+
const readyPromise = new Promise((resolve6, reject) => {
|
|
3766
|
+
resolveReady = resolve6;
|
|
3767
|
+
rejectReady = reject;
|
|
3768
|
+
});
|
|
3769
|
+
const handleEvent = (event) => {
|
|
3770
|
+
if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
|
|
3771
|
+
rejectReady(new Error("Remote Mac protocol version mismatch."));
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
if (event.type === "log")
|
|
3775
|
+
options.log?.(event.message);
|
|
3776
|
+
if (event.type === "native-log")
|
|
3777
|
+
options.nativeLog?.(event.entry);
|
|
3778
|
+
if (event.type === "state") {
|
|
3779
|
+
({ state } = event);
|
|
3780
|
+
options.onStateChange?.(state);
|
|
3781
|
+
}
|
|
3782
|
+
if (event.type === "timing")
|
|
3783
|
+
options.onPhaseTiming?.(event);
|
|
3784
|
+
if (event.type === "ready") {
|
|
3785
|
+
ready = event;
|
|
3786
|
+
resolveReady();
|
|
3787
|
+
}
|
|
3788
|
+
if (event.type === "fatal") {
|
|
3789
|
+
fatal = new Error(event.error);
|
|
3790
|
+
rejectReady(fatal);
|
|
3791
|
+
}
|
|
3792
|
+
if (event.type === "response") {
|
|
3793
|
+
const request2 = pending.get(event.id);
|
|
3794
|
+
if (!request2)
|
|
3795
|
+
return;
|
|
3796
|
+
pending.delete(event.id);
|
|
3797
|
+
if (event.ok)
|
|
3798
|
+
request2.resolve(event.result);
|
|
3799
|
+
else
|
|
3800
|
+
request2.reject(new Error(event.error ?? "Remote command failed."));
|
|
3801
|
+
}
|
|
3802
|
+
};
|
|
3803
|
+
const stdoutDone = consumeLines2(process2.stdout, (line) => {
|
|
3804
|
+
if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
|
|
3805
|
+
return;
|
|
3806
|
+
try {
|
|
3807
|
+
handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
|
|
3808
|
+
} catch {
|
|
3809
|
+
options.log?.(`Remote Mac emitted an invalid protocol event.`);
|
|
3810
|
+
}
|
|
3811
|
+
}).catch((error) => {
|
|
3812
|
+
fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
|
|
3813
|
+
rejectReady(fatal);
|
|
3814
|
+
});
|
|
3815
|
+
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."}`));
|
|
3816
|
+
process2.exited.then(async (exitCode) => {
|
|
3817
|
+
await Promise.all([stdoutDone, stderrDone]);
|
|
3818
|
+
const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
|
|
3819
|
+
if (!ready)
|
|
3820
|
+
rejectReady(error);
|
|
3821
|
+
pending.forEach(({ reject }) => reject(error));
|
|
3822
|
+
pending.clear();
|
|
3823
|
+
return;
|
|
3824
|
+
});
|
|
3825
|
+
await readyPromise;
|
|
3826
|
+
if (!ready)
|
|
3827
|
+
throw fatal ?? new Error("Remote Mac did not become ready.");
|
|
3828
|
+
const totalDuration = performance.now() - startedAt;
|
|
3829
|
+
let currentReady = {
|
|
3830
|
+
...ready,
|
|
3831
|
+
timings: {
|
|
3832
|
+
...ready.timings,
|
|
3833
|
+
"remote-agent": agentDuration,
|
|
3834
|
+
"remote-connect": performance.now() - connectStartedAt,
|
|
3835
|
+
"remote-sync": syncDuration,
|
|
3836
|
+
total: totalDuration
|
|
3837
|
+
}
|
|
3838
|
+
};
|
|
3839
|
+
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.`);
|
|
3840
|
+
const request = (commandName) => {
|
|
3841
|
+
const id = randomUUID3();
|
|
3842
|
+
const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
|
|
3843
|
+
process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
|
|
3844
|
+
`);
|
|
3845
|
+
const flush = async () => {
|
|
3846
|
+
for (let attempt = 0;attempt < REMOTE_STDIN_FLUSH_ATTEMPTS; attempt++) {
|
|
3847
|
+
try {
|
|
3848
|
+
await process2.stdin.flush();
|
|
3849
|
+
return;
|
|
3850
|
+
} catch {
|
|
3851
|
+
await Promise.resolve();
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
};
|
|
3855
|
+
return flush().then(() => response);
|
|
3856
|
+
};
|
|
3857
|
+
let closed = false;
|
|
3858
|
+
const close = async () => {
|
|
3859
|
+
if (closed)
|
|
3860
|
+
return;
|
|
3861
|
+
closed = true;
|
|
3862
|
+
await request("close").catch(() => {
|
|
3863
|
+
return;
|
|
3864
|
+
});
|
|
3865
|
+
process2.stdin.end();
|
|
3866
|
+
await process2.exited.catch(() => {
|
|
3867
|
+
return;
|
|
3868
|
+
});
|
|
3869
|
+
};
|
|
3870
|
+
const makeSession = () => ({
|
|
3871
|
+
close,
|
|
3872
|
+
nativeCacheHit: currentReady.nativeCacheHit,
|
|
3873
|
+
startedSimulator: currentReady.startedSimulator,
|
|
3874
|
+
timings: currentReady.timings,
|
|
3875
|
+
udid: currentReady.udid,
|
|
3876
|
+
rebuild: async () => {
|
|
3877
|
+
const rebuildStartedAt = performance.now();
|
|
3878
|
+
const rebuildSyncStartedAt = performance.now();
|
|
3879
|
+
await syncProject(options.project);
|
|
3880
|
+
const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
|
|
3881
|
+
const result = await request("rebuild");
|
|
3882
|
+
currentReady = {
|
|
3883
|
+
...result,
|
|
3884
|
+
timings: {
|
|
3885
|
+
...result.timings,
|
|
3886
|
+
"remote-sync": rebuildSyncDuration,
|
|
3887
|
+
total: performance.now() - rebuildStartedAt
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
return makeSession();
|
|
3891
|
+
},
|
|
3892
|
+
relaunch: async () => {
|
|
3893
|
+
await request("relaunch");
|
|
3894
|
+
},
|
|
3895
|
+
screenshot: async (destination) => {
|
|
3896
|
+
const result = await request("screenshot");
|
|
3897
|
+
const target = resolvePath2(options.project.projectRoot, destination);
|
|
3898
|
+
const targetRelative = relative6(options.project.projectRoot, target);
|
|
3899
|
+
if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
|
|
3900
|
+
throw new Error("iOS screenshot must remain inside the project.");
|
|
3901
|
+
await mkdir6(dirname5(target), { recursive: true });
|
|
3902
|
+
await writeFile7(target, Buffer.from(result.data, "base64"));
|
|
3903
|
+
return target;
|
|
3904
|
+
},
|
|
3905
|
+
get state() {
|
|
3906
|
+
return state;
|
|
3907
|
+
}
|
|
3908
|
+
});
|
|
3909
|
+
return makeSession();
|
|
3910
|
+
};
|
|
2536
3911
|
// src/mobile/associationFiles.ts
|
|
2537
3912
|
import {
|
|
2538
3913
|
access as access7,
|
|
2539
|
-
mkdir as
|
|
2540
|
-
readFile as
|
|
2541
|
-
rename as
|
|
3914
|
+
mkdir as mkdir7,
|
|
3915
|
+
readFile as readFile9,
|
|
3916
|
+
rename as rename8,
|
|
2542
3917
|
rm as rm6,
|
|
2543
|
-
writeFile as
|
|
3918
|
+
writeFile as writeFile8
|
|
2544
3919
|
} from "fs/promises";
|
|
2545
3920
|
import { resolve as resolve7 } from "path";
|
|
2546
3921
|
import { Elysia } from "elysia";
|
|
@@ -2552,18 +3927,18 @@ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
|
|
|
2552
3927
|
var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
|
|
2553
3928
|
var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
|
|
2554
3929
|
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])?))*$/;
|
|
2555
|
-
var resolveProjectPath = (projectRoot, value,
|
|
3930
|
+
var resolveProjectPath = (projectRoot, value, field2) => {
|
|
2556
3931
|
const root = resolve6(projectRoot);
|
|
2557
3932
|
const path = resolve6(root, value);
|
|
2558
3933
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
2559
|
-
throw new TypeError(`${
|
|
3934
|
+
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
2560
3935
|
}
|
|
2561
3936
|
return path;
|
|
2562
3937
|
};
|
|
2563
|
-
var requireText = (value,
|
|
3938
|
+
var requireText = (value, field2) => {
|
|
2564
3939
|
const normalized = value.trim();
|
|
2565
3940
|
if (!normalized)
|
|
2566
|
-
throw new TypeError(`${
|
|
3941
|
+
throw new TypeError(`${field2} must not be empty.`);
|
|
2567
3942
|
return normalized;
|
|
2568
3943
|
};
|
|
2569
3944
|
var normalizeEntry = (entry) => {
|
|
@@ -2575,8 +3950,9 @@ var normalizeEntry = (entry) => {
|
|
|
2575
3950
|
};
|
|
2576
3951
|
var normalizeProductionOrigin = (value) => {
|
|
2577
3952
|
const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
|
|
2578
|
-
|
|
2579
|
-
|
|
3953
|
+
const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
|
|
3954
|
+
if (parsed.protocol !== "https:" && !isLoopbackHttp) {
|
|
3955
|
+
throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
|
|
2580
3956
|
}
|
|
2581
3957
|
if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
2582
3958
|
throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
|
|
@@ -2600,9 +3976,8 @@ var normalizeHosts = (hosts, productionOrigin) => {
|
|
|
2600
3976
|
}
|
|
2601
3977
|
return value;
|
|
2602
3978
|
};
|
|
2603
|
-
const
|
|
2604
|
-
|
|
2605
|
-
]);
|
|
3979
|
+
const productionHostname = new URL(productionOrigin).hostname;
|
|
3980
|
+
const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
|
|
2606
3981
|
for (const host of hosts ?? []) {
|
|
2607
3982
|
normalized.add(normalizeHostname(host));
|
|
2608
3983
|
}
|
|
@@ -2640,7 +4015,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
|
|
|
2640
4015
|
throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
|
|
2641
4016
|
}
|
|
2642
4017
|
const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
|
|
2643
|
-
const deepLinkScheme = config.deepLinks?.scheme
|
|
4018
|
+
const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
|
|
2644
4019
|
if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
|
|
2645
4020
|
throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
|
|
2646
4021
|
}
|
|
@@ -2657,7 +4032,8 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
|
|
|
2657
4032
|
iosVersion: normalizeIosVersion(config.ios?.version),
|
|
2658
4033
|
nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
|
|
2659
4034
|
platforms: normalizePlatforms(config.platforms),
|
|
2660
|
-
productionOrigin
|
|
4035
|
+
productionOrigin,
|
|
4036
|
+
pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
|
|
2661
4037
|
};
|
|
2662
4038
|
};
|
|
2663
4039
|
|
|
@@ -2671,7 +4047,7 @@ var HTTP_OK = 200;
|
|
|
2671
4047
|
var VERIFY_TIMEOUT_MS = 1e4;
|
|
2672
4048
|
var ANDROID_ASSOCIATION_PATH = "/.well-known/assetlinks.json";
|
|
2673
4049
|
var APPLE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
|
|
2674
|
-
var missingIdentity = (
|
|
4050
|
+
var missingIdentity = (field2, platform) => new TypeError(`${field2} is required to publish ${platform} deep-link association files.`);
|
|
2675
4051
|
var createAppleDocument = (config, requireAll) => {
|
|
2676
4052
|
if (!config.platforms.includes("ios"))
|
|
2677
4053
|
return;
|
|
@@ -2742,7 +4118,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
|
|
|
2742
4118
|
var writeAtomic = async (path, source) => {
|
|
2743
4119
|
let current;
|
|
2744
4120
|
try {
|
|
2745
|
-
current = await
|
|
4121
|
+
current = await readFile9(path, "utf8");
|
|
2746
4122
|
} catch (error) {
|
|
2747
4123
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
2748
4124
|
throw error;
|
|
@@ -2751,8 +4127,8 @@ var writeAtomic = async (path, source) => {
|
|
|
2751
4127
|
if (current === source)
|
|
2752
4128
|
return false;
|
|
2753
4129
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
2754
|
-
await
|
|
2755
|
-
await
|
|
4130
|
+
await writeFile8(temporary, source, { flag: "wx" });
|
|
4131
|
+
await rename8(temporary, path);
|
|
2756
4132
|
return true;
|
|
2757
4133
|
};
|
|
2758
4134
|
var exists2 = async (path) => {
|
|
@@ -2767,7 +4143,7 @@ var assertOwnedOutput = async (root) => {
|
|
|
2767
4143
|
const path = resolve7(root, OWNERSHIP_FILE);
|
|
2768
4144
|
let ownership;
|
|
2769
4145
|
try {
|
|
2770
|
-
ownership = JSON.parse(await
|
|
4146
|
+
ownership = JSON.parse(await readFile9(path, "utf8"));
|
|
2771
4147
|
} catch {
|
|
2772
4148
|
throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
|
|
2773
4149
|
}
|
|
@@ -2781,12 +4157,12 @@ var publishGeneratedDirectory = async (temporary, root) => {
|
|
|
2781
4157
|
await assertOwnedOutput(root);
|
|
2782
4158
|
const backup = `${root}.${crypto.randomUUID()}.previous`;
|
|
2783
4159
|
if (hasCurrent)
|
|
2784
|
-
await
|
|
4160
|
+
await rename8(root, backup);
|
|
2785
4161
|
try {
|
|
2786
|
-
await
|
|
4162
|
+
await rename8(temporary, root);
|
|
2787
4163
|
} catch (error) {
|
|
2788
4164
|
if (hasCurrent)
|
|
2789
|
-
await
|
|
4165
|
+
await rename8(backup, root);
|
|
2790
4166
|
throw error;
|
|
2791
4167
|
}
|
|
2792
4168
|
if (hasCurrent)
|
|
@@ -2794,7 +4170,7 @@ var publishGeneratedDirectory = async (temporary, root) => {
|
|
|
2794
4170
|
};
|
|
2795
4171
|
var materializeHost = async (root, host, files) => {
|
|
2796
4172
|
const directory = resolve7(root, host, ".well-known");
|
|
2797
|
-
await
|
|
4173
|
+
await mkdir7(directory, { recursive: true });
|
|
2798
4174
|
return Promise.all(files.map(async ([name, document]) => {
|
|
2799
4175
|
const path = resolve7(directory, name);
|
|
2800
4176
|
await writeAtomic(path, `${JSON.stringify(document, null, 2)}
|
|
@@ -2832,7 +4208,7 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
|
|
|
2832
4208
|
if (documents.apple) {
|
|
2833
4209
|
files.push(["apple-app-site-association", documents.apple]);
|
|
2834
4210
|
}
|
|
2835
|
-
await
|
|
4211
|
+
await mkdir7(temporary, { recursive: true });
|
|
2836
4212
|
try {
|
|
2837
4213
|
const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
|
|
2838
4214
|
await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
|
|
@@ -2903,15 +4279,23 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
|
|
|
2903
4279
|
};
|
|
2904
4280
|
};
|
|
2905
4281
|
// src/mobile/buildPipeline.ts
|
|
2906
|
-
import { readFile as
|
|
2907
|
-
import { join as
|
|
4282
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
4283
|
+
import { join as join14, resolve as resolve12 } from "path";
|
|
2908
4284
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2909
4285
|
|
|
2910
4286
|
// src/mobile/buildRelease.ts
|
|
2911
|
-
import { createHash as
|
|
2912
|
-
import { readFile as
|
|
2913
|
-
import { join as
|
|
2914
|
-
var sha256 = (bytes) =>
|
|
4287
|
+
import { createHash as createHash8 } from "crypto";
|
|
4288
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
4289
|
+
import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
|
|
4290
|
+
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
|
|
4291
|
+
var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
|
|
4292
|
+
var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
|
|
4293
|
+
if (path.endsWith("/htmx.min.js"))
|
|
4294
|
+
return match;
|
|
4295
|
+
const key = toPascal(basename2(path, extname(path)));
|
|
4296
|
+
const builtPath = manifest[key];
|
|
4297
|
+
return builtPath ? `${prefix}${builtPath}${suffix}` : match;
|
|
4298
|
+
});
|
|
2915
4299
|
var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
|
|
2916
4300
|
var resolveAssetPath = (buildDirectory, assetPath) => {
|
|
2917
4301
|
const resolvedBuildDirectory = resolve8(buildDirectory);
|
|
@@ -2919,29 +4303,51 @@ var resolveAssetPath = (buildDirectory, assetPath) => {
|
|
|
2919
4303
|
if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
|
|
2920
4304
|
return resolvedAsset;
|
|
2921
4305
|
}
|
|
2922
|
-
return
|
|
4306
|
+
return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
|
|
2923
4307
|
};
|
|
2924
4308
|
var pageFor = async (metadata, manifest, buildDirectory) => {
|
|
2925
4309
|
const assetPath = manifest[metadata.bundleKey];
|
|
2926
4310
|
if (!assetPath) {
|
|
2927
4311
|
throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
|
|
2928
4312
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
4313
|
+
let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
|
|
4314
|
+
if (metadata.framework === "html" || metadata.framework === "htmx") {
|
|
4315
|
+
const source = await readFile10(resolvedAssetPath, "utf8");
|
|
4316
|
+
const rewritten = rewriteStaticScriptPaths(source, manifest);
|
|
4317
|
+
const documentHash = sha256(new TextEncoder().encode(rewritten));
|
|
4318
|
+
resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
|
|
4319
|
+
await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
|
|
4320
|
+
await writeFile9(resolvedAssetPath, rewritten);
|
|
4321
|
+
}
|
|
4322
|
+
const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
|
|
4323
|
+
const styleAssetPath = [
|
|
4324
|
+
`${pageAssetKey}BundledCSS`,
|
|
4325
|
+
`${pageAssetKey}CompiledCSS`
|
|
4326
|
+
].map((key) => manifest[key]).find((path) => typeof path === "string");
|
|
4327
|
+
const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
|
|
4328
|
+
const [bytes, styleBytes] = await Promise.all([
|
|
4329
|
+
readFile10(resolvedAssetPath),
|
|
4330
|
+
resolvedStylePath ? readFile10(resolvedStylePath) : undefined
|
|
4331
|
+
]);
|
|
4332
|
+
const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
|
|
4333
|
+
const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
|
|
2932
4334
|
return {
|
|
2933
4335
|
bundleHash: sha256(bytes),
|
|
2934
4336
|
bundlePath,
|
|
2935
4337
|
contract: metadata.contract,
|
|
2936
4338
|
framework: metadata.framework,
|
|
2937
4339
|
pageId: metadata.pageId,
|
|
2938
|
-
propsSchemaHash: metadata.propsSchemaHash
|
|
4340
|
+
propsSchemaHash: metadata.propsSchemaHash,
|
|
4341
|
+
...styleBytes && styleBundlePath ? {
|
|
4342
|
+
styleBundleHash: sha256(styleBytes),
|
|
4343
|
+
styleBundlePath
|
|
4344
|
+
} : {}
|
|
2939
4345
|
};
|
|
2940
4346
|
};
|
|
2941
4347
|
var buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
2942
4348
|
const [captured, producerBytes] = await Promise.all([
|
|
2943
4349
|
captureAbsoluteMobileRouteGraph(options.app),
|
|
2944
|
-
|
|
4350
|
+
readFile10(options.producerPath)
|
|
2945
4351
|
]);
|
|
2946
4352
|
if (captured.length === 0) {
|
|
2947
4353
|
throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
|
|
@@ -2957,11 +4363,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
|
2957
4363
|
const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
|
|
2958
4364
|
const producerHash = sha256(producerBytes);
|
|
2959
4365
|
const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
|
|
2960
|
-
pages: pages.map(({
|
|
4366
|
+
pages: pages.map(({
|
|
4367
|
+
bundleHash,
|
|
4368
|
+
bundlePath,
|
|
4369
|
+
contract,
|
|
4370
|
+
pageId,
|
|
4371
|
+
styleBundleHash,
|
|
4372
|
+
styleBundlePath
|
|
4373
|
+
}) => ({
|
|
2961
4374
|
bundleHash,
|
|
2962
4375
|
bundlePath,
|
|
2963
4376
|
contract,
|
|
2964
|
-
pageId
|
|
4377
|
+
pageId,
|
|
4378
|
+
...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
|
|
2965
4379
|
})),
|
|
2966
4380
|
producerHash,
|
|
2967
4381
|
runtime: options.runtime
|
|
@@ -2970,8 +4384,8 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
|
2970
4384
|
if (priorAppIds.size > 1 || priorAppIds.size === 1 && !priorAppIds.has(options.appId)) {
|
|
2971
4385
|
throw new TypeError("Previous mobile compatibility artifacts belong to another app.");
|
|
2972
4386
|
}
|
|
2973
|
-
const
|
|
2974
|
-
const generation =
|
|
4387
|
+
const unchanged2 = options.previousArtifacts?.find((artifact2) => artifact2.appBuild === appBuild && artifact2.runtime === options.runtime);
|
|
4388
|
+
const generation = unchanged2?.generation ?? Math.max(0, ...(options.previousArtifacts ?? []).map((artifact2) => artifact2.generation)) + 1;
|
|
2975
4389
|
const artifact = createAbsoluteMobileCompatibilityArtifact({
|
|
2976
4390
|
appBuild,
|
|
2977
4391
|
appId: options.appId,
|
|
@@ -3021,16 +4435,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
|
|
|
3021
4435
|
|
|
3022
4436
|
// src/mobile/capacitorBundle.ts
|
|
3023
4437
|
import {
|
|
4438
|
+
cp,
|
|
3024
4439
|
copyFile as copyFile5,
|
|
3025
|
-
mkdir as
|
|
4440
|
+
mkdir as mkdir9,
|
|
3026
4441
|
mkdtemp as mkdtemp4,
|
|
3027
|
-
readFile as
|
|
3028
|
-
rename as
|
|
4442
|
+
readFile as readFile11,
|
|
4443
|
+
rename as rename9,
|
|
3029
4444
|
rm as rm7,
|
|
3030
|
-
writeFile as
|
|
4445
|
+
writeFile as writeFile10
|
|
3031
4446
|
} from "fs/promises";
|
|
3032
4447
|
import { existsSync as existsSync2 } from "fs";
|
|
3033
|
-
import { basename as
|
|
4448
|
+
import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
|
|
3034
4449
|
|
|
3035
4450
|
// src/mobile/routeMatcher.ts
|
|
3036
4451
|
var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
|
|
@@ -3303,6 +4718,13 @@ class AbsoluteMobilePageProtocolError extends Error {
|
|
|
3303
4718
|
this.code = code;
|
|
3304
4719
|
}
|
|
3305
4720
|
}
|
|
4721
|
+
var disposeAbsoluteMobilePage = async (target = window) => {
|
|
4722
|
+
const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
|
|
4723
|
+
target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
|
|
4724
|
+
target.__ABSOLUTE_PAGE_READY__ = undefined;
|
|
4725
|
+
if (dispose)
|
|
4726
|
+
await dispose();
|
|
4727
|
+
};
|
|
3306
4728
|
var frameworks4 = new Set([
|
|
3307
4729
|
"angular",
|
|
3308
4730
|
"ember",
|
|
@@ -3358,12 +4780,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
|
|
|
3358
4780
|
throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
|
|
3359
4781
|
}
|
|
3360
4782
|
const target = options.target ?? window;
|
|
4783
|
+
await disposeAbsoluteMobilePage(target);
|
|
3361
4784
|
target.__INITIAL_PROPS__ = envelope.response.props;
|
|
4785
|
+
target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
|
|
3362
4786
|
target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
|
|
3363
4787
|
await options.loadPage({
|
|
3364
4788
|
contract: envelope.response.contract,
|
|
3365
4789
|
pageId: envelope.response.pageId
|
|
3366
4790
|
});
|
|
4791
|
+
if (target.__ABSOLUTE_PAGE_READY__) {
|
|
4792
|
+
await target.__ABSOLUTE_PAGE_READY__;
|
|
4793
|
+
}
|
|
3367
4794
|
return {
|
|
3368
4795
|
contract: envelope.response.contract,
|
|
3369
4796
|
kind: "rendered",
|
|
@@ -3456,19 +4883,57 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
|
|
|
3456
4883
|
}
|
|
3457
4884
|
return `${url.pathname || "/"}${url.search}${url.hash}`;
|
|
3458
4885
|
};
|
|
4886
|
+
var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
|
|
4887
|
+
const url = new URL(value, `${localOrigin}/`);
|
|
4888
|
+
const local = new URL(localOrigin);
|
|
4889
|
+
const production = new URL(manifest.productionOrigin);
|
|
4890
|
+
const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
|
|
4891
|
+
if (!matches(url, local) && !matches(url, production)) {
|
|
4892
|
+
return;
|
|
4893
|
+
}
|
|
4894
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
4895
|
+
};
|
|
3459
4896
|
|
|
3460
4897
|
// src/mobile/capacitorBundle.ts
|
|
3461
4898
|
var MANIFEST_FILE = "absolute-mobile-manifest.json";
|
|
3462
4899
|
var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
|
|
3463
4900
|
var INDEX_FILE = "index.html";
|
|
3464
|
-
var
|
|
4901
|
+
var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
|
|
4902
|
+
var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
|
|
4903
|
+
var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
|
|
4904
|
+
"angular",
|
|
4905
|
+
"html",
|
|
4906
|
+
"htmx",
|
|
4907
|
+
"react",
|
|
4908
|
+
"svelte",
|
|
4909
|
+
"vue"
|
|
4910
|
+
]);
|
|
4911
|
+
var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
|
|
3465
4912
|
var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
|
|
3466
4913
|
var shellBootstrapModule = () => {
|
|
3467
|
-
const candidate = ["js", "ts"].map((extension) =>
|
|
4914
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
|
|
3468
4915
|
if (candidate)
|
|
3469
4916
|
return candidate;
|
|
3470
4917
|
throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
|
|
3471
4918
|
};
|
|
4919
|
+
var shellAuthModule = () => {
|
|
4920
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
|
|
4921
|
+
if (candidate)
|
|
4922
|
+
return candidate;
|
|
4923
|
+
throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
|
|
4924
|
+
};
|
|
4925
|
+
var shellSyncModule = () => {
|
|
4926
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
|
|
4927
|
+
if (candidate)
|
|
4928
|
+
return candidate;
|
|
4929
|
+
throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
|
|
4930
|
+
};
|
|
4931
|
+
var shellPushModule = () => {
|
|
4932
|
+
const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellPush.${extension}`)).find(existsSync2);
|
|
4933
|
+
if (candidate)
|
|
4934
|
+
return candidate;
|
|
4935
|
+
throw new TypeError("AbsoluteJS mobile push shell module is missing.");
|
|
4936
|
+
};
|
|
3472
4937
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3473
4938
|
var indexHtml = (appName) => `<!doctype html>
|
|
3474
4939
|
<html>
|
|
@@ -3492,11 +4957,58 @@ var sourceAssetPath = (buildDirectory, bundlePath) => {
|
|
|
3492
4957
|
}
|
|
3493
4958
|
return asset;
|
|
3494
4959
|
};
|
|
3495
|
-
var
|
|
4960
|
+
var importEntryTarget = (entry) => {
|
|
4961
|
+
if (typeof entry === "string")
|
|
4962
|
+
return entry;
|
|
4963
|
+
if (typeof entry === "object" && entry !== null)
|
|
4964
|
+
return Reflect.get(entry, "import");
|
|
4965
|
+
return;
|
|
4966
|
+
};
|
|
4967
|
+
var resolveProjectImport = async (projectRoot, specifier) => {
|
|
4968
|
+
const segments = specifier.split("/");
|
|
4969
|
+
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
|
|
4970
|
+
const subpath = specifier.slice(packageName.length);
|
|
4971
|
+
const packageDirectory = join9(resolve9(projectRoot), "node_modules", packageName);
|
|
4972
|
+
const manifest = JSON.parse(await readFile11(join9(packageDirectory, "package.json"), "utf8"));
|
|
4973
|
+
const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
|
|
4974
|
+
const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
|
|
4975
|
+
const target = importEntryTarget(entry);
|
|
4976
|
+
if (typeof target !== "string" || !target.startsWith("./"))
|
|
4977
|
+
throw new TypeError(`${specifier} does not publish an import entry.`);
|
|
4978
|
+
const resolved = resolve9(packageDirectory, target);
|
|
4979
|
+
if (!resolved.startsWith(`${resolve9(packageDirectory)}/`))
|
|
4980
|
+
throw new TypeError(`${specifier} has an unsafe import entry.`);
|
|
4981
|
+
return resolved;
|
|
4982
|
+
};
|
|
4983
|
+
var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapabilities, projectRoot) => {
|
|
3496
4984
|
const modulePath = shellBootstrapModule();
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3499
|
-
|
|
4985
|
+
const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
|
|
4986
|
+
` : "";
|
|
4987
|
+
const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
|
|
4988
|
+
const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
|
|
4989
|
+
` : "";
|
|
4990
|
+
const pushIndex = deviceCapabilities.capabilities.indexOf("pushNotifications");
|
|
4991
|
+
const push = pushIndex !== -1;
|
|
4992
|
+
const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
|
|
4993
|
+
` : "";
|
|
4994
|
+
const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
|
|
4995
|
+
const provider = deviceCapabilities.providers[name];
|
|
4996
|
+
if (!provider)
|
|
4997
|
+
throw new TypeError(`Missing device capability provider ${name}.`);
|
|
4998
|
+
return `import { ${provider.factory} as absoluteDeviceCapability${index} } from ${JSON.stringify(await resolveProjectImport(projectRoot, provider.module))};`;
|
|
4999
|
+
}))).join(`
|
|
5000
|
+
`);
|
|
5001
|
+
const pushSetup = push ? `const absoluteMobilePush = createAbsoluteMobileShellPush();
|
|
5002
|
+
const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absoluteMobilePush.capabilityOptions);
|
|
5003
|
+
` : "";
|
|
5004
|
+
const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: ${name === "pushNotifications" ? "absoluteMobilePushCapability" : `absoluteDeviceCapability${index}()`}`).join(", ");
|
|
5005
|
+
const entryPath = join9(staging, ".absolute-mobile-entry.ts");
|
|
5006
|
+
const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
|
|
5007
|
+
await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
|
|
5008
|
+
import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
|
|
5009
|
+
${authImport}${syncImport}${pushImport}${capabilityImports}
|
|
5010
|
+
${pushSetup}installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
|
|
5011
|
+
void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : options});
|
|
3500
5012
|
`);
|
|
3501
5013
|
const build = await Bun.build({
|
|
3502
5014
|
entrypoints: [entryPath],
|
|
@@ -3507,7 +5019,7 @@ void startAbsoluteMobileShell();
|
|
|
3507
5019
|
if (!build.success || build.outputs.length !== 1) {
|
|
3508
5020
|
throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
|
|
3509
5021
|
}
|
|
3510
|
-
await
|
|
5022
|
+
await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
|
|
3511
5023
|
await rm7(entryPath, { force: true });
|
|
3512
5024
|
};
|
|
3513
5025
|
var removePreviousBundle = async (backup, moved) => {
|
|
@@ -3518,20 +5030,20 @@ var removePreviousBundle = async (backup, moved) => {
|
|
|
3518
5030
|
var restorePreviousBundle = async (backup, destination, moved) => {
|
|
3519
5031
|
if (!moved)
|
|
3520
5032
|
return;
|
|
3521
|
-
await
|
|
5033
|
+
await rename9(backup, destination);
|
|
3522
5034
|
};
|
|
3523
5035
|
var installBundle = async (staging, destination) => {
|
|
3524
5036
|
const backup = `${destination}.previous-${crypto.randomUUID()}`;
|
|
3525
5037
|
let movedPrevious = false;
|
|
3526
5038
|
try {
|
|
3527
|
-
await
|
|
5039
|
+
await rename9(destination, backup);
|
|
3528
5040
|
movedPrevious = true;
|
|
3529
5041
|
} catch (error) {
|
|
3530
5042
|
if (!errorHasCode2(error, "ENOENT"))
|
|
3531
5043
|
throw error;
|
|
3532
5044
|
}
|
|
3533
5045
|
try {
|
|
3534
|
-
await
|
|
5046
|
+
await rename9(staging, destination);
|
|
3535
5047
|
await removePreviousBundle(backup, movedPrevious);
|
|
3536
5048
|
} catch (error) {
|
|
3537
5049
|
await restorePreviousBundle(backup, destination, movedPrevious);
|
|
@@ -3539,21 +5051,62 @@ var installBundle = async (staging, destination) => {
|
|
|
3539
5051
|
}
|
|
3540
5052
|
};
|
|
3541
5053
|
var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
|
|
3542
|
-
if (page.framework
|
|
3543
|
-
throw new TypeError(`Capacitor
|
|
5054
|
+
if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
|
|
5055
|
+
throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
|
|
3544
5056
|
}
|
|
3545
|
-
const extension =
|
|
5057
|
+
const extension = extname2(page.bundlePath) || ".js";
|
|
3546
5058
|
const localBundlePath = `./pages/${page.bundleHash}${extension}`;
|
|
3547
5059
|
const source = sourceAssetPath(buildDirectory, page.bundlePath);
|
|
3548
|
-
await copyFile5(source,
|
|
5060
|
+
await copyFile5(source, join9(staging, localBundlePath));
|
|
3549
5061
|
await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
|
|
3550
|
-
|
|
5062
|
+
let localStylePath;
|
|
5063
|
+
if (page.styleBundlePath && page.styleBundleHash) {
|
|
5064
|
+
const styleExtension = extname2(page.styleBundlePath) || ".css";
|
|
5065
|
+
localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
|
|
5066
|
+
const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
|
|
5067
|
+
await mkdir9(dirname7(join9(staging, localStylePath)), {
|
|
5068
|
+
recursive: true
|
|
5069
|
+
});
|
|
5070
|
+
await copyFile5(styleSource, join9(staging, localStylePath));
|
|
5071
|
+
await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
|
|
5072
|
+
}
|
|
5073
|
+
return {
|
|
5074
|
+
...page,
|
|
5075
|
+
localBundlePath,
|
|
5076
|
+
...localStylePath ? { localStylePath } : {}
|
|
5077
|
+
};
|
|
3551
5078
|
};
|
|
3552
|
-
var absoluteClientImports = async (sourcePath) => {
|
|
3553
|
-
const source = await
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
5079
|
+
var absoluteClientImports = async (sourcePath, buildDirectory) => {
|
|
5080
|
+
const source = await readFile11(sourcePath, "utf8");
|
|
5081
|
+
const extension = extname2(sourcePath).toLowerCase();
|
|
5082
|
+
let scriptLoader;
|
|
5083
|
+
if (extension === ".tsx")
|
|
5084
|
+
scriptLoader = "tsx";
|
|
5085
|
+
else if (extension === ".ts")
|
|
5086
|
+
scriptLoader = "ts";
|
|
5087
|
+
else if (extension === ".jsx")
|
|
5088
|
+
scriptLoader = "jsx";
|
|
5089
|
+
else if ([".js", ".mjs", ".cjs"].includes(extension))
|
|
5090
|
+
scriptLoader = "js";
|
|
5091
|
+
const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
|
|
5092
|
+
const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
|
|
5093
|
+
const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
|
|
5094
|
+
return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
|
|
5095
|
+
if (!specifier)
|
|
5096
|
+
return [];
|
|
5097
|
+
if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
|
|
5098
|
+
return [];
|
|
5099
|
+
}
|
|
5100
|
+
const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
|
|
5101
|
+
if (clean.startsWith("/"))
|
|
5102
|
+
return [clean];
|
|
5103
|
+
const resolved = resolve9(dirname7(sourcePath), clean);
|
|
5104
|
+
const root = resolve9(buildDirectory);
|
|
5105
|
+
const relativePath = relative8(root, resolved).replaceAll("\\", "/");
|
|
5106
|
+
if (relativePath === ".." || relativePath.startsWith("../")) {
|
|
5107
|
+
throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
|
|
5108
|
+
}
|
|
5109
|
+
return [`/${relativePath}`];
|
|
3557
5110
|
});
|
|
3558
5111
|
};
|
|
3559
5112
|
var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
|
|
@@ -3561,13 +5114,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
|
|
|
3561
5114
|
return;
|
|
3562
5115
|
copied.add(specifier);
|
|
3563
5116
|
const source = sourceAssetPath(buildDirectory, specifier);
|
|
3564
|
-
const destination =
|
|
3565
|
-
await
|
|
5117
|
+
const destination = join9(staging, specifier.replace(/^\/+/, ""));
|
|
5118
|
+
await mkdir9(dirname7(destination), { recursive: true });
|
|
3566
5119
|
await copyFile5(source, destination);
|
|
3567
5120
|
await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
|
|
3568
5121
|
};
|
|
3569
5122
|
var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
|
|
3570
|
-
const dependencies = await absoluteClientImports(sourcePath);
|
|
5123
|
+
const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
|
|
3571
5124
|
await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
|
|
3572
5125
|
};
|
|
3573
5126
|
var materializeAbsoluteCapacitorWebBundle = async (options) => {
|
|
@@ -3575,31 +5128,51 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
|
|
|
3575
5128
|
throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
|
|
3576
5129
|
}
|
|
3577
5130
|
const destination = options.config.bundleDirectory;
|
|
3578
|
-
await
|
|
3579
|
-
const staging = await mkdtemp4(
|
|
5131
|
+
await mkdir9(dirname7(destination), { recursive: true });
|
|
5132
|
+
const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
|
|
3580
5133
|
try {
|
|
3581
|
-
const pageDirectory =
|
|
3582
|
-
await
|
|
5134
|
+
const pageDirectory = join9(staging, "pages");
|
|
5135
|
+
await mkdir9(pageDirectory, { recursive: true });
|
|
5136
|
+
await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
|
|
5137
|
+
destination: join9(staging, directory),
|
|
5138
|
+
source: join9(options.buildDirectory, directory)
|
|
5139
|
+
})).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
|
|
3583
5140
|
const copiedDependencies = new Set;
|
|
3584
5141
|
const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
|
|
3585
5142
|
const manifest = {
|
|
3586
5143
|
appBuild: options.artifact.appBuild,
|
|
5144
|
+
...options.auth ? { auth: options.auth } : {},
|
|
3587
5145
|
appId: options.config.appId,
|
|
3588
5146
|
appName: options.config.appName,
|
|
3589
5147
|
deepLinkHosts: options.config.deepLinkHosts,
|
|
3590
5148
|
deepLinkScheme: options.config.deepLinkScheme,
|
|
5149
|
+
deviceCapabilities: options.deviceCapabilities.capabilities,
|
|
3591
5150
|
entry: options.config.entry,
|
|
3592
5151
|
format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
3593
5152
|
pages,
|
|
3594
5153
|
productionOrigin: options.config.productionOrigin,
|
|
3595
5154
|
routes: options.artifact.routes,
|
|
3596
|
-
runtime: options.artifact.runtime
|
|
5155
|
+
runtime: options.artifact.runtime,
|
|
5156
|
+
...options.sync ? {
|
|
5157
|
+
sync: {
|
|
5158
|
+
background: {
|
|
5159
|
+
endpoint: new URL("/__absolute/sync/background", options.config.productionOrigin).href,
|
|
5160
|
+
intervalMinutes: 15
|
|
5161
|
+
},
|
|
5162
|
+
socketTickets: true,
|
|
5163
|
+
storageSchema: options.syncSchema ?? {
|
|
5164
|
+
components: [
|
|
5165
|
+
{ id: "@absolutejs/app", version: 1 }
|
|
5166
|
+
]
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
} : {}
|
|
3597
5170
|
};
|
|
3598
5171
|
await Promise.all([
|
|
3599
|
-
|
|
5172
|
+
writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
3600
5173
|
`),
|
|
3601
|
-
|
|
3602
|
-
buildShellBootstrap(staging)
|
|
5174
|
+
writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
|
|
5175
|
+
buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
|
|
3603
5176
|
]);
|
|
3604
5177
|
await installBundle(staging, destination);
|
|
3605
5178
|
return manifest;
|
|
@@ -3610,17 +5183,17 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
|
|
|
3610
5183
|
};
|
|
3611
5184
|
|
|
3612
5185
|
// src/mobile/materializedBundle.ts
|
|
3613
|
-
import { createHash as
|
|
5186
|
+
import { createHash as createHash9 } from "crypto";
|
|
3614
5187
|
import {
|
|
3615
5188
|
access as access8,
|
|
3616
|
-
mkdir as
|
|
5189
|
+
mkdir as mkdir10,
|
|
3617
5190
|
mkdtemp as mkdtemp5,
|
|
3618
|
-
readFile as
|
|
3619
|
-
rename as
|
|
5191
|
+
readFile as readFile12,
|
|
5192
|
+
rename as rename10,
|
|
3620
5193
|
rm as rm8,
|
|
3621
|
-
writeFile as
|
|
5194
|
+
writeFile as writeFile11
|
|
3622
5195
|
} from "fs/promises";
|
|
3623
|
-
import { dirname as
|
|
5196
|
+
import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
|
|
3624
5197
|
import { pathToFileURL } from "url";
|
|
3625
5198
|
var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
|
|
3626
5199
|
var CURRENT_BUNDLE_FILE = "current.json";
|
|
@@ -3634,7 +5207,7 @@ var bundleIdFor = (currentReleaseId, releases) => {
|
|
|
3634
5207
|
currentReleaseId,
|
|
3635
5208
|
releases: releases.map(({ releaseId }) => releaseId)
|
|
3636
5209
|
});
|
|
3637
|
-
return `amb_${
|
|
5210
|
+
return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
|
|
3638
5211
|
};
|
|
3639
5212
|
var parseBundleIndex = (value) => {
|
|
3640
5213
|
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)) {
|
|
@@ -3660,17 +5233,17 @@ var parseBundleIndex = (value) => {
|
|
|
3660
5233
|
};
|
|
3661
5234
|
};
|
|
3662
5235
|
var writeRelease = async (root, release) => {
|
|
3663
|
-
const directory =
|
|
3664
|
-
const producerPath =
|
|
3665
|
-
await
|
|
5236
|
+
const directory = join10(root, release.artifact.releaseId);
|
|
5237
|
+
const producerPath = join10(directory, release.artifact.producer.module);
|
|
5238
|
+
await mkdir10(dirname8(producerPath), { recursive: true });
|
|
3666
5239
|
await Promise.all([
|
|
3667
|
-
|
|
5240
|
+
writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
|
|
3668
5241
|
`),
|
|
3669
|
-
|
|
5242
|
+
writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
|
|
3670
5243
|
]);
|
|
3671
5244
|
};
|
|
3672
5245
|
var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
|
|
3673
|
-
const destination =
|
|
5246
|
+
const destination = join10(bundlesRoot, bundleId);
|
|
3674
5247
|
try {
|
|
3675
5248
|
await access8(destination);
|
|
3676
5249
|
return destination;
|
|
@@ -3678,10 +5251,10 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
|
|
|
3678
5251
|
if (!errorHasCode3(error, "ENOENT"))
|
|
3679
5252
|
throw error;
|
|
3680
5253
|
}
|
|
3681
|
-
const staging = await mkdtemp5(
|
|
5254
|
+
const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
|
|
3682
5255
|
try {
|
|
3683
5256
|
await Promise.all(releases.map((release) => writeRelease(staging, release)));
|
|
3684
|
-
await
|
|
5257
|
+
await rename10(staging, destination);
|
|
3685
5258
|
} catch (error) {
|
|
3686
5259
|
await rm8(staging, { force: true, recursive: true });
|
|
3687
5260
|
if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
|
|
@@ -3711,16 +5284,16 @@ var resolveProducerHandler = (loaded, exportName) => {
|
|
|
3711
5284
|
};
|
|
3712
5285
|
};
|
|
3713
5286
|
var loadAbsoluteMobileMaterializedBundle = async (root) => {
|
|
3714
|
-
const resolvedRoot =
|
|
3715
|
-
const serialized = await
|
|
5287
|
+
const resolvedRoot = resolvePath3(root);
|
|
5288
|
+
const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
|
|
3716
5289
|
const parsed = JSON.parse(serialized);
|
|
3717
5290
|
const index = parseBundleIndex(parsed);
|
|
3718
|
-
const bundleRoot =
|
|
5291
|
+
const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
|
|
3719
5292
|
return {
|
|
3720
5293
|
artifacts: index.releases,
|
|
3721
5294
|
currentReleaseId: index.currentReleaseId,
|
|
3722
5295
|
loadProducer: async (artifact) => {
|
|
3723
|
-
const modulePath =
|
|
5296
|
+
const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
|
|
3724
5297
|
await verifyAbsoluteMobileCompatibilityProducer({
|
|
3725
5298
|
artifact,
|
|
3726
5299
|
producer: Bun.file(modulePath)
|
|
@@ -3746,9 +5319,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
|
|
|
3746
5319
|
}
|
|
3747
5320
|
return release;
|
|
3748
5321
|
});
|
|
3749
|
-
const root =
|
|
3750
|
-
const bundlesRoot =
|
|
3751
|
-
await
|
|
5322
|
+
const root = resolvePath3(input.root);
|
|
5323
|
+
const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
|
|
5324
|
+
await mkdir10(bundlesRoot, { recursive: true });
|
|
3752
5325
|
const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
|
|
3753
5326
|
await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
|
|
3754
5327
|
const index = {
|
|
@@ -3757,22 +5330,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
|
|
|
3757
5330
|
format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
|
|
3758
5331
|
releases: artifacts
|
|
3759
5332
|
};
|
|
3760
|
-
const pointerPath =
|
|
3761
|
-
const temporaryPointerPath =
|
|
3762
|
-
await
|
|
5333
|
+
const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
|
|
5334
|
+
const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
|
|
5335
|
+
await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
|
|
3763
5336
|
`, { flag: "wx" });
|
|
3764
|
-
await
|
|
5337
|
+
await rename10(temporaryPointerPath, pointerPath);
|
|
3765
5338
|
return index;
|
|
3766
5339
|
};
|
|
3767
5340
|
var readAbsoluteMobileMaterializedReleases = async (root) => {
|
|
3768
|
-
const resolvedRoot =
|
|
5341
|
+
const resolvedRoot = resolvePath3(root);
|
|
3769
5342
|
try {
|
|
3770
|
-
const serialized = await
|
|
5343
|
+
const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
|
|
3771
5344
|
const parsed = JSON.parse(serialized);
|
|
3772
5345
|
const index = parseBundleIndex(parsed);
|
|
3773
|
-
const bundleRoot =
|
|
5346
|
+
const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
|
|
3774
5347
|
return Promise.all(index.releases.map(async (artifact) => {
|
|
3775
|
-
const producer = Bun.file(
|
|
5348
|
+
const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
|
|
3776
5349
|
await verifyAbsoluteMobileCompatibilityProducer({
|
|
3777
5350
|
artifact,
|
|
3778
5351
|
producer
|
|
@@ -3786,7 +5359,61 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
|
|
|
3786
5359
|
}
|
|
3787
5360
|
};
|
|
3788
5361
|
|
|
5362
|
+
// src/mobile/nativeAuth.ts
|
|
5363
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5364
|
+
import { join as join11 } from "path";
|
|
5365
|
+
var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
|
|
5366
|
+
var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
|
|
5367
|
+
var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
|
|
5368
|
+
var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
|
|
5369
|
+
var readPackageManifest = (projectRoot) => {
|
|
5370
|
+
try {
|
|
5371
|
+
return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
|
|
5372
|
+
} catch {
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
};
|
|
5376
|
+
var packageManifestHas = (manifest, packageName) => {
|
|
5377
|
+
if (typeof manifest !== "object" || manifest === null)
|
|
5378
|
+
return false;
|
|
5379
|
+
return [
|
|
5380
|
+
Reflect.get(manifest, "dependencies"),
|
|
5381
|
+
Reflect.get(manifest, "devDependencies"),
|
|
5382
|
+
Reflect.get(manifest, "optionalDependencies"),
|
|
5383
|
+
Reflect.get(manifest, "peerDependencies")
|
|
5384
|
+
].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
|
|
5385
|
+
};
|
|
5386
|
+
var createAbsoluteMobileAuthManifest = (config) => {
|
|
5387
|
+
const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
|
|
5388
|
+
return {
|
|
5389
|
+
clientId: `absolutejs-native:${config.appId}`,
|
|
5390
|
+
issuer: config.productionOrigin,
|
|
5391
|
+
redirectUri: `${scheme}://auth/callback`,
|
|
5392
|
+
scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
|
|
5393
|
+
};
|
|
5394
|
+
};
|
|
5395
|
+
var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
|
|
5396
|
+
const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
|
|
5397
|
+
const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
|
|
5398
|
+
if (serialized === undefined)
|
|
5399
|
+
delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
|
|
5400
|
+
else
|
|
5401
|
+
process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
|
|
5402
|
+
return auth;
|
|
5403
|
+
};
|
|
5404
|
+
var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
|
|
5405
|
+
var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
|
|
5406
|
+
var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
|
|
5407
|
+
var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
|
|
5408
|
+
{
|
|
5409
|
+
...auth,
|
|
5410
|
+
name: `${config.appName} native app`
|
|
5411
|
+
}
|
|
5412
|
+
]);
|
|
5413
|
+
|
|
3789
5414
|
// src/mobile/buildPipeline.ts
|
|
5415
|
+
init_syncSchema();
|
|
5416
|
+
init_deviceCapabilities();
|
|
3790
5417
|
var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
|
|
3791
5418
|
var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
3792
5419
|
var serverExportName = (loaded, app) => {
|
|
@@ -3796,12 +5423,11 @@ var serverExportName = (loaded, app) => {
|
|
|
3796
5423
|
return "app";
|
|
3797
5424
|
return "default";
|
|
3798
5425
|
};
|
|
3799
|
-
var
|
|
3800
|
-
if (previous !== undefined)
|
|
3801
|
-
process.env
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
delete process.env.ABSOLUTE_BUILD_DIR;
|
|
5426
|
+
var restoreEnvironmentVariable = (name, previous) => {
|
|
5427
|
+
if (previous !== undefined)
|
|
5428
|
+
process.env[name] = previous;
|
|
5429
|
+
else
|
|
5430
|
+
delete process.env[name];
|
|
3805
5431
|
};
|
|
3806
5432
|
var requireRelease = (releases, releaseId) => {
|
|
3807
5433
|
const release = releases.get(releaseId);
|
|
@@ -3821,11 +5447,11 @@ var loadServerApp = async (producerPath) => {
|
|
|
3821
5447
|
return { app, exportName };
|
|
3822
5448
|
};
|
|
3823
5449
|
var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
3824
|
-
const buildDirectory =
|
|
5450
|
+
const buildDirectory = resolve12(options.buildDirectory);
|
|
3825
5451
|
const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
|
|
3826
|
-
const root =
|
|
5452
|
+
const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
|
|
3827
5453
|
const [manifestSource, previous] = await Promise.all([
|
|
3828
|
-
|
|
5454
|
+
readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
|
|
3829
5455
|
readAbsoluteMobileMaterializedReleases(root)
|
|
3830
5456
|
]);
|
|
3831
5457
|
const manifest = JSON.parse(manifestSource);
|
|
@@ -3833,12 +5459,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
3833
5459
|
throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
|
|
3834
5460
|
}
|
|
3835
5461
|
const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
|
|
5462
|
+
const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
|
|
5463
|
+
const previousConfigPath = process.env.ABSOLUTE_CONFIG;
|
|
3836
5464
|
process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
|
|
5465
|
+
process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
|
|
5466
|
+
if (options.configPath) {
|
|
5467
|
+
process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
|
|
5468
|
+
}
|
|
3837
5469
|
let loaded;
|
|
3838
5470
|
try {
|
|
3839
|
-
loaded = await loadServerApp(
|
|
5471
|
+
loaded = await loadServerApp(resolve12(options.producerPath));
|
|
3840
5472
|
} finally {
|
|
3841
|
-
|
|
5473
|
+
restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
|
|
5474
|
+
restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
|
|
5475
|
+
restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
|
|
3842
5476
|
}
|
|
3843
5477
|
const current = await buildAbsoluteMobileCompatibilityRelease({
|
|
3844
5478
|
app: loaded.app,
|
|
@@ -3847,9 +5481,22 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
3847
5481
|
manifest,
|
|
3848
5482
|
previousArtifacts: previous.map(({ artifact }) => artifact),
|
|
3849
5483
|
producerExport: loaded.exportName,
|
|
3850
|
-
producerPath:
|
|
5484
|
+
producerPath: resolve12(options.producerPath),
|
|
3851
5485
|
runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
|
|
3852
5486
|
});
|
|
5487
|
+
const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
|
|
5488
|
+
const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
|
|
5489
|
+
const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
|
|
5490
|
+
const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
|
|
5491
|
+
const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
|
|
5492
|
+
if (usesPush && !auth)
|
|
5493
|
+
throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
|
|
5494
|
+
if (usesPush && !loaded.app.routes.some((route) => route.path === "/auth/push" || route.path === "/auth/mobile/push"))
|
|
5495
|
+
throw new TypeError("@absolutejs/devices pushNotifications is used, but Auth push is not configured. Pass a trusted server-side registrar to auth({ push: ... }).");
|
|
5496
|
+
assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
|
|
5497
|
+
if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
|
|
5498
|
+
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.");
|
|
5499
|
+
}
|
|
3853
5500
|
const releasesById = new Map([current, ...previous].map((release) => [
|
|
3854
5501
|
release.artifact.releaseId,
|
|
3855
5502
|
release
|
|
@@ -3862,11 +5509,131 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
|
|
|
3862
5509
|
});
|
|
3863
5510
|
await materializeAbsoluteCapacitorWebBundle({
|
|
3864
5511
|
artifact: current.artifact,
|
|
5512
|
+
...auth ? { auth } : {},
|
|
3865
5513
|
buildDirectory,
|
|
3866
|
-
config: mobile
|
|
5514
|
+
config: mobile,
|
|
5515
|
+
deviceCapabilities,
|
|
5516
|
+
projectRoot: options.projectRoot,
|
|
5517
|
+
...sync ? { sync: true } : {},
|
|
5518
|
+
...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
|
|
3867
5519
|
});
|
|
3868
5520
|
return current.artifact;
|
|
3869
5521
|
};
|
|
5522
|
+
|
|
5523
|
+
// src/mobile/index.ts
|
|
5524
|
+
init_syncSchema();
|
|
5525
|
+
|
|
5526
|
+
// node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
|
|
5527
|
+
var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
|
|
5528
|
+
var host2 = globalThis;
|
|
5529
|
+
var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
|
|
5530
|
+
var registry2 = (() => {
|
|
5531
|
+
const existing = host2[RUNTIME_TRANSPORT2];
|
|
5532
|
+
if (isRegistry2(existing))
|
|
5533
|
+
return existing;
|
|
5534
|
+
if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
|
|
5535
|
+
Reflect.set(existing, "clients", []);
|
|
5536
|
+
return existing;
|
|
5537
|
+
}
|
|
5538
|
+
const created = { clients: [], installations: [] };
|
|
5539
|
+
Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
|
|
5540
|
+
configurable: false,
|
|
5541
|
+
enumerable: false,
|
|
5542
|
+
value: created,
|
|
5543
|
+
writable: false
|
|
5544
|
+
});
|
|
5545
|
+
return created;
|
|
5546
|
+
})();
|
|
5547
|
+
var maximum = (values) => {
|
|
5548
|
+
const present = values.filter((value) => value !== undefined);
|
|
5549
|
+
return present.length === 0 ? undefined : Math.max(...present);
|
|
5550
|
+
};
|
|
5551
|
+
var minimum = (values) => {
|
|
5552
|
+
const present = values.filter((value) => value !== undefined);
|
|
5553
|
+
return present.length === 0 ? undefined : Math.min(...present);
|
|
5554
|
+
};
|
|
5555
|
+
var inspectSyncRuntime = async () => {
|
|
5556
|
+
const clients = [...registry2.clients];
|
|
5557
|
+
const statuses = clients.map((client) => client.status());
|
|
5558
|
+
const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
|
|
5559
|
+
attempts: record.attempts,
|
|
5560
|
+
...record.rejection?.code ? { code: record.rejection.code } : {},
|
|
5561
|
+
createdAt: record.createdAt,
|
|
5562
|
+
...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
|
|
5563
|
+
...record.rejection?.kind ? { kind: record.rejection.kind } : {},
|
|
5564
|
+
...record.rejection?.message ? { message: record.rejection.message } : {},
|
|
5565
|
+
name: record.name,
|
|
5566
|
+
operationId: record.operationId
|
|
5567
|
+
})).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
|
|
5568
|
+
const lastError = statuses.findLast((status) => status.lastError)?.lastError;
|
|
5569
|
+
return {
|
|
5570
|
+
automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
|
|
5571
|
+
clients: clients.length,
|
|
5572
|
+
conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
|
|
5573
|
+
deadLetters,
|
|
5574
|
+
...lastError ? { lastError } : {},
|
|
5575
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
|
|
5576
|
+
lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
|
|
5577
|
+
},
|
|
5578
|
+
...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
|
|
5579
|
+
lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
|
|
5580
|
+
},
|
|
5581
|
+
...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
|
|
5582
|
+
oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
|
|
5583
|
+
},
|
|
5584
|
+
...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
|
|
5585
|
+
oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
|
|
5586
|
+
},
|
|
5587
|
+
pending: statuses.reduce((total, status) => total + status.pending, 0)
|
|
5588
|
+
};
|
|
5589
|
+
};
|
|
5590
|
+
var clientWithDeadLetter = async (operationId) => {
|
|
5591
|
+
for (const client of registry2.clients)
|
|
5592
|
+
if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
|
|
5593
|
+
return client;
|
|
5594
|
+
throw new Error(`Unknown Sync dead letter "${operationId}"`);
|
|
5595
|
+
};
|
|
5596
|
+
var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
|
|
5597
|
+
var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
|
|
5598
|
+
var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
|
|
5599
|
+
|
|
5600
|
+
// src/mobile/syncRemediation.ts
|
|
5601
|
+
var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
|
|
5602
|
+
var LAST_INSTALLATION_OFFSET = -1;
|
|
5603
|
+
var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
|
|
5604
|
+
var resolveRegistry = () => {
|
|
5605
|
+
const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
|
|
5606
|
+
if (isRegistry3(existing))
|
|
5607
|
+
return existing;
|
|
5608
|
+
const created = { installations: [] };
|
|
5609
|
+
Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
|
|
5610
|
+
configurable: false,
|
|
5611
|
+
enumerable: false,
|
|
5612
|
+
value: created,
|
|
5613
|
+
writable: false
|
|
5614
|
+
});
|
|
5615
|
+
return created;
|
|
5616
|
+
};
|
|
5617
|
+
var registry3 = resolveRegistry();
|
|
5618
|
+
var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
|
|
5619
|
+
var installAbsoluteMobileSyncRemediation = (bridge = {
|
|
5620
|
+
discard: discardSyncRuntimeDeadLetter,
|
|
5621
|
+
inspect: inspectSyncRuntime,
|
|
5622
|
+
rebase: rebaseSyncRuntimeDeadLetter,
|
|
5623
|
+
retry: retrySyncRuntimeDeadLetter
|
|
5624
|
+
}) => {
|
|
5625
|
+
const installation = { bridge };
|
|
5626
|
+
registry3.installations.push(installation);
|
|
5627
|
+
return () => {
|
|
5628
|
+
const index = registry3.installations.indexOf(installation);
|
|
5629
|
+
if (index >= 0)
|
|
5630
|
+
registry3.installations.splice(index, 1);
|
|
5631
|
+
};
|
|
5632
|
+
};
|
|
5633
|
+
|
|
5634
|
+
// src/mobile/index.ts
|
|
5635
|
+
init_deviceCapabilities();
|
|
5636
|
+
|
|
3870
5637
|
// src/mobile/compatibilityDispatcher.ts
|
|
3871
5638
|
import { Elysia as Elysia2 } from "elysia";
|
|
3872
5639
|
|
|
@@ -3888,6 +5655,68 @@ var ensureProducerStorage = () => {
|
|
|
3888
5655
|
var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
|
|
3889
5656
|
|
|
3890
5657
|
// src/mobile/compatibilityDispatcher.ts
|
|
5658
|
+
var MOBILE_WEBVIEW_ORIGINS = new Set([
|
|
5659
|
+
"capacitor://localhost",
|
|
5660
|
+
"http://localhost",
|
|
5661
|
+
"https://localhost"
|
|
5662
|
+
]);
|
|
5663
|
+
var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
|
|
5664
|
+
var MOBILE_CORS_ALLOW_HEADERS = [
|
|
5665
|
+
"accept",
|
|
5666
|
+
"content-type",
|
|
5667
|
+
"authorization",
|
|
5668
|
+
"hx-current-url",
|
|
5669
|
+
"hx-request",
|
|
5670
|
+
"hx-target",
|
|
5671
|
+
"hx-trigger",
|
|
5672
|
+
"hx-trigger-name",
|
|
5673
|
+
...MOBILE_REQUEST_HEADER_NAMES
|
|
5674
|
+
].join(", ");
|
|
5675
|
+
var MOBILE_CORS_METHODS = new Set([
|
|
5676
|
+
"DELETE",
|
|
5677
|
+
"GET",
|
|
5678
|
+
"HEAD",
|
|
5679
|
+
"OPTIONS",
|
|
5680
|
+
"PATCH",
|
|
5681
|
+
"POST",
|
|
5682
|
+
"PUT"
|
|
5683
|
+
]);
|
|
5684
|
+
var mobileWebViewOrigin = (request) => {
|
|
5685
|
+
const origin = request.headers.get("origin");
|
|
5686
|
+
return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
|
|
5687
|
+
};
|
|
5688
|
+
var applyMobileCorsHeaders = (response, origin) => {
|
|
5689
|
+
response.headers.set("access-control-allow-credentials", "true");
|
|
5690
|
+
response.headers.set("access-control-allow-origin", origin);
|
|
5691
|
+
response.headers.append("vary", "Origin");
|
|
5692
|
+
return response;
|
|
5693
|
+
};
|
|
5694
|
+
var finalizeMobileResponse = (request, response) => {
|
|
5695
|
+
const origin = mobileWebViewOrigin(request);
|
|
5696
|
+
return origin ? applyMobileCorsHeaders(response, origin) : response;
|
|
5697
|
+
};
|
|
5698
|
+
var mobilePreflightResponse = (request) => {
|
|
5699
|
+
if (request.method !== "OPTIONS")
|
|
5700
|
+
return;
|
|
5701
|
+
const origin = mobileWebViewOrigin(request);
|
|
5702
|
+
if (!origin)
|
|
5703
|
+
return;
|
|
5704
|
+
const requestedHeaders = request.headers.get("access-control-request-headers");
|
|
5705
|
+
const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
|
|
5706
|
+
if (!MOBILE_CORS_METHODS.has(requestedMethod))
|
|
5707
|
+
return;
|
|
5708
|
+
return new Response(null, {
|
|
5709
|
+
headers: {
|
|
5710
|
+
"access-control-allow-credentials": "true",
|
|
5711
|
+
"access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
|
|
5712
|
+
"access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
|
|
5713
|
+
"access-control-allow-origin": origin,
|
|
5714
|
+
"access-control-max-age": "600",
|
|
5715
|
+
vary: "Origin, Access-Control-Request-Headers"
|
|
5716
|
+
},
|
|
5717
|
+
status: 204
|
|
5718
|
+
});
|
|
5719
|
+
};
|
|
3891
5720
|
var artifactOwnsRequest = (artifact, pageId, request) => {
|
|
3892
5721
|
const { pathname } = new URL(request.url);
|
|
3893
5722
|
return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
|
|
@@ -3915,46 +5744,55 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
|
|
|
3915
5744
|
return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
|
|
3916
5745
|
if (getCurrentAbsoluteMobileProducerContext())
|
|
3917
5746
|
return;
|
|
5747
|
+
const preflight = mobilePreflightResponse(request);
|
|
5748
|
+
if (preflight)
|
|
5749
|
+
return preflight;
|
|
3918
5750
|
const parsed = parseAbsoluteMobilePageRequest(request);
|
|
3919
5751
|
if (parsed.kind !== "mobile")
|
|
3920
5752
|
return;
|
|
3921
5753
|
const resolved = resolveAbsoluteMobileCompatibilityRelease(parsed.client, artifacts);
|
|
3922
5754
|
if (resolved.kind === "upgrade-required") {
|
|
3923
|
-
return createAbsoluteMobileUpgradeResponse(resolved.result);
|
|
5755
|
+
return finalizeMobileResponse(request, createAbsoluteMobileUpgradeResponse(resolved.result));
|
|
3924
5756
|
}
|
|
3925
5757
|
if (!artifactOwnsRequest(resolved.artifact, parsed.client.pageId, request)) {
|
|
3926
|
-
return createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page.");
|
|
5758
|
+
return finalizeMobileResponse(request, createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page."));
|
|
3927
5759
|
}
|
|
3928
5760
|
if (resolved.artifact.releaseId === options.currentReleaseId) {
|
|
3929
5761
|
return;
|
|
3930
5762
|
}
|
|
3931
5763
|
try {
|
|
3932
5764
|
const producer = await resolveProducer(resolved.artifact);
|
|
3933
|
-
|
|
5765
|
+
const response = await runWithAbsoluteMobileProducer({
|
|
3934
5766
|
page: resolved.page,
|
|
3935
5767
|
releaseId: resolved.artifact.releaseId
|
|
3936
5768
|
}, () => producer.handle(request));
|
|
5769
|
+
return finalizeMobileResponse(request, response);
|
|
3937
5770
|
} catch (error) {
|
|
3938
5771
|
console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
|
|
3939
|
-
return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
|
|
5772
|
+
return finalizeMobileResponse(request, createAbsoluteMobilePageErrorResponse(parsed.client.pageId));
|
|
3940
5773
|
}
|
|
5774
|
+
}).afterHandle("global", ({ request, responseValue }) => {
|
|
5775
|
+
const origin = mobileWebViewOrigin(request);
|
|
5776
|
+
if (!origin || !(responseValue instanceof Response))
|
|
5777
|
+
return;
|
|
5778
|
+
applyMobileCorsHeaders(responseValue, origin);
|
|
3941
5779
|
}).as("global");
|
|
3942
5780
|
};
|
|
3943
5781
|
// src/mobile/nativeDeepLinks.ts
|
|
3944
|
-
import { readFile as
|
|
3945
|
-
import { join as
|
|
5782
|
+
import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
|
|
5783
|
+
import { join as join15 } from "path";
|
|
3946
5784
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->";
|
|
3947
5785
|
var END_MARKER = "<!-- absolutejs:deep-links:end -->";
|
|
3948
5786
|
var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
|
|
3949
5787
|
var NOT_FOUND = -1;
|
|
3950
5788
|
var escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
3951
5789
|
var writeChangedFile = async (path, source) => {
|
|
3952
|
-
const current = await
|
|
5790
|
+
const current = await readFile14(path, "utf8");
|
|
3953
5791
|
if (current === source)
|
|
3954
5792
|
return false;
|
|
3955
5793
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
3956
|
-
await
|
|
3957
|
-
await
|
|
5794
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5795
|
+
await rename11(temporary, path);
|
|
3958
5796
|
return true;
|
|
3959
5797
|
};
|
|
3960
5798
|
var replaceManagedRegion = (source, region, insertAt) => {
|
|
@@ -3978,7 +5816,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
|
|
|
3978
5816
|
return `${source.slice(0, index)}${region}${source.slice(index)}`;
|
|
3979
5817
|
};
|
|
3980
5818
|
var androidRegion = (config) => {
|
|
3981
|
-
const hosts = config.deepLinkHosts.map((
|
|
5819
|
+
const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
|
|
3982
5820
|
`);
|
|
3983
5821
|
const customScheme = config.deepLinkScheme ? `
|
|
3984
5822
|
|
|
@@ -3999,8 +5837,8 @@ ${hosts}
|
|
|
3999
5837
|
`;
|
|
4000
5838
|
};
|
|
4001
5839
|
var configureAndroid = async (config) => {
|
|
4002
|
-
const path =
|
|
4003
|
-
const source = await
|
|
5840
|
+
const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
5841
|
+
const source = await readFile14(path, "utf8");
|
|
4004
5842
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
4005
5843
|
if (mainActivity === NOT_FOUND) {
|
|
4006
5844
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -4025,8 +5863,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
|
|
|
4025
5863
|
${END_MARKER}
|
|
4026
5864
|
`;
|
|
4027
5865
|
var configureIosInfo = async (config) => {
|
|
4028
|
-
const path =
|
|
4029
|
-
const source = await
|
|
5866
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
5867
|
+
const source = await readFile14(path, "utf8");
|
|
4030
5868
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
4031
5869
|
${END_MARKER}
|
|
4032
5870
|
`;
|
|
@@ -4034,7 +5872,7 @@ var configureIosInfo = async (config) => {
|
|
|
4034
5872
|
return writeChangedFile(path, updated);
|
|
4035
5873
|
};
|
|
4036
5874
|
var iosEntitlementsSource = (config) => {
|
|
4037
|
-
const domains = config.deepLinkHosts.map((
|
|
5875
|
+
const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
|
|
4038
5876
|
`);
|
|
4039
5877
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4040
5878
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -4049,10 +5887,10 @@ ${domains}
|
|
|
4049
5887
|
`;
|
|
4050
5888
|
};
|
|
4051
5889
|
var configureIosEntitlements = async (config) => {
|
|
4052
|
-
const path =
|
|
5890
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
4053
5891
|
let current = "";
|
|
4054
5892
|
try {
|
|
4055
|
-
current = await
|
|
5893
|
+
current = await readFile14(path, "utf8");
|
|
4056
5894
|
} catch (error) {
|
|
4057
5895
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
4058
5896
|
throw error;
|
|
@@ -4062,13 +5900,13 @@ var configureIosEntitlements = async (config) => {
|
|
|
4062
5900
|
if (current === source)
|
|
4063
5901
|
return false;
|
|
4064
5902
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
4065
|
-
await
|
|
4066
|
-
await
|
|
5903
|
+
await writeFile12(temporary, source, { flag: "wx" });
|
|
5904
|
+
await rename11(temporary, path);
|
|
4067
5905
|
return true;
|
|
4068
5906
|
};
|
|
4069
5907
|
var configureIosProject = async (config) => {
|
|
4070
|
-
const path =
|
|
4071
|
-
const source = await
|
|
5908
|
+
const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
5909
|
+
const source = await readFile14(path, "utf8");
|
|
4072
5910
|
const declarations = [
|
|
4073
5911
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
4074
5912
|
].map((match) => match[1]);
|
|
@@ -4105,9 +5943,357 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
|
|
|
4105
5943
|
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
4106
5944
|
};
|
|
4107
5945
|
};
|
|
5946
|
+
// src/mobile/nativeDeviceCapabilities.ts
|
|
5947
|
+
init_deviceCapabilities();
|
|
5948
|
+
import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
|
|
5949
|
+
import { join as join16 } from "path";
|
|
5950
|
+
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
|
|
5951
|
+
var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
|
|
5952
|
+
var NOT_FOUND2 = -1;
|
|
5953
|
+
var IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001";
|
|
5954
|
+
var IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002";
|
|
5955
|
+
var PUSH_START_MARKER = "absolutejs:push-notifications:start";
|
|
5956
|
+
var PUSH_END_MARKER = "absolutejs:push-notifications:end";
|
|
5957
|
+
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5958
|
+
var writeChangedFile2 = async (path, source) => {
|
|
5959
|
+
const current = await readFile15(path, "utf8");
|
|
5960
|
+
if (current === source)
|
|
5961
|
+
return false;
|
|
5962
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
5963
|
+
await writeFile13(temporary, source, { flag: "wx" });
|
|
5964
|
+
await rename12(temporary, path);
|
|
5965
|
+
return true;
|
|
5966
|
+
};
|
|
5967
|
+
var writeOptionalChangedFile = async (path, source) => {
|
|
5968
|
+
const current = await optionalSource(path);
|
|
5969
|
+
if (current === source)
|
|
5970
|
+
return false;
|
|
5971
|
+
if (current === null) {
|
|
5972
|
+
await writeFile13(path, source, { flag: "wx" });
|
|
5973
|
+
return true;
|
|
5974
|
+
}
|
|
5975
|
+
return writeChangedFile2(path, source);
|
|
5976
|
+
};
|
|
5977
|
+
var optionalSource = async (path) => {
|
|
5978
|
+
try {
|
|
5979
|
+
return await readFile15(path, "utf8");
|
|
5980
|
+
} catch (error) {
|
|
5981
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
5982
|
+
return null;
|
|
5983
|
+
throw error;
|
|
5984
|
+
}
|
|
5985
|
+
};
|
|
5986
|
+
var managed = (source, region, insertion) => {
|
|
5987
|
+
const start = source.indexOf(START_MARKER2);
|
|
5988
|
+
const end = source.indexOf(END_MARKER2);
|
|
5989
|
+
if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
|
|
5990
|
+
throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
|
|
5991
|
+
if (start !== NOT_FOUND2) {
|
|
5992
|
+
const lineStart = source.lastIndexOf(`
|
|
5993
|
+
`, start) + 1;
|
|
5994
|
+
const nextLine = source.indexOf(`
|
|
5995
|
+
`, end + END_MARKER2.length);
|
|
5996
|
+
const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
|
|
5997
|
+
return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
|
|
5998
|
+
}
|
|
5999
|
+
if (region.length === 0)
|
|
6000
|
+
return source;
|
|
6001
|
+
if (insertion === NOT_FOUND2)
|
|
6002
|
+
throw new TypeError("Could not find a safe native project location for device permissions.");
|
|
6003
|
+
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
6004
|
+
};
|
|
6005
|
+
var IOS_KEYS = {
|
|
6006
|
+
camera: "NSCameraUsageDescription",
|
|
6007
|
+
"location-always": "NSLocationAlwaysAndWhenInUseUsageDescription",
|
|
6008
|
+
"location-when-in-use": "NSLocationWhenInUseUsageDescription",
|
|
6009
|
+
"photo-library": "NSPhotoLibraryUsageDescription",
|
|
6010
|
+
"photo-library-add": "NSPhotoLibraryAddUsageDescription"
|
|
6011
|
+
};
|
|
6012
|
+
var iosDescription = (appName, purpose) => {
|
|
6013
|
+
if (purpose === "camera")
|
|
6014
|
+
return `${appName} uses your camera when you choose to take a photo.`;
|
|
6015
|
+
if (purpose === "photo-library")
|
|
6016
|
+
return `${appName} accesses your photo library only for photo actions you choose.`;
|
|
6017
|
+
if (purpose === "location-when-in-use")
|
|
6018
|
+
return `${appName} uses your location only while you are using the app and request a location-based action.`;
|
|
6019
|
+
if (purpose === "location-always")
|
|
6020
|
+
return `${appName} does not track location in the background; this description supports the foreground location provider required by the native runtime.`;
|
|
6021
|
+
return `${appName} adds to your photo library only for photo actions you choose.`;
|
|
6022
|
+
};
|
|
6023
|
+
var privacyEntries = (requirements) => requirements.iosPrivacyAccessedApis.map(({ api, reasons }) => ` <dict>
|
|
6024
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
6025
|
+
<string>${escapeXml2(api)}</string>
|
|
6026
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
6027
|
+
<array>
|
|
6028
|
+
${reasons.map((reason) => ` <string>${escapeXml2(reason)}</string>`).join(`
|
|
6029
|
+
`)}
|
|
6030
|
+
</array>
|
|
6031
|
+
</dict>`).join(`
|
|
6032
|
+
`);
|
|
6033
|
+
var privacyManifestSource = (source, requirements) => {
|
|
6034
|
+
const entries = privacyEntries(requirements);
|
|
6035
|
+
const wholeRegion = entries ? ` ${START_MARKER2}
|
|
6036
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
6037
|
+
<array>
|
|
6038
|
+
${entries}
|
|
6039
|
+
</array>
|
|
6040
|
+
${END_MARKER2}
|
|
6041
|
+
` : "";
|
|
6042
|
+
if (source === null)
|
|
6043
|
+
return entries ? `<?xml version="1.0" encoding="UTF-8"?>
|
|
6044
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
6045
|
+
<plist version="1.0">
|
|
6046
|
+
<dict>
|
|
6047
|
+
${wholeRegion} <key>NSPrivacyCollectedDataTypes</key>
|
|
6048
|
+
<array/>
|
|
6049
|
+
<key>NSPrivacyTracking</key>
|
|
6050
|
+
<false/>
|
|
6051
|
+
</dict>
|
|
6052
|
+
</plist>
|
|
6053
|
+
` : null;
|
|
6054
|
+
const start = source.indexOf(START_MARKER2);
|
|
6055
|
+
if (start !== NOT_FOUND2) {
|
|
6056
|
+
const end = source.indexOf(END_MARKER2);
|
|
6057
|
+
if (end === NOT_FOUND2)
|
|
6058
|
+
throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
|
|
6059
|
+
const owned = source.slice(start, end);
|
|
6060
|
+
const ownsWholeKey = owned.includes("NSPrivacyAccessedAPITypes");
|
|
6061
|
+
let region = "";
|
|
6062
|
+
if (ownsWholeKey)
|
|
6063
|
+
region = wholeRegion;
|
|
6064
|
+
else if (entries)
|
|
6065
|
+
region = ` ${START_MARKER2}
|
|
6066
|
+
${entries}
|
|
6067
|
+
${END_MARKER2}
|
|
6068
|
+
`;
|
|
6069
|
+
return managed(source, region, source.lastIndexOf("</dict>"));
|
|
6070
|
+
}
|
|
6071
|
+
const key = source.indexOf("<key>NSPrivacyAccessedAPITypes</key>");
|
|
6072
|
+
if (key === NOT_FOUND2)
|
|
6073
|
+
return managed(source, wholeRegion, source.lastIndexOf("</dict>"));
|
|
6074
|
+
if (!entries)
|
|
6075
|
+
return source;
|
|
6076
|
+
const array = source.indexOf("<array>", key);
|
|
6077
|
+
if (array === NOT_FOUND2)
|
|
6078
|
+
throw new TypeError("iOS PrivacyInfo.xcprivacy has a malformed NSPrivacyAccessedAPITypes value.");
|
|
6079
|
+
const insertion = source.indexOf(`
|
|
6080
|
+
`, array);
|
|
6081
|
+
if (insertion === NOT_FOUND2)
|
|
6082
|
+
throw new TypeError("iOS PrivacyInfo.xcprivacy array is malformed.");
|
|
6083
|
+
return managed(source, ` ${START_MARKER2}
|
|
6084
|
+
${entries}
|
|
6085
|
+
${END_MARKER2}
|
|
6086
|
+
`, insertion + 1);
|
|
6087
|
+
};
|
|
6088
|
+
var writeIosPrivacyManifest = async (path, current, source) => {
|
|
6089
|
+
if (source === null)
|
|
6090
|
+
return false;
|
|
6091
|
+
if (current !== null)
|
|
6092
|
+
return writeChangedFile2(path, source);
|
|
6093
|
+
await writeFile13(path, source, { flag: "wx" });
|
|
6094
|
+
return true;
|
|
6095
|
+
};
|
|
6096
|
+
var configureIosPrivacyProject = async (config, requirements) => {
|
|
6097
|
+
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
6098
|
+
return false;
|
|
6099
|
+
const projectPath = join16(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
6100
|
+
const project = await readFile15(projectPath, "utf8");
|
|
6101
|
+
return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
|
|
6102
|
+
};
|
|
6103
|
+
var addIosPrivacyProjectReference = (source) => {
|
|
6104
|
+
const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
|
|
6105
|
+
const fileReference = fileMatch?.[1] ?? IOS_PRIVACY_FILE_REFERENCE;
|
|
6106
|
+
const buildMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy in Resources \*\/ = \{isa = PBXBuildFile;/u);
|
|
6107
|
+
const buildFile = buildMatch?.[1] ?? IOS_PRIVACY_BUILD_FILE;
|
|
6108
|
+
if (!fileMatch && source.includes(fileReference) || !buildMatch && source.includes(buildFile))
|
|
6109
|
+
throw new TypeError("AbsoluteJS iOS privacy-manifest identifiers collide.");
|
|
6110
|
+
let next = source;
|
|
6111
|
+
if (!buildMatch) {
|
|
6112
|
+
const marker = "/* End PBXBuildFile section */";
|
|
6113
|
+
const index = next.indexOf(marker);
|
|
6114
|
+
if (index === NOT_FOUND2)
|
|
6115
|
+
throw new TypeError("Could not find the iOS PBXBuildFile section.");
|
|
6116
|
+
next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ${fileReference} /* PrivacyInfo.xcprivacy */; };
|
|
6117
|
+
${next.slice(index)}`;
|
|
6118
|
+
}
|
|
6119
|
+
if (!fileMatch) {
|
|
6120
|
+
const marker = "/* End PBXFileReference section */";
|
|
6121
|
+
const index = next.indexOf(marker);
|
|
6122
|
+
if (index === NOT_FOUND2)
|
|
6123
|
+
throw new TypeError("Could not find the iOS PBXFileReference section.");
|
|
6124
|
+
next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
|
6125
|
+
${next.slice(index)}`;
|
|
6126
|
+
}
|
|
6127
|
+
const groupsStart = next.indexOf("/* Begin PBXGroup section */");
|
|
6128
|
+
const groupsEnd = next.indexOf("/* End PBXGroup section */");
|
|
6129
|
+
const groups = next.slice(groupsStart, groupsEnd);
|
|
6130
|
+
if (!groups.includes(`${fileReference} /* PrivacyInfo.xcprivacy */`)) {
|
|
6131
|
+
const appGroup = groups.match(/[A-F0-9]{24} \/\* App \*\/ = \{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = \(\n/u);
|
|
6132
|
+
if (!appGroup || appGroup.index === undefined)
|
|
6133
|
+
throw new TypeError("Could not find the iOS App PBXGroup.");
|
|
6134
|
+
const index = groupsStart + appGroup.index + appGroup[0].length;
|
|
6135
|
+
next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */,
|
|
6136
|
+
${next.slice(index)}`;
|
|
6137
|
+
}
|
|
6138
|
+
const resourcesStart = next.indexOf("/* Begin PBXResourcesBuildPhase section */");
|
|
6139
|
+
const resourcesEnd = next.indexOf("/* End PBXResourcesBuildPhase section */");
|
|
6140
|
+
const resources = next.slice(resourcesStart, resourcesEnd);
|
|
6141
|
+
if (!resources.includes(`${buildFile} /* PrivacyInfo.xcprivacy in Resources */`)) {
|
|
6142
|
+
const files = resources.match(/isa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = \d+;\n\t\t\tfiles = \(\n/u);
|
|
6143
|
+
if (!files || files.index === undefined)
|
|
6144
|
+
throw new TypeError("Could not find the iOS Resources build phase.");
|
|
6145
|
+
const index = resourcesStart + files.index + files[0].length;
|
|
6146
|
+
next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */,
|
|
6147
|
+
${next.slice(index)}`;
|
|
6148
|
+
}
|
|
6149
|
+
return next;
|
|
6150
|
+
};
|
|
6151
|
+
var configureIos2 = async (config, plan) => {
|
|
6152
|
+
const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
6153
|
+
const source = await readFile15(path, "utf8");
|
|
6154
|
+
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
6155
|
+
const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
|
|
6156
|
+
const ownedStart = source.indexOf(START_MARKER2);
|
|
6157
|
+
const ownedEnd = source.indexOf(END_MARKER2);
|
|
6158
|
+
const ownsSystemBars = ownedStart >= 0 && ownedEnd > ownedStart && source.slice(ownedStart, ownedEnd).includes("UIViewControllerBasedStatusBarAppearance");
|
|
6159
|
+
if (requirements.iosSystemBars && existingSystemBars?.[1] === "false" && !ownsSystemBars)
|
|
6160
|
+
throw new TypeError("iOS system bars require UIViewControllerBasedStatusBarAppearance to be true.");
|
|
6161
|
+
const usageContent = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
|
|
6162
|
+
<string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
|
|
6163
|
+
`);
|
|
6164
|
+
const systemBarsContent = requirements.iosSystemBars && (existingSystemBars === null || ownsSystemBars) ? ` <key>UIViewControllerBasedStatusBarAppearance</key>
|
|
6165
|
+
<true/>` : "";
|
|
6166
|
+
const content = [usageContent, systemBarsContent].filter(Boolean).join(`
|
|
6167
|
+
`);
|
|
6168
|
+
const region = content ? ` ${START_MARKER2}
|
|
6169
|
+
${content}
|
|
6170
|
+
${END_MARKER2}
|
|
6171
|
+
` : "";
|
|
6172
|
+
const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
|
|
6173
|
+
const privacyPath = join16(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
|
|
6174
|
+
const privacyCurrent = await optionalSource(privacyPath);
|
|
6175
|
+
const privacySource = privacyManifestSource(privacyCurrent, requirements);
|
|
6176
|
+
const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
|
|
6177
|
+
writeIosPrivacyManifest(privacyPath, privacyCurrent, privacySource),
|
|
6178
|
+
configureIosPrivacyProject(config, requirements),
|
|
6179
|
+
configureIosPushNotifications(config, requirements.iosPushNotifications)
|
|
6180
|
+
]);
|
|
6181
|
+
return infoChanged || privacyChanged || projectChanged || pushChanged;
|
|
6182
|
+
};
|
|
6183
|
+
var configureIosPushNotifications = async (config, enabled) => {
|
|
6184
|
+
const entitlementsPath = join16(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
6185
|
+
const entitlements = await optionalSource(entitlementsPath);
|
|
6186
|
+
if (entitlements === null && !enabled)
|
|
6187
|
+
return false;
|
|
6188
|
+
if (entitlements === null)
|
|
6189
|
+
throw new TypeError("AbsoluteJS iOS entitlements are missing. Run native deep-link projection before device capabilities.");
|
|
6190
|
+
const entitlementRegion = enabled ? ` <!-- ${PUSH_START_MARKER} -->
|
|
6191
|
+
<key>aps-environment</key>
|
|
6192
|
+
<string>development</string>
|
|
6193
|
+
<!-- ${PUSH_END_MARKER} -->
|
|
6194
|
+
` : "";
|
|
6195
|
+
const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
|
|
6196
|
+
const delegatePath = join16(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
6197
|
+
const delegate = await optionalSource(delegatePath);
|
|
6198
|
+
if (delegate === null && !enabled)
|
|
6199
|
+
return false;
|
|
6200
|
+
if (delegate === null)
|
|
6201
|
+
throw new TypeError("Capacitor AppDelegate.swift is missing for iOS push notifications.");
|
|
6202
|
+
const hasSuccess = delegate.includes("didRegisterForRemoteNotificationsWithDeviceToken");
|
|
6203
|
+
const hasFailure = delegate.includes("didFailToRegisterForRemoteNotificationsWithError");
|
|
6204
|
+
if (enabled && hasSuccess !== hasFailure && !delegate.includes(PUSH_START_MARKER))
|
|
6205
|
+
throw new TypeError("iOS AppDelegate has a partial custom remote-notification registration implementation.");
|
|
6206
|
+
const alreadyForwarded = enabled && hasSuccess && hasFailure && delegate.includes("capacitorDidRegisterForRemoteNotifications") && delegate.includes("capacitorDidFailToRegisterForRemoteNotifications");
|
|
6207
|
+
const swiftRegion = enabled && !alreadyForwarded ? `
|
|
6208
|
+
// ${PUSH_START_MARKER}
|
|
6209
|
+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
6210
|
+
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
|
|
6211
|
+
}
|
|
6212
|
+
|
|
6213
|
+
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
6214
|
+
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
|
|
6215
|
+
}
|
|
6216
|
+
// ${PUSH_END_MARKER}
|
|
6217
|
+
` : "";
|
|
6218
|
+
const nextDelegate = alreadyForwarded ? delegate : replacePushRegion(delegate, swiftRegion, delegate.lastIndexOf("}"));
|
|
6219
|
+
const changed = await Promise.all([
|
|
6220
|
+
writeChangedFile2(entitlementsPath, nextEntitlements),
|
|
6221
|
+
writeChangedFile2(delegatePath, nextDelegate)
|
|
6222
|
+
]);
|
|
6223
|
+
return changed.some(Boolean);
|
|
6224
|
+
};
|
|
6225
|
+
var replacePushRegion = (source, region, insertion) => {
|
|
6226
|
+
const start = source.indexOf(PUSH_START_MARKER);
|
|
6227
|
+
const end = source.indexOf(PUSH_END_MARKER);
|
|
6228
|
+
if (start < 0 !== end < 0 || start >= 0 && end < start)
|
|
6229
|
+
throw new TypeError("AbsoluteJS push-notification ownership markers are malformed.");
|
|
6230
|
+
if (start >= 0) {
|
|
6231
|
+
const lineStart = source.lastIndexOf(`
|
|
6232
|
+
`, start) + 1;
|
|
6233
|
+
const nextLine = source.indexOf(`
|
|
6234
|
+
`, end + PUSH_END_MARKER.length);
|
|
6235
|
+
const lineEnd = nextLine < 0 ? source.length : nextLine + 1;
|
|
6236
|
+
return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
|
|
6237
|
+
}
|
|
6238
|
+
if (!region)
|
|
6239
|
+
return source;
|
|
6240
|
+
if (insertion < 0)
|
|
6241
|
+
throw new TypeError("Could not find a safe native project location for push notifications.");
|
|
6242
|
+
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
6243
|
+
};
|
|
6244
|
+
var configureAndroid2 = async (config, plan) => {
|
|
6245
|
+
const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
6246
|
+
const source = await readFile15(path, "utf8");
|
|
6247
|
+
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
6248
|
+
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
6249
|
+
`);
|
|
6250
|
+
const region = content ? ` ${START_MARKER2}
|
|
6251
|
+
${content}
|
|
6252
|
+
${END_MARKER2}
|
|
6253
|
+
` : "";
|
|
6254
|
+
const application = source.indexOf("<application");
|
|
6255
|
+
const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
|
|
6256
|
+
`, application) + 1;
|
|
6257
|
+
const nextManifest = managed(source, region, insertion);
|
|
6258
|
+
if (!plan.capabilities.includes("pushNotifications"))
|
|
6259
|
+
return writeChangedFile2(path, nextManifest);
|
|
6260
|
+
const firebaseSource = await optionalSource(config.pushAndroidGoogleServicesFile);
|
|
6261
|
+
if (firebaseSource === null)
|
|
6262
|
+
throw new TypeError(`Push notifications require Firebase config at ${config.pushAndroidGoogleServicesFile}. Set mobile.pushNotifications.android.googleServicesFile to override it.`);
|
|
6263
|
+
let firebase;
|
|
6264
|
+
try {
|
|
6265
|
+
firebase = JSON.parse(firebaseSource);
|
|
6266
|
+
} catch (error) {
|
|
6267
|
+
throw new TypeError("Android google-services.json is invalid JSON.", {
|
|
6268
|
+
cause: error
|
|
6269
|
+
});
|
|
6270
|
+
}
|
|
6271
|
+
const clients = typeof firebase === "object" && firebase !== null ? Reflect.get(firebase, "client") : undefined;
|
|
6272
|
+
const matchesApp = Array.isArray(clients) && clients.some((client) => {
|
|
6273
|
+
const info = typeof client === "object" && client !== null ? Reflect.get(client, "client_info") : undefined;
|
|
6274
|
+
const android = typeof info === "object" && info !== null ? Reflect.get(info, "android_client_info") : undefined;
|
|
6275
|
+
return typeof android === "object" && android !== null && Reflect.get(android, "package_name") === config.appId;
|
|
6276
|
+
});
|
|
6277
|
+
if (!matchesApp)
|
|
6278
|
+
throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
|
|
6279
|
+
const [manifestChanged, firebaseChanged] = await Promise.all([
|
|
6280
|
+
writeChangedFile2(path, nextManifest),
|
|
6281
|
+
writeOptionalChangedFile(join16(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
|
|
6282
|
+
]);
|
|
6283
|
+
return manifestChanged || firebaseChanged;
|
|
6284
|
+
};
|
|
6285
|
+
var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
|
|
6286
|
+
const results = await Promise.all(platforms.map(async (platform) => ({
|
|
6287
|
+
didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
|
|
6288
|
+
platform
|
|
6289
|
+
})));
|
|
6290
|
+
return {
|
|
6291
|
+
changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
|
|
6292
|
+
};
|
|
6293
|
+
};
|
|
4108
6294
|
// src/mobile/releasePublisher.ts
|
|
4109
6295
|
import { access as access9 } from "fs/promises";
|
|
4110
|
-
import { isAbsolute as
|
|
6296
|
+
import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
|
|
4111
6297
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
4112
6298
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
4113
6299
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -4133,10 +6319,10 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
4133
6319
|
var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4134
6320
|
var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
|
|
4135
6321
|
var publisherModulePath = (projectRoot, requested) => {
|
|
4136
|
-
const root =
|
|
4137
|
-
const path =
|
|
4138
|
-
const projectRelative =
|
|
4139
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
6322
|
+
const root = resolve13(projectRoot);
|
|
6323
|
+
const path = resolve13(root, requested);
|
|
6324
|
+
const projectRelative = relative10(root, path);
|
|
6325
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
|
|
4140
6326
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
4141
6327
|
}
|
|
4142
6328
|
return path;
|
|
@@ -4204,67 +6390,112 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
4204
6390
|
return publication;
|
|
4205
6391
|
};
|
|
4206
6392
|
// src/mobile/routeMetadataTransform.ts
|
|
4207
|
-
import { existsSync as existsSync3, readFileSync as
|
|
4208
|
-
import { dirname as
|
|
4209
|
-
import
|
|
6393
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
6394
|
+
import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
|
|
6395
|
+
import ts2 from "typescript";
|
|
4210
6396
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
4211
6397
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
4212
|
-
var
|
|
6398
|
+
var PAGE_HANDLERS = new Map([
|
|
6399
|
+
[
|
|
6400
|
+
"handleHTMLPageRequest",
|
|
6401
|
+
{ framework: "html", inputKind: "static", propsProperty: "props" }
|
|
6402
|
+
],
|
|
6403
|
+
[
|
|
6404
|
+
"handleHTMXPageRequest",
|
|
6405
|
+
{ framework: "htmx", inputKind: "static", propsProperty: "props" }
|
|
6406
|
+
],
|
|
6407
|
+
[
|
|
6408
|
+
"handleAngularPageRequest",
|
|
6409
|
+
{
|
|
6410
|
+
bundleProperty: "indexPath",
|
|
6411
|
+
framework: "angular",
|
|
6412
|
+
propsProperty: "requestContext",
|
|
6413
|
+
sourceProperty: "pagePath"
|
|
6414
|
+
}
|
|
6415
|
+
],
|
|
6416
|
+
[
|
|
6417
|
+
"handleReactPageRequest",
|
|
6418
|
+
{
|
|
6419
|
+
bundleProperty: "index",
|
|
6420
|
+
framework: "react",
|
|
6421
|
+
pageProperty: "Page",
|
|
6422
|
+
propsProperty: "props"
|
|
6423
|
+
}
|
|
6424
|
+
],
|
|
6425
|
+
[
|
|
6426
|
+
"handleSveltePageRequest",
|
|
6427
|
+
{
|
|
6428
|
+
bundleProperty: "indexPath",
|
|
6429
|
+
framework: "svelte",
|
|
6430
|
+
propsProperty: "props",
|
|
6431
|
+
sourceProperty: "pagePath"
|
|
6432
|
+
}
|
|
6433
|
+
],
|
|
6434
|
+
[
|
|
6435
|
+
"handleVuePageRequest",
|
|
6436
|
+
{
|
|
6437
|
+
bundleProperty: "indexPath",
|
|
6438
|
+
framework: "vue",
|
|
6439
|
+
propsProperty: "props",
|
|
6440
|
+
sourceProperty: "pagePath"
|
|
6441
|
+
}
|
|
6442
|
+
]
|
|
6443
|
+
]);
|
|
4213
6444
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
4214
|
-
var findTsconfig = (entry, projectRoot) =>
|
|
6445
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
|
|
4215
6446
|
var createProgram = (entry, projectRoot) => {
|
|
4216
6447
|
const configPath = findTsconfig(entry, projectRoot);
|
|
4217
6448
|
if (!configPath) {
|
|
4218
|
-
return
|
|
6449
|
+
return ts2.createProgram([entry], {
|
|
4219
6450
|
allowJs: true,
|
|
4220
|
-
jsx:
|
|
4221
|
-
module:
|
|
4222
|
-
moduleResolution:
|
|
4223
|
-
target:
|
|
6451
|
+
jsx: ts2.JsxEmit.ReactJSX,
|
|
6452
|
+
module: ts2.ModuleKind.ESNext,
|
|
6453
|
+
moduleResolution: ts2.ModuleResolutionKind.Bundler,
|
|
6454
|
+
target: ts2.ScriptTarget.ESNext
|
|
4224
6455
|
});
|
|
4225
6456
|
}
|
|
4226
|
-
const parsed =
|
|
6457
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
|
|
4227
6458
|
if (!parsed.fileNames.includes(entry))
|
|
4228
6459
|
parsed.fileNames.push(entry);
|
|
4229
|
-
return
|
|
6460
|
+
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
4230
6461
|
};
|
|
4231
6462
|
var propertyName = (property) => {
|
|
4232
6463
|
if (!("name" in property) || !property.name)
|
|
4233
6464
|
return;
|
|
4234
|
-
if (
|
|
6465
|
+
if (ts2.isIdentifier(property.name))
|
|
4235
6466
|
return property.name.text;
|
|
4236
|
-
if (
|
|
6467
|
+
if (ts2.isStringLiteralLike(property.name))
|
|
4237
6468
|
return property.name.text;
|
|
4238
6469
|
return;
|
|
4239
6470
|
};
|
|
4240
|
-
var objectPropertyExpression = (
|
|
4241
|
-
const property =
|
|
4242
|
-
if (property &&
|
|
6471
|
+
var objectPropertyExpression = (object3, name) => {
|
|
6472
|
+
const property = object3.properties.find((candidate) => propertyName(candidate) === name);
|
|
6473
|
+
if (property && ts2.isPropertyAssignment(property)) {
|
|
4243
6474
|
return property.initializer;
|
|
4244
6475
|
}
|
|
4245
|
-
if (property &&
|
|
6476
|
+
if (property && ts2.isShorthandPropertyAssignment(property)) {
|
|
4246
6477
|
return property.name;
|
|
4247
6478
|
}
|
|
4248
6479
|
return;
|
|
4249
6480
|
};
|
|
4250
6481
|
var serializeType = (type, checker, ancestors = new Set) => {
|
|
4251
|
-
if (type.flags &
|
|
6482
|
+
if (type.flags & ts2.TypeFlags.Any)
|
|
4252
6483
|
return { type: "any" };
|
|
4253
|
-
if (type.flags &
|
|
6484
|
+
if (type.flags & ts2.TypeFlags.Unknown)
|
|
4254
6485
|
return { type: "unknown" };
|
|
4255
|
-
if (type.flags &
|
|
6486
|
+
if (type.flags & ts2.TypeFlags.Never)
|
|
4256
6487
|
return { type: "never" };
|
|
4257
|
-
if (type.flags &
|
|
6488
|
+
if (type.flags & ts2.TypeFlags.StringLike)
|
|
4258
6489
|
return { type: "string" };
|
|
4259
|
-
if (type.flags &
|
|
6490
|
+
if (type.flags & ts2.TypeFlags.NumberLike)
|
|
4260
6491
|
return { type: "number" };
|
|
4261
|
-
if (type.flags &
|
|
6492
|
+
if (type.flags & ts2.TypeFlags.BooleanLike)
|
|
4262
6493
|
return { type: "boolean" };
|
|
4263
|
-
if (type.flags &
|
|
6494
|
+
if (type.flags & ts2.TypeFlags.BigIntLike)
|
|
4264
6495
|
return { type: "bigint" };
|
|
4265
|
-
if (type.flags &
|
|
6496
|
+
if (type.flags & ts2.TypeFlags.Null)
|
|
4266
6497
|
return { type: "null" };
|
|
4267
|
-
if (type.flags &
|
|
6498
|
+
if (type.flags & ts2.TypeFlags.Undefined)
|
|
4268
6499
|
return { type: "undefined" };
|
|
4269
6500
|
if (type.isUnion()) {
|
|
4270
6501
|
return {
|
|
@@ -4278,11 +6509,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
4278
6509
|
}
|
|
4279
6510
|
if (ancestors.has(type)) {
|
|
4280
6511
|
return {
|
|
4281
|
-
ref: checker.typeToString(type, undefined,
|
|
6512
|
+
ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
4282
6513
|
};
|
|
4283
6514
|
}
|
|
4284
6515
|
ancestors.add(type);
|
|
4285
|
-
const arrayElement = checker.getIndexTypeOfType(type,
|
|
6516
|
+
const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
|
|
4286
6517
|
const properties = checker.getPropertiesOfType(type);
|
|
4287
6518
|
let schema;
|
|
4288
6519
|
if (arrayElement && properties.some(({ name }) => name === "length")) {
|
|
@@ -4297,7 +6528,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
4297
6528
|
return [
|
|
4298
6529
|
property.name,
|
|
4299
6530
|
{
|
|
4300
|
-
optional: Boolean(property.flags &
|
|
6531
|
+
optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
|
|
4301
6532
|
schema: serializeType(propertyType, checker, ancestors)
|
|
4302
6533
|
}
|
|
4303
6534
|
];
|
|
@@ -4305,7 +6536,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
|
|
|
4305
6536
|
schema = { properties: Object.fromEntries(entries), type: "object" };
|
|
4306
6537
|
} else {
|
|
4307
6538
|
schema = {
|
|
4308
|
-
type: checker.typeToString(type, undefined,
|
|
6539
|
+
type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
|
|
4309
6540
|
};
|
|
4310
6541
|
}
|
|
4311
6542
|
ancestors.delete(type);
|
|
@@ -4323,25 +6554,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
|
|
|
4323
6554
|
};
|
|
4324
6555
|
var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
|
|
4325
6556
|
let symbol = checker.getSymbolAtLocation(expression);
|
|
4326
|
-
if (symbol?.flags && symbol.flags &
|
|
6557
|
+
if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
|
|
4327
6558
|
symbol = checker.getAliasedSymbol(symbol);
|
|
4328
6559
|
}
|
|
4329
6560
|
const declaration = symbol?.declarations?.[0];
|
|
4330
6561
|
const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
|
|
4331
6562
|
const exportedName = symbol?.name ?? expression.getText(sourceFile);
|
|
4332
|
-
const source = posixPath(
|
|
6563
|
+
const source = posixPath(relative11(projectRoot, file));
|
|
4333
6564
|
return `${source}#${exportedName}`;
|
|
4334
6565
|
};
|
|
4335
6566
|
var resolveAlias = (symbol, checker) => {
|
|
4336
|
-
if (!(symbol.flags &
|
|
6567
|
+
if (!(symbol.flags & ts2.SymbolFlags.Alias))
|
|
4337
6568
|
return symbol;
|
|
4338
6569
|
return checker.getAliasedSymbol(symbol);
|
|
4339
6570
|
};
|
|
4340
6571
|
var assetKey = (expression, checker, seen = new Set) => {
|
|
4341
6572
|
if (!expression)
|
|
4342
6573
|
return;
|
|
4343
|
-
if (
|
|
4344
|
-
const unresolved =
|
|
6574
|
+
if (ts2.isIdentifier(expression)) {
|
|
6575
|
+
const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
|
|
4345
6576
|
if (!unresolved)
|
|
4346
6577
|
return;
|
|
4347
6578
|
const symbol = resolveAlias(unresolved, checker);
|
|
@@ -4349,28 +6580,123 @@ var assetKey = (expression, checker, seen = new Set) => {
|
|
|
4349
6580
|
return;
|
|
4350
6581
|
seen.add(symbol);
|
|
4351
6582
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
4352
|
-
if (!declaration || !
|
|
6583
|
+
if (!declaration || !ts2.isVariableDeclaration(declaration))
|
|
4353
6584
|
return;
|
|
4354
6585
|
return assetKey(declaration.initializer, checker, seen);
|
|
4355
6586
|
}
|
|
4356
|
-
if (!
|
|
6587
|
+
if (!ts2.isCallExpression(expression))
|
|
4357
6588
|
return;
|
|
4358
|
-
if (!
|
|
6589
|
+
if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
|
|
4359
6590
|
return;
|
|
4360
6591
|
}
|
|
4361
6592
|
const [, key] = expression.arguments;
|
|
4362
|
-
return key &&
|
|
6593
|
+
return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
|
|
6594
|
+
};
|
|
6595
|
+
var staticString = (expression, bindings) => {
|
|
6596
|
+
if (ts2.isStringLiteralLike(expression))
|
|
6597
|
+
return expression.text;
|
|
6598
|
+
if (ts2.isIdentifier(expression))
|
|
6599
|
+
return bindings.get(expression.text);
|
|
6600
|
+
if (ts2.isNoSubstitutionTemplateLiteral(expression))
|
|
6601
|
+
return expression.text;
|
|
6602
|
+
if (!ts2.isTemplateExpression(expression))
|
|
6603
|
+
return;
|
|
6604
|
+
let value = expression.head.text;
|
|
6605
|
+
for (const span of expression.templateSpans) {
|
|
6606
|
+
const substitution = staticString(span.expression, bindings);
|
|
6607
|
+
if (substitution === undefined)
|
|
6608
|
+
return;
|
|
6609
|
+
value += substitution + span.literal.text;
|
|
6610
|
+
}
|
|
6611
|
+
return value;
|
|
6612
|
+
};
|
|
6613
|
+
var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
|
|
6614
|
+
if (!expression)
|
|
6615
|
+
return;
|
|
6616
|
+
if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
|
|
6617
|
+
const [, key] = expression.arguments;
|
|
6618
|
+
return key ? staticString(key, bindings) : undefined;
|
|
6619
|
+
}
|
|
6620
|
+
return assetKey(expression, checker);
|
|
6621
|
+
};
|
|
6622
|
+
var callableObject = (call, checker) => {
|
|
6623
|
+
const symbol = checker.getSymbolAtLocation(call.expression);
|
|
6624
|
+
const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
|
|
6625
|
+
const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
|
|
6626
|
+
let callable;
|
|
6627
|
+
if (declaration && ts2.isFunctionDeclaration(declaration)) {
|
|
6628
|
+
callable = declaration;
|
|
6629
|
+
} else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
|
|
6630
|
+
callable = declaration.initializer;
|
|
6631
|
+
}
|
|
6632
|
+
if (!callable)
|
|
6633
|
+
return;
|
|
6634
|
+
const bindings = new Map;
|
|
6635
|
+
callable.parameters.forEach((parameter, index) => {
|
|
6636
|
+
if (!ts2.isIdentifier(parameter.name))
|
|
6637
|
+
return;
|
|
6638
|
+
const argument = call.arguments[index];
|
|
6639
|
+
if (!argument)
|
|
6640
|
+
return;
|
|
6641
|
+
const value = staticString(argument, new Map);
|
|
6642
|
+
if (value !== undefined)
|
|
6643
|
+
bindings.set(parameter.name.text, value);
|
|
6644
|
+
});
|
|
6645
|
+
const { body } = callable;
|
|
6646
|
+
if (!body)
|
|
6647
|
+
return;
|
|
6648
|
+
const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
|
|
6649
|
+
if (ts2.isObjectLiteralExpression(expressionBody)) {
|
|
6650
|
+
return { bindings, object: expressionBody };
|
|
6651
|
+
}
|
|
6652
|
+
if (ts2.isBlock(body)) {
|
|
6653
|
+
const returned = body.statements.find(ts2.isReturnStatement)?.expression;
|
|
6654
|
+
if (returned && ts2.isObjectLiteralExpression(returned)) {
|
|
6655
|
+
return { bindings, object: returned };
|
|
6656
|
+
}
|
|
6657
|
+
}
|
|
6658
|
+
return;
|
|
6659
|
+
};
|
|
6660
|
+
var spreadObject = (expression, checker, bindings) => {
|
|
6661
|
+
if (ts2.isObjectLiteralExpression(expression)) {
|
|
6662
|
+
return { bindings, object: expression };
|
|
6663
|
+
}
|
|
6664
|
+
if (!ts2.isCallExpression(expression))
|
|
6665
|
+
return;
|
|
6666
|
+
return callableObject(expression, checker);
|
|
6667
|
+
};
|
|
6668
|
+
var objectAssetKey = (object3, name, checker, bindings = new Map) => {
|
|
6669
|
+
for (const property of [...object3.properties].reverse()) {
|
|
6670
|
+
if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
|
|
6671
|
+
return assetKeyWithBindings(property.name, checker, bindings);
|
|
6672
|
+
}
|
|
6673
|
+
if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
|
|
6674
|
+
return assetKeyWithBindings(property.initializer, checker, bindings);
|
|
6675
|
+
}
|
|
6676
|
+
if (!ts2.isSpreadAssignment(property))
|
|
6677
|
+
continue;
|
|
6678
|
+
const nestedObject = spreadObject(property.expression, checker, bindings);
|
|
6679
|
+
if (!nestedObject)
|
|
6680
|
+
continue;
|
|
6681
|
+
const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
|
|
6682
|
+
if (nested)
|
|
6683
|
+
return nested;
|
|
6684
|
+
}
|
|
6685
|
+
return;
|
|
4363
6686
|
};
|
|
4364
6687
|
var findPageCall = (nodes) => {
|
|
4365
6688
|
let found;
|
|
4366
6689
|
const visit = (candidate) => {
|
|
4367
6690
|
if (found)
|
|
4368
6691
|
return;
|
|
4369
|
-
if (
|
|
4370
|
-
|
|
6692
|
+
if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
|
|
6693
|
+
const definition = PAGE_HANDLERS.get(candidate.expression.text);
|
|
6694
|
+
if (!definition)
|
|
6695
|
+
return;
|
|
6696
|
+
found = { definition, node: candidate };
|
|
4371
6697
|
return;
|
|
4372
6698
|
}
|
|
4373
|
-
|
|
6699
|
+
ts2.forEachChild(candidate, visit);
|
|
4374
6700
|
};
|
|
4375
6701
|
for (const node of nodes)
|
|
4376
6702
|
visit(node);
|
|
@@ -4379,37 +6705,74 @@ var findPageCall = (nodes) => {
|
|
|
4379
6705
|
var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
|
|
4380
6706
|
var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
|
|
4381
6707
|
const callee = node.expression;
|
|
4382
|
-
if (!
|
|
6708
|
+
if (!ts2.isPropertyAccessExpression(callee))
|
|
4383
6709
|
return;
|
|
4384
6710
|
if (!ROUTE_METHODS.has(callee.name.text))
|
|
4385
6711
|
return;
|
|
4386
6712
|
const [routePath] = node.arguments;
|
|
4387
|
-
if (!routePath || !
|
|
6713
|
+
if (!routePath || !ts2.isStringLiteralLike(routePath))
|
|
4388
6714
|
return;
|
|
4389
|
-
const
|
|
6715
|
+
const foundPageCall = findPageCall(node.arguments.slice(1));
|
|
6716
|
+
const pageCall = foundPageCall?.node;
|
|
6717
|
+
const definition = foundPageCall?.definition;
|
|
4390
6718
|
const [input] = pageCall?.arguments ?? [];
|
|
4391
|
-
if (!pageCall || !input
|
|
6719
|
+
if (!pageCall || !input) {
|
|
4392
6720
|
return;
|
|
4393
6721
|
}
|
|
4394
|
-
|
|
4395
|
-
if (!page)
|
|
6722
|
+
if (!definition)
|
|
4396
6723
|
return;
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
6724
|
+
if (definition.inputKind === "static") {
|
|
6725
|
+
const bundleKey2 = assetKey(input, checker);
|
|
6726
|
+
if (!bundleKey2)
|
|
6727
|
+
return;
|
|
6728
|
+
const pageId2 = `${definition.framework}:${bundleKey2}`;
|
|
6729
|
+
const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
|
|
6730
|
+
properties: {},
|
|
6731
|
+
type: "object"
|
|
6732
|
+
});
|
|
6733
|
+
return {
|
|
6734
|
+
inputKind: "static",
|
|
6735
|
+
metadata: {
|
|
6736
|
+
bundleKey: bundleKey2,
|
|
6737
|
+
contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
|
|
6738
|
+
framework: definition.framework,
|
|
6739
|
+
pageId: pageId2,
|
|
6740
|
+
propsSchemaHash: propsSchemaHash2
|
|
6741
|
+
},
|
|
6742
|
+
pageCallStart: pageCall.getStart(sourceFile),
|
|
6743
|
+
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
|
|
6747
|
+
return;
|
|
6748
|
+
}
|
|
6749
|
+
const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
|
|
6750
|
+
const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
|
|
6751
|
+
if (definition.pageProperty && !page)
|
|
6752
|
+
return;
|
|
6753
|
+
if (definition.sourceProperty && !source)
|
|
6754
|
+
return;
|
|
6755
|
+
const props = objectPropertyExpression(input, definition.propsProperty);
|
|
6756
|
+
const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
|
|
4400
6757
|
if (!bundleKey)
|
|
4401
6758
|
return;
|
|
4402
|
-
const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot)
|
|
4403
|
-
|
|
6759
|
+
const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
|
|
6760
|
+
let propsType;
|
|
6761
|
+
if (page)
|
|
6762
|
+
propsType = pagePropsType(page, props, checker);
|
|
6763
|
+
else if (props)
|
|
6764
|
+
propsType = checker.getTypeAtLocation(props);
|
|
6765
|
+
const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
|
|
4404
6766
|
const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
|
|
4405
6767
|
const metadata = {
|
|
4406
6768
|
bundleKey,
|
|
4407
|
-
contract:
|
|
4408
|
-
framework:
|
|
6769
|
+
contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
|
|
6770
|
+
framework: definition.framework,
|
|
4409
6771
|
pageId,
|
|
4410
6772
|
propsSchemaHash
|
|
4411
6773
|
};
|
|
4412
6774
|
const result = {
|
|
6775
|
+
inputKind: "object",
|
|
4413
6776
|
metadata,
|
|
4414
6777
|
pageCallStart: pageCall.getStart(sourceFile),
|
|
4415
6778
|
routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
|
|
@@ -4422,21 +6785,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
|
|
|
4422
6785
|
byRouteCall: new Map
|
|
4423
6786
|
};
|
|
4424
6787
|
const visit = (node) => {
|
|
4425
|
-
const result =
|
|
6788
|
+
const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
|
|
4426
6789
|
if (result) {
|
|
4427
6790
|
analysis.byPageCall.set(result.pageCallStart, result);
|
|
4428
6791
|
analysis.byRouteCall.set(result.routeCallSpan, result);
|
|
4429
6792
|
}
|
|
4430
|
-
|
|
6793
|
+
ts2.forEachChild(node, visit);
|
|
4431
6794
|
};
|
|
4432
|
-
|
|
6795
|
+
ts2.forEachChild(sourceFile, visit);
|
|
4433
6796
|
return analysis;
|
|
4434
6797
|
};
|
|
4435
6798
|
var analyzeProgram = (program, projectRoot) => {
|
|
4436
6799
|
const checker = program.getTypeChecker();
|
|
4437
6800
|
const analyzed = new Map;
|
|
4438
6801
|
for (const sourceFile of program.getSourceFiles()) {
|
|
4439
|
-
const resolvedFile =
|
|
6802
|
+
const resolvedFile = resolve14(sourceFile.fileName);
|
|
4440
6803
|
if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
|
|
4441
6804
|
continue;
|
|
4442
6805
|
const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
|
|
@@ -4445,34 +6808,44 @@ var analyzeProgram = (program, projectRoot) => {
|
|
|
4445
6808
|
}
|
|
4446
6809
|
return analyzed;
|
|
4447
6810
|
};
|
|
4448
|
-
var metadataExpression = (metadata) =>
|
|
6811
|
+
var metadataExpression = (metadata) => ts2.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(key), ts2.factory.createStringLiteral(item))), false);
|
|
4449
6812
|
var routeOptions = (existing, metadata) => {
|
|
4450
|
-
const detail =
|
|
4451
|
-
|
|
6813
|
+
const detail = ts2.factory.createObjectLiteralExpression([
|
|
6814
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
4452
6815
|
]);
|
|
4453
6816
|
if (!existing) {
|
|
4454
|
-
return
|
|
4455
|
-
|
|
6817
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6818
|
+
ts2.factory.createPropertyAssignment("detail", detail)
|
|
4456
6819
|
]);
|
|
4457
6820
|
}
|
|
4458
|
-
return
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
6821
|
+
return ts2.factory.createObjectLiteralExpression([
|
|
6822
|
+
ts2.factory.createSpreadAssignment(existing),
|
|
6823
|
+
ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
|
|
6824
|
+
ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
|
|
6825
|
+
ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
|
|
4463
6826
|
]))
|
|
4464
6827
|
]);
|
|
4465
6828
|
};
|
|
4466
6829
|
var transformPageCall = (node, page) => {
|
|
4467
6830
|
if (!page)
|
|
4468
6831
|
return;
|
|
6832
|
+
if (page.inputKind === "static") {
|
|
6833
|
+
const [pagePath, existingOptions, ...rest] = node.arguments;
|
|
6834
|
+
if (!pagePath)
|
|
6835
|
+
return;
|
|
6836
|
+
const options = ts2.factory.createObjectLiteralExpression([
|
|
6837
|
+
...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
|
|
6838
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
6839
|
+
]);
|
|
6840
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
|
|
6841
|
+
}
|
|
4469
6842
|
const [input] = node.arguments;
|
|
4470
|
-
if (!input || !
|
|
6843
|
+
if (!input || !ts2.isObjectLiteralExpression(input))
|
|
4471
6844
|
return;
|
|
4472
|
-
return
|
|
4473
|
-
|
|
6845
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
|
|
6846
|
+
ts2.factory.updateObjectLiteralExpression(input, [
|
|
4474
6847
|
...input.properties,
|
|
4475
|
-
|
|
6848
|
+
ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
|
|
4476
6849
|
]),
|
|
4477
6850
|
...node.arguments.slice(1)
|
|
4478
6851
|
]);
|
|
@@ -4485,16 +6858,16 @@ var transformRouteCall = (node, route) => {
|
|
|
4485
6858
|
return;
|
|
4486
6859
|
const options = maybeHandler ? maybeOptions : undefined;
|
|
4487
6860
|
const handler = maybeHandler ?? maybeOptions;
|
|
4488
|
-
return
|
|
6861
|
+
return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
|
|
4489
6862
|
};
|
|
4490
6863
|
var transformFile = (source, fileName, analysis) => {
|
|
4491
|
-
const sourceFile =
|
|
6864
|
+
const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
|
|
4492
6865
|
const transformer = (context) => {
|
|
4493
6866
|
const visit = (node) => {
|
|
4494
|
-
if (!
|
|
4495
|
-
return
|
|
6867
|
+
if (!ts2.isCallExpression(node)) {
|
|
6868
|
+
return ts2.visitEachChild(node, visit, context);
|
|
4496
6869
|
}
|
|
4497
|
-
const transformedChildren =
|
|
6870
|
+
const transformedChildren = ts2.visitEachChild(node, visit, context);
|
|
4498
6871
|
const page = analysis.byPageCall.get(node.getStart(sourceFile));
|
|
4499
6872
|
const transformedPage = transformPageCall(transformedChildren, page);
|
|
4500
6873
|
if (transformedPage)
|
|
@@ -4505,45 +6878,45 @@ var transformFile = (source, fileName, analysis) => {
|
|
|
4505
6878
|
return transformedRoute;
|
|
4506
6879
|
return transformedChildren;
|
|
4507
6880
|
};
|
|
4508
|
-
return (node) =>
|
|
6881
|
+
return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
|
|
4509
6882
|
};
|
|
4510
|
-
const result =
|
|
6883
|
+
const result = ts2.transform(sourceFile, [transformer]);
|
|
4511
6884
|
try {
|
|
4512
6885
|
const [transformed] = result.transformed;
|
|
4513
6886
|
if (!transformed)
|
|
4514
6887
|
throw new TypeError("Mobile route transform failed.");
|
|
4515
|
-
return
|
|
6888
|
+
return ts2.createPrinter().printFile(transformed);
|
|
4516
6889
|
} finally {
|
|
4517
6890
|
result.dispose();
|
|
4518
6891
|
}
|
|
4519
6892
|
};
|
|
4520
6893
|
var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
|
|
4521
6894
|
var createAbsoluteMobileRouteMetadataPlugin = (options) => {
|
|
4522
|
-
const projectRoot =
|
|
4523
|
-
const entry =
|
|
6895
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6896
|
+
const entry = resolve14(options.entry);
|
|
4524
6897
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
4525
6898
|
return {
|
|
4526
6899
|
name: "absolute-mobile-route-metadata",
|
|
4527
6900
|
setup(build) {
|
|
4528
6901
|
build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
|
|
4529
|
-
const analysis = analyzed.get(
|
|
6902
|
+
const analysis = analyzed.get(resolve14(path));
|
|
4530
6903
|
if (!analysis)
|
|
4531
6904
|
return;
|
|
4532
6905
|
const source = await Bun.file(path).text();
|
|
4533
6906
|
return {
|
|
4534
6907
|
contents: transformFile(source, path, analysis),
|
|
4535
|
-
loader:
|
|
6908
|
+
loader: extname4(path).endsWith("x") ? "tsx" : "ts"
|
|
4536
6909
|
};
|
|
4537
6910
|
});
|
|
4538
6911
|
}
|
|
4539
6912
|
};
|
|
4540
6913
|
};
|
|
4541
6914
|
var inspectAbsoluteMobileRouteMetadata = (options) => {
|
|
4542
|
-
const projectRoot =
|
|
4543
|
-
const entry =
|
|
6915
|
+
const projectRoot = resolve14(options.projectRoot ?? process.cwd());
|
|
6916
|
+
const entry = resolve14(options.entry);
|
|
4544
6917
|
const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
|
|
4545
6918
|
return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
|
|
4546
|
-
file: posixPath(
|
|
6919
|
+
file: posixPath(relative11(projectRoot, file)),
|
|
4547
6920
|
metadata
|
|
4548
6921
|
})));
|
|
4549
6922
|
};
|
|
@@ -4552,17 +6925,30 @@ export {
|
|
|
4552
6925
|
waitForAbsoluteIosHmrLog,
|
|
4553
6926
|
verifyAbsoluteMobileCompatibilityProducer,
|
|
4554
6927
|
verifyAbsoluteMobileAssociationFiles,
|
|
6928
|
+
validateAbsoluteSshDestination,
|
|
6929
|
+
validateAbsoluteRemoteMacProfileName,
|
|
6930
|
+
syncAbsoluteRemoteMacProject,
|
|
6931
|
+
startAbsoluteRemoteIosDevSession,
|
|
4555
6932
|
startAbsoluteIosDevSession,
|
|
6933
|
+
serializeAbsoluteMobileAuthEnvironment,
|
|
4556
6934
|
runWithAbsoluteMobileProducer,
|
|
6935
|
+
runAbsoluteAndroidUpgradeConformance,
|
|
4557
6936
|
retainAbsoluteMobileCompatibilityArtifacts,
|
|
4558
6937
|
resolveAbsoluteMobileRoute,
|
|
6938
|
+
resolveAbsoluteMobileNavigation,
|
|
4559
6939
|
resolveAbsoluteMobileDeepLink,
|
|
4560
6940
|
resolveAbsoluteMobileCompatibilityRelease,
|
|
6941
|
+
resolveAbsoluteMobileAuthManifest,
|
|
6942
|
+
resolveAbsoluteDeviceCapabilityPlan,
|
|
4561
6943
|
repairAbsoluteIosDevSession,
|
|
6944
|
+
removeAbsoluteRemoteMacProfile,
|
|
4562
6945
|
redactAbsoluteIosLog,
|
|
4563
6946
|
readAbsoluteMobileMaterializedReleases,
|
|
4564
6947
|
publishAbsoluteIosRelease,
|
|
4565
6948
|
publishAbsoluteAndroidRelease,
|
|
6949
|
+
projectUsesAbsoluteSync,
|
|
6950
|
+
projectUsesAbsoluteAuth,
|
|
6951
|
+
projectImportsAbsoluteDeviceCapability,
|
|
4566
6952
|
prepareAbsoluteIosRelease,
|
|
4567
6953
|
prepareAbsoluteIosDevProject,
|
|
4568
6954
|
prepareAbsoluteAndroidRelease,
|
|
@@ -4575,23 +6961,41 @@ export {
|
|
|
4575
6961
|
parseAbsoluteMobileBuildPageMetadata,
|
|
4576
6962
|
parseAbsoluteIosLogLine,
|
|
4577
6963
|
parseAbsoluteIosHmrLog,
|
|
6964
|
+
parseAbsoluteAndroidInstalledApp,
|
|
6965
|
+
pairAbsoluteRemoteMac,
|
|
4578
6966
|
normalizeAbsoluteMobileConfig,
|
|
4579
6967
|
navigateAbsoluteMobilePage,
|
|
6968
|
+
missingAbsoluteDeviceCapabilityPackages,
|
|
6969
|
+
materializeAbsoluteRemoteMacAgent,
|
|
4580
6970
|
materializeAbsoluteMobileCompatibilityBundle,
|
|
4581
6971
|
materializeAbsoluteMobileAssociationFiles,
|
|
4582
6972
|
materializeAbsoluteCapacitorWebBundle,
|
|
4583
6973
|
matchesAbsoluteMobileRoutePattern,
|
|
4584
6974
|
loadAbsoluteNativeReleasePublisher,
|
|
4585
6975
|
loadAbsoluteMobileMaterializedBundle,
|
|
6976
|
+
loadAbsoluteDeviceCapabilityProviders,
|
|
6977
|
+
listAbsoluteRemoteMacProfiles,
|
|
4586
6978
|
isAbsoluteIosNativeRootInput,
|
|
6979
|
+
installAbsoluteRemoteMacAgent,
|
|
6980
|
+
installAbsoluteMobileSyncRemediation,
|
|
6981
|
+
installAbsoluteMobileAuthEnvironment,
|
|
6982
|
+
inspectAbsoluteRemoteMac,
|
|
4587
6983
|
inspectAbsoluteMobileRouteMetadata,
|
|
6984
|
+
inspectAbsoluteAndroidInstalledApp,
|
|
4588
6985
|
hashAbsoluteMobilePropsSchema,
|
|
4589
6986
|
getCurrentAbsoluteMobileProducerContext,
|
|
6987
|
+
getAbsoluteRemoteMacProfile,
|
|
6988
|
+
getAbsoluteMobileSyncRemediation,
|
|
4590
6989
|
fingerprintAbsoluteIosNativeProject,
|
|
4591
6990
|
fingerprintAbsoluteIosDevProject,
|
|
4592
6991
|
finalizeAbsoluteMobilePage,
|
|
4593
6992
|
finalizeAbsoluteMobileCompatibilityBuild,
|
|
4594
6993
|
fetchAbsoluteMobilePage,
|
|
6994
|
+
disposeAbsoluteMobilePage,
|
|
6995
|
+
discoverAbsoluteSyncSchema,
|
|
6996
|
+
discoverAbsoluteDeviceCapabilities,
|
|
6997
|
+
directAbsoluteProjectPackages,
|
|
6998
|
+
createAbsoluteRemoteIosDevProject,
|
|
4595
6999
|
createAbsoluteMobileUpgradeResponse,
|
|
4596
7000
|
createAbsoluteMobileRouteMetadataPlugin,
|
|
4597
7001
|
createAbsoluteMobilePageRequest,
|
|
@@ -4601,6 +7005,7 @@ export {
|
|
|
4601
7005
|
createAbsoluteMobileCompatibilityDispatcher,
|
|
4602
7006
|
createAbsoluteMobileCompatibilityArtifact,
|
|
4603
7007
|
createAbsoluteMobileBlobArtifactStore,
|
|
7008
|
+
createAbsoluteMobileAuthManifest,
|
|
4604
7009
|
createAbsoluteMobileAssociationPlugin,
|
|
4605
7010
|
createAbsoluteMobileAssociationDocuments,
|
|
4606
7011
|
createAbsoluteIosNativeWatcher,
|
|
@@ -4609,13 +7014,23 @@ export {
|
|
|
4609
7014
|
buildAbsoluteMobileCompatibilityRelease,
|
|
4610
7015
|
buildAbsoluteIosRelease,
|
|
4611
7016
|
buildAbsoluteAndroidRelease,
|
|
7017
|
+
assertAbsoluteDeviceCapabilityPackages,
|
|
7018
|
+
applyAbsoluteNativeDeviceCapabilities,
|
|
4612
7019
|
applyAbsoluteNativeDeepLinks,
|
|
4613
7020
|
activateAbsoluteMobilePage,
|
|
4614
7021
|
acceptsAbsoluteMobilePage,
|
|
7022
|
+
absoluteRemoteProjectSyncCommands,
|
|
7023
|
+
absoluteRemoteMacSshBase,
|
|
7024
|
+
absoluteDeviceNativeRequirements,
|
|
4615
7025
|
MOBILE_PAGE_REQUEST_HEADERS,
|
|
4616
7026
|
AbsoluteMobilePageProtocolError,
|
|
4617
7027
|
APPLE_ASSOCIATION_PATH,
|
|
4618
7028
|
ANDROID_ASSOCIATION_PATH,
|
|
7029
|
+
ABSOLUTE_SYNC_PACKAGE,
|
|
7030
|
+
ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
|
|
7031
|
+
ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
|
|
7032
|
+
ABSOLUTE_NATIVE_AUTH_SCOPES,
|
|
7033
|
+
ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
|
|
4619
7034
|
ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
|
|
4620
7035
|
ABSOLUTE_MOBILE_ROUTE_DETAIL,
|
|
4621
7036
|
ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
|
|
@@ -4626,8 +7041,9 @@ export {
|
|
|
4626
7041
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
4627
7042
|
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
4628
7043
|
ABSOLUTE_IOS_RELEASE_FORMAT,
|
|
7044
|
+
ABSOLUTE_AUTH_PACKAGE,
|
|
4629
7045
|
ABSOLUTE_ANDROID_RELEASE_FORMAT
|
|
4630
7046
|
};
|
|
4631
7047
|
|
|
4632
|
-
//# debugId=
|
|
7048
|
+
//# debugId=D67F614C7C9ABE9264756E2164756E21
|
|
4633
7049
|
//# sourceMappingURL=index.js.map
|