@graphcommerce/next-config 8.1.0-canary.2 → 8.1.0-canary.5

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 (44) hide show
  1. package/CHANGELOG.md +133 -1
  2. package/Config.graphqls +4 -2
  3. package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +19 -2
  4. package/__tests__/config/utils/replaceConfigInString.ts +4 -0
  5. package/__tests__/interceptors/findPlugins.ts +473 -113
  6. package/__tests__/interceptors/generateInterceptors.ts +610 -322
  7. package/__tests__/interceptors/parseStructure.ts +158 -0
  8. package/__tests__/interceptors/writeInterceptors.ts +23 -14
  9. package/__tests__/utils/resolveDependenciesSync.ts +28 -25
  10. package/dist/config/commands/generateConfig.js +5 -2
  11. package/dist/config/demoConfig.js +19 -4
  12. package/dist/generated/config.js +8 -1
  13. package/dist/interceptors/InterceptorPlugin.js +70 -25
  14. package/dist/interceptors/RenameVisitor.js +19 -0
  15. package/dist/interceptors/Visitor.js +1418 -0
  16. package/dist/interceptors/extractExports.js +201 -0
  17. package/dist/interceptors/findOriginalSource.js +87 -0
  18. package/dist/interceptors/findPlugins.js +21 -53
  19. package/dist/interceptors/generateInterceptor.js +200 -0
  20. package/dist/interceptors/generateInterceptors.js +38 -179
  21. package/dist/interceptors/parseStructure.js +71 -0
  22. package/dist/interceptors/swc.js +16 -0
  23. package/dist/interceptors/writeInterceptors.js +19 -10
  24. package/dist/utils/resolveDependency.js +27 -5
  25. package/dist/withGraphCommerce.js +2 -1
  26. package/package.json +4 -1
  27. package/src/config/commands/generateConfig.ts +5 -2
  28. package/src/config/demoConfig.ts +19 -4
  29. package/src/config/index.ts +4 -2
  30. package/src/generated/config.ts +25 -3
  31. package/src/index.ts +16 -6
  32. package/src/interceptors/InterceptorPlugin.ts +90 -32
  33. package/src/interceptors/RenameVisitor.ts +17 -0
  34. package/src/interceptors/Visitor.ts +1847 -0
  35. package/src/interceptors/extractExports.ts +230 -0
  36. package/src/interceptors/findOriginalSource.ts +105 -0
  37. package/src/interceptors/findPlugins.ts +36 -87
  38. package/src/interceptors/generateInterceptor.ts +271 -0
  39. package/src/interceptors/generateInterceptors.ts +67 -237
  40. package/src/interceptors/parseStructure.ts +82 -0
  41. package/src/interceptors/swc.ts +13 -0
  42. package/src/interceptors/writeInterceptors.ts +26 -10
  43. package/src/utils/resolveDependency.ts +51 -12
  44. package/src/withGraphCommerce.ts +2 -1
@@ -3,194 +3,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateInterceptors = exports.generateInterceptor = exports.isPluginConfig = exports.isMethodPluginConfig = exports.isReactPluginConfig = exports.isPluginBaseConfig = void 0;
6
+ exports.generateInterceptors = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- function isPluginBaseConfig(plugin) {
9
- return (typeof plugin.exported === 'string' &&
10
- typeof plugin.plugin === 'string' &&
11
- typeof plugin.enabled === 'boolean');
12
- }
13
- exports.isPluginBaseConfig = isPluginBaseConfig;
14
- function isReactPluginConfig(plugin) {
15
- if (!isPluginBaseConfig(plugin))
16
- return false;
17
- return plugin.component !== undefined;
18
- }
19
- exports.isReactPluginConfig = isReactPluginConfig;
20
- function isMethodPluginConfig(plugin) {
21
- if (!isPluginBaseConfig(plugin))
22
- return false;
23
- return plugin.func !== undefined;
24
- }
25
- exports.isMethodPluginConfig = isMethodPluginConfig;
26
- function isPluginConfig(plugin) {
27
- return isReactPluginConfig(plugin) || isMethodPluginConfig(plugin);
28
- }
29
- exports.isPluginConfig = isPluginConfig;
30
- function moveRelativeDown(plugins) {
31
- return [...plugins].sort((a, b) => {
32
- if (a.plugin.startsWith('.') && !b.plugin.startsWith('.'))
33
- return 1;
34
- if (!a.plugin.startsWith('.') && b.plugin.startsWith('.'))
35
- return -1;
36
- return 0;
37
- });
38
- }
39
- function generateInterceptor(interceptor, config) {
40
- const { fromModule, dependency, components, funcs } = interceptor;
41
- const pluginConfigs = [...Object.entries(components), ...Object.entries(funcs)]
42
- .map(([, plugins]) => plugins)
43
- .flat();
44
- const duplicateImports = new Set();
45
- const pluginImports = moveRelativeDown([...pluginConfigs].sort((a, b) => a.plugin.localeCompare(b.plugin)))
46
- .map((plugin) => {
47
- const { plugin: p } = plugin;
48
- if (isReactPluginConfig(plugin))
49
- return `import { Plugin as ${p.split('/')[p.split('/').length - 1]} } from '${p}'`;
50
- return `import { plugin as ${p.split('/')[p.split('/').length - 1]} } from '${p}'`;
51
- })
52
- .filter((str) => {
53
- if (duplicateImports.has(str))
54
- return false;
55
- duplicateImports.add(str);
56
- return true;
57
- })
58
- .join('\n');
59
- const imports = [
60
- ...Object.entries(components).map(([component]) => `${component} as ${component}Base`),
61
- ...Object.entries(funcs).map(([func]) => `${func} as ${func}Base`),
62
- ];
63
- const importInjectables = imports.length > 1
64
- ? `import {
65
- ${imports.join(',\n ')},
66
- } from '${fromModule}'`
67
- : `import { ${imports[0]} } from '${fromModule}'`;
68
- const entries = [
69
- ...Object.entries(components),
70
- ...Object.entries(funcs),
71
- ];
72
- const pluginExports = entries
73
- .map(([base, plugins]) => {
74
- const duplicateInterceptors = new Set();
75
- const name = (p) => p.plugin.split('/')[p.plugin.split('/').length - 1];
76
- const filterNoDuplicate = (p) => {
77
- if (duplicateInterceptors.has(name(p)))
78
- return false;
79
- duplicateInterceptors.add(name(p));
80
- return true;
81
- };
82
- let carry = `${base}Base`;
83
- const pluginStr = plugins
84
- .reverse()
85
- .filter(filterNoDuplicate)
86
- .map((p) => {
87
- let result;
88
- if (isReactPluginConfig(p)) {
89
- const wrapChain = plugins
90
- .reverse()
91
- .map((pl) => `<${name(pl)}/>`)
92
- .join(' wrapping ');
93
- const debugLog = carry === `${base}Base` && config.pluginStatus
94
- ? `\n logInterceptor(\`🔌 Rendering ${base} with plugin(s): ${wrapChain} wrapping <${base}/>\`)`
95
- : '';
96
- result = `function ${name(p)}Interceptor(props: ${base}Props) {${debugLog}
97
- return <${name(p)} {...props} Prev={${carry}} />
98
- }`;
99
- }
100
- else {
101
- const wrapChain = plugins
102
- .reverse()
103
- .map((pl) => `${name(pl)}()`)
104
- .join(' wrapping ');
105
- const debugLog = carry === `${base}Base` && config.pluginStatus
106
- ? `\n logInterceptor(\`🔌 Calling ${base} with plugin(s): ${wrapChain} wrapping ${base}()\`)`
107
- : '';
108
- result = `const ${name(p)}Interceptor: typeof ${base}Base = (...args) => {${debugLog}
109
- return ${name(p)}(${carry}, ...args)
110
- }`;
111
- }
112
- carry = `${name(p)}Interceptor`;
113
- return result;
114
- })
115
- .join('\n');
116
- const isComponent = plugins.every((p) => isReactPluginConfig(p));
117
- if (isComponent && plugins.some((p) => isMethodPluginConfig(p))) {
118
- throw new Error(`Cannot mix React and Method plugins for ${base} in ${dependency}.`);
119
- }
120
- return `
121
- /**
122
- * Interceptor for \`${isComponent ? `<${base}/>` : `${base}()`}\` with these plugins:
123
- *
124
- ${plugins.map((p) => ` * - \`${p.plugin}\``).join('\n')}
125
- */
126
- ${isComponent ? `type ${base}Props = ComponentProps<typeof ${base}Base>\n\n` : ``}${pluginStr}
127
- export const ${base} = ${carry}`;
128
- })
129
- .join('\n');
130
- const logOnce = config.pluginStatus
131
- ? `
132
- const logged: Set<string> = new Set();
133
- const logInterceptor = (log: string, ...additional: unknown[]) => {
134
- if (logged.has(log)) return
135
- logged.add(log)
136
- console.log(log, ...additional)
137
- }
138
- `
139
- : '';
140
- const componentExports = `export * from '${fromModule}'`;
141
- const template = `/* This file is automatically generated for ${dependency} */
142
-
143
- ${componentExports}
144
- ${pluginImports}
145
- import { ComponentProps } from 'react'
146
- ${importInjectables}
147
- ${logOnce}${pluginExports}
148
- `;
149
- return { ...interceptor, template };
150
- }
151
- exports.generateInterceptor = generateInterceptor;
152
- function generateInterceptors(plugins, resolve, config) {
153
- // todo: Do not use reduce as we're passing the accumulator to the next iteration
154
- const byExportedComponent = moveRelativeDown(plugins).reduce((acc, plug) => {
155
- const { exported, plugin } = plug;
156
- if (!isPluginConfig(plug) || !plug.enabled)
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const findOriginalSource_1 = require("./findOriginalSource");
10
+ const generateInterceptor_1 = require("./generateInterceptor");
11
+ async function generateInterceptors(plugins, resolve, config) {
12
+ const byTargetModuleAndExport = (0, generateInterceptor_1.moveRelativeDown)(plugins).reduce((acc, plug) => {
13
+ let { sourceModule: pluginPath } = plug;
14
+ if (!(0, generateInterceptor_1.isPluginConfig)(plug) || !plug.enabled)
15
+ return acc;
16
+ const result = resolve(plug.targetModule, { includeSources: true });
17
+ const { error, resolved } = (0, findOriginalSource_1.findOriginalSource)(plug, result, resolve);
18
+ if (error) {
19
+ console.log(error.message);
157
20
  return acc;
158
- const resolved = resolve(exported);
159
- let pluginPathFromResolved = plugin;
160
- if (plugin.startsWith('.')) {
161
- const resolvedPlugin = resolve(plugin);
162
- pluginPathFromResolved = node_path_1.default.relative(resolved.fromRoot.split('/').slice(0, -1).join('/'), resolvedPlugin.fromRoot);
163
21
  }
164
- if (!acc[resolved.fromRoot])
22
+ const { fromRoot } = resolved;
23
+ if (pluginPath.startsWith('.')) {
24
+ const resolvedPlugin = resolve(pluginPath);
25
+ if (resolvedPlugin) {
26
+ pluginPath = node_path_1.default.relative(resolved.fromRoot.split('/').slice(0, -1).join('/'), resolvedPlugin.fromRoot);
27
+ }
28
+ }
29
+ if (!acc[resolved.fromRoot]) {
165
30
  acc[resolved.fromRoot] = {
166
31
  ...resolved,
167
32
  target: `${resolved.fromRoot}.interceptor`,
168
- components: {},
169
- funcs: {},
33
+ targetExports: {},
170
34
  };
171
- if (isReactPluginConfig(plug)) {
172
- const { component } = plug;
173
- if (!acc[resolved.fromRoot].components[component])
174
- acc[resolved.fromRoot].components[component] = [];
175
- acc[resolved.fromRoot].components[component].push({
176
- ...plug,
177
- plugin: pluginPathFromResolved,
178
- });
179
- }
180
- if (isMethodPluginConfig(plug)) {
181
- const { func } = plug;
182
- if (!acc[resolved.fromRoot].funcs[func])
183
- acc[resolved.fromRoot].funcs[func] = [];
184
- acc[resolved.fromRoot].funcs[func].push({
185
- ...plug,
186
- plugin: pluginPathFromResolved,
187
- });
188
35
  }
36
+ if (!acc[fromRoot].targetExports[plug.targetExport])
37
+ acc[fromRoot].targetExports[plug.targetExport] = [];
38
+ acc[fromRoot].targetExports[plug.targetExport].push({ ...plug, sourceModule: pluginPath });
189
39
  return acc;
190
40
  }, {});
191
- return Object.fromEntries(Object.entries(byExportedComponent).map(([target, interceptor]) => [
192
- target,
193
- generateInterceptor(interceptor, config ?? {}),
194
- ]));
41
+ return Object.fromEntries(await Promise.all(Object.entries(byTargetModuleAndExport).map(async ([target, interceptor]) => {
42
+ const file = `${interceptor.fromRoot}.interceptor.tsx`;
43
+ const originalSource = (await promises_1.default
44
+ .access(file, promises_1.default.constants.F_OK)
45
+ .then(() => true)
46
+ .catch(() => false))
47
+ ? (await promises_1.default.readFile(file)).toString()
48
+ : undefined;
49
+ return [
50
+ target,
51
+ await (0, generateInterceptor_1.generateInterceptor)(interceptor, config ?? {}, originalSource),
52
+ ];
53
+ })));
195
54
  }
196
55
  exports.generateInterceptors = generateInterceptors;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseStructure = void 0;
7
+ const get_1 = __importDefault(require("lodash/get"));
8
+ const zod_1 = require("zod");
9
+ const extractExports_1 = require("./extractExports");
10
+ const pluginConfigParsed = zod_1.z.object({
11
+ type: zod_1.z.enum(['component', 'function', 'replace']),
12
+ module: zod_1.z.string(),
13
+ export: zod_1.z.string(),
14
+ ifConfig: zod_1.z.union([zod_1.z.string(), zod_1.z.tuple([zod_1.z.string(), zod_1.z.string()])]).optional(),
15
+ });
16
+ function nonNullable(value) {
17
+ return value !== null && value !== undefined;
18
+ }
19
+ const isObject = (input) => typeof input === 'object' && input !== null && !Array.isArray(input);
20
+ function parseStructure(ast, gcConfig, sourceModule) {
21
+ const [exports, errors] = (0, extractExports_1.extractExports)(ast);
22
+ if (errors.length)
23
+ console.error(`Plugin error for`, errors.join('\n'));
24
+ const { config: moduleConfig, component, func, exported, ifConfig, plugin, Plugin, ...rest } = exports;
25
+ const exportVals = Object.keys(rest);
26
+ if (component && !moduleConfig)
27
+ exportVals.push('Plugin');
28
+ if (func && !moduleConfig)
29
+ exportVals.push('plugin');
30
+ return exportVals
31
+ .map((exportVal) => {
32
+ let config = isObject(moduleConfig) ? moduleConfig : {};
33
+ if (!moduleConfig && component) {
34
+ config = { type: 'component', module: exported, ifConfig, export: 'Plugin' };
35
+ }
36
+ else if (!moduleConfig && func) {
37
+ config = { type: 'function', module: exported, ifConfig, export: 'plugin' };
38
+ }
39
+ else if (isObject(moduleConfig)) {
40
+ config = { ...moduleConfig, export: exportVal };
41
+ }
42
+ else {
43
+ console.error(`Plugin configuration invalid! See ${sourceModule}`);
44
+ }
45
+ const parsed = pluginConfigParsed.safeParse(config);
46
+ if (!parsed.success) {
47
+ if (errors.length)
48
+ console.error(parsed.error.errors.map((e) => `${e.path} ${e.message}`).join('\n'));
49
+ return undefined;
50
+ }
51
+ let enabled = true;
52
+ if (parsed.data.ifConfig) {
53
+ enabled = Array.isArray(parsed.data.ifConfig)
54
+ ? (0, get_1.default)(gcConfig, parsed.data.ifConfig[0]) === parsed.data.ifConfig[1]
55
+ : Boolean((0, get_1.default)(gcConfig, parsed.data.ifConfig));
56
+ }
57
+ const val = {
58
+ targetExport: exports.component || exports.func || parsed.data.export,
59
+ sourceModule,
60
+ sourceExport: parsed.data.export,
61
+ targetModule: parsed.data.module,
62
+ type: parsed.data.type,
63
+ enabled,
64
+ };
65
+ if (parsed.data.ifConfig)
66
+ val.ifConfig = parsed.data.ifConfig;
67
+ return val;
68
+ })
69
+ .filter(nonNullable);
70
+ }
71
+ exports.parseStructure = parseStructure;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printSync = exports.parseSync = void 0;
4
+ const core_1 = require("@swc/core");
5
+ function parseSync(src) {
6
+ return (0, core_1.parseSync)(src, {
7
+ syntax: 'typescript',
8
+ tsx: true,
9
+ comments: true,
10
+ });
11
+ }
12
+ exports.parseSync = parseSync;
13
+ function printSync(m) {
14
+ return (0, core_1.printSync)(m);
15
+ }
16
+ exports.printSync = printSync;
@@ -4,20 +4,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.writeInterceptors = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  // eslint-disable-next-line import/no-extraneous-dependencies
10
- const glob_1 = __importDefault(require("glob"));
10
+ const glob_1 = require("glob");
11
11
  const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
12
- function writeInterceptors(interceptors, cwd = process.cwd()) {
12
+ function checkFileExists(file) {
13
+ return promises_1.default
14
+ .access(file, promises_1.default.constants.F_OK)
15
+ .then(() => true)
16
+ .catch(() => false);
17
+ }
18
+ async function writeInterceptors(interceptors, cwd = process.cwd()) {
13
19
  const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
14
20
  const existing = [];
15
21
  dependencies.forEach((dependency) => {
16
- const files = glob_1.default.sync(`${dependency}/**/*.interceptor.tsx`, { cwd });
22
+ const files = (0, glob_1.sync)([`${dependency}/**/*.interceptor.tsx`, `${dependency}/**/*.interceptor.ts`], { cwd });
17
23
  existing.push(...files);
18
24
  });
19
- Object.entries(interceptors).forEach(([, plugin]) => {
20
- const relativeFile = `${plugin.fromRoot}.interceptor.tsx`;
25
+ const written = Object.entries(interceptors).map(async ([, plugin]) => {
26
+ const extension = plugin.sourcePath.endsWith('.tsx') ? '.tsx' : '.ts';
27
+ const relativeFile = `${plugin.fromRoot}.interceptor${extension}`;
21
28
  if (existing.includes(relativeFile)) {
22
29
  delete existing[existing.indexOf(relativeFile)];
23
30
  }
@@ -25,12 +32,14 @@ function writeInterceptors(interceptors, cwd = process.cwd()) {
25
32
  delete existing[existing.indexOf(`./${relativeFile}`)];
26
33
  }
27
34
  const fileToWrite = path_1.default.join(cwd, relativeFile);
28
- const isSame = node_fs_1.default.existsSync(fileToWrite) &&
29
- node_fs_1.default.readFileSync(fileToWrite, 'utf8').toString() === plugin.template;
35
+ const isSame = (await checkFileExists(fileToWrite)) &&
36
+ (await promises_1.default.readFile(fileToWrite, 'utf8')).toString() === plugin.template;
30
37
  if (!isSame)
31
- node_fs_1.default.writeFileSync(fileToWrite, plugin.template);
38
+ await promises_1.default.writeFile(fileToWrite, plugin.template);
32
39
  });
33
40
  // Cleanup unused interceptors
34
- existing.forEach((file) => node_fs_1.default.existsSync(file) && node_fs_1.default.unlinkSync(file));
41
+ const cleaned = existing.map(async (file) => (await checkFileExists(file)) && (await promises_1.default.unlink(file)));
42
+ await Promise.all(written);
43
+ await Promise.all(cleaned);
35
44
  }
36
45
  exports.writeInterceptors = writeInterceptors;
@@ -8,9 +8,12 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const resolveDependenciesSync_1 = require("./resolveDependenciesSync");
9
9
  const resolveDependency = (cwd = process.cwd()) => {
10
10
  const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
11
- return (dependency) => {
11
+ function resolve(dependency, options = {}) {
12
+ const { includeSources = false } = options;
12
13
  let dependencyPaths = {
13
14
  root: '.',
15
+ source: '',
16
+ sourcePath: '',
14
17
  dependency,
15
18
  fromRoot: dependency,
16
19
  fromModule: dependency,
@@ -20,13 +23,23 @@ const resolveDependency = (cwd = process.cwd()) => {
20
23
  if (dependency === depCandidate || dependency.startsWith(`${depCandidate}/`)) {
21
24
  const relative = dependency.replace(depCandidate, '');
22
25
  const rootCandidate = dependency.replace(depCandidate, root);
26
+ let source = '';
27
+ let sourcePath = '';
23
28
  const fromRoot = [
24
29
  `${rootCandidate}`,
25
30
  `${rootCandidate}/index`,
26
31
  `${rootCandidate}/src/index`,
27
- ].find((location) => ['ts', 'tsx'].find((extension) => node_fs_1.default.existsSync(`${location}.${extension}`)));
32
+ ].find((location) => ['ts', 'tsx'].find((extension) => {
33
+ const candidatePath = `${location}.${extension}`;
34
+ const exists = node_fs_1.default.existsSync(candidatePath);
35
+ if (includeSources && exists) {
36
+ source = node_fs_1.default.readFileSync(candidatePath, 'utf-8');
37
+ sourcePath = candidatePath;
38
+ }
39
+ return exists;
40
+ }));
28
41
  if (!fromRoot) {
29
- throw Error(`Can't find plugin ${dependency}`);
42
+ return;
30
43
  }
31
44
  const denormalized = fromRoot.replace(root, depCandidate);
32
45
  let fromModule = !relative
@@ -34,10 +47,19 @@ const resolveDependency = (cwd = process.cwd()) => {
34
47
  : `./${relative.split('/')[relative.split('/').length - 1]}`;
35
48
  if (dependency.startsWith('./'))
36
49
  fromModule = `.${relative}`;
37
- dependencyPaths = { root, dependency, denormalized, fromRoot, fromModule };
50
+ dependencyPaths = {
51
+ root,
52
+ dependency,
53
+ denormalized,
54
+ fromRoot,
55
+ fromModule,
56
+ source,
57
+ sourcePath,
58
+ };
38
59
  }
39
60
  });
40
61
  return dependencyPaths;
41
- };
62
+ }
63
+ return resolve;
42
64
  };
43
65
  exports.resolveDependency = resolveDependency;
@@ -47,6 +47,7 @@ function withGraphCommerce(nextConfig, cwd) {
47
47
  experimental: {
48
48
  ...nextConfig.experimental,
49
49
  scrollRestoration: true,
50
+ bundlePagesExternals: true,
50
51
  swcPlugins: [...(nextConfig.experimental?.swcPlugins ?? []), ['@lingui/swc-plugin', {}]],
51
52
  },
52
53
  i18n: {
@@ -145,7 +146,7 @@ function withGraphCommerce(nextConfig, cwd) {
145
146
  '@mui/system': '@mui/system/modern',
146
147
  };
147
148
  }
148
- config.plugins.push(new InterceptorPlugin_1.InterceptorPlugin(graphcommerceConfig));
149
+ config.plugins.push(new InterceptorPlugin_1.InterceptorPlugin(graphcommerceConfig, !options.isServer));
149
150
  return typeof nextConfig.webpack === 'function' ? nextConfig.webpack(config, options) : config;
150
151
  },
151
152
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/next-config",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "8.1.0-canary.2",
5
+ "version": "8.1.0-canary.5",
6
6
  "type": "commonjs",
7
7
  "main": "dist/index.js",
8
8
  "types": "src/index.ts",
@@ -16,10 +16,13 @@
16
16
  "@lingui/loader": "4.7.0",
17
17
  "@lingui/swc-plugin": "4.0.4",
18
18
  "@swc/core": "1.3.104",
19
+ "@swc/wasm-web": "^1.4.8",
19
20
  "circular-dependency-plugin": "^5.2.2",
21
+ "glob": "^10.3.10",
20
22
  "inspectpack": "^4.7.1",
21
23
  "js-yaml-loader": "^1.2.2",
22
24
  "lodash": "^4.17.21",
25
+ "woodpile": "^0.0.5",
23
26
  "znv": "^0.4.0",
24
27
  "zod": "^3.22.4"
25
28
  },
@@ -13,8 +13,11 @@ const resolve = resolveDependency()
13
13
  const schemaLocations = packages.map((p) => `${p}/**/Config.graphqls`)
14
14
 
15
15
  export async function generateConfig() {
16
- const targetTs = `${resolve('@graphcommerce/next-config').root}/src/generated/config.ts`
17
- const targetJs = `${resolve('@graphcommerce/next-config').root}/dist/generated/config.js`
16
+ const resolved = resolve('@graphcommerce/next-config')
17
+ if (!resolved) throw Error('Could not resolve @graphcommerce/next-config')
18
+
19
+ const targetTs = `${resolved.root}/src/generated/config.ts`
20
+ const targetJs = `${resolved.root}/dist/generated/config.js`
18
21
 
19
22
  await generate({
20
23
  silent: true,
@@ -15,10 +15,25 @@ export const demoConfig: PartialDeep<GraphCommerceConfig, { recurseIntoArrays: t
15
15
  hygraphLocales: ['nl', 'en_us'],
16
16
  cartDisplayPricesInclTax: true,
17
17
  },
18
- { locale: 'fr-be', magentoStoreCode: 'fr_BE', cartDisplayPricesInclTax: true },
19
- { locale: 'nl-be', magentoStoreCode: 'nl_BE', cartDisplayPricesInclTax: true },
20
- { locale: 'en-gb', magentoStoreCode: 'en_GB', cartDisplayPricesInclTax: true },
21
- { locale: 'en-ca', magentoStoreCode: 'en_CA' },
18
+ {
19
+ locale: 'fr-be',
20
+ magentoStoreCode: 'fr_BE',
21
+ cartDisplayPricesInclTax: true,
22
+ linguiLocale: 'fr',
23
+ },
24
+ {
25
+ locale: 'nl-be',
26
+ magentoStoreCode: 'nl_BE',
27
+ cartDisplayPricesInclTax: true,
28
+ linguiLocale: 'nl',
29
+ },
30
+ {
31
+ locale: 'en-gb',
32
+ magentoStoreCode: 'en_GB',
33
+ cartDisplayPricesInclTax: true,
34
+ linguiLocale: 'en',
35
+ },
36
+ { locale: 'en-ca', magentoStoreCode: 'en_CA', linguiLocale: 'en' },
22
37
  ],
23
38
  productFiltersPro: true,
24
39
  productFiltersLayout: 'DEFAULT',
@@ -1,4 +1,4 @@
1
- import type { Path } from 'react-hook-form'
1
+ import type { Path, PathValue } from 'react-hook-form'
2
2
  import { GraphCommerceConfig } from '../generated/config'
3
3
 
4
4
  export * from './commands/generateConfig'
@@ -12,4 +12,6 @@ declare global {
12
12
  }
13
13
  }
14
14
 
15
- export type IfConfig = Path<GraphCommerceConfig>
15
+ export type IfConfig<P extends Path<GraphCommerceConfig> = Path<GraphCommerceConfig>> =
16
+ | P
17
+ | [P, PathValue<GraphCommerceConfig, P>]
@@ -20,6 +20,12 @@ export type CompareVariant =
20
20
  | 'CHECKBOX'
21
21
  | 'ICON';
22
22
 
23
+ /** GoogleDatalayerConfig to allow enabling certain aspects of the datalayer */
24
+ export type DatalayerConfig = {
25
+ /** Enable core web vitals tracking for GraphCommerce */
26
+ coreWebVitals?: InputMaybe<Scalars['Boolean']['input']>;
27
+ };
28
+
23
29
  /**
24
30
  * # GraphCommerce configuration system
25
31
  *
@@ -80,7 +86,7 @@ export type CompareVariant =
80
86
  *
81
87
  * You can export configuration by running `yarn graphcommerce export-config`
82
88
  *
83
- * ## Extending the configuration in your project
89
+ * ## Extending the configuration in your project
84
90
  *
85
91
  * Create a graphql/Config.graphqls file in your project and extend the GraphCommerceConfig, GraphCommerceStorefrontConfig inputs to add configuration.
86
92
  *
@@ -159,6 +165,7 @@ export type GraphCommerceConfig = {
159
165
  * `customer/create_account/confirm` and should be removed once we can query
160
166
  */
161
167
  customerRequireEmailConfirmation?: InputMaybe<Scalars['Boolean']['input']>;
168
+ dataLayer?: InputMaybe<DatalayerConfig>;
162
169
  /** Debug configuration for GraphCommerce */
163
170
  debug?: InputMaybe<GraphCommerceDebugConfig>;
164
171
  /**
@@ -358,9 +365,17 @@ export type GraphCommerceStorefrontConfig = {
358
365
  googleTagmanagerId?: InputMaybe<Scalars['String']['input']>;
359
366
  /** Add a gcms-locales header to make sure queries return in a certain language, can be an array to define fallbacks. */
360
367
  hygraphLocales?: InputMaybe<Array<Scalars['String']['input']>>;
361
- /** Specify a custom locale for to load translations. */
368
+ /**
369
+ * Specify a custom locale for to load translations. Must be lowercase valid locale.
370
+ *
371
+ * This value is also used for the Intl.
372
+ */
362
373
  linguiLocale?: InputMaybe<Scalars['String']['input']>;
363
- /** Must be a locale string https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers */
374
+ /**
375
+ * Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work.
376
+ *
377
+ * This value can be used as a sub-path identifier only, make sure linguiLocale is configured for each URL.
378
+ */
364
379
  locale: Scalars['String']['input'];
365
380
  /**
366
381
  * Magento store code.
@@ -432,6 +447,12 @@ export const ProductFiltersLayoutSchema = z.enum(['DEFAULT', 'SIDEBAR']);
432
447
 
433
448
  export const SidebarGalleryPaginationVariantSchema = z.enum(['DOTS', 'THUMBNAILS_BOTTOM']);
434
449
 
450
+ export function DatalayerConfigSchema(): z.ZodObject<Properties<DatalayerConfig>> {
451
+ return z.object({
452
+ coreWebVitals: z.boolean().nullish()
453
+ })
454
+ }
455
+
435
456
  export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerceConfig>> {
436
457
  return z.object({
437
458
  canonicalBaseUrl: z.string().min(1),
@@ -443,6 +464,7 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc
443
464
  crossSellsHideCartItems: z.boolean().nullish(),
444
465
  crossSellsRedirectItems: z.boolean().nullish(),
445
466
  customerRequireEmailConfirmation: z.boolean().nullish(),
467
+ dataLayer: DatalayerConfigSchema().nullish(),
446
468
  debug: GraphCommerceDebugConfigSchema().nullish(),
447
469
  demoMode: z.boolean().nullish(),
448
470
  enableGuestCheckoutLogin: z.boolean().nullish(),
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
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
5
  export * from './utils/isMonorepo'
5
6
  export * from './utils/resolveDependenciesSync'
6
7
  export * from './utils/packageRoots'
@@ -13,14 +14,23 @@ export type PluginProps<P extends Record<string, unknown> = Record<string, unkno
13
14
  Prev: React.FC<P>
14
15
  }
15
16
 
16
- export type ReactPlugin<
17
- T extends React.FC<any>,
18
- AdditionalOptionalProps extends Record<string, unknown> = Record<string, unknown>,
19
- > = (
20
- props: Parameters<T>[0] & AdditionalOptionalProps & { Prev: React.FC<Parameters<T>[0]> },
17
+ export type FunctionPlugin<T extends (...args: any[]) => any> = (
18
+ prev: T,
19
+ ...args: Parameters<T>
21
20
  ) => ReturnType<T>
22
21
 
22
+ /**
23
+ * @deprecated use FunctionPlugin instead
24
+ */
23
25
  export type MethodPlugin<T extends (...args: any[]) => any> = (
24
26
  prev: T,
25
27
  ...args: Parameters<T>
26
28
  ) => ReturnType<T>
29
+
30
+ export type PluginConfig<P extends Path<GraphCommerceConfig> = Path<GraphCommerceConfig>> = {
31
+ type: PluginType
32
+ module: string
33
+ ifConfig?: P | [P, PathValue<GraphCommerceConfig, P>]
34
+ }
35
+
36
+ export type PluginType = 'component' | 'function' | 'replace'