@vxrn/utils 2.10.0 → 2.11.1

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.
@@ -0,0 +1,148 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname, join, relative, resolve } from 'node:path'
4
+
5
+ export type DesktopPlatform = 'macos' | 'linux'
6
+
7
+ const IOS_EXTENSIONS = ['.ios.tsx', '.ios.ts', '.ios.jsx', '.ios.js']
8
+ const OVERRIDE_EXTENSIONS = ['', '.tsx', '.ts', '.jsx', '.js']
9
+
10
+ const reactNativeRootCache = new Map<string, string | null>()
11
+
12
+ export function findOnePackageRoot(appRoot: string): string | null {
13
+ try {
14
+ const req = createRequire(join(appRoot, 'package.json'))
15
+ return dirname(req.resolve('one/package.json'))
16
+ } catch {
17
+ return null
18
+ }
19
+ }
20
+
21
+ export function requireOnePackageRoot(appRoot: string, platform: DesktopPlatform): string {
22
+ const root = findOnePackageRoot(appRoot)
23
+ if (!root) {
24
+ throw new Error(
25
+ `${platform} build cannot resolve one from ${appRoot}. Add "one" as a dependency.`
26
+ )
27
+ }
28
+ return root
29
+ }
30
+
31
+ function reactNativeRootOf(importerFile: string): string | null {
32
+ const start = dirname(importerFile)
33
+ const cached = reactNativeRootCache.get(start)
34
+ if (cached !== undefined) return cached
35
+ let dir = start
36
+ let found: string | null = null
37
+ for (let i = 0; i < 12; i++) {
38
+ const manifest = join(dir, 'package.json')
39
+ if (existsSync(manifest)) {
40
+ try {
41
+ const name = JSON.parse(readFileSync(manifest, 'utf8')).name
42
+ if (name === 'react-native') found = realpathSync(dir)
43
+ } catch {
44
+ found = null
45
+ }
46
+ break
47
+ }
48
+ const parent = dirname(dir)
49
+ if (parent === dir) break
50
+ dir = parent
51
+ }
52
+ reactNativeRootCache.set(start, found)
53
+ return found
54
+ }
55
+
56
+ function fileIfPresent(path: string): string | null {
57
+ if (!existsSync(path)) return null
58
+ try {
59
+ return statSync(path).isFile() ? path : null
60
+ } catch {
61
+ return null
62
+ }
63
+ }
64
+
65
+ const PLATFORM_MODULE = 'Libraries/Utilities/Platform'
66
+ const BARE_PLATFORM = /^react-native\/Libraries\/Utilities\/Platform(?:\.(?:js|ts|tsx|jsx))?$/
67
+
68
+ function slashRelative(from: string, to: string): string {
69
+ return relative(from, to).split('\\').join('/')
70
+ }
71
+
72
+ function isPackageEntry(importerFile: string, rnRoot: string): boolean {
73
+ try {
74
+ return slashRelative(rnRoot, realpathSync(importerFile)) === 'index.js'
75
+ } catch {
76
+ return false
77
+ }
78
+ }
79
+
80
+ function isPlatformTarget(rnRoot: string, target: string): boolean {
81
+ const rel = slashRelative(rnRoot, target).replace(/\.(tsx|ts|jsx|js)$/, '')
82
+ return rel === PLATFORM_MODULE
83
+ }
84
+
85
+ function platformOverrideFile(oneRoot: string, platform: DesktopPlatform): string | null {
86
+ return fileIfPresent(join(oneRoot, platform, 'react-native', PLATFORM_MODULE + '.js'))
87
+ }
88
+
89
+ /**
90
+ * The desktop platforms load react-native's iOS files, and only for importers
91
+ * inside that package. React Native's own modules keep Platform.ios.js, so
92
+ * Platform.OS stays ios there. The package entry and every importer outside
93
+ * react-native get the one Platform override, where OS is the desktop
94
+ * platform. Returns an absolute file, or null to keep the normal resolver.
95
+ */
96
+ export function desktopReactNativeFile(
97
+ importerFile: string | undefined,
98
+ source: string,
99
+ oneRoot: string | null,
100
+ platform: DesktopPlatform
101
+ ): string | null {
102
+ if (BARE_PLATFORM.test(source)) {
103
+ if (!oneRoot) return null
104
+ if (importerFile) {
105
+ const rnRoot = reactNativeRootOf(importerFile)
106
+ if (rnRoot && !isPackageEntry(importerFile, rnRoot)) {
107
+ let dir = dirname(importerFile)
108
+ try {
109
+ dir = realpathSync(dir)
110
+ } catch {
111
+ // keep the unresolved directory
112
+ }
113
+ const inside = slashRelative(rnRoot, dir)
114
+ if (!inside.startsWith('..')) return null
115
+ }
116
+ }
117
+ return platformOverrideFile(oneRoot, platform)
118
+ }
119
+
120
+ if (!importerFile || !source.startsWith('.')) return null
121
+ const rnRoot = reactNativeRootOf(importerFile)
122
+ if (!rnRoot) return null
123
+ let importerReal: string
124
+ try {
125
+ importerReal = realpathSync(dirname(importerFile))
126
+ } catch {
127
+ return null
128
+ }
129
+ const fromRoot = relative(rnRoot, importerReal)
130
+ if (fromRoot.startsWith('..')) return null
131
+
132
+ const target = resolve(dirname(importerFile), source)
133
+ // the package entry re-exports Platform. its importer is index.js, so the
134
+ // public Platform is the override, while library files keep the ios one.
135
+ const internalPlatform = isPlatformTarget(rnRoot, target) && !isPackageEntry(importerFile, rnRoot)
136
+ if (oneRoot && !internalPlatform) {
137
+ const overrideBase = join(oneRoot, platform, 'react-native', relative(rnRoot, target))
138
+ for (const ext of OVERRIDE_EXTENSIONS) {
139
+ const file = fileIfPresent(overrideBase + ext)
140
+ if (file) return file
141
+ }
142
+ }
143
+ for (const ext of IOS_EXTENSIONS) {
144
+ const file = fileIfPresent(target + ext)
145
+ if (file) return file
146
+ }
147
+ return null
148
+ }
@@ -9,12 +9,14 @@ describe('one platform env contract', () => {
9
9
  it('exposes ONE_PLATFORM for every vite environment', () => {
10
10
  expect(getPlatformEnv('ios').ONE_PLATFORM).toBe('ios')
11
11
  expect(getPlatformEnv('android').ONE_PLATFORM).toBe('android')
12
+ expect(getPlatformEnv('macos').ONE_PLATFORM).toBe('macos')
13
+ expect(getPlatformEnv('linux').ONE_PLATFORM).toBe('linux')
12
14
  expect(getPlatformEnv('client').ONE_PLATFORM).toBe('web')
13
15
  expect(getPlatformEnv('ssr').ONE_PLATFORM).toBe('web')
14
16
  })
15
17
 
16
18
  it('defines the Expo platform alias only on native', () => {
17
- for (const environment of ['ios', 'android'] as const) {
19
+ for (const environment of ['ios', 'android', 'macos', 'linux'] as const) {
18
20
  expect(getPlatformEnv(environment).EXPO_OS).toBe(
19
21
  getPlatformEnv(environment).ONE_PLATFORM
20
22
  )
@@ -38,6 +40,7 @@ describe('one platform env contract', () => {
38
40
  it('maps metro platforms to vite environments without probing', () => {
39
41
  expect(metroPlatformToViteEnvironment('ios')).toBe('ios')
40
42
  expect(metroPlatformToViteEnvironment('android')).toBe('android')
43
+ expect(metroPlatformToViteEnvironment('macos')).toBe('macos')
41
44
  expect(metroPlatformToViteEnvironment(null)).toBe('client')
42
45
  expect(metroPlatformToViteEnvironment(undefined)).toBe('client')
43
46
  })
@@ -1,11 +1,12 @@
1
- export type Platform = 'ios' | 'android' | 'web'
2
- export type ViteEnvironment = 'client' | 'ssr' | 'ios' | 'android'
1
+ export type NativePlatform = 'ios' | 'android' | 'macos' | 'linux'
2
+ export type Platform = NativePlatform | 'web'
3
+ export type ViteEnvironment = 'client' | 'ssr' | NativePlatform
3
4
 
4
5
  export type PlatformEnv = {
5
6
  VITE_ENVIRONMENT: ViteEnvironment
6
7
  VITE_NATIVE: '' | '1'
7
- ONE_PLATFORM: 'web' | 'ios' | 'android'
8
- EXPO_OS?: 'ios' | 'android'
8
+ ONE_PLATFORM: Platform
9
+ EXPO_OS?: NativePlatform
9
10
  TAMAGUI_TARGET: 'web' | 'native'
10
11
  TAMAGUI_ENVIRONMENT: ViteEnvironment
11
12
  }
@@ -41,6 +42,28 @@ const platformEnvMap: Record<ViteEnvironment, PlatformEnv> = {
41
42
  TAMAGUI_TARGET: 'native',
42
43
  TAMAGUI_ENVIRONMENT: 'android',
43
44
  },
45
+ macos: {
46
+ VITE_ENVIRONMENT: 'macos',
47
+ VITE_NATIVE: '1',
48
+ ONE_PLATFORM: 'macos',
49
+ EXPO_OS: 'macos',
50
+ TAMAGUI_TARGET: 'native',
51
+ TAMAGUI_ENVIRONMENT: 'macos',
52
+ },
53
+ linux: {
54
+ VITE_ENVIRONMENT: 'linux',
55
+ VITE_NATIVE: '1',
56
+ ONE_PLATFORM: 'linux',
57
+ EXPO_OS: 'linux',
58
+ TAMAGUI_TARGET: 'native',
59
+ TAMAGUI_ENVIRONMENT: 'linux',
60
+ },
61
+ }
62
+
63
+ const nativePlatforms = new Set<string>(['ios', 'android', 'macos', 'linux'])
64
+
65
+ export function isNativePlatform(platform: string | null | undefined): platform is NativePlatform {
66
+ return platform != null && nativePlatforms.has(platform)
44
67
  }
45
68
 
46
69
  export function getPlatformEnv(environment: ViteEnvironment): PlatformEnv {
@@ -50,8 +73,7 @@ export function getPlatformEnv(environment: ViteEnvironment): PlatformEnv {
50
73
  export function metroPlatformToViteEnvironment(
51
74
  platform: Platform | string | null | undefined
52
75
  ): ViteEnvironment {
53
- if (platform === 'ios') return 'ios'
54
- if (platform === 'android') return 'android'
76
+ if (isNativePlatform(platform)) return platform
55
77
  return 'client'
56
78
  }
57
79
 
@@ -0,0 +1,12 @@
1
+ export type DesktopPlatform = 'macos' | 'linux';
2
+ export declare function findOnePackageRoot(appRoot: string): string | null;
3
+ export declare function requireOnePackageRoot(appRoot: string, platform: DesktopPlatform): string;
4
+ /**
5
+ * The desktop platforms load react-native's iOS files, and only for importers
6
+ * inside that package. React Native's own modules keep Platform.ios.js, so
7
+ * Platform.OS stays ios there. The package entry and every importer outside
8
+ * react-native get the one Platform override, where OS is the desktop
9
+ * platform. Returns an absolute file, or null to keep the normal resolver.
10
+ */
11
+ export declare function desktopReactNativeFile(importerFile: string | undefined, source: string, oneRoot: string | null, platform: DesktopPlatform): string | null;
12
+ //# sourceMappingURL=desktopReactNativeResolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktopReactNativeResolve.d.ts","sourceRoot":"","sources":["../src/desktopReactNativeResolve.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAA;AAO/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,MAAM,CAQxF;AA4DD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,QAAQ,EAAE,eAAe,GACxB,MAAM,GAAG,IAAI,CA+Cf"}
@@ -1,13 +1,15 @@
1
- export type Platform = 'ios' | 'android' | 'web';
2
- export type ViteEnvironment = 'client' | 'ssr' | 'ios' | 'android';
1
+ export type NativePlatform = 'ios' | 'android' | 'macos' | 'linux';
2
+ export type Platform = NativePlatform | 'web';
3
+ export type ViteEnvironment = 'client' | 'ssr' | NativePlatform;
3
4
  export type PlatformEnv = {
4
5
  VITE_ENVIRONMENT: ViteEnvironment;
5
6
  VITE_NATIVE: '' | '1';
6
- ONE_PLATFORM: 'web' | 'ios' | 'android';
7
- EXPO_OS?: 'ios' | 'android';
7
+ ONE_PLATFORM: Platform;
8
+ EXPO_OS?: NativePlatform;
8
9
  TAMAGUI_TARGET: 'web' | 'native';
9
10
  TAMAGUI_ENVIRONMENT: ViteEnvironment;
10
11
  };
12
+ export declare function isNativePlatform(platform: string | null | undefined): platform is NativePlatform;
11
13
  export declare function getPlatformEnv(environment: ViteEnvironment): PlatformEnv;
12
14
  export declare function metroPlatformToViteEnvironment(platform: Platform | string | null | undefined): ViteEnvironment;
13
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"platformEnv.d.ts","sourceRoot":"","sources":["../src/platformEnv.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAAA;AAChD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;AAElE,MAAM,MAAM,WAAW,GAAG;IACxB,gBAAgB,EAAE,eAAe,CAAA;IACjC,WAAW,EAAE,EAAE,GAAG,GAAG,CAAA;IACrB,YAAY,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;IACvC,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAA;IAC3B,cAAc,EAAE,KAAK,GAAG,QAAQ,CAAA;IAChC,mBAAmB,EAAE,eAAe,CAAA;CACrC,CAAA;AAmCD,wBAAgB,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,WAAW,CAExE;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7C,eAAe,CAIjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,eAAe,GAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}
1
+ {"version":3,"file":"platformEnv.d.ts","sourceRoot":"","sources":["../src/platformEnv.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAClE,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,KAAK,CAAA;AAC7C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,KAAK,GAAG,cAAc,CAAA;AAE/D,MAAM,MAAM,WAAW,GAAG;IACxB,gBAAgB,EAAE,eAAe,CAAA;IACjC,WAAW,EAAE,EAAE,GAAG,GAAG,CAAA;IACrB,YAAY,EAAE,QAAQ,CAAA;IACtB,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,cAAc,EAAE,KAAK,GAAG,QAAQ,CAAA;IAChC,mBAAmB,EAAE,eAAe,CAAA;CACrC,CAAA;AAqDD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,IAAI,cAAc,CAEhG;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,eAAe,GAAG,WAAW,CAExE;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7C,eAAe,CAGjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,eAAe,GAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}