@graphcommerce/next-config 9.0.0-canary.99 → 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.
Files changed (69) hide show
  1. package/CHANGELOG.md +219 -1151
  2. package/__tests__/commands/copyFiles.ts +512 -0
  3. package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +6 -0
  4. package/__tests__/config/utils/mergeEnvIntoConfig.ts +9 -20
  5. package/__tests__/config/utils/rewriteLegancyEnv.ts +32 -36
  6. package/__tests__/interceptors/findPlugins.ts +76 -78
  7. package/__tests__/interceptors/generateInterceptors.ts +78 -135
  8. package/__tests__/interceptors/parseStructure.ts +2 -2
  9. package/__tests__/utils/resolveDependenciesSync.ts +11 -10
  10. package/dist/commands/codegen.js +18 -0
  11. package/dist/commands/copyFiles.js +292 -0
  12. package/dist/commands/copyRoutes.js +20 -0
  13. package/dist/config/commands/exportConfig.js +1 -1
  14. package/dist/config/commands/generateConfig.js +2 -2
  15. package/dist/config/demoConfig.js +2 -2
  16. package/dist/config/utils/mergeEnvIntoConfig.js +18 -20
  17. package/dist/config/utils/rewriteLegacyEnv.js +2 -2
  18. package/dist/generated/config.js +13 -1
  19. package/dist/index.js +3 -1
  20. package/dist/interceptors/InterceptorPlugin.js +4 -3
  21. package/dist/interceptors/Visitor.js +5 -9
  22. package/dist/interceptors/commands/codegenInterceptors.js +2 -2
  23. package/dist/interceptors/extractExports.js +9 -54
  24. package/dist/interceptors/findOriginalSource.js +2 -1
  25. package/dist/interceptors/findPlugins.js +5 -8
  26. package/dist/interceptors/generateInterceptor.js +12 -10
  27. package/dist/interceptors/generateInterceptors.js +3 -2
  28. package/dist/interceptors/parseStructure.js +1 -1
  29. package/dist/interceptors/writeInterceptors.js +2 -2
  30. package/dist/utils/TopologicalSort.js +4 -0
  31. package/dist/utils/isMonorepo.js +40 -6
  32. package/dist/utils/resolveDependenciesSync.js +9 -2
  33. package/dist/utils/sig.js +34 -0
  34. package/dist/withGraphCommerce.js +3 -2
  35. package/package.json +17 -16
  36. package/src/commands/codegen.ts +18 -0
  37. package/src/commands/copyFiles.ts +328 -0
  38. package/src/config/commands/exportConfig.ts +1 -1
  39. package/src/config/commands/generateConfig.ts +3 -3
  40. package/src/config/demoConfig.ts +5 -5
  41. package/src/config/index.ts +1 -1
  42. package/src/config/utils/exportConfigToEnv.ts +1 -1
  43. package/src/config/utils/mergeEnvIntoConfig.ts +22 -25
  44. package/src/config/utils/replaceConfigInString.ts +1 -1
  45. package/src/config/utils/rewriteLegacyEnv.ts +5 -4
  46. package/src/generated/config.ts +36 -0
  47. package/src/index.ts +6 -5
  48. package/src/interceptors/InterceptorPlugin.ts +10 -7
  49. package/src/interceptors/RenameVisitor.ts +1 -1
  50. package/src/interceptors/Visitor.ts +10 -15
  51. package/src/interceptors/commands/codegenInterceptors.ts +2 -2
  52. package/src/interceptors/extractExports.ts +4 -46
  53. package/src/interceptors/findOriginalSource.ts +5 -4
  54. package/src/interceptors/findPlugins.ts +8 -9
  55. package/src/interceptors/generateInterceptor.ts +15 -12
  56. package/src/interceptors/generateInterceptors.ts +7 -13
  57. package/src/interceptors/parseStructure.ts +4 -4
  58. package/src/interceptors/swc.ts +2 -1
  59. package/src/interceptors/writeInterceptors.ts +3 -3
  60. package/src/utils/TopologicalSort.ts +4 -0
  61. package/src/utils/isMonorepo.ts +46 -5
  62. package/src/utils/packageRoots.ts +1 -1
  63. package/src/utils/resolveDependenciesSync.ts +14 -2
  64. package/src/utils/sig.ts +37 -0
  65. package/src/withGraphCommerce.ts +7 -5
  66. package/dist/config/commands/generateIntercetors.js +0 -9
  67. package/dist/interceptors/commands/generateIntercetors.js +0 -9
  68. package/dist/runtimeCachingOptimizations.js +0 -28
  69. 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,6 +1,6 @@
1
+ import dotenv from 'dotenv'
1
2
  import { loadConfig } from '../loadConfig'
2
3
  import { exportConfigToEnv } from '../utils/exportConfigToEnv'
3
- import dotenv from 'dotenv'
4
4
 
5
5
  dotenv.config()
6
6
 
@@ -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 { isMonorepo } from '../../utils/isMonorepo'
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
- ...(isMonorepo() && {
45
+ ...(findParentPath(process.cwd()) && {
46
46
  '../../docs/framework/config.md': {
47
47
  plugins: ['@graphcommerce/graphql-codegen-markdown-docs'],
48
48
  },
@@ -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://backend.reachdigital.dev/graphql',
10
- magentoVersion: 246,
9
+ magentoEndpoint: 'https://configurator.reachdigital.dev/graphql',
10
+ magentoVersion: 247,
11
11
  storefront: [
12
12
  { locale: 'en', magentoStoreCode: 'en_US', defaultLocale: true },
13
13
  {
@@ -1,5 +1,5 @@
1
1
  import type { Path, PathValue } from 'react-hook-form'
2
- import { GraphCommerceConfig } from '../generated/config'
2
+ import type { GraphCommerceConfig } from '../generated/config'
3
3
 
4
4
  export * from './commands/generateConfig'
5
5
  export * from './commands/exportConfig'
@@ -1,4 +1,4 @@
1
- import { GraphCommerceConfig } from '../../generated/config'
1
+ import type { GraphCommerceConfig } from '../../generated/config'
2
2
  import { toEnvStr } from './mergeEnvIntoConfig'
3
3
 
4
4
  const fmt = (value: string | number | boolean | object | null) => {
@@ -1,29 +1,27 @@
1
1
  /* eslint-disable import/no-extraneous-dependencies */
2
- import { mergeDeep, cloneDeep } from '@apollo/client/utilities'
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
- ZodString,
12
- ZodNumber,
13
- ZodNullable,
14
- ZodOptional,
10
+ ZodBoolean,
11
+ ZodDefault,
15
12
  ZodEffects,
16
- ZodRawShape,
17
13
  ZodEnum,
18
- ZodTypeAny,
19
- ZodAny,
20
- ZodDefault,
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
- export const dotNotation = (pathParts: string[]) =>
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: `Invalid JSON` })
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: `Invalid JSON` })
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
- export const filterEnv = (env: Record<string, string | undefined>) =>
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}='${envValue}'`
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
- return ` = ${baseLog}: (ignored, no change/wrong format)`
230
- if (from === undefined && to !== undefined) return ` ${chalk.green('+')} ${baseLog}: ${toFmt}`
231
- if (from !== undefined && to === undefined) return ` ${chalk.red('-')} ${baseLog}: ${fromFmt}`
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(`info`)
236
- if (hasWarning) header = chalk.yellowBright(`warning`)
237
- if (hasError) header = chalk.yellowBright(`error`)
232
+ let header = chalk.blueBright('info')
233
+ if (hasWarning) header = chalk.yellowBright('warning')
234
+ if (hasError) header = chalk.yellowBright('error')
238
235
 
239
- header += ` - Loaded GraphCommerce env variables`
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, mergeEnvIntoConfig, ZodNode } from './mergeEnvIntoConfig'
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: [`should be removed`],
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
- `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
+ '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,
@@ -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>