@absolutejs/absolute 0.20.0-beta.46 → 0.20.0-beta.47

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.
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-dpszxM/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-e0fFLY/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-dpszxM/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-e0fFLY/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
48
48
  getWarningController()?.maybeWarn(primitiveName);
49
49
  };
50
50
 
51
- // .angular-partial-tmp-dpszxM/src/core/streamingSlotRegistry.ts
51
+ // .angular-partial-tmp-e0fFLY/src/core/streamingSlotRegistry.ts
52
52
  var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
53
53
  var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
54
54
  var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
package/dist/cli/index.js CHANGED
@@ -718,7 +718,7 @@ var init_portScan = () => {};
718
718
 
719
719
  // src/mobile/config.ts
720
720
  import { resolve as resolve2 } from "path";
721
- var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, HOSTNAME_PATTERN, resolveProjectPath = (projectRoot, value, field) => {
721
+ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field) => {
722
722
  const root = resolve2(projectRoot);
723
723
  const path = resolve2(root, value);
724
724
  if (path !== root && !path.startsWith(`${root}/`)) {
@@ -791,11 +791,31 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
791
791
  }
792
792
  return value.match(/.{2}/g)?.join(":") ?? value;
793
793
  }))
794
- ].sort(), normalizeExpoNativeRoutes = (config, projectRoot) => {
794
+ ].sort(), validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
795
+ if (segment === "*" && (index !== count - 1 || count === 1)) {
796
+ throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
797
+ }
798
+ if (segment === "*")
799
+ return;
800
+ if (!segment.startsWith(":") && (segment.includes("*") || segment.includes(":"))) {
801
+ throw new TypeError(`mobile.routes.native route ${path} contains invalid segment ${segment}.`);
802
+ }
803
+ if (!segment.startsWith(":"))
804
+ return;
805
+ const name = segment.slice(1);
806
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/u.test(name)) {
807
+ throw new TypeError(`mobile.routes.native route ${path} has invalid parameter ${segment}.`);
808
+ }
809
+ if (parameters.has(name)) {
810
+ throw new TypeError(`mobile.routes.native route ${path} repeats parameter ${segment}.`);
811
+ }
812
+ parameters.add(name);
813
+ }, normalizeExpoNativeRoutes = (config, projectRoot) => {
795
814
  if (config.engine !== "expo")
796
815
  return {};
797
816
  const routes = config.routes?.native ?? {};
798
817
  const normalized = {};
818
+ const ownership = new Map;
799
819
  for (const [route, module] of Object.entries(routes)) {
800
820
  const path = normalizeEntry(route);
801
821
  if (path.includes("?") || path.includes("#") || path !== "/" && path.endsWith("/")) {
@@ -804,9 +824,18 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
804
824
  if (path === "/__absolute/native") {
805
825
  throw new TypeError("mobile.routes.native reserves /__absolute/native for the Expo diagnostic screen.");
806
826
  }
807
- if (path.includes("*") || path.includes(":")) {
808
- throw new TypeError(`mobile.routes.native route ${path} must be static during the Expo experiment; parameters and wildcards are not supported yet.`);
827
+ const segments = path.split("/").filter(Boolean);
828
+ if (segments[0] && EXPO_RESERVED_ROUTE_PREFIXES.has(segments[0])) {
829
+ throw new TypeError(`mobile.routes.native route ${path} conflicts with an Expo Router or Metro reserved path.`);
830
+ }
831
+ const parameters = new Set;
832
+ segments.forEach((segment, index) => validateExpoNativeRouteSegment(path, segment, index, segments.length, parameters));
833
+ const signature = segments.map((segment) => segment.startsWith(":") ? ":" : segment).join("/");
834
+ const existing = ownership.get(signature);
835
+ if (existing) {
836
+ throw new TypeError(`mobile.routes.native routes ${existing} and ${path} claim the same Expo route pattern.`);
809
837
  }
838
+ ownership.set(signature, path);
810
839
  normalized[path] = resolveProjectPath(projectRoot, requireText(module, `mobile.routes.native[${path}]`), `mobile.routes.native[${path}]`);
811
840
  }
812
841
  return Object.fromEntries(Object.entries(normalized).sort(([left], [right]) => left.localeCompare(right)));
@@ -845,6 +874,16 @@ var init_config = __esm(() => {
845
874
  APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
846
875
  CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
847
876
  HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
877
+ EXPO_RESERVED_ROUTE_PREFIXES = new Set([
878
+ "_expo",
879
+ "_flight",
880
+ "_sitemap",
881
+ "assets",
882
+ "expo-dev-plugins",
883
+ "inspector",
884
+ "manifest",
885
+ "public"
886
+ ]);
848
887
  });
849
888
 
850
889
  // src/mobile/nativeAuth.ts
@@ -1600,7 +1639,7 @@ import {
1600
1639
  writeFile
1601
1640
  } from "fs/promises";
1602
1641
  import { createHash } from "crypto";
1603
- import { basename as basename2, dirname as dirname4, join as join8, relative as relative2, resolve as resolve5 } from "path";
1642
+ import { basename as basename2, dirname as dirname4, join as join8, relative as relative2, resolve as resolve5, sep } from "path";
1604
1643
  var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
1605
1644
  `, EXPO_ASSET_EXTENSION = ".absasset", EXPO_PROJECT_MARKER = ".absolutejs-expo-project", exists = async (path) => {
1606
1645
  try {
@@ -1612,14 +1651,19 @@ var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts
1612
1651
  }, portableRelative = (from, destination) => {
1613
1652
  const value = relative2(from, destination).replaceAll("\\", "/");
1614
1653
  return value.startsWith(".") ? value : `./${value}`;
1615
- }, routeSegments = (route) => route.split("/").filter(Boolean).map((segment) => {
1616
- if (segment.startsWith(":"))
1617
- return `[${segment.slice(1)}]`;
1618
- if (segment === "." || segment === ".." || !/^[A-Za-z0-9._~-]+$/u.test(segment)) {
1619
- throw new TypeError(`Expo native route ${route} contains an unsupported segment ${segment}.`);
1620
- }
1621
- return segment;
1622
- }), routeFile = (project, route) => join8(project, "app", ...routeSegments(route), "index.tsx"), packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
1654
+ }, routeSegments = (route) => {
1655
+ const segments = route.split("/").filter(Boolean);
1656
+ return segments.map((segment, index) => {
1657
+ if (segment.startsWith(":"))
1658
+ return `[${segment.slice(1)}]`;
1659
+ if (segment === "*" && index === segments.length - 1)
1660
+ return "[...absoluteWildcard]";
1661
+ if (segment === "." || segment === ".." || !/^[A-Za-z0-9._~-]+$/u.test(segment)) {
1662
+ throw new TypeError(`Expo native route ${route} contains an unsupported segment ${segment}.`);
1663
+ }
1664
+ return segment;
1665
+ });
1666
+ }, routeFile = (project, route) => join8(project, "app", ...routeSegments(route), "index.tsx"), packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
1623
1667
  const separator = spec.lastIndexOf("@");
1624
1668
  return [spec.slice(0, separator), spec.slice(separator + 1)];
1625
1669
  })), expoPackage = (auth, sync, devices) => ({
@@ -2005,6 +2049,7 @@ export const createAbsoluteExpoSyncBridge = async (
2005
2049
  "/__absolute/native",
2006
2050
  ...Object.keys(config.expoNativeRoutes)
2007
2051
  ];
2052
+ const nativeRoutePatterns = nativeRoutes.map((route) => route.split("/").filter(Boolean));
2008
2053
  return `${EXPO_GENERATED_HEADER}import * as Linking from 'expo-linking';
2009
2054
  import { router, usePathname } from 'expo-router';
2010
2055
  import { useEffect, useRef, useState } from 'react';
@@ -2019,7 +2064,7 @@ ${sync ? "import { createAbsoluteExpoSyncBridge, startAbsoluteExpoSync } from '.
2019
2064
  const BRIDGE_FORMAT = 3;
2020
2065
  const MAX_MESSAGE_BYTES = 64 * 1024;
2021
2066
  const MAX_HTTP_BODY_BYTES = 48 * 1024;
2022
- const NATIVE_ROUTES = new Set(${JSON.stringify(nativeRoutes)});
2067
+ const NATIVE_ROUTE_PATTERNS = ${JSON.stringify(nativeRoutePatterns)};
2023
2068
  const PRODUCTION_ORIGIN = ${JSON.stringify(config.productionOrigin)};
2024
2069
  const DEV_ORIGIN = Platform.OS === 'android'
2025
2070
  ? process.env.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN
@@ -2028,6 +2073,19 @@ const HMR_TARGET = Platform.OS === 'android' ? 'expo-android' : 'expo-ios';
2028
2073
  const AUTH_ENABLED = ${auth ? "true" : "false"};
2029
2074
  const SYNC_ENABLED = ${sync ? "true" : "false"};
2030
2075
 
2076
+ const isNativeRoute = (pathname: string) => {
2077
+ const segments = pathname.split('/').filter(Boolean);
2078
+ return NATIVE_ROUTE_PATTERNS.some(pattern => {
2079
+ for (let index = 0; index < pattern.length; index += 1) {
2080
+ const expected = pattern[index]!;
2081
+ if (expected === '*') return segments.length > index;
2082
+ if (segments[index] === undefined) return false;
2083
+ if (!expected.startsWith(':') && expected !== segments[index]) return false;
2084
+ }
2085
+ return segments.length === pattern.length;
2086
+ });
2087
+ };
2088
+
2031
2089
  const bridgeBootstrap = (path: string) => {
2032
2090
  const initialPath = DEV_ORIGIN
2033
2091
  ? 'location.pathname + location.search + location.hash'
@@ -2103,7 +2161,7 @@ const bridgeBootstrap = (path: string) => {
2103
2161
  const anchor = event.target instanceof Element ? event.target.closest('a[href]') : null;
2104
2162
  if (!anchor) return;
2105
2163
  const url = new URL(anchor.href, location.href);
2106
- if (!${JSON.stringify(nativeRoutes)}.includes(url.pathname)) return;
2164
+ if (!isNativeRoute(url.pathname)) return;
2107
2165
  event.preventDefault();
2108
2166
  send({ format: 3, kind: 'event', event: 'navigation', path: url.pathname + url.search + url.hash });
2109
2167
  }, true);
@@ -2226,7 +2284,7 @@ export function AbsoluteWebHost() {
2226
2284
  if (message.kind === 'event' && (message.event === 'navigation' || message.event === 'ready')) {
2227
2285
  const target = new URL(message.path, PRODUCTION_ORIGIN);
2228
2286
  if (target.origin !== PRODUCTION_ORIGIN) return;
2229
- if (NATIVE_ROUTES.has(target.pathname)) router.push(message.path as never);
2287
+ if (isNativeRoute(target.pathname)) router.push(message.path as never);
2230
2288
  else activeWebPath.current = message.path;
2231
2289
  return;
2232
2290
  }
@@ -2332,6 +2390,17 @@ const styles = StyleSheet.create({ loading: { alignItems: 'center', flex: 1, jus
2332
2390
  await writeFile(temporary, source, { flag: "wx" });
2333
2391
  await rename(temporary, path);
2334
2392
  return true;
2393
+ }, pruneStaleManagedExpoRoutes = async (project, expected) => {
2394
+ const appDirectory = join8(project, "app");
2395
+ if (!await exists(appDirectory))
2396
+ return 0;
2397
+ const files = await walkFiles(appDirectory);
2398
+ const stale = (await Promise.all(files.map(async (path) => ({
2399
+ managed: path.endsWith(".tsx") && (await readFile(path, "utf8")).startsWith(EXPO_GENERATED_HEADER),
2400
+ path
2401
+ })))).filter(({ managed, path }) => managed && !expected.has(path));
2402
+ await Promise.all(stale.map(({ path }) => rm(path, { force: true })));
2403
+ return stale.length;
2335
2404
  }, jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
2336
2405
  `, emptyWebAssetsSource, writeAbsoluteExpoProject = async (config, options) => {
2337
2406
  if (config.engine !== "expo")
@@ -2425,8 +2494,9 @@ node_modules/
2425
2494
  const wrapper = route === "/" ? join8(project, "app", "index.tsx") : routeFile(project, route);
2426
2495
  files.set(wrapper, nativeWrapperSource(wrapper, module));
2427
2496
  }
2497
+ const removed = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join8(project, "app")}${sep}`))));
2428
2498
  const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
2429
- const changed = changes.filter(Boolean).length;
2499
+ const changed = removed + changes.filter(Boolean).length;
2430
2500
  return { changed, path: project, written: [...files.keys()] };
2431
2501
  }, walkFiles = async (root, directory = root) => {
2432
2502
  const entries = await readdir(directory, { withFileTypes: true });
@@ -3142,7 +3212,7 @@ import {
3142
3212
  join as join11,
3143
3213
  relative as relative4,
3144
3214
  resolve as resolve8,
3145
- sep,
3215
+ sep as sep2,
3146
3216
  win32
3147
3217
  } from "path";
3148
3218
  var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT = 1, NATIVE_CACHE_FORMAT = 1, HASH_RADIX = 16, EXECUTABLE_MODE_MASK = 73, NATIVE_PUBLIC_PATH_SEGMENTS = 5, CAPACITOR_PROJECT_DIRECTORY_PATTERN, ANDROID_TIMING_PHASES, androidTimingSummary = (timings, physicalDevice = false) => ANDROID_TIMING_PHASES.map(([phase, label]) => {
@@ -3324,7 +3394,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3324
3394
  const resolvedRoot = resolve8(root);
3325
3395
  const resolvedPath = resolve8(path);
3326
3396
  const relativePath = relative4(resolvedRoot, resolvedPath);
3327
- return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
3397
+ return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) && !isAbsolute(relativePath);
3328
3398
  }, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), nativeCachePath = (projectRoot) => join11(projectRoot, ".absolutejs", "mobile", "cache", "android-debug.json"), parseNativeCache = (value) => {
3329
3399
  if (!isRecord(value))
3330
3400
  return null;
@@ -3368,7 +3438,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3368
3438
  }
3369
3439
  return { dependencies, settings };
3370
3440
  }, shouldIgnoreNativePath = (relativePath, ignorePublicBundle) => {
3371
- const parts = relativePath.split(sep);
3441
+ const parts = relativePath.split(sep2);
3372
3442
  if (parts.includes(".gradle") || parts.includes("build") || parts.includes(".absolutejs-dependencies")) {
3373
3443
  return true;
3374
3444
  }
@@ -3377,7 +3447,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3377
3447
  const relativePath = relative4(root, path);
3378
3448
  if (shouldIgnoreNativePath(relativePath, ignorePublicBundle))
3379
3449
  return [];
3380
- const identity = `${label}:${relativePath.split(sep).join("/")}\x00`;
3450
+ const identity = `${label}:${relativePath.split(sep2).join("/")}\x00`;
3381
3451
  if (isDirectory) {
3382
3452
  return collectNativeDirectory(root, label, path, ignorePublicBundle);
3383
3453
  }
@@ -4442,7 +4512,7 @@ import {
4442
4512
  stat,
4443
4513
  writeFile as writeFile4
4444
4514
  } from "fs/promises";
4445
- import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, relative as relative5, resolve as resolve9, sep as sep2 } from "path";
4515
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, relative as relative5, resolve as resolve9, sep as sep3 } from "path";
4446
4516
  var developmentTeamArgument = (value) => {
4447
4517
  if (value === undefined)
4448
4518
  return;
@@ -4524,7 +4594,7 @@ var developmentTeamArgument = (value) => {
4524
4594
  const root = resolve9(projectRoot);
4525
4595
  const output = resolve9(root, requested ?? ".absolutejs/mobile/releases/ios");
4526
4596
  const projectRelative = relative5(root, output);
4527
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep2}`) || isAbsolute2(projectRelative)) {
4597
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute2(projectRelative)) {
4528
4598
  throw new TypeError("mobile build --outdir must remain inside the project.");
4529
4599
  }
4530
4600
  return output;
@@ -4720,7 +4790,7 @@ import {
4720
4790
  writeFile as writeFile5
4721
4791
  } from "fs/promises";
4722
4792
  import { isIP as isIP2 } from "net";
4723
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join14, relative as relative6, resolve as resolve10, sep as sep3 } from "path";
4793
+ import { dirname as dirname7, isAbsolute as isAbsolute3, join as join14, relative as relative6, resolve as resolve10, sep as sep4 } from "path";
4724
4794
  var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000, BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT2 = 1, NATIVE_CACHE_FORMAT2 = 1, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), pathExists4 = async (path) => {
4725
4795
  try {
4726
4796
  await access7(path);
@@ -4947,7 +5017,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
4947
5017
  };
4948
5018
  }, nativeCachePath2 = (projectRoot) => join14(projectRoot, ".absolutejs", "mobile", "ios-native-cache.json"), isInside2 = (root, path) => {
4949
5019
  const value = relative6(resolve10(root), resolve10(path));
4950
- return value === "" || !value.startsWith(`..${sep3}`) && value !== ".." && !isAbsolute3(value);
5020
+ return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute3(value);
4951
5021
  }, parseJournal2 = (value) => {
4952
5022
  if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT2)
4953
5023
  return null;
@@ -5624,7 +5694,7 @@ import {
5624
5694
  posix,
5625
5695
  relative as relative7,
5626
5696
  resolve as resolvePath,
5627
- sep as sep4
5697
+ sep as sep5
5628
5698
  } from "path";
5629
5699
  var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, REMOTE_SESSION_CLOSE_TIMEOUT_MS = 2000, PROFILE_NAME, SSH_DESTINATION, defaultProfilePath = () => join15(homedir4(), ".absolutejs", "mobile", "remote-macs.json"), emptyStore = () => ({
5630
5700
  format: PROFILE_FORMAT,
@@ -5884,7 +5954,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, REMOTE_SESSION_CLOSE_TI
5884
5954
  const bytes = await Bun.file(path).arrayBuffer();
5885
5955
  const sha256 = createHash6("sha256").update(new Uint8Array(bytes)).digest("hex");
5886
5956
  return { bytes: bytes.byteLength, path, sha256 };
5887
- }, portableRelativePath = (root, path) => relative7(root, path).split(sep4).join(posix.sep), portableMobileConfig = (project) => ({
5957
+ }, portableRelativePath = (root, path) => relative7(root, path).split(sep5).join(posix.sep), portableMobileConfig = (project) => ({
5888
5958
  appId: project.config.appId,
5889
5959
  appName: project.config.appName,
5890
5960
  bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
@@ -15887,7 +15957,7 @@ import {
15887
15957
  writeFileSync as writeFileSync19
15888
15958
  } from "fs";
15889
15959
  import { createRequire } from "module";
15890
- import { dirname as dirname25, join as join43, resolve as resolve32, sep as sep5 } from "path";
15960
+ import { dirname as dirname25, join as join43, resolve as resolve32, sep as sep6 } from "path";
15891
15961
  var DEPENDENCY_FIELDS, TYPE_GRAPH_PACKAGES, readManifest = (path) => {
15892
15962
  try {
15893
15963
  const parsed = JSON.parse(readFileSync35(path, "utf-8"));
@@ -16047,8 +16117,8 @@ var DEPENDENCY_FIELDS, TYPE_GRAPH_PACKAGES, readManifest = (path) => {
16047
16117
  }, removeDuplicateTypeGraphPackages = (report) => {
16048
16118
  const manifest = readManifest(join43(report.installRoot, "package.json")) ?? {};
16049
16119
  const rootName = manifestName(manifest, "<workspace>");
16050
- const installPrefix = `${realpathSync2(report.installRoot)}${sep5}`;
16051
- const nodeModulesSegment = `${sep5}node_modules${sep5}`;
16120
+ const installPrefix = `${realpathSync2(report.installRoot)}${sep6}`;
16121
+ const nodeModulesSegment = `${sep6}node_modules${sep6}`;
16052
16122
  const removed = [];
16053
16123
  const stalePaths = duplicateTypeGraphPackages(report).flatMap((duplicate) => {
16054
16124
  const selected = preferredIdentity(duplicate, rootName);
@@ -19916,7 +19986,7 @@ import {
19916
19986
  stat as stat3,
19917
19987
  writeFile as writeFile15
19918
19988
  } from "fs/promises";
19919
- import { dirname as dirname31, isAbsolute as isAbsolute7, join as join53, relative as relative27, resolve as resolve40, sep as sep6 } from "path";
19989
+ import { dirname as dirname31, isAbsolute as isAbsolute7, join as join53, relative as relative27, resolve as resolve40, sep as sep7 } from "path";
19920
19990
  var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
19921
19991
  if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
19922
19992
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
@@ -19984,7 +20054,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
19984
20054
  const root = resolve40(projectRoot);
19985
20055
  const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
19986
20056
  const projectRelative = relative27(root, output);
19987
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute7(projectRelative)) {
20057
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute7(projectRelative)) {
19988
20058
  throw new TypeError("mobile build --outdir must remain inside the project.");
19989
20059
  }
19990
20060
  return output;
@@ -20570,7 +20640,7 @@ var init_androidTestReport = __esm(() => {
20570
20640
 
20571
20641
  // src/mobile/releasePublisher.ts
20572
20642
  import { access as access13 } from "fs/promises";
20573
- import { isAbsolute as isAbsolute8, relative as relative28, resolve as resolve41, sep as sep7 } from "path";
20643
+ import { isAbsolute as isAbsolute8, relative as relative28, resolve as resolve41, sep as sep8 } from "path";
20574
20644
  import { pathToFileURL as pathToFileURL2 } from "url";
20575
20645
  var prepareAbsoluteIosRelease = async (publisher, options) => {
20576
20646
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -20595,7 +20665,7 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
20595
20665
  const root = resolve41(projectRoot);
20596
20666
  const path = resolve41(root, requested);
20597
20667
  const projectRelative = relative28(root, path);
20598
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute8(projectRelative)) {
20668
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep8}`) || isAbsolute8(projectRelative)) {
20599
20669
  throw new TypeError("mobile publish --registry must remain inside the project.");
20600
20670
  }
20601
20671
  return path;
@@ -20812,7 +20882,7 @@ var init_mobileInspect = __esm(() => {
20812
20882
  // src/mobile/ciWorkflow.ts
20813
20883
  import { existsSync as existsSync43 } from "fs";
20814
20884
  import { access as access15, mkdir as mkdir15, readFile as readFile23, writeFile as writeFile17 } from "fs/promises";
20815
- import { dirname as dirname32, extname as extname9, relative as relative30, resolve as resolve43, sep as sep8 } from "path";
20885
+ import { dirname as dirname32, extname as extname9, relative as relative30, resolve as resolve43, sep as sep9 } from "path";
20816
20886
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTATION = 6, RESERVED_SECRET_NAMES, exists4 = async (path) => {
20817
20887
  try {
20818
20888
  await access15(path);
@@ -20824,7 +20894,7 @@ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTAT
20824
20894
  const root = resolve43(projectRoot);
20825
20895
  const path = resolve43(root, value);
20826
20896
  const portable = relative30(root, path).replaceAll("\\", "/");
20827
- if (portable === ".." || portable.startsWith(`..${sep8}`) || portable.startsWith("../") || portable === "") {
20897
+ if (portable === ".." || portable.startsWith(`..${sep9}`) || portable.startsWith("../") || portable === "") {
20828
20898
  throw new TypeError(`${field} must remain inside the project root.`);
20829
20899
  }
20830
20900
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
@@ -20837,7 +20907,7 @@ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1, SECRET_NAME_PATTERN, CI_ENV_INDENTAT
20837
20907
  const workflows = resolve43(root, ".github/workflows");
20838
20908
  const path = resolve43(root, value ?? ".github/workflows/absolute-mobile.yml");
20839
20909
  const portable = relative30(workflows, path);
20840
- if (portable === ".." || portable.startsWith(`..${sep8}`) || extname9(path) !== ".yml" && extname9(path) !== ".yaml") {
20910
+ if (portable === ".." || portable.startsWith(`..${sep9}`) || extname9(path) !== ".yml" && extname9(path) !== ".yaml") {
20841
20911
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
20842
20912
  }
20843
20913
  return path;
package/dist/index.js CHANGED
@@ -9034,7 +9034,7 @@ var init_loadConfig = __esm(() => {
9034
9034
 
9035
9035
  // src/mobile/config.ts
9036
9036
  import { resolve as resolve11 } from "path";
9037
- var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, HOSTNAME_PATTERN, resolveProjectPath = (projectRoot, value, field) => {
9037
+ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field) => {
9038
9038
  const root = resolve11(projectRoot);
9039
9039
  const path = resolve11(root, value);
9040
9040
  if (path !== root && !path.startsWith(`${root}/`)) {
@@ -9107,11 +9107,31 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
9107
9107
  }
9108
9108
  return value.match(/.{2}/g)?.join(":") ?? value;
9109
9109
  }))
9110
- ].sort(), normalizeExpoNativeRoutes = (config, projectRoot) => {
9110
+ ].sort(), validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
9111
+ if (segment === "*" && (index !== count - 1 || count === 1)) {
9112
+ throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
9113
+ }
9114
+ if (segment === "*")
9115
+ return;
9116
+ if (!segment.startsWith(":") && (segment.includes("*") || segment.includes(":"))) {
9117
+ throw new TypeError(`mobile.routes.native route ${path} contains invalid segment ${segment}.`);
9118
+ }
9119
+ if (!segment.startsWith(":"))
9120
+ return;
9121
+ const name = segment.slice(1);
9122
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/u.test(name)) {
9123
+ throw new TypeError(`mobile.routes.native route ${path} has invalid parameter ${segment}.`);
9124
+ }
9125
+ if (parameters.has(name)) {
9126
+ throw new TypeError(`mobile.routes.native route ${path} repeats parameter ${segment}.`);
9127
+ }
9128
+ parameters.add(name);
9129
+ }, normalizeExpoNativeRoutes = (config, projectRoot) => {
9111
9130
  if (config.engine !== "expo")
9112
9131
  return {};
9113
9132
  const routes = config.routes?.native ?? {};
9114
9133
  const normalized = {};
9134
+ const ownership = new Map;
9115
9135
  for (const [route, module] of Object.entries(routes)) {
9116
9136
  const path = normalizeEntry(route);
9117
9137
  if (path.includes("?") || path.includes("#") || path !== "/" && path.endsWith("/")) {
@@ -9120,9 +9140,18 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
9120
9140
  if (path === "/__absolute/native") {
9121
9141
  throw new TypeError("mobile.routes.native reserves /__absolute/native for the Expo diagnostic screen.");
9122
9142
  }
9123
- if (path.includes("*") || path.includes(":")) {
9124
- throw new TypeError(`mobile.routes.native route ${path} must be static during the Expo experiment; parameters and wildcards are not supported yet.`);
9143
+ const segments = path.split("/").filter(Boolean);
9144
+ if (segments[0] && EXPO_RESERVED_ROUTE_PREFIXES.has(segments[0])) {
9145
+ throw new TypeError(`mobile.routes.native route ${path} conflicts with an Expo Router or Metro reserved path.`);
9125
9146
  }
9147
+ const parameters = new Set;
9148
+ segments.forEach((segment, index) => validateExpoNativeRouteSegment(path, segment, index, segments.length, parameters));
9149
+ const signature = segments.map((segment) => segment.startsWith(":") ? ":" : segment).join("/");
9150
+ const existing = ownership.get(signature);
9151
+ if (existing) {
9152
+ throw new TypeError(`mobile.routes.native routes ${existing} and ${path} claim the same Expo route pattern.`);
9153
+ }
9154
+ ownership.set(signature, path);
9126
9155
  normalized[path] = resolveProjectPath(projectRoot, requireText(module, `mobile.routes.native[${path}]`), `mobile.routes.native[${path}]`);
9127
9156
  }
9128
9157
  return Object.fromEntries(Object.entries(normalized).sort(([left], [right]) => left.localeCompare(right)));
@@ -9161,6 +9190,16 @@ var init_config = __esm(() => {
9161
9190
  APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
9162
9191
  CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
9163
9192
  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])?))*$/;
9193
+ EXPO_RESERVED_ROUTE_PREFIXES = new Set([
9194
+ "_expo",
9195
+ "_flight",
9196
+ "_sitemap",
9197
+ "assets",
9198
+ "expo-dev-plugins",
9199
+ "inspector",
9200
+ "manifest",
9201
+ "public"
9202
+ ]);
9164
9203
  });
9165
9204
 
9166
9205
  // src/cli/scripts/telemetry.ts
@@ -41040,5 +41079,5 @@ export {
41040
41079
  wrapPageHandlerWithStreamingSlots
41041
41080
  };
41042
41081
 
41043
- //# debugId=F434733973E11FBE64756E2164756E21
41082
+ //# debugId=4071543943813FF564756E2164756E21
41044
41083
  //# sourceMappingURL=index.js.map