@absolutejs/absolute 0.20.0-beta.65 → 0.20.0-beta.67

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.
Files changed (38) hide show
  1. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  2. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  3. package/dist/build.js +768 -544
  4. package/dist/build.js.map +10 -8
  5. package/dist/cli/index.js +148 -5
  6. package/dist/dev/serverBootstrap.js +544 -6
  7. package/dist/index.js +905 -619
  8. package/dist/index.js.map +14 -11
  9. package/dist/mobile/index.js +5430 -5
  10. package/dist/mobile/index.js.map +11 -8
  11. package/dist/mobile/mobilePreviewClient.ts +246 -0
  12. package/dist/mobile/remoteMacAgentEntry.js +241 -180
  13. package/dist/mobile/shellAuth.js +16 -0
  14. package/dist/mobile/shellBootstrap.js +5427 -117
  15. package/dist/mobile/shellExpoAuth.js +16 -0
  16. package/dist/mobile/shellExpoDevices.js +16 -0
  17. package/dist/mobile/shellExpoSync.js +16 -0
  18. package/dist/mobile/shellSync.js +16 -0
  19. package/dist/mobile/shellUpdate.js +16 -0
  20. package/dist/mobile/uiPrimitives.js +16 -0
  21. package/dist/src/build/chainInlineSourcemaps.d.ts +2 -0
  22. package/dist/src/build/vueCompileCache.d.ts +43 -0
  23. package/dist/src/cli/config/server.d.ts +1 -1
  24. package/dist/src/dev/bootLifecycle.d.ts +11 -0
  25. package/dist/src/dev/earlyListener.d.ts +41 -0
  26. package/dist/src/mobile/capacitorBundle.d.ts +8 -1
  27. package/dist/src/mobile/config.d.ts +6 -0
  28. package/dist/src/mobile/index.d.ts +1 -0
  29. package/dist/src/mobile/mobileInspect.d.ts +14 -0
  30. package/dist/src/mobile/mobilePreview.d.ts +1 -1
  31. package/dist/src/mobile/shellBootstrap.d.ts +2 -0
  32. package/dist/src/mobile/shellObservability.d.ts +11 -0
  33. package/dist/src/mobile/transport.d.ts +6 -0
  34. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  35. package/dist/src/utils/startupTimings.d.ts +4 -0
  36. package/dist/types/build.d.ts +12 -0
  37. package/dist/types/globals.d.ts +15 -0
  38. package/package.json +9 -7
package/dist/cli/index.js CHANGED
@@ -818,6 +818,30 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
818
818
  throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
819
819
  }
820
820
  return normalized;
821
+ }, normalizeObservability = (config, productionOrigin) => {
822
+ if (!config.observability)
823
+ return;
824
+ const project = requireText(config.observability.project, "mobile.observability.project");
825
+ if (project.length > 255)
826
+ throw new TypeError("mobile.observability.project must not exceed 255 characters.");
827
+ const environment = config.observability.environment?.trim();
828
+ if (environment !== undefined && (!environment || environment.length > 64))
829
+ throw new TypeError("mobile.observability.environment must contain 1 through 64 characters.");
830
+ const sampleRate = config.observability.sampleRate ?? 1;
831
+ if (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1)
832
+ throw new TypeError("mobile.observability.sampleRate must be between 0 and 1.");
833
+ const route = config.observability.route ?? "/api/observability/errors";
834
+ if (!route.startsWith("/") || route.startsWith("//"))
835
+ throw new TypeError("mobile.observability.route must be a root-relative trusted-server path.");
836
+ const endpoint = new URL(route, `${productionOrigin}/`);
837
+ if (endpoint.origin !== productionOrigin || endpoint.username || endpoint.password || endpoint.hash || endpoint.search)
838
+ throw new TypeError("mobile.observability.route must remain on productionOrigin and cannot contain credentials, a query, or a fragment.");
839
+ return {
840
+ endpoint: endpoint.href,
841
+ ...environment ? { environment } : {},
842
+ project,
843
+ sampleRate
844
+ };
821
845
  }, normalizeCertificateFingerprints = (values) => [
822
846
  ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
823
847
  if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
@@ -972,6 +996,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
972
996
  throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
973
997
  }
974
998
  const updates = normalizeUpdates(config, productionOrigin, projectRoot);
999
+ const observability = normalizeObservability(config, productionOrigin);
975
1000
  return {
976
1001
  androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
977
1002
  appId,
@@ -986,6 +1011,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
986
1011
  ...config.engine === "expo" ? { expoSdkVersion: config.expo?.sdkVersion ?? 57 } : {},
987
1012
  iosVersion: normalizeIosVersion(config.ios?.version),
988
1013
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? (config.engine === "expo" ? ".absolutejs/mobile/expo" : "mobile"), "mobile.nativeProject.directory"),
1014
+ ...observability ? { observability } : {},
989
1015
  platforms: normalizePlatforms(config.platforms),
990
1016
  productionOrigin,
991
1017
  pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile"),
@@ -1969,6 +1995,14 @@ var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts
1969
1995
  });
1970
1996
  return {
1971
1997
  appBuild: requireManifestString(value.appBuild, "appBuild"),
1998
+ ...isRecord(value.observability) && typeof value.observability.endpoint === "string" && typeof value.observability.project === "string" && typeof value.observability.sampleRate === "number" ? {
1999
+ observability: {
2000
+ endpoint: value.observability.endpoint,
2001
+ ...typeof value.observability.environment === "string" ? { environment: value.observability.environment } : {},
2002
+ project: value.observability.project,
2003
+ sampleRate: value.observability.sampleRate
2004
+ }
2005
+ } : {},
1972
2006
  pages,
1973
2007
  productionOrigin: requireManifestString(value.productionOrigin, "productionOrigin"),
1974
2008
  routes,
@@ -2740,7 +2774,7 @@ export function AbsoluteWebHost() {
2740
2774
 
2741
2775
  const styles = StyleSheet.create({ loading: { alignItems: 'center', flex: 1, justifyContent: 'center' }, web: { flex: 1 } });
2742
2776
  `;
2743
- }, webRouteSource, catchAllRouteSource, nativeRouteRuntimeSource = (auth) => `${EXPO_GENERATED_HEADER}import { useLocalSearchParams, usePathname } from 'expo-router';
2777
+ }, webRouteSource, catchAllRouteSource, nativeRouteRuntimeSource = (auth) => `${EXPO_GENERATED_HEADER}import { type ErrorBoundaryProps, useLocalSearchParams, usePathname } from 'expo-router';
2744
2778
  import { type ComponentType, useEffect, useMemo, useState } from 'react';
2745
2779
  import { ActivityIndicator, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
2746
2780
  import { ABSOLUTE_MOBILE_MANIFEST } from './webAssets';
@@ -2767,6 +2801,47 @@ type NativeRouteState<PageProps> =
2767
2801
 
2768
2802
  const isRecord = (value: unknown): value is Record<string, unknown> =>
2769
2803
  typeof value === 'object' && value !== null && !Array.isArray(value);
2804
+ const redactErrorText = (value: string) => value
2805
+ .replace(/\\b(Bearer)\\s+[A-Za-z0-9._~+/-]+=*/gi, '$1 [REDACTED]')
2806
+ .replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\\s]*/gi, '$1[REDACTED]')
2807
+ .replace(/\\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\\s*[:=]\\s*)[^,\\s;]+/gi, '$1[REDACTED]')
2808
+ .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g, '[REDACTED]');
2809
+ const captureNativeException = (value: unknown, context: { bundle?: string | undefined; contract?: string | undefined; pageId?: string | undefined; phase: string; route: string }) => {
2810
+ const manifest = ABSOLUTE_MOBILE_MANIFEST;
2811
+ const config = manifest?.observability;
2812
+ if (!manifest || !config || config.sampleRate === 0 || config.sampleRate < 1 && Math.random() >= config.sampleRate) return;
2813
+ const error = value instanceof Error ? value : new Error(String(value));
2814
+ const event = {
2815
+ level: 'error',
2816
+ message: redactErrorText(error.message),
2817
+ name: error.name,
2818
+ ...(error.stack ? { stack: redactErrorText(error.stack) } : {}),
2819
+ tags: {
2820
+ absoluteMobile: 'true',
2821
+ mobileAppBuild: manifest.appBuild,
2822
+ mobileEngine: 'expo',
2823
+ mobileFailurePhase: context.phase,
2824
+ mobileManifestFormat: '1',
2825
+ mobilePlatform: Platform.OS,
2826
+ mobileRoute: context.route,
2827
+ mobileRuntime: manifest.runtime,
2828
+ ...(context.bundle ? { mobilePageBundle: context.bundle } : {}),
2829
+ ...(context.contract ? { mobilePageContract: context.contract } : {}),
2830
+ ...(context.pageId ? { mobilePageId: context.pageId } : {})
2831
+ }
2832
+ };
2833
+ void fetch(config.endpoint, {
2834
+ body: JSON.stringify({
2835
+ ...(config.environment ? { environment: config.environment } : {}),
2836
+ events: [event],
2837
+ project: config.project,
2838
+ release: manifest.appBuild,
2839
+ v: 1
2840
+ }),
2841
+ headers: { 'content-type': 'application/json' },
2842
+ method: 'POST'
2843
+ }).catch(() => undefined);
2844
+ };
2770
2845
  const routeSegmentPattern = (segment: string) => {
2771
2846
  if (segment === '*') return '.*';
2772
2847
  if (segment.startsWith(':') && segment.endsWith('?')) return '[^/]*';
@@ -2794,6 +2869,17 @@ const productionPage = (pathname: string) => {
2794
2869
  if (!page) throw new Error('The embedded AbsoluteJS page contract is incomplete.');
2795
2870
  return page;
2796
2871
  };
2872
+ export const createAbsoluteNativeRouteErrorBoundary = (pattern: string) => {
2873
+ return function AbsoluteNativeRouteErrorBoundary({ error, retry }: ErrorBoundaryProps) {
2874
+ const pathname = usePathname() || '/';
2875
+ useEffect(() => {
2876
+ let page: ReturnType<typeof productionPage> | undefined;
2877
+ try { if (ABSOLUTE_MOBILE_MANIFEST) page = productionPage(pathname); } catch {}
2878
+ captureNativeException(error, { bundle: page?.bundleHash, contract: page?.contract, pageId: page?.pageId, phase: 'native-route-render', route: pattern });
2879
+ }, [error, pathname]);
2880
+ return <View style={styles.center}><Text accessibilityRole="alert" style={styles.error}>This screen stopped unexpectedly.</Text><Pressable accessibilityRole="button" onPress={() => { void retry(); }} style={styles.button}><Text>Try again</Text></Pressable></View>;
2881
+ };
2882
+ };
2797
2883
  const createDataRequest = (path: string) => {
2798
2884
  const development = typeof DEV_ORIGIN === 'string' && DEV_ORIGIN.length > 0;
2799
2885
  const origin = development ? DEV_ORIGIN : ABSOLUTE_MOBILE_MANIFEST?.productionOrigin;
@@ -2846,7 +2932,14 @@ export const createAbsoluteNativeRoute = <
2846
2932
  setState({ kind: 'loading' });
2847
2933
  void requestData<PageProps>(requestPath, controller.signal).then(
2848
2934
  pageProps => { if (!controller.signal.aborted) setState({ kind: 'ready', pageProps }); },
2849
- () => { if (!controller.signal.aborted) setState({ kind: 'error' }); }
2935
+ error => {
2936
+ if (!controller.signal.aborted) {
2937
+ let page: ReturnType<typeof productionPage> | undefined;
2938
+ try { if (ABSOLUTE_MOBILE_MANIFEST) page = productionPage(pathname); } catch {}
2939
+ captureNativeException(error, { bundle: page?.bundleHash, contract: page?.contract, pageId: page?.pageId, phase: 'native-route-load', route: pattern });
2940
+ setState({ kind: 'error' });
2941
+ }
2942
+ }
2850
2943
  );
2851
2944
  return () => controller.abort();
2852
2945
  }, [requestPath, revision]);
@@ -2864,8 +2957,9 @@ const styles = StyleSheet.create({
2864
2957
  error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
2865
2958
  });
2866
2959
  `, nativeWrapperSource = (wrapper, module, runtime, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative(dirname4(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2867
- import { createAbsoluteNativeRoute } from ${JSON.stringify(portableRelative(dirname4(wrapper), runtime).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2960
+ import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative(dirname4(wrapper), runtime).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2868
2961
 
2962
+ export const ErrorBoundary = createAbsoluteNativeRouteErrorBoundary(${JSON.stringify(route)});
2869
2963
  export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
2870
2964
  `, expoTsConfig = (projectRoot, project, auth, sync) => ({
2871
2965
  compilerOptions: {
@@ -2930,6 +3024,7 @@ export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringif
2930
3024
  }, jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
2931
3025
  `, nativeDataManifestTypeSource = `type AbsoluteMobileManifest = {
2932
3026
  appBuild: string;
3027
+ observability?: { endpoint: string; environment?: string; project: string; sampleRate: number };
2933
3028
  pages: readonly { bundleHash: string; contract: string; pageId: string }[];
2934
3029
  productionOrigin: string;
2935
3030
  routes: readonly { method: 'GET'; pageId: string; pattern: string }[];
@@ -9496,7 +9591,9 @@ const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absolu
9496
9591
  const baseAdapterModule = capacitor ? await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor") : shellExpoDevicesModule();
9497
9592
  const adapterImport = capacitor ? `import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};` : `import { createAbsoluteExpoBridgeFetch, installAbsoluteExpoWebDeviceAdapter } from ${JSON.stringify(baseAdapterModule)};`;
9498
9593
  const adapterInstall = capacitor ? `installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });` : `installAbsoluteExpoWebDeviceAdapter(${JSON.stringify(deviceCapabilities.capabilities)});`;
9499
- const shellOptionProperties = [];
9594
+ const shellOptionProperties = [
9595
+ `engine: ${JSON.stringify(engine)}`
9596
+ ];
9500
9597
  if (push) {
9501
9598
  shellOptionProperties.push("createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options)", "beforeSignOut: absoluteMobilePush.beforeSignOut", "connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)");
9502
9599
  } else if (auth)
@@ -9516,6 +9613,7 @@ void startAbsoluteMobileShell(${shellOptions});
9516
9613
  `);
9517
9614
  const build = await Bun.build({
9518
9615
  entrypoints: [entryPath],
9616
+ external: ["web-vitals", "web-vitals/*"],
9519
9617
  minify: true,
9520
9618
  outdir: staging,
9521
9619
  target: "browser"
@@ -9575,6 +9673,40 @@ void startAbsoluteMobileShell(${shellOptions});
9575
9673
  localBundlePath,
9576
9674
  ...localStylePath ? { localStylePath } : {}
9577
9675
  };
9676
+ }, materializeAbsoluteMobileSourcemaps = async (pages, appBuild, projectRoot) => {
9677
+ const safeLeaf = (value, field) => {
9678
+ if (value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/u.test(value))
9679
+ throw new TypeError(`${field} is not a safe source-map key.`);
9680
+ return value;
9681
+ };
9682
+ const release = safeLeaf(appBuild, "Mobile app build");
9683
+ const privateRoot = join21(projectRoot, "sourcemaps");
9684
+ const releaseRoot = join21(privateRoot, "mobile", release);
9685
+ const copied = [];
9686
+ const available = pages.flatMap((page) => {
9687
+ const source = join21(privateRoot, `${basename8(page.bundlePath)}.map`);
9688
+ return existsSync10(source) ? [{ page, source }] : [];
9689
+ });
9690
+ if (available.length > 0)
9691
+ await mkdir9(releaseRoot, { recursive: true });
9692
+ await Promise.all(available.map(async ({ page, source }) => {
9693
+ const extension = extname5(page.bundlePath) || ".js";
9694
+ if (!/^\.[a-z0-9]+$/iu.test(extension))
9695
+ throw new TypeError("Mobile page extension is not a safe source-map key.");
9696
+ const bundleHash = safeLeaf(page.bundleHash, "Mobile page bundle hash");
9697
+ const file = `${bundleHash}${extension}.map`;
9698
+ await copyFile4(source, join21(releaseRoot, file));
9699
+ copied.push({ bundleHash, file, pageId: page.pageId });
9700
+ }));
9701
+ if (copied.length > 0) {
9702
+ await writeFile8(join21(releaseRoot, "manifest.json"), `${JSON.stringify({
9703
+ appBuild,
9704
+ format: 1,
9705
+ pages: copied.sort((left, right) => left.pageId.localeCompare(right.pageId))
9706
+ }, null, "\t")}
9707
+ `);
9708
+ }
9709
+ return copied.length;
9578
9710
  }, absoluteClientImports = async (sourcePath, buildDirectory) => {
9579
9711
  const source = await readFile10(sourcePath, "utf8");
9580
9712
  const extension = extname5(sourcePath).toLowerCase();
@@ -9650,6 +9782,7 @@ void startAbsoluteMobileShell(${shellOptions});
9650
9782
  productionOrigin: options.config.productionOrigin,
9651
9783
  routes: options.artifact.routes,
9652
9784
  runtime: options.artifact.runtime,
9785
+ ...options.config.observability ? { observability: options.config.observability } : {},
9653
9786
  ...options.config.updates ? { updates: options.config.updates } : {},
9654
9787
  ...options.sync ? {
9655
9788
  sync: {
@@ -9673,6 +9806,7 @@ void startAbsoluteMobileShell(${shellOptions});
9673
9806
  buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.config.engine === "capacitor" && options.config.updates !== undefined, options.deviceCapabilities, options.projectRoot)
9674
9807
  ]);
9675
9808
  await installBundle(staging, destination);
9809
+ await materializeAbsoluteMobileSourcemaps(options.artifact.pages, options.artifact.appBuild, options.projectRoot);
9676
9810
  return manifest;
9677
9811
  } catch (error) {
9678
9812
  await rm7(staging, { force: true, recursive: true });
@@ -22660,7 +22794,16 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
22660
22794
  iosVersion: config.iosVersion,
22661
22795
  nativeProjectDirectory: portablePath2(projectRoot, config.nativeProjectDirectory),
22662
22796
  platforms: config.platforms,
22663
- productionOrigin: config.productionOrigin
22797
+ productionOrigin: config.productionOrigin,
22798
+ ...config.observability ? {
22799
+ observability: {
22800
+ enabled: true,
22801
+ endpoint: config.observability.endpoint,
22802
+ environment: config.observability.environment,
22803
+ project: config.observability.project,
22804
+ sampleRate: config.observability.sampleRate
22805
+ }
22806
+ } : {}
22664
22807
  },
22665
22808
  format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
22666
22809
  nativeProjects: await Promise.all(config.platforms.map(async (platform6) => {