one 1.16.12 → 1.17.0
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/dist/cjs/babel-preset/index.cjs +154 -0
- package/dist/cjs/babel-preset/index.native.js +186 -0
- package/dist/cjs/babel-preset/index.native.js.map +1 -0
- package/dist/cjs/babel-preset/index.test.cjs +143 -0
- package/dist/cjs/babel-preset/index.test.native.js +165 -0
- package/dist/cjs/babel-preset/index.test.native.js.map +1 -0
- package/dist/cjs/babel-preset/integration.test.cjs +94 -0
- package/dist/cjs/babel-preset/integration.test.native.js +97 -0
- package/dist/cjs/babel-preset/integration.test.native.js.map +1 -0
- package/dist/cjs/cli/generateBundlerConfig.cjs +247 -0
- package/dist/cjs/cli/generateBundlerConfig.native.js +316 -0
- package/dist/cjs/cli/generateBundlerConfig.native.js.map +1 -0
- package/dist/cjs/cli/generateBundlerConfig.test.cjs +350 -0
- package/dist/cjs/cli/generateBundlerConfig.test.native.js +380 -0
- package/dist/cjs/cli/generateBundlerConfig.test.native.js.map +1 -0
- package/dist/cjs/cli/patch.cjs +19 -2
- package/dist/cjs/cli/patch.native.js +26 -2
- package/dist/cjs/cli/patch.native.js.map +1 -1
- package/dist/cjs/cli/patch.test.cjs +56 -0
- package/dist/cjs/cli/patch.test.native.js +65 -0
- package/dist/cjs/cli/patch.test.native.js.map +1 -0
- package/dist/cjs/cli.cjs +39 -2
- package/dist/cjs/cli.native.js +40 -2
- package/dist/cjs/cli.native.js.map +1 -1
- package/dist/cjs/metro-config/buildOneMetroResolverOverrides.cjs +102 -0
- package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js +109 -0
- package/dist/cjs/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -0
- package/dist/cjs/metro-config/getViteMetroPluginOptions.cjs +17 -150
- package/dist/cjs/metro-config/getViteMetroPluginOptions.integration.test.cjs +84 -0
- package/dist/cjs/metro-config/getViteMetroPluginOptions.integration.test.native.js +90 -0
- package/dist/cjs/metro-config/getViteMetroPluginOptions.integration.test.native.js.map +1 -0
- package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js +17 -158
- package/dist/cjs/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
- package/dist/cjs/metro-config/withOne.cjs +82 -0
- package/dist/cjs/metro-config/withOne.native.js +88 -0
- package/dist/cjs/metro-config/withOne.native.js.map +1 -0
- package/dist/cjs/metro-config/withOne.test.cjs +129 -0
- package/dist/cjs/metro-config/withOne.test.native.js +156 -0
- package/dist/cjs/metro-config/withOne.test.native.js.map +1 -0
- package/dist/cjs/vite/loadConfig.cjs +20 -1
- package/dist/cjs/vite/loadConfig.native.js +20 -1
- package/dist/cjs/vite/loadConfig.native.js.map +1 -1
- package/dist/cjs/vite/plugins/warmRoutesPlugin.cjs +13 -7
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js +13 -7
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/dist/esm/babel-preset/index.mjs +116 -0
- package/dist/esm/babel-preset/index.mjs.map +1 -0
- package/dist/esm/babel-preset/index.native.js +145 -0
- package/dist/esm/babel-preset/index.native.js.map +1 -0
- package/dist/esm/babel-preset/index.test.mjs +120 -0
- package/dist/esm/babel-preset/index.test.mjs.map +1 -0
- package/dist/esm/babel-preset/index.test.native.js +139 -0
- package/dist/esm/babel-preset/index.test.native.js.map +1 -0
- package/dist/esm/babel-preset/integration.test.mjs +71 -0
- package/dist/esm/babel-preset/integration.test.mjs.map +1 -0
- package/dist/esm/babel-preset/integration.test.native.js +71 -0
- package/dist/esm/babel-preset/integration.test.native.js.map +1 -0
- package/dist/esm/cli/generateBundlerConfig.mjs +207 -0
- package/dist/esm/cli/generateBundlerConfig.mjs.map +1 -0
- package/dist/esm/cli/generateBundlerConfig.native.js +273 -0
- package/dist/esm/cli/generateBundlerConfig.native.js.map +1 -0
- package/dist/esm/cli/generateBundlerConfig.test.mjs +327 -0
- package/dist/esm/cli/generateBundlerConfig.test.mjs.map +1 -0
- package/dist/esm/cli/generateBundlerConfig.test.native.js +354 -0
- package/dist/esm/cli/generateBundlerConfig.test.native.js.map +1 -0
- package/dist/esm/cli/patch.mjs +19 -2
- package/dist/esm/cli/patch.mjs.map +1 -1
- package/dist/esm/cli/patch.native.js +26 -2
- package/dist/esm/cli/patch.native.js.map +1 -1
- package/dist/esm/cli/patch.test.mjs +57 -0
- package/dist/esm/cli/patch.test.mjs.map +1 -0
- package/dist/esm/cli/patch.test.native.js +63 -0
- package/dist/esm/cli/patch.test.native.js.map +1 -0
- package/dist/esm/cli.mjs +39 -2
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/cli.native.js +40 -2
- package/dist/esm/cli.native.js.map +1 -1
- package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs +66 -0
- package/dist/esm/metro-config/buildOneMetroResolverOverrides.mjs.map +1 -0
- package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js +70 -0
- package/dist/esm/metro-config/buildOneMetroResolverOverrides.native.js.map +1 -0
- package/dist/esm/metro-config/getViteMetroPluginOptions.integration.test.mjs +61 -0
- package/dist/esm/metro-config/getViteMetroPluginOptions.integration.test.mjs.map +1 -0
- package/dist/esm/metro-config/getViteMetroPluginOptions.integration.test.native.js +64 -0
- package/dist/esm/metro-config/getViteMetroPluginOptions.integration.test.native.js.map +1 -0
- package/dist/esm/metro-config/getViteMetroPluginOptions.mjs +17 -138
- package/dist/esm/metro-config/getViteMetroPluginOptions.mjs.map +1 -1
- package/dist/esm/metro-config/getViteMetroPluginOptions.native.js +17 -146
- package/dist/esm/metro-config/getViteMetroPluginOptions.native.js.map +1 -1
- package/dist/esm/metro-config/withOne.mjs +45 -0
- package/dist/esm/metro-config/withOne.mjs.map +1 -0
- package/dist/esm/metro-config/withOne.native.js +48 -0
- package/dist/esm/metro-config/withOne.native.js.map +1 -0
- package/dist/esm/metro-config/withOne.test.mjs +106 -0
- package/dist/esm/metro-config/withOne.test.mjs.map +1 -0
- package/dist/esm/metro-config/withOne.test.native.js +130 -0
- package/dist/esm/metro-config/withOne.test.native.js.map +1 -0
- package/dist/esm/vite/loadConfig.mjs +20 -1
- package/dist/esm/vite/loadConfig.mjs.map +1 -1
- package/dist/esm/vite/loadConfig.native.js +20 -1
- package/dist/esm/vite/loadConfig.native.js.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs +13 -7
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js +13 -7
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/package.json +20 -9
- package/src/babel-preset/index.test.ts +148 -0
- package/src/babel-preset/index.ts +250 -0
- package/src/babel-preset/integration.test.ts +91 -0
- package/src/cli/generateBundlerConfig.test.ts +343 -0
- package/src/cli/generateBundlerConfig.ts +339 -0
- package/src/cli/patch.test.ts +65 -0
- package/src/cli/patch.ts +30 -2
- package/src/cli.ts +31 -0
- package/src/metro-config/buildOneMetroResolverOverrides.ts +104 -0
- package/src/metro-config/getViteMetroPluginOptions.integration.test.ts +75 -0
- package/src/metro-config/getViteMetroPluginOptions.ts +15 -230
- package/src/metro-config/withOne.test.ts +120 -0
- package/src/metro-config/withOne.ts +112 -0
- package/src/vite/loadConfig.ts +22 -0
- package/src/vite/plugins/warmRoutesPlugin.ts +22 -6
- package/types/babel-preset/index.d.ts +68 -0
- package/types/babel-preset/index.d.ts.map +1 -0
- package/types/babel-preset/index.test.d.ts +2 -0
- package/types/babel-preset/index.test.d.ts.map +1 -0
- package/types/babel-preset/integration.test.d.ts +2 -0
- package/types/babel-preset/integration.test.d.ts.map +1 -0
- package/types/cli/generateBundlerConfig.d.ts +61 -0
- package/types/cli/generateBundlerConfig.d.ts.map +1 -0
- package/types/cli/generateBundlerConfig.test.d.ts +2 -0
- package/types/cli/generateBundlerConfig.test.d.ts.map +1 -0
- package/types/cli/patch.d.ts.map +1 -1
- package/types/cli/patch.test.d.ts +2 -0
- package/types/cli/patch.test.d.ts.map +1 -0
- package/types/metro-config/buildOneMetroResolverOverrides.d.ts +20 -0
- package/types/metro-config/buildOneMetroResolverOverrides.d.ts.map +1 -0
- package/types/metro-config/getViteMetroPluginOptions.d.ts +0 -5
- package/types/metro-config/getViteMetroPluginOptions.d.ts.map +1 -1
- package/types/metro-config/getViteMetroPluginOptions.integration.test.d.ts +2 -0
- package/types/metro-config/getViteMetroPluginOptions.integration.test.d.ts.map +1 -0
- package/types/metro-config/withOne.d.ts +44 -0
- package/types/metro-config/withOne.d.ts.map +1 -0
- package/types/metro-config/withOne.test.d.ts +2 -0
- package/types/metro-config/withOne.test.d.ts.map +1 -0
- package/types/vite/loadConfig.d.ts +1 -0
- package/types/vite/loadConfig.d.ts.map +1 -1
- package/types/vite/plugins/warmRoutesPlugin.d.ts.map +1 -1
- package/dist/cjs/metro-config/getViteMetroPluginOptions.test.cjs +0 -23
- package/dist/cjs/metro-config/getViteMetroPluginOptions.test.native.js +0 -26
- package/dist/cjs/metro-config/getViteMetroPluginOptions.test.native.js.map +0 -1
- package/dist/esm/metro-config/getViteMetroPluginOptions.test.mjs +0 -24
- package/dist/esm/metro-config/getViteMetroPluginOptions.test.mjs.map +0 -1
- package/dist/esm/metro-config/getViteMetroPluginOptions.test.native.js +0 -24
- package/dist/esm/metro-config/getViteMetroPluginOptions.test.native.js.map +0 -1
- package/src/metro-config/getViteMetroPluginOptions.test.ts +0 -34
- package/types/metro-config/getViteMetroPluginOptions.test.d.ts +0 -2
- package/types/metro-config/getViteMetroPluginOptions.test.d.ts.map +0 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import nodeModule from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import colors from 'picocolors'
|
|
5
|
+
import { getRouterRootFromOneOptions } from '../utils/getRouterRootFromOneOptions'
|
|
6
|
+
import type { One } from '../vite/types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Marker that identifies a bundler config as One-generated. If the file
|
|
10
|
+
* still contains this marker we can safely regenerate it; if the user
|
|
11
|
+
* removed the marker we treat the file as customized and never overwrite.
|
|
12
|
+
*/
|
|
13
|
+
export const ONE_GENERATED_MARKER = '@one/generated bundler-config'
|
|
14
|
+
|
|
15
|
+
export type OneBundlerConfigOptions = {
|
|
16
|
+
routerRoot?: string
|
|
17
|
+
ignoredRouteFiles?: Array<`**/*${string}`>
|
|
18
|
+
linking?: NonNullable<One.PluginOptions['router']>['linking']
|
|
19
|
+
setupFile?: One.PluginOptions['setupFile']
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function buildBabelConfigContent({
|
|
23
|
+
eject,
|
|
24
|
+
options,
|
|
25
|
+
}: {
|
|
26
|
+
eject: boolean
|
|
27
|
+
options: OneBundlerConfigOptions
|
|
28
|
+
}) {
|
|
29
|
+
const header = eject
|
|
30
|
+
? `// you own this file. edit freely — \`one\` will not regenerate it.
|
|
31
|
+
// delegates to one/babel-preset which holds the canonical plugin chain.
|
|
32
|
+
`
|
|
33
|
+
: `// ${ONE_GENERATED_MARKER}
|
|
34
|
+
//
|
|
35
|
+
// auto-generated by \`one patch\` on ci/eas workers when expo-updates is
|
|
36
|
+
// in deps. delegates to one/babel-preset so expo export / eas update
|
|
37
|
+
// use the same router/setup options as \`one dev\` and \`one build\`.
|
|
38
|
+
//
|
|
39
|
+
// to customize, delete this header and edit freely — re-runs will then
|
|
40
|
+
// leave this file alone.
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
return `${header}
|
|
44
|
+
const oneBabelPreset = require('one/babel-preset')
|
|
45
|
+
const preset = oneBabelPreset.default || oneBabelPreset
|
|
46
|
+
const oneBundlerOptions = ${serializeBundlerConfigOptions(options)}
|
|
47
|
+
|
|
48
|
+
module.exports = function (api) {
|
|
49
|
+
return preset(api, oneBundlerOptions)
|
|
50
|
+
}
|
|
51
|
+
`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildMetroConfigContent({
|
|
55
|
+
eject,
|
|
56
|
+
options,
|
|
57
|
+
}: {
|
|
58
|
+
eject: boolean
|
|
59
|
+
options: OneBundlerConfigOptions
|
|
60
|
+
}) {
|
|
61
|
+
const header = eject
|
|
62
|
+
? `// you own this file. edit freely — \`one\` will not regenerate it.
|
|
63
|
+
// withOne() invokes the same Metro pipeline One uses for production bundles.
|
|
64
|
+
`
|
|
65
|
+
: `// ${ONE_GENERATED_MARKER}
|
|
66
|
+
//
|
|
67
|
+
// auto-generated by \`one patch\` on ci/eas workers when expo-updates is
|
|
68
|
+
// in deps. delegates to one/metro-config which invokes the exact same
|
|
69
|
+
// metro pipeline one uses for production native bundles with your
|
|
70
|
+
// router/setup options — no separate expo/metro-config setup needed.
|
|
71
|
+
//
|
|
72
|
+
// to customize, delete this header and edit freely — re-runs will then
|
|
73
|
+
// leave this file alone.
|
|
74
|
+
`
|
|
75
|
+
|
|
76
|
+
return `${header}
|
|
77
|
+
const { withOne } = require('one/metro-config')
|
|
78
|
+
const oneBundlerOptions = ${serializeBundlerConfigOptions(options)}
|
|
79
|
+
|
|
80
|
+
module.exports = withOne(__dirname, oneBundlerOptions)
|
|
81
|
+
`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type FileSpec = {
|
|
85
|
+
name: string
|
|
86
|
+
getContent: (args: {
|
|
87
|
+
eject: boolean
|
|
88
|
+
options: OneBundlerConfigOptions
|
|
89
|
+
}) => string
|
|
90
|
+
conflicting: readonly string[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const FILES: readonly FileSpec[] = [
|
|
94
|
+
{
|
|
95
|
+
name: 'babel.config.cjs',
|
|
96
|
+
getContent: buildBabelConfigContent,
|
|
97
|
+
conflicting: ['babel.config.js', 'babel.config.mjs', '.babelrc', '.babelrc.js'],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'metro.config.cjs',
|
|
101
|
+
getContent: buildMetroConfigContent,
|
|
102
|
+
conflicting: ['metro.config.js', 'metro.config.mjs'],
|
|
103
|
+
},
|
|
104
|
+
] as const
|
|
105
|
+
|
|
106
|
+
function stripUndefined(value: unknown): unknown {
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
return value.map(stripUndefined)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (value && typeof value === 'object') {
|
|
112
|
+
return Object.fromEntries(
|
|
113
|
+
Object.entries(value)
|
|
114
|
+
.filter(([, entry]) => entry !== undefined)
|
|
115
|
+
.map(([key, entry]) => [key, stripUndefined(entry)])
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function assertSerializable(value: unknown, keyPath = 'one bundler options') {
|
|
123
|
+
if (
|
|
124
|
+
typeof value === 'function' ||
|
|
125
|
+
typeof value === 'symbol' ||
|
|
126
|
+
typeof value === 'bigint'
|
|
127
|
+
) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[one] ${keyPath} must be JSON-serializable to generate Babel/Metro config files. Move function-valued native linking/customization into an ejected config.`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
value.forEach((entry, index) => assertSerializable(entry, `${keyPath}[${index}]`))
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (value && typeof value === 'object') {
|
|
139
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
140
|
+
assertSerializable(entry, `${keyPath}.${key}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function serializeBundlerConfigOptions(options: OneBundlerConfigOptions): string {
|
|
146
|
+
const clean = stripUndefined(options) as OneBundlerConfigOptions
|
|
147
|
+
assertSerializable(clean)
|
|
148
|
+
return JSON.stringify(clean, null, 2)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getBundlerConfigOptionsFromOneOptions(
|
|
152
|
+
oneOptions: One.PluginOptions = {}
|
|
153
|
+
): OneBundlerConfigOptions {
|
|
154
|
+
return stripUndefined({
|
|
155
|
+
routerRoot: getRouterRootFromOneOptions(oneOptions),
|
|
156
|
+
ignoredRouteFiles: oneOptions.router?.ignoredRouteFiles,
|
|
157
|
+
linking: oneOptions.router?.linking,
|
|
158
|
+
setupFile: oneOptions.setupFile,
|
|
159
|
+
}) as OneBundlerConfigOptions
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type GenerateBundlerConfigArgs = {
|
|
163
|
+
/** Project root. Defaults to `process.cwd()`. */
|
|
164
|
+
cwd?: string
|
|
165
|
+
/** loaded one plugin options from vite.config. */
|
|
166
|
+
oneOptions?: One.PluginOptions
|
|
167
|
+
/** Overwrite even when the file has been customized (marker removed). */
|
|
168
|
+
force?: boolean
|
|
169
|
+
/** Just verify state without writing — exits non-zero when out of sync. */
|
|
170
|
+
check?: boolean
|
|
171
|
+
/** Suppress logging. */
|
|
172
|
+
quiet?: boolean
|
|
173
|
+
/**
|
|
174
|
+
* Write files WITHOUT the `@one/generated` marker. The user owns the file
|
|
175
|
+
* after this; subsequent CI auto-gen runs will treat it as customized and
|
|
176
|
+
* skip it. used by `one metro-eject`.
|
|
177
|
+
*/
|
|
178
|
+
eject?: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type FileResult = {
|
|
182
|
+
filePath: string
|
|
183
|
+
action: 'wrote' | 'kept' | 'skipped-customized' | 'skipped-other-format' | 'would-write' | 'would-overwrite'
|
|
184
|
+
reason?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function generateBundlerConfig(args: GenerateBundlerConfigArgs = {}): {
|
|
188
|
+
results: FileResult[]
|
|
189
|
+
ok: boolean
|
|
190
|
+
} {
|
|
191
|
+
const cwd = path.resolve(args.cwd ?? process.cwd())
|
|
192
|
+
const force = !!args.force
|
|
193
|
+
const check = !!args.check
|
|
194
|
+
const quiet = !!args.quiet
|
|
195
|
+
|
|
196
|
+
const log = (msg: string) => {
|
|
197
|
+
if (!quiet) console.info(msg)
|
|
198
|
+
}
|
|
199
|
+
const warn = (msg: string) => {
|
|
200
|
+
if (!quiet) console.warn(msg)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const results: FileResult[] = []
|
|
204
|
+
|
|
205
|
+
const eject = !!args.eject
|
|
206
|
+
const bundlerOptions = getBundlerConfigOptionsFromOneOptions(args.oneOptions)
|
|
207
|
+
|
|
208
|
+
for (const file of FILES) {
|
|
209
|
+
const filePath = path.join(cwd, file.name)
|
|
210
|
+
const targetContent = file.getContent({ eject, options: bundlerOptions })
|
|
211
|
+
|
|
212
|
+
// detect conflicting other-extension variants the user might be using
|
|
213
|
+
const conflict = file.conflicting.find((alt) => existsSync(path.join(cwd, alt)))
|
|
214
|
+
if (conflict && !existsSync(filePath)) {
|
|
215
|
+
results.push({
|
|
216
|
+
filePath: path.join(cwd, conflict),
|
|
217
|
+
action: 'skipped-other-format',
|
|
218
|
+
reason: `Found ${conflict}; not creating ${file.name}. To switch, delete ${conflict} and re-run with --force.`,
|
|
219
|
+
})
|
|
220
|
+
warn(
|
|
221
|
+
colors.yellow(
|
|
222
|
+
`[one] found ${conflict} — leaving it alone. Delete it and re-run with --force to switch to ${file.name}.`
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!existsSync(filePath)) {
|
|
229
|
+
if (check) {
|
|
230
|
+
results.push({ filePath, action: 'would-write' })
|
|
231
|
+
log(colors.yellow(`[one] missing: ${file.name}`))
|
|
232
|
+
continue
|
|
233
|
+
}
|
|
234
|
+
writeFileSync(filePath, targetContent)
|
|
235
|
+
results.push({ filePath, action: 'wrote' })
|
|
236
|
+
log(colors.green(`[one] wrote ${file.name}`))
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const existing = readFileSync(filePath, 'utf8')
|
|
241
|
+
|
|
242
|
+
if (existing === targetContent) {
|
|
243
|
+
results.push({ filePath, action: 'kept' })
|
|
244
|
+
log(colors.dim(`[one] up to date: ${file.name}`))
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const hasMarker = existing.includes(ONE_GENERATED_MARKER)
|
|
249
|
+
|
|
250
|
+
if (!hasMarker && !force) {
|
|
251
|
+
results.push({
|
|
252
|
+
filePath,
|
|
253
|
+
action: 'skipped-customized',
|
|
254
|
+
reason: `${file.name} has been customized (no @one marker). Re-add the marker comment or pass --force to overwrite.`,
|
|
255
|
+
})
|
|
256
|
+
warn(
|
|
257
|
+
colors.yellow(
|
|
258
|
+
`[one] ${file.name} appears customized — skipping. Pass --force to overwrite.`
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
continue
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (check) {
|
|
265
|
+
results.push({ filePath, action: 'would-overwrite' })
|
|
266
|
+
log(colors.yellow(`[one] out of date: ${file.name}`))
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
writeFileSync(filePath, targetContent)
|
|
271
|
+
results.push({ filePath, action: 'wrote' })
|
|
272
|
+
log(colors.green(`[one] updated ${file.name}`))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// "ok" means the on-disk state is something we can live with — either we
|
|
276
|
+
// wrote what we wanted, the existing file is up to date, or the user has
|
|
277
|
+
// explicitly customized (their intent, not our problem).
|
|
278
|
+
// check mode is stricter: missing/stale files mean a regen is needed.
|
|
279
|
+
const acceptableAlways = new Set<FileResult['action']>([
|
|
280
|
+
'wrote',
|
|
281
|
+
'kept',
|
|
282
|
+
'skipped-other-format',
|
|
283
|
+
'skipped-customized',
|
|
284
|
+
])
|
|
285
|
+
const acceptableInCheck = new Set<FileResult['action']>([
|
|
286
|
+
'kept',
|
|
287
|
+
'skipped-other-format',
|
|
288
|
+
'skipped-customized',
|
|
289
|
+
])
|
|
290
|
+
const ok = (check ? acceptableInCheck : acceptableAlways).size
|
|
291
|
+
? results.every((r) =>
|
|
292
|
+
(check ? acceptableInCheck : acceptableAlways).has(r.action)
|
|
293
|
+
)
|
|
294
|
+
: false
|
|
295
|
+
|
|
296
|
+
return { results, ok }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* True when running on a CI/EAS worker. We only auto-generate bundler-config
|
|
301
|
+
* files in CI so they never appear in a developer's local working tree.
|
|
302
|
+
*
|
|
303
|
+
* Accepts any truthy value for `CI` / `EAS_BUILD` since providers vary:
|
|
304
|
+
* GitHub Actions sets `CI=true`, others use `CI=1`, EAS sets `EAS_BUILD=true`.
|
|
305
|
+
*
|
|
306
|
+
* Set `CI=1` (or `EAS_BUILD=true`) ahead of `eas update` if you need to
|
|
307
|
+
* publish from a local machine.
|
|
308
|
+
*/
|
|
309
|
+
export function isCiEnvironment(): boolean {
|
|
310
|
+
const truthy = (v: string | undefined) =>
|
|
311
|
+
!!v && v !== 'false' && v !== '0'
|
|
312
|
+
return truthy(process.env.EAS_BUILD) || truthy(process.env.CI)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Postinstall hook: when expo-updates is in deps AND we're running on
|
|
317
|
+
* a CI/EAS worker, ensure the bundler-config files exist so the
|
|
318
|
+
* subsequent `expo export` / EXUpdates Metro pass succeeds.
|
|
319
|
+
*
|
|
320
|
+
* No-op locally so the files never show up in a developer's working tree.
|
|
321
|
+
*/
|
|
322
|
+
export function maybeGenerateBundlerConfigOnInstall(
|
|
323
|
+
cwd: string = process.cwd(),
|
|
324
|
+
oneOptions?: One.PluginOptions
|
|
325
|
+
): void {
|
|
326
|
+
if (!isCiEnvironment()) return
|
|
327
|
+
|
|
328
|
+
// detect expo-updates via the project's own resolver — same check used
|
|
329
|
+
// by the vxrn expo-plugin and one prebuild
|
|
330
|
+
try {
|
|
331
|
+
nodeModule
|
|
332
|
+
.createRequire(cwd + '/')
|
|
333
|
+
.resolve('expo-updates/package.json')
|
|
334
|
+
} catch {
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
generateBundlerConfig({ cwd, quiet: false, oneOptions })
|
|
339
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { run } from './patch'
|
|
3
|
+
|
|
4
|
+
const { patchMock, loadUserOneOptionsMock } = vi.hoisted(() => ({
|
|
5
|
+
patchMock: vi.fn(),
|
|
6
|
+
loadUserOneOptionsMock: vi.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
vi.mock('vxrn', () => ({
|
|
10
|
+
patch: patchMock,
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../vite/loadConfig', () => ({
|
|
14
|
+
loadUserOneOptions: loadUserOneOptionsMock,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
describe('one patch', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
patchMock.mockReset()
|
|
20
|
+
loadUserOneOptionsMock.mockReset()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('applies built-in patches when a native-only app has no vite config', async () => {
|
|
24
|
+
loadUserOneOptionsMock.mockRejectedValueOnce(
|
|
25
|
+
new Error('No config config in /tmp/native-only-app. Is this the correct directory?')
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
await run({})
|
|
29
|
+
|
|
30
|
+
expect(patchMock).toHaveBeenCalledWith({
|
|
31
|
+
root: process.cwd(),
|
|
32
|
+
deps: undefined,
|
|
33
|
+
force: undefined,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('passes configured user patches through to vxrn', async () => {
|
|
38
|
+
const patches = {
|
|
39
|
+
'example-package': {
|
|
40
|
+
version: '1',
|
|
41
|
+
'index.js': 'export default 1',
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
loadUserOneOptionsMock.mockResolvedValueOnce({
|
|
45
|
+
oneOptions: { patches },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
await run({ force: true })
|
|
49
|
+
|
|
50
|
+
expect(patchMock).toHaveBeenCalledWith({
|
|
51
|
+
root: process.cwd(),
|
|
52
|
+
deps: patches,
|
|
53
|
+
force: true,
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('keeps failing when a vite config exists but does not load one', async () => {
|
|
58
|
+
loadUserOneOptionsMock.mockRejectedValueOnce(
|
|
59
|
+
new Error('One not loaded properly, is the one() plugin in your vite.config.ts?')
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await expect(run({})).rejects.toThrow('One not loaded properly')
|
|
63
|
+
expect(patchMock).not.toHaveBeenCalled()
|
|
64
|
+
})
|
|
65
|
+
})
|
package/src/cli/patch.ts
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
import type { SimpleDepPatchObject, PatchOptions } from 'vxrn'
|
|
2
2
|
import { loadUserOneOptions } from '../vite/loadConfig'
|
|
3
|
+
import { maybeGenerateBundlerConfigOnInstall } from './generateBundlerConfig'
|
|
4
|
+
|
|
5
|
+
function isMissingViteConfigError(error: unknown) {
|
|
6
|
+
return (
|
|
7
|
+
error instanceof Error &&
|
|
8
|
+
error.message.startsWith('No config config in ') &&
|
|
9
|
+
error.message.endsWith(' Is this the correct directory?')
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function loadUserOptions() {
|
|
14
|
+
try {
|
|
15
|
+
return await loadUserOneOptions('build')
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (isMissingViteConfigError(error)) {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
throw error
|
|
21
|
+
}
|
|
22
|
+
}
|
|
3
23
|
|
|
4
24
|
export async function run(args: { force?: boolean }) {
|
|
5
25
|
process.env.IS_VXRN_CLI = 'true'
|
|
6
26
|
const { patch } = await import('vxrn')
|
|
7
27
|
|
|
8
|
-
const options = await
|
|
9
|
-
|
|
28
|
+
const options = await loadUserOptions()
|
|
29
|
+
|
|
30
|
+
// ensure babel.config.cjs + metro.config.cjs exist when a project uses
|
|
31
|
+
// expo-updates and we're on an eas/ci worker. the generated files capture
|
|
32
|
+
// the loaded one() router/setup options so standalone metro matches one.
|
|
33
|
+
if (options) {
|
|
34
|
+
maybeGenerateBundlerConfigOnInstall(process.cwd(), options.oneOptions)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const patches = options?.oneOptions.patches as SimpleDepPatchObject | undefined
|
|
10
38
|
|
|
11
39
|
if (process.env.DEBUG) {
|
|
12
40
|
console.info('User patches:', Object.keys(patches || {}))
|
package/src/cli.ts
CHANGED
|
@@ -33,6 +33,7 @@ const docsLinks = {
|
|
|
33
33
|
patch: `${DOCS_BASE}/configuration`,
|
|
34
34
|
'generate-routes': `${DOCS_BASE}/routing-typed-routes`,
|
|
35
35
|
typegen: `${DOCS_BASE}/routing-typed-routes`,
|
|
36
|
+
'metro-eject': `${DOCS_BASE}/guides-ota-updates`,
|
|
36
37
|
} as const
|
|
37
38
|
|
|
38
39
|
function withDocsLink(description: string, command: keyof typeof docsLinks): string {
|
|
@@ -353,6 +354,35 @@ const typegen = defineCommand({
|
|
|
353
354
|
},
|
|
354
355
|
})
|
|
355
356
|
|
|
357
|
+
const metroEject = defineCommand({
|
|
358
|
+
meta: {
|
|
359
|
+
name: 'metro-eject',
|
|
360
|
+
version: version,
|
|
361
|
+
description: withDocsLink(
|
|
362
|
+
'Write babel.config.cjs + metro.config.cjs so you can own them and customize freely',
|
|
363
|
+
'metro-eject'
|
|
364
|
+
),
|
|
365
|
+
},
|
|
366
|
+
args: {
|
|
367
|
+
force: {
|
|
368
|
+
type: 'boolean',
|
|
369
|
+
description: 'Overwrite existing files',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
async run({ args }) {
|
|
373
|
+
const { generateBundlerConfig } = await import('./cli/generateBundlerConfig')
|
|
374
|
+
const { loadUserOneOptions } = await import('./vite/loadConfig')
|
|
375
|
+
process.env.IS_VXRN_CLI = 'true'
|
|
376
|
+
const { oneOptions } = await loadUserOneOptions('build')
|
|
377
|
+
const { ok } = generateBundlerConfig({
|
|
378
|
+
force: !!args.force,
|
|
379
|
+
eject: true,
|
|
380
|
+
oneOptions,
|
|
381
|
+
})
|
|
382
|
+
if (!ok) process.exit(1)
|
|
383
|
+
},
|
|
384
|
+
})
|
|
385
|
+
|
|
356
386
|
const daemonCommand = defineCommand({
|
|
357
387
|
meta: {
|
|
358
388
|
name: 'daemon',
|
|
@@ -412,6 +442,7 @@ const subCommands = {
|
|
|
412
442
|
'generate-routes': generateRoutes,
|
|
413
443
|
typegen,
|
|
414
444
|
daemon: daemonCommand,
|
|
445
|
+
'metro-eject': metroEject,
|
|
415
446
|
}
|
|
416
447
|
|
|
417
448
|
// workaround for having sub-commands but also positional arg for naming in the create flow
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import module from 'node:module'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export type BuildOneMetroResolverOverridesOptions = {
|
|
5
|
+
projectRoot: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type MetroConfigLike = { resolver?: Record<string, any> } | undefined
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build the Metro resolver overrides One needs for native bundles.
|
|
12
|
+
*
|
|
13
|
+
* Used by getViteMetroPluginOptions, which feeds these into the same
|
|
14
|
+
* getMetroConfigFromViteConfig pipeline both production native bundles and
|
|
15
|
+
* standalone Metro invocations (expo export, eas update) go through. The
|
|
16
|
+
* overrides handle One-specific concerns: server-only stripping, .css → empty,
|
|
17
|
+
* _middleware → empty, react-native-svg fix.
|
|
18
|
+
*
|
|
19
|
+
* Returns a function that takes Metro's default config and produces an
|
|
20
|
+
* overridden config. Callers compose any additional overrides on top.
|
|
21
|
+
*/
|
|
22
|
+
export function buildOneMetroResolverOverrides({
|
|
23
|
+
projectRoot,
|
|
24
|
+
}: BuildOneMetroResolverOverridesOptions): <T extends MetroConfigLike>(
|
|
25
|
+
defaultConfig: T
|
|
26
|
+
) => T {
|
|
27
|
+
const require = module.createRequire(projectRoot + '/')
|
|
28
|
+
const emptyPath = require.resolve('@vxrn/vite-plugin-metro/empty', {
|
|
29
|
+
paths: [projectRoot],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return <T extends MetroConfigLike>(defaultConfig: T): T => {
|
|
33
|
+
const resolver: Record<string, any> = {
|
|
34
|
+
...defaultConfig?.resolver,
|
|
35
|
+
extraNodeModules: {
|
|
36
|
+
...defaultConfig?.resolver?.extraNodeModules,
|
|
37
|
+
},
|
|
38
|
+
nodeModulesPaths: defaultConfig?.resolver?.nodeModulesPaths,
|
|
39
|
+
resolveRequest: (context: any, moduleName: string, platform: string) => {
|
|
40
|
+
if (moduleName.endsWith('.css')) {
|
|
41
|
+
return {
|
|
42
|
+
type: 'sourceFile',
|
|
43
|
+
filePath: emptyPath,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// On Vite side this is done by excludeAPIAndMiddlewareRoutesPlugin
|
|
48
|
+
if (/_middleware.tsx?$/.test(moduleName)) {
|
|
49
|
+
return {
|
|
50
|
+
type: 'sourceFile',
|
|
51
|
+
filePath: emptyPath,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// server-only files should never be in the native bundle.
|
|
56
|
+
// metro follows dynamic import chains (e.g. zero models →
|
|
57
|
+
// server effects → server packages) and tries to resolve
|
|
58
|
+
// everything, even though the code only runs on the server.
|
|
59
|
+
if (/\.server(\.[jt]sx?)?$/.test(moduleName)) {
|
|
60
|
+
return {
|
|
61
|
+
type: 'sourceFile',
|
|
62
|
+
filePath: emptyPath,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// react-native-svg's package.json has "react-native": "src/index.ts"
|
|
67
|
+
// which points to TS source that only type-exports Svg/Circle/Path etc.
|
|
68
|
+
// force resolution to the compiled JS which has proper named value exports.
|
|
69
|
+
if (moduleName === 'react-native-svg') {
|
|
70
|
+
const defaultResolveRequest =
|
|
71
|
+
defaultConfig?.resolver?.resolveRequest || context.resolveRequest
|
|
72
|
+
const res = defaultResolveRequest(context, moduleName, platform)
|
|
73
|
+
const svgSrcSuffix = `${path.sep}src${path.sep}index.ts`
|
|
74
|
+
if (res && 'filePath' in res && res.filePath.includes(svgSrcSuffix)) {
|
|
75
|
+
return {
|
|
76
|
+
...res,
|
|
77
|
+
filePath: res.filePath.replace(
|
|
78
|
+
svgSrcSuffix,
|
|
79
|
+
`${path.sep}lib${path.sep}commonjs${path.sep}index.js`
|
|
80
|
+
),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return res
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const defaultResolveRequest =
|
|
87
|
+
defaultConfig?.resolver?.resolveRequest || context.resolveRequest
|
|
88
|
+
const res = defaultResolveRequest(context, moduleName, platform)
|
|
89
|
+
|
|
90
|
+
// catch .server files that were resolved by path
|
|
91
|
+
if (res && 'filePath' in res && /\.server\.[jt]sx?$/.test(res.filePath)) {
|
|
92
|
+
return { type: 'sourceFile', filePath: emptyPath }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return res
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
...(defaultConfig as any),
|
|
101
|
+
resolver,
|
|
102
|
+
} as T
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
+
import { getViteMetroPluginOptions } from './getViteMetroPluginOptions'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* the vite-driven metro path must always inject one's required babel plugins.
|
|
8
|
+
* a project babel.config can customize normal babel behavior, but it cannot be
|
|
9
|
+
* the source of truth for one's router/server-code transforms because ordinary
|
|
10
|
+
* react native configs do not know about them.
|
|
11
|
+
*/
|
|
12
|
+
describe('getViteMetroPluginOptions babel-config coexistence', () => {
|
|
13
|
+
let tmpDir: string
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
// place the fixture under the workspace root so Node can walk up to
|
|
17
|
+
// node_modules and resolve `one/metro-entry` + `@vxrn/vite-plugin-metro/empty`
|
|
18
|
+
const workspaceRoot = path.resolve(__dirname, '../../../../')
|
|
19
|
+
tmpDir = fs.mkdtempSync(path.join(workspaceRoot, '.tmp-one-vite-metro-test-'))
|
|
20
|
+
fs.writeFileSync(
|
|
21
|
+
path.join(tmpDir, 'tsconfig.json'),
|
|
22
|
+
JSON.stringify({ compilerOptions: { paths: {} } })
|
|
23
|
+
)
|
|
24
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'tmp' }))
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('injects plugins when there is no user babel.config', () => {
|
|
32
|
+
const opts = getViteMetroPluginOptions({
|
|
33
|
+
projectRoot: tmpDir,
|
|
34
|
+
relativeRouterRoot: 'app',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(opts?.babelConfig?.plugins).toBeDefined()
|
|
38
|
+
expect(opts?.oneViteMetroBabelConfig).toBe(true)
|
|
39
|
+
// 5 One plugins (Vite path skips import-meta-env-plugin since it's
|
|
40
|
+
// injected separately via getMetroBabelConfigFromViteConfig)
|
|
41
|
+
expect(opts?.babelConfig?.plugins?.length).toBe(5)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('still injects One plugins when the user has a babel.config.cjs', () => {
|
|
45
|
+
const cfgPath = path.join(tmpDir, 'babel.config.cjs')
|
|
46
|
+
fs.writeFileSync(cfgPath, "module.exports = require('one/babel-preset')\n")
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const opts = getViteMetroPluginOptions({
|
|
50
|
+
projectRoot: tmpDir,
|
|
51
|
+
relativeRouterRoot: 'app',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(opts?.babelConfig?.plugins?.length).toBe(5)
|
|
55
|
+
} finally {
|
|
56
|
+
fs.unlinkSync(cfgPath)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('still injects One plugins when the user has a babel.config.js', () => {
|
|
61
|
+
const cfgPath = path.join(tmpDir, 'babel.config.js')
|
|
62
|
+
fs.writeFileSync(cfgPath, "module.exports = require('one/babel-preset')\n")
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const opts = getViteMetroPluginOptions({
|
|
66
|
+
projectRoot: tmpDir,
|
|
67
|
+
relativeRouterRoot: 'app',
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(opts?.babelConfig?.plugins?.length).toBe(5)
|
|
71
|
+
} finally {
|
|
72
|
+
fs.unlinkSync(cfgPath)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
})
|