@graphcommerce/next-config 9.1.0-canary.54 → 10.0.0-canary.56
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/CHANGELOG.md +82 -0
- package/Config.graphqls +3 -3
- package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +1 -1
- package/__tests__/interceptors/generateInterceptors.ts +133 -150
- package/dist/config/loadConfig.js +7 -0
- package/dist/generated/config.js +9 -9
- package/dist/index.js +804 -2436
- package/dist/loadConfig-nJiCKeL1.js +311 -0
- package/dist/utils/findParentPath.js +36 -0
- package/package.json +41 -20
- package/src/commands/cleanupInterceptors.ts +26 -0
- package/src/commands/codegen.ts +13 -15
- package/src/commands/codegenInterceptors.ts +31 -0
- package/src/{config/commands → commands}/exportConfig.ts +3 -3
- package/src/{config/commands → commands}/generateConfig.ts +12 -9
- package/src/commands/generateConfigValues.ts +265 -0
- package/src/commands/index.ts +7 -0
- package/src/config/index.ts +0 -9
- package/src/config/loadConfig.ts +0 -1
- package/src/config/utils/mergeEnvIntoConfig.ts +27 -4
- package/src/generated/config.ts +13 -14
- package/src/index.ts +7 -39
- package/src/interceptors/generateInterceptor.ts +192 -157
- package/src/interceptors/generateInterceptors.ts +9 -2
- package/src/interceptors/updatePackageExports.ts +147 -0
- package/src/interceptors/writeInterceptors.ts +90 -35
- package/src/types.ts +26 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/resolveDependenciesSync.ts +5 -7
- package/src/withGraphCommerce.ts +30 -49
- package/tsconfig.json +3 -1
- package/__tests__/config/utils/configToImportMeta.ts +0 -121
- package/src/interceptors/InterceptorPlugin.ts +0 -141
- package/src/interceptors/commands/codegenInterceptors.ts +0 -27
- /package/src/utils/{isMonorepo.ts → findParentPath.ts} +0 -0
|
@@ -2,8 +2,6 @@ import prettierConf from '@graphcommerce/prettier-config-pwa'
|
|
|
2
2
|
import prettier from 'prettier'
|
|
3
3
|
import type { GraphCommerceDebugConfig } from '../generated/config'
|
|
4
4
|
import type { ResolveDependencyReturn } from '../utils/resolveDependency'
|
|
5
|
-
import { RenameVisitor } from './RenameVisitor'
|
|
6
|
-
import { parseSync, printSync } from './swc'
|
|
7
5
|
|
|
8
6
|
type PluginBaseConfig = {
|
|
9
7
|
type: 'component' | 'function' | 'replace'
|
|
@@ -49,7 +47,7 @@ export function isMethodPluginConfig(
|
|
|
49
47
|
/** @public */
|
|
50
48
|
export function isReplacePluginConfig(
|
|
51
49
|
plugin: Partial<PluginBaseConfig>,
|
|
52
|
-
): plugin is
|
|
50
|
+
): plugin is ReplacePluginConfig {
|
|
53
51
|
if (!isPluginBaseConfig(plugin)) return false
|
|
54
52
|
return plugin.type === 'replace'
|
|
55
53
|
}
|
|
@@ -63,14 +61,19 @@ export function isPluginConfig(plugin: Partial<PluginConfig>): plugin is PluginC
|
|
|
63
61
|
export type Interceptor = ResolveDependencyReturn & {
|
|
64
62
|
targetExports: Record<string, PluginConfig[]>
|
|
65
63
|
target: string
|
|
66
|
-
source: string
|
|
67
|
-
template?: string
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
export type MaterializedPlugin = Interceptor & {
|
|
66
|
+
export type MaterializedPlugin = Interceptor & {
|
|
67
|
+
template: string
|
|
68
|
+
}
|
|
71
69
|
|
|
72
|
-
export
|
|
73
|
-
|
|
70
|
+
export function moveRelativeDown(plugins: PluginConfig[]) {
|
|
71
|
+
return [...plugins].sort((a, b) => {
|
|
72
|
+
if (a.sourceModule.startsWith('.') && !b.sourceModule.startsWith('.')) return 1
|
|
73
|
+
if (!a.sourceModule.startsWith('.') && b.sourceModule.startsWith('.')) return -1
|
|
74
|
+
return 0
|
|
75
|
+
})
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
const originalSuffix = 'Original'
|
|
76
79
|
const interceptorSuffix = 'Interceptor'
|
|
@@ -87,25 +90,29 @@ const sourceName = (n: string) => `${n}`
|
|
|
87
90
|
const interceptorName = (n: string) => `${n}${interceptorSuffix}`
|
|
88
91
|
const interceptorPropsName = (n: string) => `${n}Props`
|
|
89
92
|
|
|
90
|
-
export function moveRelativeDown(plugins: PluginConfig[]) {
|
|
91
|
-
return [...plugins].sort((a, b) => {
|
|
92
|
-
if (a.sourceModule.startsWith('.') && !b.sourceModule.startsWith('.')) return 1
|
|
93
|
-
if (!a.sourceModule.startsWith('.') && b.sourceModule.startsWith('.')) return -1
|
|
94
|
-
return 0
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
93
|
const generateIdentifyer = (s: string) =>
|
|
99
94
|
Math.abs(
|
|
100
95
|
s.split('').reduce((a, b) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// eslint-disable-next-line no-bitwise
|
|
104
|
-
return a & a
|
|
96
|
+
const value = ((a << 5) - a + b.charCodeAt(0)) & 0xffffffff
|
|
97
|
+
return value < 0 ? value * -2 : value
|
|
105
98
|
}, 0),
|
|
106
99
|
).toString()
|
|
107
100
|
|
|
108
|
-
|
|
101
|
+
// Create a stable string representation of an object by sorting keys recursively
|
|
102
|
+
const stableStringify = (obj: any): string => {
|
|
103
|
+
if (obj === null || obj === undefined) return String(obj)
|
|
104
|
+
if (typeof obj !== 'object') return String(obj)
|
|
105
|
+
if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(',')}]`
|
|
106
|
+
|
|
107
|
+
const keys = Object.keys(obj).sort()
|
|
108
|
+
const pairs = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`)
|
|
109
|
+
return `{${pairs.join(',')}}`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract the identifier from the first line of the source file. The identifier is in the format:
|
|
114
|
+
* slash-star hash:${identifer} star-slash
|
|
115
|
+
*/
|
|
109
116
|
function extractIdentifier(source: string | undefined) {
|
|
110
117
|
if (!source) return null
|
|
111
118
|
const match = source.match(/\/\* hash:(\d+) \*\//)
|
|
@@ -118,18 +125,24 @@ export async function generateInterceptor(
|
|
|
118
125
|
config: GraphCommerceDebugConfig,
|
|
119
126
|
oldInterceptorSource?: string,
|
|
120
127
|
): Promise<MaterializedPlugin> {
|
|
121
|
-
|
|
128
|
+
// Create a stable hash based only on the content that affects the output
|
|
129
|
+
const hashInput = {
|
|
130
|
+
dependency: interceptor.dependency,
|
|
131
|
+
targetExports: interceptor.targetExports,
|
|
132
|
+
// Only include config properties that affect the output
|
|
133
|
+
debugConfig: config.pluginStatus ? { pluginStatus: config.pluginStatus } : {},
|
|
134
|
+
}
|
|
135
|
+
const identifer = generateIdentifyer(stableStringify(hashInput))
|
|
122
136
|
|
|
123
|
-
const { dependency, targetExports
|
|
137
|
+
const { dependency, targetExports } = interceptor
|
|
124
138
|
|
|
125
139
|
if (oldInterceptorSource && identifer === extractIdentifier(oldInterceptorSource))
|
|
126
140
|
return { ...interceptor, template: oldInterceptorSource }
|
|
127
141
|
|
|
128
142
|
const pluginConfigs = [...Object.entries(targetExports)].map(([, plugins]) => plugins).flat()
|
|
129
143
|
|
|
130
|
-
//
|
|
144
|
+
// Generate plugin imports
|
|
131
145
|
const duplicateImports = new Set()
|
|
132
|
-
|
|
133
146
|
const pluginImports = moveRelativeDown(
|
|
134
147
|
[...pluginConfigs].sort((a, b) => a.sourceModule.localeCompare(b.sourceModule)),
|
|
135
148
|
)
|
|
@@ -142,128 +155,153 @@ export async function generateInterceptor(
|
|
|
142
155
|
duplicateImports.add(str)
|
|
143
156
|
return true
|
|
144
157
|
})
|
|
145
|
-
.join('\n')
|
|
146
|
-
|
|
147
|
-
const ast = parseSync(source)
|
|
148
|
-
|
|
149
|
-
new RenameVisitor(Object.keys(targetExports), (s) => originalName(s)).visitModule(ast)
|
|
150
|
-
|
|
151
|
-
const pluginExports = Object.entries(targetExports)
|
|
152
|
-
.map(([base, plugins]) => {
|
|
153
|
-
const duplicateInterceptors = new Set()
|
|
154
|
-
|
|
155
|
-
let carry = originalName(base)
|
|
156
|
-
let carryProps: string[] = []
|
|
157
|
-
const pluginSee: string[] = []
|
|
158
|
-
|
|
159
|
-
pluginSee.push(
|
|
160
|
-
`@see {@link file://${interceptor.sourcePathRelative}} for original source file`,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
const pluginStr = plugins
|
|
164
|
-
.reverse()
|
|
165
|
-
.filter((p: PluginConfig) => {
|
|
166
|
-
if (duplicateInterceptors.has(name(p))) return false
|
|
167
|
-
duplicateInterceptors.add(name(p))
|
|
168
|
-
return true
|
|
169
|
-
})
|
|
170
|
-
.map((p) => {
|
|
171
|
-
let result
|
|
172
|
-
|
|
173
|
-
const wrapChain = plugins
|
|
174
|
-
.reverse()
|
|
175
|
-
.map((pl) => name(pl))
|
|
176
|
-
.join(' wrapping ')
|
|
177
|
-
|
|
178
|
-
if (isReplacePluginConfig(p)) {
|
|
179
|
-
new RenameVisitor([originalName(p.targetExport)], (s) =>
|
|
180
|
-
s.replace(originalSuffix, disabledSuffix),
|
|
181
|
-
).visitModule(ast)
|
|
182
|
-
|
|
183
|
-
carryProps.push(`React.ComponentProps<typeof ${sourceName(name(p))}>`)
|
|
184
|
-
|
|
185
|
-
pluginSee.push(
|
|
186
|
-
`@see {${sourceName(name(p))}} for replacement of the original source (original source not used)`,
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (isReactPluginConfig(p)) {
|
|
191
|
-
const withBraces = config.pluginStatus || process.env.NODE_ENV === 'development'
|
|
192
|
-
|
|
193
|
-
result = `
|
|
194
|
-
type ${interceptorPropsName(name(p))} = ${carryProps.join(' & ')} & OmitPrev<React.ComponentProps<typeof ${sourceName(name(p))}>, 'Prev'>
|
|
195
|
-
|
|
196
|
-
const ${interceptorName(name(p))} = (props: ${interceptorPropsName(name(p))}) => ${withBraces ? '{' : '('}
|
|
197
|
-
${config.pluginStatus ? `logOnce(\`🔌 Rendering ${base} with plugin(s): ${wrapChain} wrapping <${base}/>\`)` : ''}
|
|
198
|
-
|
|
199
|
-
${
|
|
200
|
-
process.env.NODE_ENV === 'development'
|
|
201
|
-
? `if(!props['data-plugin'])
|
|
202
|
-
logOnce('${fileName(p)} does not spread props to prev: <Prev {...props}/>. This will cause issues if multiple plugins are applied to this component.')`
|
|
203
|
-
: ''
|
|
204
|
-
}
|
|
205
|
-
${withBraces ? 'return' : ''} <${sourceName(name(p))} {...props} Prev={${carry}} />
|
|
206
|
-
${withBraces ? '}' : ')'}`
|
|
207
|
-
|
|
208
|
-
carryProps = [interceptorPropsName(name(p))]
|
|
209
|
-
pluginSee.push(`@see {${sourceName(name(p))}} for source of applied plugin`)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (isMethodPluginConfig(p)) {
|
|
213
|
-
result = `const ${interceptorName(name(p))}: typeof ${carry} = (...args) => {
|
|
214
|
-
${config.pluginStatus ? `logOnce(\`🔌 Calling ${base} with plugin(s): ${wrapChain} wrapping ${base}()\`)` : ''}
|
|
215
|
-
return ${sourceName(name(p))}(${carry}, ...args)
|
|
216
|
-
}`
|
|
217
|
-
pluginSee.push(`@see {${sourceName(name(p))}} for source of applied plugin`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
carry = p.type === 'replace' ? sourceName(name(p)) : interceptorName(name(p))
|
|
221
|
-
return result
|
|
222
|
-
})
|
|
223
|
-
.filter((v) => !!v)
|
|
224
|
-
.join('\n')
|
|
225
|
-
|
|
226
|
-
const isComponent = plugins.every((p) => isReactPluginConfig(p))
|
|
227
|
-
if (isComponent && plugins.some((p) => isMethodPluginConfig(p))) {
|
|
228
|
-
throw new Error(`Cannot mix React and Method plugins for ${base} in ${dependency}.`)
|
|
229
|
-
}
|
|
158
|
+
.join('\n ')
|
|
230
159
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
*
|
|
237
|
-
${pluginSee.map((s) => `* ${s}`).join('\n')}
|
|
238
|
-
*/`
|
|
239
|
-
|
|
240
|
-
if (process.env.NODE_ENV === 'development' && isComponent) {
|
|
241
|
-
return `${pluginStr}
|
|
242
|
-
${seeString}
|
|
243
|
-
export const ${base}: typeof ${carry} = (props) => {
|
|
244
|
-
return <${carry} {...props} data-plugin />
|
|
245
|
-
}`
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return `
|
|
249
|
-
${pluginStr}
|
|
250
|
-
${seeString}
|
|
251
|
-
export const ${base} = ${carry}
|
|
252
|
-
`
|
|
160
|
+
// Generate imports for original components (only when no replace plugin exists)
|
|
161
|
+
const originalImports = [...Object.entries(targetExports)]
|
|
162
|
+
.filter(([targetExport, plugins]) => {
|
|
163
|
+
// Only import original if there's no replace plugin for this export
|
|
164
|
+
return !plugins.some((p) => p.type === 'replace')
|
|
253
165
|
})
|
|
254
|
-
.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
166
|
+
.map(([targetExport]) => {
|
|
167
|
+
const extension = interceptor.sourcePath.endsWith('.tsx') ? '.tsx' : '.ts'
|
|
168
|
+
const importPath = `./${interceptor.target.split('/').pop()}.original`
|
|
169
|
+
return `import { ${targetExport} as ${targetExport}${originalSuffix} } from '${importPath}'`
|
|
170
|
+
})
|
|
171
|
+
.join('\n ')
|
|
172
|
+
|
|
173
|
+
let logOnce = ''
|
|
174
|
+
// Note: logInterceptors config option removed for now
|
|
175
|
+
// if (config.logInterceptors)
|
|
176
|
+
// logOnce = `
|
|
177
|
+
// if (process.env.NODE_ENV === 'development') {
|
|
178
|
+
// console.log('🚦 Intercepted ${dependency}')
|
|
179
|
+
// }
|
|
180
|
+
// `
|
|
181
|
+
|
|
182
|
+
// Generate the plugin chain for each target export
|
|
183
|
+
const pluginExports = [...Object.entries(targetExports)]
|
|
184
|
+
.map(([targetExport, plugins]) => {
|
|
185
|
+
if (plugins.some((p) => p.type === 'component')) {
|
|
186
|
+
// Component plugins
|
|
187
|
+
const componentPlugins = plugins.filter((p) => p.type === 'component')
|
|
188
|
+
|
|
189
|
+
// Build interceptor chain - each plugin wraps the previous one
|
|
190
|
+
// Check if there's a replace plugin to use as the base instead of original
|
|
191
|
+
const replacePlugin = plugins.find((p) => p.type === 'replace')
|
|
192
|
+
let carry = replacePlugin
|
|
193
|
+
? sourceName(name(replacePlugin))
|
|
194
|
+
: `${targetExport}${originalSuffix}`
|
|
195
|
+
const pluginSee: string[] = []
|
|
196
|
+
|
|
197
|
+
if (replacePlugin) {
|
|
198
|
+
pluginSee.push(
|
|
199
|
+
`@see {${sourceName(name(replacePlugin))}} for source of replaced component`,
|
|
200
|
+
)
|
|
201
|
+
} else {
|
|
202
|
+
pluginSee.push(`@see {@link file://./${targetExport}.tsx} for original source file`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const pluginInterceptors = componentPlugins
|
|
206
|
+
.reverse() // Start from the last plugin and work backwards
|
|
207
|
+
.map((plugin) => {
|
|
208
|
+
const pluginName = sourceName(name(plugin))
|
|
209
|
+
const interceptorName = `${pluginName}${interceptorSuffix}`
|
|
210
|
+
const propsName = `${pluginName}Props`
|
|
211
|
+
|
|
212
|
+
pluginSee.push(`@see {${pluginName}} for source of applied plugin`)
|
|
213
|
+
|
|
214
|
+
const result = `type ${propsName} = OmitPrev<
|
|
215
|
+
React.ComponentProps<typeof ${pluginName}>,
|
|
216
|
+
'Prev'
|
|
217
|
+
>
|
|
218
|
+
|
|
219
|
+
const ${interceptorName} = (
|
|
220
|
+
props: ${propsName},
|
|
221
|
+
) => (
|
|
222
|
+
<${pluginName}
|
|
223
|
+
{...props}
|
|
224
|
+
Prev={${carry}}
|
|
225
|
+
/>
|
|
226
|
+
)`
|
|
227
|
+
carry = interceptorName
|
|
228
|
+
return result
|
|
229
|
+
})
|
|
230
|
+
.join('\n\n')
|
|
231
|
+
|
|
232
|
+
const seeString = `/**
|
|
233
|
+
* Here you see the 'interceptor' that is applying all the configured plugins.
|
|
234
|
+
*
|
|
235
|
+
* This file is NOT meant to be modified directly and is auto-generated if the plugins or the
|
|
236
|
+
* original source changes.
|
|
237
|
+
*
|
|
238
|
+
${pluginSee.map((s) => ` * ${s}`).join('\n')}
|
|
239
|
+
*/`
|
|
240
|
+
|
|
241
|
+
return `${pluginInterceptors}
|
|
242
|
+
|
|
243
|
+
${seeString}
|
|
244
|
+
export const ${targetExport} = ${carry}`
|
|
245
|
+
} else if (plugins.some((p) => p.type === 'function')) {
|
|
246
|
+
// Function plugins
|
|
247
|
+
const functionPlugins = plugins.filter((p) => p.type === 'function')
|
|
248
|
+
|
|
249
|
+
// Build interceptor chain - each plugin wraps the previous one
|
|
250
|
+
// Check if there's a replace plugin to use as the base instead of original
|
|
251
|
+
const replacePlugin = plugins.find((p) => p.type === 'replace')
|
|
252
|
+
let carry = replacePlugin
|
|
253
|
+
? sourceName(name(replacePlugin))
|
|
254
|
+
: `${targetExport}${originalSuffix}`
|
|
255
|
+
const pluginSee: string[] = []
|
|
256
|
+
|
|
257
|
+
if (replacePlugin) {
|
|
258
|
+
pluginSee.push(
|
|
259
|
+
`@see {${sourceName(name(replacePlugin))}} for source of replaced function`,
|
|
260
|
+
)
|
|
261
|
+
} else {
|
|
262
|
+
pluginSee.push(`@see {@link file://./${targetExport}.ts} for original source file`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const pluginInterceptors = functionPlugins
|
|
266
|
+
.reverse() // Start from the last plugin and work backwards
|
|
267
|
+
.map((plugin) => {
|
|
268
|
+
const pluginName = sourceName(name(plugin))
|
|
269
|
+
const interceptorName = `${pluginName}${interceptorSuffix}`
|
|
270
|
+
|
|
271
|
+
pluginSee.push(`@see {${pluginName}} for source of applied plugin`)
|
|
272
|
+
|
|
273
|
+
const result = `const ${interceptorName}: typeof ${carry} = (...args) => {
|
|
274
|
+
return ${pluginName}(${carry}, ...args)
|
|
275
|
+
}`
|
|
276
|
+
carry = interceptorName
|
|
277
|
+
return result
|
|
278
|
+
})
|
|
279
|
+
.join('\n')
|
|
280
|
+
|
|
281
|
+
const seeString = `/**
|
|
282
|
+
* Here you see the 'interceptor' that is applying all the configured plugins.
|
|
283
|
+
*
|
|
284
|
+
* This file is NOT meant to be modified directly and is auto-generated if the plugins or the
|
|
285
|
+
* original source changes.
|
|
286
|
+
*
|
|
287
|
+
${pluginSee.map((s) => ` * ${s}`).join('\n')}
|
|
288
|
+
*/`
|
|
289
|
+
|
|
290
|
+
return `${pluginInterceptors}
|
|
291
|
+
|
|
292
|
+
${seeString}
|
|
293
|
+
export const ${targetExport} = ${carry}`
|
|
294
|
+
} else if (plugins.some((p) => p.type === 'replace')) {
|
|
295
|
+
// Replace plugins (just export the replacement)
|
|
296
|
+
const replacePlugin = plugins.find((p) => p.type === 'replace')
|
|
297
|
+
if (replacePlugin) {
|
|
298
|
+
return `export { ${replacePlugin.sourceExport} as ${targetExport} } from '${replacePlugin.sourceModule}'`
|
|
264
299
|
}
|
|
265
|
-
|
|
266
|
-
|
|
300
|
+
}
|
|
301
|
+
return ''
|
|
302
|
+
})
|
|
303
|
+
.filter(Boolean)
|
|
304
|
+
.join('\n\n ')
|
|
267
305
|
|
|
268
306
|
const template = `/* hash:${identifer} */
|
|
269
307
|
/* eslint-disable */
|
|
@@ -276,21 +314,18 @@ export async function generateInterceptor(
|
|
|
276
314
|
|
|
277
315
|
${pluginImports}
|
|
278
316
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
317
|
+
${originalImports}
|
|
318
|
+
|
|
319
|
+
// Re-export everything from the original file except the intercepted exports
|
|
320
|
+
export * from './${interceptor.target.split('/').pop()}.original'
|
|
321
|
+
|
|
283
322
|
${logOnce}${pluginExports}
|
|
284
323
|
`
|
|
285
324
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
// eslint-disable-next-line no-console
|
|
291
|
-
console.log('Error formatting interceptor: ', e, 'using raw template.')
|
|
292
|
-
templateFormatted = template
|
|
293
|
-
}
|
|
325
|
+
const formatted = await prettier.format(template, {
|
|
326
|
+
...prettierConf,
|
|
327
|
+
parser: 'typescript',
|
|
328
|
+
})
|
|
294
329
|
|
|
295
|
-
return { ...interceptor, template:
|
|
330
|
+
return { ...interceptor, template: formatted }
|
|
296
331
|
}
|
|
@@ -43,7 +43,7 @@ export async function generateInterceptors(
|
|
|
43
43
|
if (!acc[resolved.fromRoot]) {
|
|
44
44
|
acc[resolved.fromRoot] = {
|
|
45
45
|
...resolved,
|
|
46
|
-
target:
|
|
46
|
+
target: resolved.fromRoot,
|
|
47
47
|
targetExports: {},
|
|
48
48
|
} as Interceptor
|
|
49
49
|
}
|
|
@@ -61,7 +61,14 @@ export async function generateInterceptors(
|
|
|
61
61
|
return Object.fromEntries(
|
|
62
62
|
await Promise.all(
|
|
63
63
|
Object.entries(byTargetModuleAndExport).map(async ([target, interceptor]) => {
|
|
64
|
-
|
|
64
|
+
// In the new system, we don't look for existing .interceptor files
|
|
65
|
+
// Instead, we check if we need to regenerate based on the main file
|
|
66
|
+
const mainFile = `${interceptor.fromRoot}.tsx`
|
|
67
|
+
const tsFile = `${interceptor.fromRoot}.ts`
|
|
68
|
+
|
|
69
|
+
// Try .tsx first, then .ts
|
|
70
|
+
const extension = interceptor.sourcePath.endsWith('.tsx') ? '.tsx' : '.ts'
|
|
71
|
+
const file = `${interceptor.fromRoot}${extension}`
|
|
65
72
|
|
|
66
73
|
const originalSource =
|
|
67
74
|
!force &&
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { sync as globSync } from 'glob'
|
|
4
|
+
import { packageRoots } from '../utils'
|
|
5
|
+
import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
|
|
6
|
+
import type { PluginConfig } from './generateInterceptor'
|
|
7
|
+
|
|
8
|
+
export interface PackageJson {
|
|
9
|
+
name: string
|
|
10
|
+
exports?: Record<string, string | { types?: string; default?: string }>
|
|
11
|
+
[key: string]: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Updates package.json exports to include all plugin files automatically */
|
|
15
|
+
export async function updatePackageExports(
|
|
16
|
+
plugins: PluginConfig[],
|
|
17
|
+
cwd: string = process.cwd(),
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
// Use packageRoots to discover ALL packages in the monorepo, not just dependencies
|
|
20
|
+
const deps = resolveDependenciesSync()
|
|
21
|
+
const packages = [...deps.values()].filter((p) => p !== '.')
|
|
22
|
+
const roots = packageRoots(packages)
|
|
23
|
+
|
|
24
|
+
console.log(`🔍 Scanning ${roots.length} package roots for plugins...`)
|
|
25
|
+
|
|
26
|
+
// Group plugins by package - discover ALL plugin files in ALL packages in the monorepo
|
|
27
|
+
const pluginsByPackage = new Map<string, Set<string>>()
|
|
28
|
+
|
|
29
|
+
// Scan all individual packages within the package roots
|
|
30
|
+
for (const root of roots) {
|
|
31
|
+
// Find all package directories within this root
|
|
32
|
+
const packageDirs = globSync(`${root}/*/package.json`).map((pkgPath) => path.dirname(pkgPath))
|
|
33
|
+
|
|
34
|
+
for (const packagePath of packageDirs) {
|
|
35
|
+
const pluginFiles = globSync(`${packagePath}/plugins/**/*.{ts,tsx}`)
|
|
36
|
+
|
|
37
|
+
if (pluginFiles.length > 0) {
|
|
38
|
+
const exportPaths = new Set<string>()
|
|
39
|
+
|
|
40
|
+
pluginFiles.forEach((file) => {
|
|
41
|
+
// Convert file path to export path
|
|
42
|
+
const relativePath = path.relative(packagePath, file)
|
|
43
|
+
const exportPath = `./${relativePath.replace(/\.(ts|tsx)$/, '')}`
|
|
44
|
+
exportPaths.add(exportPath)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if (exportPaths.size > 0) {
|
|
48
|
+
const packageJsonPath = path.join(packagePath, 'package.json')
|
|
49
|
+
try {
|
|
50
|
+
// Read package.json to get the package name for logging
|
|
51
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8')
|
|
52
|
+
const packageJson = JSON.parse(packageJsonContent)
|
|
53
|
+
const packageName = packageJson.name || path.basename(packagePath)
|
|
54
|
+
|
|
55
|
+
pluginsByPackage.set(packagePath, exportPaths)
|
|
56
|
+
// console.log(`🔍 Found ${exportPaths.size} plugin files in ${packageName}`)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.warn(`⚠️ Could not read package.json for ${packagePath}:`, error)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`📦 Total packages with plugins: ${pluginsByPackage.size}`)
|
|
66
|
+
|
|
67
|
+
// Update package.json for each package that has plugins
|
|
68
|
+
const updatePromises = Array.from(pluginsByPackage.entries()).map(
|
|
69
|
+
async ([packagePath, exportPaths]) => {
|
|
70
|
+
const packageJsonPath = path.join(packagePath, 'package.json')
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8')
|
|
74
|
+
const packageJson: PackageJson = JSON.parse(packageJsonContent)
|
|
75
|
+
|
|
76
|
+
// Initialize exports if it doesn't exist
|
|
77
|
+
if (!packageJson.exports) {
|
|
78
|
+
packageJson.exports = { '.': './index.ts' }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Ensure main export exists
|
|
82
|
+
if (typeof packageJson.exports === 'object' && !packageJson.exports['.']) {
|
|
83
|
+
packageJson.exports['.'] = './index.ts'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let hasChanges = false
|
|
87
|
+
|
|
88
|
+
// Add plugin exports
|
|
89
|
+
exportPaths.forEach((exportPath) => {
|
|
90
|
+
const exportKey = exportPath.startsWith('./') ? exportPath : `./${exportPath}`
|
|
91
|
+
const filePath = `${exportPath}.tsx`
|
|
92
|
+
const tsFilePath = `${exportPath}.ts`
|
|
93
|
+
|
|
94
|
+
// Check if .tsx or .ts file exists
|
|
95
|
+
const targetFile = globSync(path.join(packagePath, `${exportPath.slice(2)}.{ts,tsx}`))[0]
|
|
96
|
+
if (targetFile) {
|
|
97
|
+
const extension = targetFile.endsWith('.tsx') ? '.tsx' : '.ts'
|
|
98
|
+
const targetPath = `${exportPath}${extension}`
|
|
99
|
+
|
|
100
|
+
if (packageJson.exports && !packageJson.exports[exportKey]) {
|
|
101
|
+
packageJson.exports[exportKey] = targetPath
|
|
102
|
+
hasChanges = true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (hasChanges) {
|
|
108
|
+
// Sort exports for consistency (. first, then alphabetically)
|
|
109
|
+
const sortedExports: Record<string, string | { types?: string; default?: string }> = {}
|
|
110
|
+
|
|
111
|
+
if (packageJson.exports['.']) {
|
|
112
|
+
sortedExports['.'] = packageJson.exports['.']
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Object.keys(packageJson.exports)
|
|
116
|
+
.filter((key) => key !== '.')
|
|
117
|
+
.sort()
|
|
118
|
+
.forEach((key) => {
|
|
119
|
+
sortedExports[key] = packageJson.exports![key]
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
packageJson.exports = sortedExports
|
|
123
|
+
|
|
124
|
+
const updatedContent = JSON.stringify(packageJson, null, 2) + '\n'
|
|
125
|
+
await fs.writeFile(packageJsonPath, updatedContent)
|
|
126
|
+
|
|
127
|
+
console.log(`✅ Updated exports in ${packageJson.name}`)
|
|
128
|
+
|
|
129
|
+
// Log the new exports
|
|
130
|
+
const newExports = Object.keys(packageJson.exports).filter((key) => key !== '.')
|
|
131
|
+
if (newExports.length > 0) {
|
|
132
|
+
console.log(` Added exports: ${newExports.join(', ')}`)
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// // Log packages that were scanned but had no changes
|
|
136
|
+
// console.log(
|
|
137
|
+
// `ℹ️ No changes needed for ${packageJson.name} (${exportPaths.size} plugins already exported)`,
|
|
138
|
+
// )
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(`❌ Failed to update package.json for ${packagePath}:`, error)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
await Promise.all(updatePromises)
|
|
147
|
+
}
|