@graphcommerce/next-config 9.0.0-canary.98 → 9.0.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/CHANGELOG.md +219 -1149
- package/__tests__/commands/copyFiles.ts +512 -0
- package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +6 -0
- package/__tests__/config/utils/mergeEnvIntoConfig.ts +9 -20
- package/__tests__/config/utils/rewriteLegancyEnv.ts +32 -36
- package/__tests__/interceptors/findPlugins.ts +80 -74
- package/__tests__/interceptors/generateInterceptors.ts +78 -135
- package/__tests__/interceptors/parseStructure.ts +2 -2
- package/__tests__/utils/resolveDependenciesSync.ts +11 -10
- package/dist/commands/codegen.js +18 -0
- package/dist/commands/copyFiles.js +292 -0
- package/dist/commands/copyRoutes.js +20 -0
- package/dist/config/commands/exportConfig.js +1 -1
- package/dist/config/commands/generateConfig.js +2 -2
- package/dist/config/demoConfig.js +2 -2
- package/dist/config/utils/mergeEnvIntoConfig.js +18 -20
- package/dist/config/utils/rewriteLegacyEnv.js +2 -2
- package/dist/generated/config.js +13 -1
- package/dist/index.js +3 -1
- package/dist/interceptors/InterceptorPlugin.js +4 -3
- package/dist/interceptors/Visitor.js +5 -9
- package/dist/interceptors/commands/codegenInterceptors.js +2 -2
- package/dist/interceptors/extractExports.js +9 -54
- package/dist/interceptors/findOriginalSource.js +2 -1
- package/dist/interceptors/findPlugins.js +5 -8
- package/dist/interceptors/generateInterceptor.js +12 -10
- package/dist/interceptors/generateInterceptors.js +3 -2
- package/dist/interceptors/parseStructure.js +1 -1
- package/dist/interceptors/writeInterceptors.js +2 -2
- package/dist/utils/TopologicalSort.js +4 -0
- package/dist/utils/isMonorepo.js +40 -6
- package/dist/utils/resolveDependenciesSync.js +9 -2
- package/dist/utils/sig.js +34 -0
- package/dist/withGraphCommerce.js +3 -2
- package/package.json +17 -16
- package/src/commands/codegen.ts +18 -0
- package/src/commands/copyFiles.ts +328 -0
- package/src/config/commands/exportConfig.ts +1 -1
- package/src/config/commands/generateConfig.ts +3 -3
- package/src/config/demoConfig.ts +5 -5
- package/src/config/index.ts +1 -1
- package/src/config/utils/exportConfigToEnv.ts +1 -1
- package/src/config/utils/mergeEnvIntoConfig.ts +22 -25
- package/src/config/utils/replaceConfigInString.ts +1 -1
- package/src/config/utils/rewriteLegacyEnv.ts +5 -4
- package/src/generated/config.ts +36 -0
- package/src/index.ts +6 -5
- package/src/interceptors/InterceptorPlugin.ts +10 -7
- package/src/interceptors/RenameVisitor.ts +1 -1
- package/src/interceptors/Visitor.ts +10 -15
- package/src/interceptors/commands/codegenInterceptors.ts +2 -2
- package/src/interceptors/extractExports.ts +4 -46
- package/src/interceptors/findOriginalSource.ts +5 -4
- package/src/interceptors/findPlugins.ts +8 -9
- package/src/interceptors/generateInterceptor.ts +15 -12
- package/src/interceptors/generateInterceptors.ts +7 -13
- package/src/interceptors/parseStructure.ts +4 -4
- package/src/interceptors/swc.ts +2 -1
- package/src/interceptors/writeInterceptors.ts +3 -3
- package/src/utils/TopologicalSort.ts +4 -0
- package/src/utils/isMonorepo.ts +46 -5
- package/src/utils/packageRoots.ts +1 -1
- package/src/utils/resolveDependenciesSync.ts +14 -2
- package/src/utils/sig.ts +37 -0
- package/src/withGraphCommerce.ts +7 -5
- package/dist/config/commands/generateIntercetors.js +0 -9
- package/dist/interceptors/commands/generateIntercetors.js +0 -9
- package/dist/runtimeCachingOptimizations.js +0 -28
- package/src/runtimeCachingOptimizations.ts +0 -27
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import fs from 'fs/promises'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import fg from 'fast-glob'
|
|
5
|
+
import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
|
|
6
|
+
|
|
7
|
+
// Add debug logging helper
|
|
8
|
+
const debug = (...args: unknown[]) => {
|
|
9
|
+
if (process.env.DEBUG) console.log('[copy-files]', ...args)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Add constants for the magic comments
|
|
13
|
+
const MANAGED_BY_GC = '// managed by: graphcommerce'
|
|
14
|
+
const MANAGED_LOCALLY = '// managed by: local'
|
|
15
|
+
|
|
16
|
+
const GITIGNORE_SECTION_START = '# managed by: graphcommerce'
|
|
17
|
+
const GITIGNORE_SECTION_END = '# end managed by: graphcommerce'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Updates the .gitignore file with a list of GraphCommerce managed files
|
|
21
|
+
*
|
|
22
|
+
* - Removes any existing GraphCommerce managed files section
|
|
23
|
+
* - If managedFiles is not empty, adds a new section with the files
|
|
24
|
+
* - If managedFiles is empty, just cleans up the existing section
|
|
25
|
+
* - Ensures the file ends with a newline
|
|
26
|
+
*/
|
|
27
|
+
async function updateGitignore(managedFiles: string[]) {
|
|
28
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
29
|
+
let content: string
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
content = await fs.readFile(gitignorePath, 'utf-8')
|
|
33
|
+
debug('Reading existing .gitignore')
|
|
34
|
+
} catch (err) {
|
|
35
|
+
debug('.gitignore not found, creating new file')
|
|
36
|
+
content = ''
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Remove existing GraphCommerce section if it exists
|
|
40
|
+
const sectionRegex = new RegExp(
|
|
41
|
+
`${GITIGNORE_SECTION_START}[\\s\\S]*?${GITIGNORE_SECTION_END}\\n?`,
|
|
42
|
+
'g',
|
|
43
|
+
)
|
|
44
|
+
content = content.replace(sectionRegex, '')
|
|
45
|
+
|
|
46
|
+
// Only add new section if there are files to manage
|
|
47
|
+
if (managedFiles.length > 0) {
|
|
48
|
+
const newSection = [
|
|
49
|
+
GITIGNORE_SECTION_START,
|
|
50
|
+
...managedFiles.sort(),
|
|
51
|
+
GITIGNORE_SECTION_END,
|
|
52
|
+
'', // Empty line at the end
|
|
53
|
+
].join('\n')
|
|
54
|
+
|
|
55
|
+
// Append the new section
|
|
56
|
+
content = `${content.trim()}\n\n${newSection}`
|
|
57
|
+
debug(`Updated .gitignore with ${managedFiles.length} managed files`)
|
|
58
|
+
} else {
|
|
59
|
+
content = `${content.trim()}\n`
|
|
60
|
+
debug('Cleaned up .gitignore managed section')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await fs.writeFile(gitignorePath, content)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Determines how a file should be managed based on its content */
|
|
67
|
+
function getFileManagement(content: Buffer | undefined): 'local' | 'graphcommerce' | 'unmanaged' {
|
|
68
|
+
if (!content) return 'graphcommerce'
|
|
69
|
+
const contentStr = content.toString()
|
|
70
|
+
if (contentStr.startsWith(MANAGED_LOCALLY)) return 'local'
|
|
71
|
+
if (contentStr.startsWith(MANAGED_BY_GC)) return 'graphcommerce'
|
|
72
|
+
return 'unmanaged'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The packages are @graphcommerce/* packages and have special treatment.
|
|
77
|
+
*
|
|
78
|
+
* 1. Glob the `copy/**` directory for each package and generate a list of files that need to be
|
|
79
|
+
* copied. Error if a file with the same path exists in another package.
|
|
80
|
+
* 2. Copy the files to the project directory (cwd).
|
|
81
|
+
*
|
|
82
|
+
* 1. If the file doesn't exist: Create directories and the file with "managed by: graphcommerce"
|
|
83
|
+
* 2. If the file exists and starts with "managed by: local": Skip the file
|
|
84
|
+
* 3. If the file exists but doesn't have a management comment: Suggest adding "managed by: local"
|
|
85
|
+
* 4. If the file is managed by graphcommerce: Update if content differs
|
|
86
|
+
*/
|
|
87
|
+
export async function copyFiles() {
|
|
88
|
+
const startTime = performance.now()
|
|
89
|
+
debug('Starting copyFiles')
|
|
90
|
+
|
|
91
|
+
const cwd = process.cwd()
|
|
92
|
+
const deps = resolveDependenciesSync()
|
|
93
|
+
const packages = [...deps.values()].filter((p) => p !== '.')
|
|
94
|
+
|
|
95
|
+
// Track files and their source packages to detect conflicts
|
|
96
|
+
const fileMap = new Map<string, { sourcePath: string; packagePath: string }>()
|
|
97
|
+
const managedFiles = new Set<string>()
|
|
98
|
+
const existingManagedFiles = new Set<string>()
|
|
99
|
+
|
|
100
|
+
// First scan existing files to find GraphCommerce managed ones
|
|
101
|
+
const scanStart = performance.now()
|
|
102
|
+
try {
|
|
103
|
+
// Use only default patterns for testing
|
|
104
|
+
const gitignorePatterns = [
|
|
105
|
+
'**/dist/**',
|
|
106
|
+
'**/build/**',
|
|
107
|
+
'**/.next/**',
|
|
108
|
+
'**/.git/**',
|
|
109
|
+
'**/node_modules/**',
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const allFiles = await fg('**/*', {
|
|
113
|
+
cwd,
|
|
114
|
+
dot: true,
|
|
115
|
+
ignore: gitignorePatterns,
|
|
116
|
+
onlyFiles: true,
|
|
117
|
+
})
|
|
118
|
+
debug(
|
|
119
|
+
`Found ${allFiles.length} project files in ${(performance.now() - scanStart).toFixed(0)}ms`,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const readStart = performance.now()
|
|
123
|
+
await Promise.all(
|
|
124
|
+
allFiles.map(async (file) => {
|
|
125
|
+
const filePath = path.join(cwd, file)
|
|
126
|
+
try {
|
|
127
|
+
const content = await fs.readFile(filePath)
|
|
128
|
+
if (getFileManagement(content) === 'graphcommerce') {
|
|
129
|
+
existingManagedFiles.add(file)
|
|
130
|
+
debug(`Found existing managed file: ${file}`)
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
debug(`Error reading file ${file}:`, err)
|
|
134
|
+
}
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
debug(
|
|
138
|
+
`Read ${existingManagedFiles.size} managed files in ${(performance.now() - readStart).toFixed(0)}ms`,
|
|
139
|
+
)
|
|
140
|
+
} catch (err) {
|
|
141
|
+
debug('Error scanning project files:', err)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// First pass: collect all files and check for conflicts
|
|
145
|
+
const collectStart = performance.now()
|
|
146
|
+
await Promise.all(
|
|
147
|
+
packages.map(async (pkg) => {
|
|
148
|
+
const copyDir = path.join(pkg, 'copy')
|
|
149
|
+
try {
|
|
150
|
+
const files = await fg('**/*', { cwd: copyDir, dot: true, suppressErrors: true })
|
|
151
|
+
if (files.length > 0) {
|
|
152
|
+
debug(`Found files in ${pkg}:`, files)
|
|
153
|
+
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
const sourcePath = path.join(copyDir, file)
|
|
156
|
+
const existing = fileMap.get(file)
|
|
157
|
+
|
|
158
|
+
if (existing) {
|
|
159
|
+
console.error(`Error: File conflict detected for '${file}'
|
|
160
|
+
Found in packages:
|
|
161
|
+
- ${existing.packagePath} -> ${existing.sourcePath}
|
|
162
|
+
- ${pkg} -> ${sourcePath}`)
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fileMap.set(file, { sourcePath, packagePath: pkg })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if ((err as { code?: string }).code === 'ENOENT') return
|
|
171
|
+
console.error(
|
|
172
|
+
`Error scanning directory ${copyDir}: ${(err as Error).message}\nPath: ${copyDir}`,
|
|
173
|
+
)
|
|
174
|
+
process.exit(1)
|
|
175
|
+
}
|
|
176
|
+
}),
|
|
177
|
+
)
|
|
178
|
+
debug(`Collected ${fileMap.size} files in ${(performance.now() - collectStart).toFixed(0)}ms`)
|
|
179
|
+
|
|
180
|
+
// Second pass: copy files and handle removals
|
|
181
|
+
const copyStart = performance.now()
|
|
182
|
+
await Promise.all(
|
|
183
|
+
Array.from(fileMap.entries()).map(async ([file, { sourcePath }]) => {
|
|
184
|
+
const targetPath = path.join(cwd, file)
|
|
185
|
+
debug(`Processing file: ${file}`)
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true })
|
|
189
|
+
|
|
190
|
+
const sourceContent = await fs.readFile(sourcePath)
|
|
191
|
+
const contentWithComment = Buffer.concat([
|
|
192
|
+
Buffer.from(
|
|
193
|
+
`${MANAGED_BY_GC}\n// to modify this file, change it to managed by: local\n\n`,
|
|
194
|
+
),
|
|
195
|
+
sourceContent,
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
let targetContent: Buffer | undefined
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
targetContent = await fs.readFile(targetPath)
|
|
202
|
+
|
|
203
|
+
const management = getFileManagement(targetContent)
|
|
204
|
+
if (management === 'local') {
|
|
205
|
+
debug(`File ${file} is managed locally, skipping`)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
if (management === 'unmanaged') {
|
|
209
|
+
console.log(
|
|
210
|
+
`Note: File ${file} has been modified. Add '${MANAGED_LOCALLY.trim()}' at the top to manage it locally.`,
|
|
211
|
+
)
|
|
212
|
+
debug(`File ${file} doesn't have management comment, skipping`)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
debug(`File ${file} is managed by graphcommerce, will update if needed`)
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if ((err as { code?: string }).code !== 'ENOENT') {
|
|
219
|
+
console.error(`Error reading file ${file}: ${(err as Error).message}
|
|
220
|
+
Source: ${sourcePath}`)
|
|
221
|
+
process.exit(1)
|
|
222
|
+
}
|
|
223
|
+
console.log(`Creating new file: ${file}\nSource: ${sourcePath}`)
|
|
224
|
+
debug('File does not exist yet')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Skip if content is identical (including magic comment)
|
|
228
|
+
if (targetContent && Buffer.compare(contentWithComment, targetContent) === 0) {
|
|
229
|
+
debug(`File ${file} content is identical to source, skipping`)
|
|
230
|
+
managedFiles.add(file)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Copy the file with magic comment
|
|
235
|
+
await fs.writeFile(targetPath, contentWithComment)
|
|
236
|
+
if (targetContent) {
|
|
237
|
+
console.log(`Updated managed file: ${file}`)
|
|
238
|
+
debug(`Overwrote existing file: ${file}`)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// If the file is managed by GraphCommerce (new or updated), add it to managedFiles
|
|
242
|
+
if (!targetContent || targetContent.toString().startsWith(MANAGED_BY_GC)) {
|
|
243
|
+
managedFiles.add(file)
|
|
244
|
+
debug('Added managed file:', file)
|
|
245
|
+
}
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error(`Error copying file ${file}: ${(err as Error).message}
|
|
248
|
+
Source: ${sourcePath}`)
|
|
249
|
+
process.exit(1)
|
|
250
|
+
}
|
|
251
|
+
}),
|
|
252
|
+
)
|
|
253
|
+
debug(`Copied ${managedFiles.size} files in ${(performance.now() - copyStart).toFixed(0)}ms`)
|
|
254
|
+
|
|
255
|
+
// Remove files that are no longer provided
|
|
256
|
+
const removeStart = performance.now()
|
|
257
|
+
const filesToRemove = Array.from(existingManagedFiles).filter((file) => !managedFiles.has(file))
|
|
258
|
+
debug(`Files to remove: ${filesToRemove.length}`)
|
|
259
|
+
|
|
260
|
+
// Helper function to recursively clean up empty directories
|
|
261
|
+
async function cleanupEmptyDirs(startPath: string) {
|
|
262
|
+
let currentDir = startPath
|
|
263
|
+
while (currentDir !== cwd) {
|
|
264
|
+
try {
|
|
265
|
+
const dirContents = await fs.readdir(currentDir)
|
|
266
|
+
if (dirContents.length === 0) {
|
|
267
|
+
await fs.rmdir(currentDir)
|
|
268
|
+
debug(`Removed empty directory: ${currentDir}`)
|
|
269
|
+
currentDir = path.dirname(currentDir)
|
|
270
|
+
} else {
|
|
271
|
+
break // Stop if directory is not empty
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if ((err as { code?: string }).code === 'EACCES') {
|
|
275
|
+
console.error(`Error cleaning up directory ${currentDir}: ${(err as Error).message}`)
|
|
276
|
+
process.exit(1)
|
|
277
|
+
}
|
|
278
|
+
break // Stop on other errors (like ENOENT)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Process file removals in parallel
|
|
284
|
+
await Promise.all(
|
|
285
|
+
filesToRemove.map(async (file) => {
|
|
286
|
+
const filePath = path.join(cwd, file)
|
|
287
|
+
const dirPath = path.dirname(filePath)
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// First check if the directory exists and is accessible
|
|
291
|
+
await fs.readdir(dirPath)
|
|
292
|
+
|
|
293
|
+
// Then try to remove the file
|
|
294
|
+
try {
|
|
295
|
+
await fs.unlink(filePath)
|
|
296
|
+
console.log(`Removed managed file: ${file}`)
|
|
297
|
+
debug(`Removed file: ${file}`)
|
|
298
|
+
} catch (err) {
|
|
299
|
+
if ((err as { code?: string }).code !== 'ENOENT') {
|
|
300
|
+
console.error(`Error removing file ${file}: ${(err as Error).message}`)
|
|
301
|
+
process.exit(1)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Finally, try to clean up empty directories
|
|
306
|
+
await cleanupEmptyDirs(dirPath)
|
|
307
|
+
} catch (err) {
|
|
308
|
+
if ((err as { code?: string }).code === 'EACCES') {
|
|
309
|
+
console.error(`Error accessing directory ${dirPath}: ${(err as Error).message}`)
|
|
310
|
+
process.exit(1)
|
|
311
|
+
}
|
|
312
|
+
// Ignore ENOENT errors for directories that don't exist
|
|
313
|
+
}
|
|
314
|
+
}),
|
|
315
|
+
)
|
|
316
|
+
debug(`Removed files in ${(performance.now() - removeStart).toFixed(0)}ms`)
|
|
317
|
+
|
|
318
|
+
// Update .gitignore with current list of managed files
|
|
319
|
+
if (managedFiles.size > 0) {
|
|
320
|
+
debug('Found managed files:', Array.from(managedFiles))
|
|
321
|
+
await updateGitignore(Array.from(managedFiles))
|
|
322
|
+
} else {
|
|
323
|
+
debug('No managed files found, cleaning up .gitignore section')
|
|
324
|
+
await updateGitignore([])
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
debug(`Total execution time: ${(performance.now() - startTime).toFixed(0)}ms`)
|
|
328
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { writeFileSync } from 'fs'
|
|
2
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
+
import { writeFileSync } from 'fs'
|
|
3
3
|
import { generate } from '@graphql-codegen/cli'
|
|
4
4
|
import { transformFileSync } from '@swc/core'
|
|
5
5
|
import dotenv from 'dotenv'
|
|
6
|
-
import {
|
|
6
|
+
import { findParentPath } from '../../utils/isMonorepo'
|
|
7
7
|
import { resolveDependenciesSync } from '../../utils/resolveDependenciesSync'
|
|
8
8
|
import { resolveDependency } from '../../utils/resolveDependency'
|
|
9
9
|
|
|
@@ -42,7 +42,7 @@ export async function generateConfig() {
|
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
-
...(
|
|
45
|
+
...(findParentPath(process.cwd()) && {
|
|
46
46
|
'../../docs/framework/config.md': {
|
|
47
47
|
plugins: ['@graphcommerce/graphql-codegen-markdown-docs'],
|
|
48
48
|
},
|
package/src/config/demoConfig.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import { PartialDeep } from 'type-fest'
|
|
3
|
-
import { GraphCommerceConfig } from '../generated/config'
|
|
2
|
+
import type { PartialDeep } from 'type-fest'
|
|
3
|
+
import type { GraphCommerceConfig, GraphCommerceStorefrontConfig } from '../generated/config'
|
|
4
4
|
|
|
5
5
|
export const demoConfig: PartialDeep<GraphCommerceConfig, { recurseIntoArrays: true }> &
|
|
6
|
-
Record<string, unknown> = {
|
|
6
|
+
Record<string, unknown> & { storefront: PartialDeep<GraphCommerceStorefrontConfig>[] } = {
|
|
7
7
|
canonicalBaseUrl: 'https://graphcommerce.vercel.app',
|
|
8
8
|
hygraphEndpoint: 'https://eu-central-1.cdn.hygraph.com/content/ckhx7xadya6xs01yxdujt8i80/master',
|
|
9
|
-
magentoEndpoint: 'https://
|
|
10
|
-
magentoVersion:
|
|
9
|
+
magentoEndpoint: 'https://configurator.reachdigital.dev/graphql',
|
|
10
|
+
magentoVersion: 247,
|
|
11
11
|
storefront: [
|
|
12
12
|
{ locale: 'en', magentoStoreCode: 'en_US', defaultLocale: true },
|
|
13
13
|
{
|
package/src/config/index.ts
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
-
import {
|
|
2
|
+
import { cloneDeep, mergeDeep } from '@apollo/client/utilities'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
import { get, set } from 'lodash'
|
|
5
5
|
import snakeCase from 'lodash/snakeCase'
|
|
6
|
+
import type { ZodAny, ZodRawShape, ZodTypeAny } from 'zod'
|
|
6
7
|
import {
|
|
7
8
|
z,
|
|
8
|
-
ZodObject,
|
|
9
|
-
ZodBoolean,
|
|
10
9
|
ZodArray,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ZodNullable,
|
|
14
|
-
ZodOptional,
|
|
10
|
+
ZodBoolean,
|
|
11
|
+
ZodDefault,
|
|
15
12
|
ZodEffects,
|
|
16
|
-
ZodRawShape,
|
|
17
13
|
ZodEnum,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
ZodNullable,
|
|
15
|
+
ZodNumber,
|
|
16
|
+
ZodObject,
|
|
17
|
+
ZodOptional,
|
|
18
|
+
ZodString,
|
|
21
19
|
} from 'zod'
|
|
22
20
|
import diff from './diff'
|
|
23
21
|
|
|
24
22
|
const fmt = (s: string) => s.split(/(\d+)/).map(snakeCase).join('')
|
|
25
23
|
export const toEnvStr = (path: string[]) => ['GC', ...path].map(fmt).join('_').toUpperCase()
|
|
26
|
-
|
|
24
|
+
const dotNotation = (pathParts: string[]) =>
|
|
27
25
|
pathParts
|
|
28
26
|
.map((v) => {
|
|
29
27
|
const idx = Number(v)
|
|
@@ -69,7 +67,7 @@ export function configToEnvSchema(schema: ZodNode) {
|
|
|
69
67
|
envSchema[toEnvStr(path)] = z
|
|
70
68
|
.string()
|
|
71
69
|
.optional()
|
|
72
|
-
.refine(isJSON, { message:
|
|
70
|
+
.refine(isJSON, { message: 'Invalid JSON' })
|
|
73
71
|
.transform((val) => (val ? JSON.parse(val) : val))
|
|
74
72
|
envToDot[toEnvStr(path)] = dotNotation(path)
|
|
75
73
|
}
|
|
@@ -89,7 +87,7 @@ export function configToEnvSchema(schema: ZodNode) {
|
|
|
89
87
|
envSchema[toEnvStr(path)] = z
|
|
90
88
|
.string()
|
|
91
89
|
.optional()
|
|
92
|
-
.refine(isJSON, { message:
|
|
90
|
+
.refine(isJSON, { message: 'Invalid JSON' })
|
|
93
91
|
.transform((val) => (val ? JSON.parse(val) : val))
|
|
94
92
|
envToDot[toEnvStr(path)] = dotNotation(path)
|
|
95
93
|
}
|
|
@@ -146,7 +144,7 @@ export type ApplyResultItem = {
|
|
|
146
144
|
}
|
|
147
145
|
export type ApplyResult = ApplyResultItem[]
|
|
148
146
|
|
|
149
|
-
|
|
147
|
+
const filterEnv = (env: Record<string, string | undefined>) =>
|
|
150
148
|
Object.fromEntries(Object.entries(env).filter(([key]) => key.startsWith('GC_')))
|
|
151
149
|
|
|
152
150
|
export function mergeEnvIntoConfig(
|
|
@@ -209,7 +207,7 @@ export function formatAppliedEnv(applyResult: ApplyResult) {
|
|
|
209
207
|
const lines = applyResult.map(({ from, to, envValue, envVar, dotVar, error, warning }) => {
|
|
210
208
|
const fromFmt = chalk.red(JSON.stringify(from))
|
|
211
209
|
const toFmt = chalk.green(JSON.stringify(to))
|
|
212
|
-
const envVariableFmt = `${envVar}
|
|
210
|
+
const envVariableFmt = `${envVar}`
|
|
213
211
|
const dotVariableFmt = chalk.bold.underline(`${dotVar}`)
|
|
214
212
|
|
|
215
213
|
const baseLog = `${envVariableFmt} => ${dotVariableFmt}`
|
|
@@ -225,18 +223,17 @@ export function formatAppliedEnv(applyResult: ApplyResult) {
|
|
|
225
223
|
|
|
226
224
|
if (!dotVar) return chalk.red(`${envVariableFmt} => ignored (no matching config)`)
|
|
227
225
|
|
|
228
|
-
if (from === undefined && to === undefined)
|
|
229
|
-
|
|
230
|
-
if (from
|
|
231
|
-
|
|
232
|
-
return ` ${chalk.yellowBright('~')} ${baseLog}: ${fromFmt} => ${toFmt}`
|
|
226
|
+
if (from === undefined && to === undefined) return ` = ${baseLog}: (ignored)`
|
|
227
|
+
if (from === undefined && to !== undefined) return ` ${chalk.green('+')} ${baseLog}`
|
|
228
|
+
if (from !== undefined && to === undefined) return ` ${chalk.red('-')} ${baseLog}`
|
|
229
|
+
return ` ${chalk.yellowBright('~')} ${baseLog}`
|
|
233
230
|
})
|
|
234
231
|
|
|
235
|
-
let header = chalk.blueBright(
|
|
236
|
-
if (hasWarning) header = chalk.yellowBright(
|
|
237
|
-
if (hasError) header = chalk.yellowBright(
|
|
232
|
+
let header = chalk.blueBright('info')
|
|
233
|
+
if (hasWarning) header = chalk.yellowBright('warning')
|
|
234
|
+
if (hasError) header = chalk.yellowBright('error')
|
|
238
235
|
|
|
239
|
-
header +=
|
|
236
|
+
header += ' - Loaded GraphCommerce env variables'
|
|
240
237
|
|
|
241
238
|
return [header, ...lines].join('\n')
|
|
242
239
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphCommerceConfig } from '../../generated/config'
|
|
1
|
+
import type { GraphCommerceConfig } from '../../generated/config'
|
|
2
2
|
import { configToImportMeta } from './configToImportMeta'
|
|
3
3
|
|
|
4
4
|
export function replaceConfigInString(str: string, config: Partial<GraphCommerceConfig>) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cloneDeep from 'lodash/cloneDeep'
|
|
2
|
-
import { GraphCommerceConfig } from '../../generated/config'
|
|
3
|
-
import { ApplyResult,
|
|
2
|
+
import type { GraphCommerceConfig } from '../../generated/config'
|
|
3
|
+
import type { ApplyResult, ZodNode } from './mergeEnvIntoConfig'
|
|
4
|
+
import { mergeEnvIntoConfig } from './mergeEnvIntoConfig'
|
|
4
5
|
|
|
5
6
|
export function rewriteLegacyEnv(
|
|
6
7
|
schema: ZodNode,
|
|
@@ -24,7 +25,7 @@ export function rewriteLegacyEnv(
|
|
|
24
25
|
function notUsed() {
|
|
25
26
|
return (envVar: string, envValue: string) => {
|
|
26
27
|
applied.push({
|
|
27
|
-
warning: [
|
|
28
|
+
warning: ['should be removed'],
|
|
28
29
|
envVar,
|
|
29
30
|
envValue,
|
|
30
31
|
})
|
|
@@ -38,7 +39,7 @@ export function rewriteLegacyEnv(
|
|
|
38
39
|
IMAGE_DOMAINS: (envVar: string, envValue: string) => {
|
|
39
40
|
applied.push({
|
|
40
41
|
warning: [
|
|
41
|
-
|
|
42
|
+
'should be removed: will automatically add the Magento/Hygraph URL. For more advanced configurations, see: https://nextjs.org/docs/api-reference/next/image#configuration-options',
|
|
42
43
|
],
|
|
43
44
|
envVar,
|
|
44
45
|
envValue,
|
package/src/generated/config.ts
CHANGED
|
@@ -25,6 +25,11 @@ export type CompareVariant =
|
|
|
25
25
|
| 'CHECKBOX'
|
|
26
26
|
| 'ICON';
|
|
27
27
|
|
|
28
|
+
/** Configure whether the layout should be full width or should be constrained by a max breakpoint. Configurable in theme.ts */
|
|
29
|
+
export type ContainerSizing =
|
|
30
|
+
| 'BREAKPOINT'
|
|
31
|
+
| 'FULL_WIDTH';
|
|
32
|
+
|
|
28
33
|
export type CustomerAccountPermissions =
|
|
29
34
|
| 'DISABLED'
|
|
30
35
|
| 'DISABLE_REGISTRATION'
|
|
@@ -157,6 +162,10 @@ export type GraphCommerceConfig = {
|
|
|
157
162
|
* Enabling options here will allow switching of those variants.
|
|
158
163
|
*/
|
|
159
164
|
configurableVariantValues?: InputMaybe<MagentoConfigurableVariantValues>;
|
|
165
|
+
/** Configures the max width of the content (main content area) */
|
|
166
|
+
containerSizingContent?: InputMaybe<ContainerSizing>;
|
|
167
|
+
/** Configures the max width of the shell (header, footer, overlays, etc.) */
|
|
168
|
+
containerSizingShell?: InputMaybe<ContainerSizing>;
|
|
160
169
|
/**
|
|
161
170
|
* Determines if cross sell items should be shown when the user already has the product in their cart. This will result in a product will popping off the screen when you add it to the cart.
|
|
162
171
|
*
|
|
@@ -215,6 +224,8 @@ export type GraphCommerceConfig = {
|
|
|
215
224
|
* To override the value for a specific locale, configure in i18n config.
|
|
216
225
|
*/
|
|
217
226
|
googleAnalyticsId?: InputMaybe<Scalars['String']['input']>;
|
|
227
|
+
/** To create an assetlinks.json file for the Android app. */
|
|
228
|
+
googlePlaystore?: InputMaybe<GraphCommerceGooglePlaystoreConfig>;
|
|
218
229
|
/**
|
|
219
230
|
* Google reCAPTCHA site key.
|
|
220
231
|
* When using reCAPTCHA, this value is required, even if you are configuring different values for each locale.
|
|
@@ -348,6 +359,8 @@ export type GraphCommerceConfig = {
|
|
|
348
359
|
|
|
349
360
|
/** Debug configuration for GraphCommerce */
|
|
350
361
|
export type GraphCommerceDebugConfig = {
|
|
362
|
+
/** Enable debugging interface to debug sessions */
|
|
363
|
+
cart?: InputMaybe<Scalars['Boolean']['input']>;
|
|
351
364
|
/** Reports which plugins are enabled or disabled. */
|
|
352
365
|
pluginStatus?: InputMaybe<Scalars['Boolean']['input']>;
|
|
353
366
|
/** Enable debugging interface to debug sessions */
|
|
@@ -369,6 +382,15 @@ export type GraphCommerceDebugConfig = {
|
|
|
369
382
|
webpackDuplicatesPlugin?: InputMaybe<Scalars['Boolean']['input']>;
|
|
370
383
|
};
|
|
371
384
|
|
|
385
|
+
/** See https://developer.android.com/training/app-links/verify-android-applinks#web-assoc */
|
|
386
|
+
export type GraphCommerceGooglePlaystoreConfig = {
|
|
387
|
+
/** The package name of the Android app. */
|
|
388
|
+
packageName: Scalars['String']['input'];
|
|
389
|
+
/** The sha256 certificate fingerprint of the Android app. */
|
|
390
|
+
sha256CertificateFingerprint: Scalars['String']['input'];
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/** Permissions input */
|
|
372
394
|
export type GraphCommercePermissions = {
|
|
373
395
|
/** Changes the availability of the add to cart buttons and the cart page to either customer only or completely disables it. */
|
|
374
396
|
cart?: InputMaybe<CartPermissions>;
|
|
@@ -376,6 +398,7 @@ export type GraphCommercePermissions = {
|
|
|
376
398
|
checkout?: InputMaybe<CartPermissions>;
|
|
377
399
|
/** Enables / disabled the account section of the website. DISABLE_REGISTRATION will only disable the registration page. */
|
|
378
400
|
customerAccount?: InputMaybe<CustomerAccountPermissions>;
|
|
401
|
+
/** Allows the option to require login or completely disable the site. */
|
|
379
402
|
website?: InputMaybe<WebsitePermissions>;
|
|
380
403
|
};
|
|
381
404
|
|
|
@@ -508,6 +531,8 @@ export const CartPermissionsSchema = z.enum(['CUSTOMER_ONLY', 'DISABLED', 'ENABL
|
|
|
508
531
|
|
|
509
532
|
export const CompareVariantSchema = z.enum(['CHECKBOX', 'ICON']);
|
|
510
533
|
|
|
534
|
+
export const ContainerSizingSchema = z.enum(['BREAKPOINT', 'FULL_WIDTH']);
|
|
535
|
+
|
|
511
536
|
export const CustomerAccountPermissionsSchema = z.enum(['DISABLED', 'DISABLE_REGISTRATION', 'ENABLED']);
|
|
512
537
|
|
|
513
538
|
export const PaginationVariantSchema = z.enum(['COMPACT', 'EXTENDED']);
|
|
@@ -533,6 +558,8 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc
|
|
|
533
558
|
compareVariant: CompareVariantSchema.default("ICON").nullish(),
|
|
534
559
|
configurableVariantForSimple: z.boolean().default(false).nullish(),
|
|
535
560
|
configurableVariantValues: MagentoConfigurableVariantValuesSchema().nullish(),
|
|
561
|
+
containerSizingContent: ContainerSizingSchema.default("FULL_WIDTH").nullish(),
|
|
562
|
+
containerSizingShell: ContainerSizingSchema.default("FULL_WIDTH").nullish(),
|
|
536
563
|
crossSellsHideCartItems: z.boolean().default(false).nullish(),
|
|
537
564
|
crossSellsRedirectItems: z.boolean().default(false).nullish(),
|
|
538
565
|
customerAddressNoteEnable: z.boolean().nullish(),
|
|
@@ -544,6 +571,7 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc
|
|
|
544
571
|
demoMode: z.boolean().default(true).nullish(),
|
|
545
572
|
enableGuestCheckoutLogin: z.boolean().nullish(),
|
|
546
573
|
googleAnalyticsId: z.string().nullish(),
|
|
574
|
+
googlePlaystore: GraphCommerceGooglePlaystoreConfigSchema().nullish(),
|
|
547
575
|
googleRecaptchaKey: z.string().nullish(),
|
|
548
576
|
googleTagmanagerId: z.string().nullish(),
|
|
549
577
|
hygraphEndpoint: z.string().min(1),
|
|
@@ -570,6 +598,7 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc
|
|
|
570
598
|
|
|
571
599
|
export function GraphCommerceDebugConfigSchema(): z.ZodObject<Properties<GraphCommerceDebugConfig>> {
|
|
572
600
|
return z.object({
|
|
601
|
+
cart: z.boolean().nullish(),
|
|
573
602
|
pluginStatus: z.boolean().nullish(),
|
|
574
603
|
sessions: z.boolean().nullish(),
|
|
575
604
|
webpackCircularDependencyPlugin: z.boolean().nullish(),
|
|
@@ -577,6 +606,13 @@ export function GraphCommerceDebugConfigSchema(): z.ZodObject<Properties<GraphCo
|
|
|
577
606
|
})
|
|
578
607
|
}
|
|
579
608
|
|
|
609
|
+
export function GraphCommerceGooglePlaystoreConfigSchema(): z.ZodObject<Properties<GraphCommerceGooglePlaystoreConfig>> {
|
|
610
|
+
return z.object({
|
|
611
|
+
packageName: z.string().min(1),
|
|
612
|
+
sha256CertificateFingerprint: z.string().min(1)
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
|
|
580
616
|
export function GraphCommercePermissionsSchema(): z.ZodObject<Properties<GraphCommercePermissions>> {
|
|
581
617
|
return z.object({
|
|
582
618
|
cart: CartPermissionsSchema.nullish(),
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type React from 'react'
|
|
3
3
|
import type { Path, PathValue } from 'react-hook-form'
|
|
4
|
-
import { GraphCommerceConfig } from './generated/config'
|
|
4
|
+
import type { GraphCommerceConfig } from './generated/config'
|
|
5
|
+
|
|
5
6
|
export * from './utils/isMonorepo'
|
|
6
7
|
export * from './utils/resolveDependenciesSync'
|
|
7
8
|
export * from './utils/packageRoots'
|
|
9
|
+
export * from './utils/sig'
|
|
8
10
|
export * from './withGraphCommerce'
|
|
9
11
|
export * from './generated/config'
|
|
10
12
|
export * from './config'
|
|
11
|
-
export * from './runtimeCachingOptimizations'
|
|
12
13
|
export * from './interceptors/commands/codegenInterceptors'
|
|
14
|
+
export * from './commands/copyFiles'
|
|
15
|
+
export * from './commands/codegen'
|
|
13
16
|
|
|
14
17
|
export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
|
|
15
18
|
Prev: React.FC<P>
|
|
@@ -20,9 +23,7 @@ export type FunctionPlugin<T extends (...args: any[]) => any> = (
|
|
|
20
23
|
...args: Parameters<T>
|
|
21
24
|
) => ReturnType<T>
|
|
22
25
|
|
|
23
|
-
/**
|
|
24
|
-
* @deprecated use FunctionPlugin instead
|
|
25
|
-
*/
|
|
26
|
+
/** @deprecated Use FunctionPlugin instead */
|
|
26
27
|
export type MethodPlugin<T extends (...args: any[]) => any> = (
|
|
27
28
|
prev: T,
|
|
28
29
|
...args: Parameters<T>
|