@graphcommerce/next-config 6.1.1-canary.4 → 6.2.0-canary.6
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 +8 -0
- package/__tests__/config/utils/replaceConfigInString.ts +4 -0
- package/__tests__/interceptors/findPlugins.ts +10 -13
- package/__tests__/interceptors/generateInterceptors.ts +143 -0
- package/dist/config/demoConfig.js +6 -1
- package/dist/interceptors/InterceptorPlugin.js +4 -3
- package/dist/interceptors/findPlugins.js +45 -45
- package/dist/interceptors/generateInterceptors.js +118 -38
- package/package.json +1 -1
- package/src/config/demoConfig.ts +6 -1
- package/src/index.ts +9 -0
- package/src/interceptors/InterceptorPlugin.ts +4 -3
- package/src/interceptors/findPlugins.ts +64 -49
- package/src/interceptors/generateInterceptors.ts +144 -44
- package/src/withGraphCommerce.ts +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 6.2.0-canary.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1915](https://github.com/graphcommerce-org/graphcommerce/pull/1915) [`f4a8c3881`](https://github.com/graphcommerce-org/graphcommerce/commit/f4a8c388183e17c52e7f66536c5448749f494d7f) - Added the ability to create functional plugins for usage in non-component areas and hooks ([@paales](https://github.com/paales))
|
|
8
|
+
|
|
9
|
+
## 6.1.1-canary.5
|
|
10
|
+
|
|
3
11
|
## 6.1.1-canary.4
|
|
4
12
|
|
|
5
13
|
## 6.1.1-canary.3
|
|
@@ -9,10 +9,12 @@ it('finds plugins', () => {
|
|
|
9
9
|
googleAnalyticsId: '123',
|
|
10
10
|
} as GraphCommerceConfig
|
|
11
11
|
|
|
12
|
-
const plugins = findPlugins(fakeconfig, projectRoot)
|
|
12
|
+
const [plugins, errors] = findPlugins(fakeconfig, projectRoot)
|
|
13
13
|
const disabled = plugins.filter((p) => !p.enabled)
|
|
14
14
|
const enabled = plugins.filter((p) => p.enabled)
|
|
15
15
|
|
|
16
|
+
expect(errors).toMatchInlineSnapshot(`[]`)
|
|
17
|
+
|
|
16
18
|
expect(enabled).toMatchInlineSnapshot(`
|
|
17
19
|
[
|
|
18
20
|
{
|
|
@@ -96,6 +98,7 @@ it('finds plugins', () => {
|
|
|
96
98
|
"component": "ProductPageMeta",
|
|
97
99
|
"enabled": true,
|
|
98
100
|
"exported": "@graphcommerce/magento-product",
|
|
101
|
+
"ifConfig": "googleAnalyticsId",
|
|
99
102
|
"plugin": "@graphcommerce/googleanalytics/plugins/GaViewItem",
|
|
100
103
|
},
|
|
101
104
|
{
|
|
@@ -127,10 +130,10 @@ it('finds plugins', () => {
|
|
|
127
130
|
"plugin": "@graphcommerce/googlerecaptcha/plugins/GrecaptchaGraphQLProvider",
|
|
128
131
|
},
|
|
129
132
|
{
|
|
130
|
-
"component": "GraphQLProvider",
|
|
131
133
|
"enabled": true,
|
|
132
|
-
"exported": "@graphcommerce/graphql",
|
|
133
|
-
"
|
|
134
|
+
"exported": "@graphcommerce/graphql/config",
|
|
135
|
+
"func": "graphqlConfig",
|
|
136
|
+
"plugin": "@graphcommerce/graphcms-ui/plugins/hygraphGraphqlConfig",
|
|
134
137
|
},
|
|
135
138
|
{
|
|
136
139
|
"component": "PaymentMethodContextProvider",
|
|
@@ -157,16 +160,10 @@ it('finds plugins', () => {
|
|
|
157
160
|
"plugin": "@graphcommerce/magento-customer/plugins/MagentoCustomerGraphqlProvider",
|
|
158
161
|
},
|
|
159
162
|
{
|
|
160
|
-
"component": "GraphQLProvider",
|
|
161
163
|
"enabled": true,
|
|
162
|
-
"exported": "@graphcommerce/graphql",
|
|
163
|
-
"
|
|
164
|
-
|
|
165
|
-
{
|
|
166
|
-
"component": "GraphQLProvider",
|
|
167
|
-
"enabled": true,
|
|
168
|
-
"exported": "@graphcommerce/graphql",
|
|
169
|
-
"plugin": "@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider",
|
|
164
|
+
"exported": "@graphcommerce/graphql/config",
|
|
165
|
+
"func": "graphqlConfig",
|
|
166
|
+
"plugin": "@graphcommerce/magento-store/plugins/magentoStoreGraphqlConfig",
|
|
170
167
|
},
|
|
171
168
|
]
|
|
172
169
|
`)
|
|
@@ -267,3 +267,146 @@ it('it handles root plugins deeper nested', () => {
|
|
|
267
267
|
interceptors['packages/next-ui/Overlay/components/OverlaySsr'].components.OverlaySsr[0].plugin,
|
|
268
268
|
).toMatchInlineSnapshot(`"../../../../examples/magento-graphcms/plugins/EnableCrosssellsPlugin"`)
|
|
269
269
|
})
|
|
270
|
+
|
|
271
|
+
it('generates method interceptors alognside component interceptors', () => {
|
|
272
|
+
const plugins = [
|
|
273
|
+
{
|
|
274
|
+
enabled: true,
|
|
275
|
+
exported: '@graphcommerce/graphql',
|
|
276
|
+
component: 'GraphQLProvider',
|
|
277
|
+
plugin: '@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
enabled: true,
|
|
281
|
+
exported: '@graphcommerce/graphql',
|
|
282
|
+
func: 'inMemoryCache',
|
|
283
|
+
plugin: '@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
enabled: true,
|
|
287
|
+
exported: '@graphcommerce/graphql',
|
|
288
|
+
func: 'inMemoryCache',
|
|
289
|
+
plugin: '@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache',
|
|
290
|
+
},
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
const resolve = resolveDependency(projectRoot)
|
|
294
|
+
const interceptors = generateInterceptors(plugins, resolve)
|
|
295
|
+
|
|
296
|
+
expect(interceptors['packages/graphql/index']?.template).toMatchInlineSnapshot(`
|
|
297
|
+
"/* This file is automatically generated for @graphcommerce/graphql */
|
|
298
|
+
|
|
299
|
+
export * from '.'
|
|
300
|
+
import { Plugin as MagentoGraphqlGraphqlProvider } from '@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider'
|
|
301
|
+
import { plugin as magentoInitMemoryCache } from '@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache'
|
|
302
|
+
import { plugin as hygraphInitMemoryCache } from '@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache'
|
|
303
|
+
import { ComponentProps } from 'react'
|
|
304
|
+
import {
|
|
305
|
+
GraphQLProvider as GraphQLProviderBase,
|
|
306
|
+
inMemoryCache as inMemoryCacheBase,
|
|
307
|
+
} from '.'
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Interceptor for \`<GraphQLProvider/>\` with these plugins:
|
|
311
|
+
*
|
|
312
|
+
* - \`@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider\`
|
|
313
|
+
*/
|
|
314
|
+
type GraphQLProviderProps = ComponentProps<typeof GraphQLProviderBase>
|
|
315
|
+
|
|
316
|
+
function MagentoGraphqlGraphqlProviderInterceptor(props: GraphQLProviderProps) {
|
|
317
|
+
return <MagentoGraphqlGraphqlProvider {...props} Prev={GraphQLProviderBase} />
|
|
318
|
+
}
|
|
319
|
+
export const GraphQLProvider = MagentoGraphqlGraphqlProviderInterceptor
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Interceptor for \`inMemoryCache()\` with these plugins:
|
|
323
|
+
*
|
|
324
|
+
* - \`@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache\`
|
|
325
|
+
* - \`@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache\`
|
|
326
|
+
*/
|
|
327
|
+
const hygraphInitMemoryCacheInterceptor: typeof inMemoryCacheBase = (...args) => {
|
|
328
|
+
return hygraphInitMemoryCache(inMemoryCacheBase, ...args)
|
|
329
|
+
}
|
|
330
|
+
const magentoInitMemoryCacheInterceptor: typeof inMemoryCacheBase = (...args) => {
|
|
331
|
+
return magentoInitMemoryCache(hygraphInitMemoryCacheInterceptor, ...args)
|
|
332
|
+
}
|
|
333
|
+
export const inMemoryCache = magentoInitMemoryCacheInterceptor
|
|
334
|
+
"
|
|
335
|
+
`)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('adds debug logging to interceptors for components', () => {
|
|
339
|
+
const plugins = [
|
|
340
|
+
{
|
|
341
|
+
enabled: true,
|
|
342
|
+
exported: '@graphcommerce/graphql',
|
|
343
|
+
component: 'GraphQLProvider',
|
|
344
|
+
plugin: '@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
enabled: true,
|
|
348
|
+
exported: '@graphcommerce/graphql',
|
|
349
|
+
func: 'inMemoryCache',
|
|
350
|
+
plugin: '@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
enabled: true,
|
|
354
|
+
exported: '@graphcommerce/graphql',
|
|
355
|
+
func: 'inMemoryCache',
|
|
356
|
+
plugin: '@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache',
|
|
357
|
+
},
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
const resolve = resolveDependency(projectRoot)
|
|
361
|
+
const interceptors = generateInterceptors(plugins, resolve, { pluginStatus: true })
|
|
362
|
+
|
|
363
|
+
expect(interceptors['packages/graphql/index']?.template).toMatchInlineSnapshot(`
|
|
364
|
+
"/* This file is automatically generated for @graphcommerce/graphql */
|
|
365
|
+
|
|
366
|
+
export * from '.'
|
|
367
|
+
import { Plugin as MagentoGraphqlGraphqlProvider } from '@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider'
|
|
368
|
+
import { plugin as magentoInitMemoryCache } from '@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache'
|
|
369
|
+
import { plugin as hygraphInitMemoryCache } from '@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache'
|
|
370
|
+
import { ComponentProps } from 'react'
|
|
371
|
+
import {
|
|
372
|
+
GraphQLProvider as GraphQLProviderBase,
|
|
373
|
+
inMemoryCache as inMemoryCacheBase,
|
|
374
|
+
} from '.'
|
|
375
|
+
|
|
376
|
+
const logged: Set<string> = new Set();
|
|
377
|
+
const logInterceptor = (log: string, ...additional: unknown[]) => {
|
|
378
|
+
if (logged.has(log)) return
|
|
379
|
+
logged.add(log)
|
|
380
|
+
console.log(log, ...additional)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Interceptor for \`<GraphQLProvider/>\` with these plugins:
|
|
385
|
+
*
|
|
386
|
+
* - \`@graphcommerce/magento-graphql/plugins/MagentoGraphqlGraphqlProvider\`
|
|
387
|
+
*/
|
|
388
|
+
type GraphQLProviderProps = ComponentProps<typeof GraphQLProviderBase>
|
|
389
|
+
|
|
390
|
+
function MagentoGraphqlGraphqlProviderInterceptor(props: GraphQLProviderProps) {
|
|
391
|
+
logInterceptor(\`🔌 Rendering GraphQLProvider with plugin(s): <MagentoGraphqlGraphqlProvider/> wrapping <GraphQLProvider/>\`)
|
|
392
|
+
return <MagentoGraphqlGraphqlProvider {...props} Prev={GraphQLProviderBase} />
|
|
393
|
+
}
|
|
394
|
+
export const GraphQLProvider = MagentoGraphqlGraphqlProviderInterceptor
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Interceptor for \`inMemoryCache()\` with these plugins:
|
|
398
|
+
*
|
|
399
|
+
* - \`@graphcommerce/magento-hygraph/plugins/hygraphInitMemoryCache\`
|
|
400
|
+
* - \`@graphcommerce/magento-graphql/plugins/magentoInitMemoryCache\`
|
|
401
|
+
*/
|
|
402
|
+
const hygraphInitMemoryCacheInterceptor: typeof inMemoryCacheBase = (...args) => {
|
|
403
|
+
logInterceptor(\`🔌 Calling inMemoryCache with plugin(s): magentoInitMemoryCache() wrapping hygraphInitMemoryCache() wrapping inMemoryCache()\`)
|
|
404
|
+
return hygraphInitMemoryCache(inMemoryCacheBase, ...args)
|
|
405
|
+
}
|
|
406
|
+
const magentoInitMemoryCacheInterceptor: typeof inMemoryCacheBase = (...args) => {
|
|
407
|
+
return magentoInitMemoryCache(hygraphInitMemoryCacheInterceptor, ...args)
|
|
408
|
+
}
|
|
409
|
+
export const inMemoryCache = magentoInitMemoryCacheInterceptor
|
|
410
|
+
"
|
|
411
|
+
`)
|
|
412
|
+
})
|
|
@@ -7,7 +7,12 @@ exports.demoConfig = {
|
|
|
7
7
|
magentoEndpoint: 'https://backend.reachdigital.dev/graphql',
|
|
8
8
|
storefront: [
|
|
9
9
|
{ locale: 'en', magentoStoreCode: 'en_US', defaultLocale: true },
|
|
10
|
-
{
|
|
10
|
+
{
|
|
11
|
+
locale: 'nl',
|
|
12
|
+
magentoStoreCode: 'nl_NL',
|
|
13
|
+
hygraphLocales: ['nl', 'en_us'],
|
|
14
|
+
cartDisplayPricesInclTax: true,
|
|
15
|
+
},
|
|
11
16
|
{ locale: 'fr-be', magentoStoreCode: 'fr_BE', cartDisplayPricesInclTax: true },
|
|
12
17
|
{ locale: 'nl-be', magentoStoreCode: 'nl_BE', cartDisplayPricesInclTax: true },
|
|
13
18
|
{ locale: 'en-gb', magentoStoreCode: 'en_GB', cartDisplayPricesInclTax: true },
|
|
@@ -13,7 +13,8 @@ class InterceptorPlugin {
|
|
|
13
13
|
constructor(config) {
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.resolveDependency = (0, resolveDependency_1.resolveDependency)();
|
|
16
|
-
|
|
16
|
+
const [plugins, errors] = (0, findPlugins_1.findPlugins)(this.config);
|
|
17
|
+
this.interceptors = (0, generateInterceptors_1.generateInterceptors)(plugins, this.resolveDependency, this.config.debug);
|
|
17
18
|
this.interceptorByDepependency = Object.fromEntries(Object.values(this.interceptors).map((i) => [i.dependency, i]));
|
|
18
19
|
(0, writeInterceptors_1.writeInterceptors)(this.interceptors);
|
|
19
20
|
}
|
|
@@ -21,12 +22,12 @@ class InterceptorPlugin {
|
|
|
21
22
|
const logger = compiler.getInfrastructureLogger('InterceptorPlugin');
|
|
22
23
|
// After the compilation has succeeded we watch all possible plugin locations.
|
|
23
24
|
compiler.hooks.afterCompile.tap('InterceptorPlugin', (compilation) => {
|
|
24
|
-
const plugins = (0, findPlugins_1.findPlugins)(this.config);
|
|
25
|
+
const [plugins, errors] = (0, findPlugins_1.findPlugins)(this.config);
|
|
25
26
|
plugins.forEach((p) => {
|
|
26
27
|
const absoluteFilePath = `${path_1.default.join(process.cwd(), this.resolveDependency(p.plugin).fromRoot)}.tsx`;
|
|
27
28
|
compilation.fileDependencies.add(absoluteFilePath);
|
|
28
29
|
});
|
|
29
|
-
this.interceptors = (0, generateInterceptors_1.generateInterceptors)(plugins, this.resolveDependency);
|
|
30
|
+
this.interceptors = (0, generateInterceptors_1.generateInterceptors)(plugins, this.resolveDependency, this.config.debug);
|
|
30
31
|
this.interceptorByDepependency = Object.fromEntries(Object.values(this.interceptors).map((i) => [i.dependency, i]));
|
|
31
32
|
(0, writeInterceptors_1.writeInterceptors)(this.interceptors);
|
|
32
33
|
});
|
|
@@ -4,35 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.findPlugins = void 0;
|
|
7
|
-
const console_1 = require("console");
|
|
8
|
-
const stream_1 = require("stream");
|
|
9
7
|
const core_1 = require("@swc/core");
|
|
10
8
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
11
|
const glob_1 = __importDefault(require("glob"));
|
|
12
12
|
const get_1 = __importDefault(require("lodash/get"));
|
|
13
|
-
const diff_1 = __importDefault(require("../config/utils/diff"));
|
|
14
13
|
const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
|
|
15
|
-
|
|
16
|
-
// @see https://stackoverflow.com/a/67859384
|
|
17
|
-
const ts = new stream_1.Transform({
|
|
18
|
-
transform(chunk, enc, cb) {
|
|
19
|
-
cb(null, chunk);
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
const logger = new console_1.Console({ stdout: ts });
|
|
23
|
-
logger.table(input);
|
|
24
|
-
const t = (ts.read() || '').toString();
|
|
25
|
-
let result = '';
|
|
26
|
-
for (const row of t.split(/[\r\n]+/)) {
|
|
27
|
-
let r = row.replace(/[^┬]*┬/, '┌');
|
|
28
|
-
r = r.replace(/^├─*┼/, '├');
|
|
29
|
-
r = r.replace(/│[^│]*/, '');
|
|
30
|
-
r = r.replace(/^└─*┴/, '└');
|
|
31
|
-
r = r.replace(/'/g, ' ');
|
|
32
|
-
result += `${r}\n`;
|
|
33
|
-
}
|
|
34
|
-
console.log(result);
|
|
35
|
-
}
|
|
14
|
+
const generateInterceptors_1 = require("./generateInterceptors");
|
|
36
15
|
function parseStructure(file) {
|
|
37
16
|
const ast = (0, core_1.parseFileSync)(file, { syntax: 'typescript', tsx: true });
|
|
38
17
|
const imports = {};
|
|
@@ -55,45 +34,66 @@ function parseStructure(file) {
|
|
|
55
34
|
});
|
|
56
35
|
return exports;
|
|
57
36
|
}
|
|
58
|
-
|
|
37
|
+
const pluginLogs = {};
|
|
59
38
|
function findPlugins(config, cwd = process.cwd()) {
|
|
60
39
|
const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
|
|
61
40
|
const debug = Boolean(config.debug?.pluginStatus);
|
|
62
|
-
|
|
63
|
-
console.time('findPlugins');
|
|
41
|
+
const errors = [];
|
|
64
42
|
const plugins = [];
|
|
65
43
|
dependencies.forEach((dependency, path) => {
|
|
66
|
-
const files = glob_1.default.sync(`${dependency}/plugins/**/*.tsx`);
|
|
44
|
+
const files = glob_1.default.sync(`${dependency}/plugins/**/*.{ts,tsx}`);
|
|
67
45
|
files.forEach((file) => {
|
|
68
46
|
try {
|
|
69
47
|
const result = parseStructure(file);
|
|
70
48
|
if (!result)
|
|
71
49
|
return;
|
|
72
|
-
|
|
73
|
-
plugin: file.replace(dependency, path).replace('.tsx', ''),
|
|
50
|
+
const pluginConfig = {
|
|
51
|
+
plugin: file.replace(dependency, path).replace('.tsx', '').replace('.ts', ''),
|
|
74
52
|
...result,
|
|
75
53
|
enabled: !result.ifConfig || Boolean((0, get_1.default)(config, result.ifConfig)),
|
|
76
|
-
}
|
|
54
|
+
};
|
|
55
|
+
if (!(0, generateInterceptors_1.isPluginConfig)(pluginConfig)) {
|
|
56
|
+
if (!(0, generateInterceptors_1.isPluginBaseConfig)(pluginConfig))
|
|
57
|
+
errors.push(`Plugin ${file} is not a valid plugin, make it has "export const exported = '@graphcommerce/my-package"`);
|
|
58
|
+
else if (file.endsWith('.ts')) {
|
|
59
|
+
errors.push(`Plugin ${file} is not a valid plugin, please define the method to create a plugin for "export const method = 'someMethod'"`);
|
|
60
|
+
}
|
|
61
|
+
else if (file.endsWith('.tsx')) {
|
|
62
|
+
errors.push(`Plugin ${file} is not a valid plugin, please define the compoennt to create a plugin for "export const component = 'SomeComponent'"`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
plugins.push(pluginConfig);
|
|
67
|
+
}
|
|
77
68
|
}
|
|
78
69
|
catch (e) {
|
|
79
70
|
console.error(`Error parsing ${file}`, e);
|
|
80
71
|
}
|
|
81
72
|
});
|
|
82
73
|
});
|
|
83
|
-
if (debug) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
if (process.env.NODE_ENV === 'development' && debug) {
|
|
75
|
+
const byExported = plugins.reduce((acc, plugin) => {
|
|
76
|
+
const componentStr = (0, generateInterceptors_1.isReactPluginConfig)(plugin) ? plugin.component : '';
|
|
77
|
+
const funcStr = (0, generateInterceptors_1.isMethodPluginConfig)(plugin) ? plugin.func : '';
|
|
78
|
+
const key = `🔌 ${chalk_1.default.greenBright(`Plugins loaded for ${plugin.exported}#${componentStr}${funcStr}`)}`;
|
|
79
|
+
if (!acc[key])
|
|
80
|
+
acc[key] = [];
|
|
81
|
+
acc[key].push(plugin);
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
const toLog = [];
|
|
85
|
+
Object.entries(byExported).forEach(([key, p]) => {
|
|
86
|
+
const logStr = p
|
|
87
|
+
.filter((c) => debug || c.enabled)
|
|
88
|
+
.map((c) => `${c.enabled ? `🟢` : `⚪️`} ${c.plugin} ${c.ifConfig ? `(${c.ifConfig}: ${c.enabled ? 'true' : 'false'})` : ''}`)
|
|
89
|
+
.join('\n');
|
|
90
|
+
if (logStr && pluginLogs[key] !== logStr) {
|
|
91
|
+
toLog.push(`${key}\n${logStr}`);
|
|
92
|
+
pluginLogs[key] = logStr;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
console.log(toLog.join('\n\n'));
|
|
96
96
|
}
|
|
97
|
-
return plugins;
|
|
97
|
+
return [plugins, errors];
|
|
98
98
|
}
|
|
99
99
|
exports.findPlugins = findPlugins;
|
|
@@ -3,8 +3,30 @@ 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 = void 0;
|
|
6
|
+
exports.generateInterceptors = exports.generateInterceptor = exports.isPluginConfig = exports.isMethodPluginConfig = exports.isReactPluginConfig = exports.isPluginBaseConfig = 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;
|
|
8
30
|
function moveRelativeDown(plugins) {
|
|
9
31
|
return [...plugins].sort((a, b) => {
|
|
10
32
|
if (a.plugin.startsWith('.') && !b.plugin.startsWith('.'))
|
|
@@ -14,15 +36,19 @@ function moveRelativeDown(plugins) {
|
|
|
14
36
|
return 0;
|
|
15
37
|
});
|
|
16
38
|
}
|
|
17
|
-
function generateInterceptor(interceptor) {
|
|
18
|
-
const { fromModule, dependency, components } = interceptor;
|
|
19
|
-
const
|
|
39
|
+
function generateInterceptor(interceptor, config) {
|
|
40
|
+
const { fromModule, dependency, components, funcs } = interceptor;
|
|
41
|
+
const pluginConfigs = [...Object.entries(components), ...Object.entries(funcs)]
|
|
20
42
|
.map(([, plugins]) => plugins)
|
|
21
43
|
.flat();
|
|
22
44
|
const duplicateImports = new Set();
|
|
23
|
-
const pluginImports = moveRelativeDown([...
|
|
24
|
-
.map((
|
|
25
|
-
|
|
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
|
+
})
|
|
26
52
|
.filter((str) => {
|
|
27
53
|
if (duplicateImports.has(str))
|
|
28
54
|
return false;
|
|
@@ -30,46 +56,87 @@ function generateInterceptor(interceptor) {
|
|
|
30
56
|
return true;
|
|
31
57
|
})
|
|
32
58
|
.join('\n');
|
|
33
|
-
const imports =
|
|
59
|
+
const imports = [
|
|
60
|
+
...Object.entries(components).map(([component]) => `${component} as ${component}Base`),
|
|
61
|
+
...Object.entries(funcs).map(([func]) => `${func} as ${func}Base`),
|
|
62
|
+
];
|
|
34
63
|
const importInjectables = imports.length > 1
|
|
35
64
|
? `import {
|
|
36
65
|
${imports.join(',\n ')},
|
|
37
66
|
} from '${fromModule}'`
|
|
38
67
|
: `import { ${imports[0]} } from '${fromModule}'`;
|
|
39
|
-
const
|
|
40
|
-
.
|
|
68
|
+
const entries = [
|
|
69
|
+
...Object.entries(components),
|
|
70
|
+
...Object.entries(funcs),
|
|
71
|
+
];
|
|
72
|
+
const pluginExports = entries
|
|
73
|
+
.map(([base, plugins]) => {
|
|
41
74
|
const duplicateInterceptors = new Set();
|
|
42
|
-
|
|
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`;
|
|
43
83
|
const pluginStr = plugins
|
|
44
84
|
.reverse()
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}`;
|
|
49
99
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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)
|
|
56
110
|
}`;
|
|
57
|
-
|
|
111
|
+
}
|
|
112
|
+
carry = `${name(p)}Interceptor`;
|
|
58
113
|
return result;
|
|
59
114
|
})
|
|
60
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
|
+
}
|
|
61
120
|
return `
|
|
62
121
|
/**
|
|
63
|
-
* Interceptor for
|
|
122
|
+
* Interceptor for \`${isComponent ? `<${base}/>` : `${base}()`}\` with these plugins:
|
|
64
123
|
*
|
|
65
124
|
${plugins.map((p) => ` * - \`${p.plugin}\``).join('\n')}
|
|
66
125
|
*/
|
|
67
|
-
type ${
|
|
68
|
-
|
|
69
|
-
${pluginStr}
|
|
70
|
-
export const ${component} = ${carry}`;
|
|
126
|
+
${isComponent ? `type ${base}Props = ComponentProps<typeof ${base}Base>\n\n` : ``}${pluginStr}
|
|
127
|
+
export const ${base} = ${carry}`;
|
|
71
128
|
})
|
|
72
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
|
+
: '';
|
|
73
140
|
const componentExports = `export * from '${fromModule}'`;
|
|
74
141
|
const template = `/* This file is automatically generated for ${dependency} */
|
|
75
142
|
|
|
@@ -77,16 +144,16 @@ ${componentExports}
|
|
|
77
144
|
${pluginImports}
|
|
78
145
|
import { ComponentProps } from 'react'
|
|
79
146
|
${importInjectables}
|
|
80
|
-
${pluginExports}
|
|
147
|
+
${logOnce}${pluginExports}
|
|
81
148
|
`;
|
|
82
149
|
return { ...interceptor, template };
|
|
83
150
|
}
|
|
84
151
|
exports.generateInterceptor = generateInterceptor;
|
|
85
|
-
function generateInterceptors(plugins, resolve) {
|
|
152
|
+
function generateInterceptors(plugins, resolve, config) {
|
|
86
153
|
// todo: Do not use reduce as we're passing the accumulator to the next iteration
|
|
87
154
|
const byExportedComponent = moveRelativeDown(plugins).reduce((acc, plug) => {
|
|
88
|
-
const { exported,
|
|
89
|
-
if (!
|
|
155
|
+
const { exported, plugin } = plug;
|
|
156
|
+
if (!isPluginConfig(plug) || !plug.enabled)
|
|
90
157
|
return acc;
|
|
91
158
|
const resolved = resolve(exported);
|
|
92
159
|
let pluginPathFromResolved = plugin;
|
|
@@ -99,18 +166,31 @@ function generateInterceptors(plugins, resolve) {
|
|
|
99
166
|
...resolved,
|
|
100
167
|
target: `${resolved.fromRoot}.interceptor`,
|
|
101
168
|
components: {},
|
|
169
|
+
funcs: {},
|
|
102
170
|
};
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
189
|
return acc;
|
|
110
190
|
}, {});
|
|
111
191
|
return Object.fromEntries(Object.entries(byExportedComponent).map(([target, interceptor]) => [
|
|
112
192
|
target,
|
|
113
|
-
generateInterceptor(interceptor),
|
|
193
|
+
generateInterceptor(interceptor, config ?? {}),
|
|
114
194
|
]));
|
|
115
195
|
}
|
|
116
196
|
exports.generateInterceptors = generateInterceptors;
|
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": "6.
|
|
5
|
+
"version": "6.2.0-canary.6",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "src/index.ts",
|
package/src/config/demoConfig.ts
CHANGED
|
@@ -6,7 +6,12 @@ export const demoConfig: Partial<GraphCommerceConfig> & Record<string, unknown>
|
|
|
6
6
|
magentoEndpoint: 'https://backend.reachdigital.dev/graphql',
|
|
7
7
|
storefront: [
|
|
8
8
|
{ locale: 'en', magentoStoreCode: 'en_US', defaultLocale: true },
|
|
9
|
-
{
|
|
9
|
+
{
|
|
10
|
+
locale: 'nl',
|
|
11
|
+
magentoStoreCode: 'nl_NL',
|
|
12
|
+
hygraphLocales: ['nl', 'en_us'],
|
|
13
|
+
cartDisplayPricesInclTax: true,
|
|
14
|
+
},
|
|
10
15
|
{ locale: 'fr-be', magentoStoreCode: 'fr_BE', cartDisplayPricesInclTax: true },
|
|
11
16
|
{ locale: 'nl-be', magentoStoreCode: 'nl_BE', cartDisplayPricesInclTax: true },
|
|
12
17
|
{ locale: 'en-gb', magentoStoreCode: 'en_GB', cartDisplayPricesInclTax: true },
|
package/src/index.ts
CHANGED
|
@@ -9,3 +9,12 @@ export * from './config'
|
|
|
9
9
|
export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
|
|
10
10
|
Prev: React.FC<P>
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
export type ReactPlugin<T extends React.FC<any>> = (
|
|
14
|
+
props: Parameters<T>[0] & { Prev: React.FC<Parameters<T>[0]> },
|
|
15
|
+
) => ReturnType<T>
|
|
16
|
+
|
|
17
|
+
export type MethodPlugin<T extends (...args: any[]) => any> = (
|
|
18
|
+
prev: T,
|
|
19
|
+
...args: Parameters<T>
|
|
20
|
+
) => ReturnType<T>
|
|
@@ -16,7 +16,8 @@ export class InterceptorPlugin {
|
|
|
16
16
|
constructor(private config: GraphCommerceConfig) {
|
|
17
17
|
this.resolveDependency = resolveDependency()
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const [plugins, errors] = findPlugins(this.config)
|
|
20
|
+
this.interceptors = generateInterceptors(plugins, this.resolveDependency, this.config.debug)
|
|
20
21
|
this.interceptorByDepependency = Object.fromEntries(
|
|
21
22
|
Object.values(this.interceptors).map((i) => [i.dependency, i]),
|
|
22
23
|
)
|
|
@@ -29,7 +30,7 @@ export class InterceptorPlugin {
|
|
|
29
30
|
|
|
30
31
|
// After the compilation has succeeded we watch all possible plugin locations.
|
|
31
32
|
compiler.hooks.afterCompile.tap('InterceptorPlugin', (compilation) => {
|
|
32
|
-
const plugins = findPlugins(this.config)
|
|
33
|
+
const [plugins, errors] = findPlugins(this.config)
|
|
33
34
|
|
|
34
35
|
plugins.forEach((p) => {
|
|
35
36
|
const absoluteFilePath = `${path.join(
|
|
@@ -39,7 +40,7 @@ export class InterceptorPlugin {
|
|
|
39
40
|
compilation.fileDependencies.add(absoluteFilePath)
|
|
40
41
|
})
|
|
41
42
|
|
|
42
|
-
this.interceptors = generateInterceptors(plugins, this.resolveDependency)
|
|
43
|
+
this.interceptors = generateInterceptors(plugins, this.resolveDependency, this.config.debug)
|
|
43
44
|
this.interceptorByDepependency = Object.fromEntries(
|
|
44
45
|
Object.values(this.interceptors).map((i) => [i.dependency, i]),
|
|
45
46
|
)
|
|
@@ -1,36 +1,19 @@
|
|
|
1
|
-
import { Console } from 'console'
|
|
2
|
-
import { Transform } from 'stream'
|
|
3
1
|
import { parseFileSync } from '@swc/core'
|
|
4
2
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
5
5
|
import glob from 'glob'
|
|
6
6
|
import get from 'lodash/get'
|
|
7
7
|
import type { Path } from 'react-hook-form'
|
|
8
|
-
import diff from '../config/utils/diff'
|
|
9
8
|
import { GraphCommerceConfig } from '../generated/config'
|
|
10
9
|
import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
const logger = new Console({ stdout: ts })
|
|
21
|
-
logger.table(input)
|
|
22
|
-
const t = (ts.read() || '').toString()
|
|
23
|
-
let result = ''
|
|
24
|
-
for (const row of t.split(/[\r\n]+/)) {
|
|
25
|
-
let r = row.replace(/[^┬]*┬/, '┌')
|
|
26
|
-
r = r.replace(/^├─*┼/, '├')
|
|
27
|
-
r = r.replace(/│[^│]*/, '')
|
|
28
|
-
r = r.replace(/^└─*┴/, '└')
|
|
29
|
-
r = r.replace(/'/g, ' ')
|
|
30
|
-
result += `${r}\n`
|
|
31
|
-
}
|
|
32
|
-
console.log(result)
|
|
33
|
-
}
|
|
10
|
+
import {
|
|
11
|
+
isMethodPluginConfig,
|
|
12
|
+
isPluginBaseConfig,
|
|
13
|
+
isPluginConfig,
|
|
14
|
+
isReactPluginConfig,
|
|
15
|
+
PluginConfig,
|
|
16
|
+
} from './generateInterceptors'
|
|
34
17
|
|
|
35
18
|
type ParseResult = {
|
|
36
19
|
component?: string
|
|
@@ -64,51 +47,83 @@ function parseStructure(file: string): ParseResult {
|
|
|
64
47
|
return exports as ParseResult
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
|
|
50
|
+
const pluginLogs: Record<string, string> = {}
|
|
68
51
|
|
|
69
52
|
export function findPlugins(config: GraphCommerceConfig, cwd: string = process.cwd()) {
|
|
70
53
|
const dependencies = resolveDependenciesSync(cwd)
|
|
71
54
|
|
|
72
55
|
const debug = Boolean(config.debug?.pluginStatus)
|
|
73
56
|
|
|
74
|
-
|
|
75
|
-
|
|
57
|
+
const errors: string[] = []
|
|
76
58
|
const plugins: PluginConfig[] = []
|
|
77
59
|
dependencies.forEach((dependency, path) => {
|
|
78
|
-
const files = glob.sync(`${dependency}/plugins/**/*.tsx`)
|
|
60
|
+
const files = glob.sync(`${dependency}/plugins/**/*.{ts,tsx}`)
|
|
79
61
|
files.forEach((file) => {
|
|
80
62
|
try {
|
|
81
63
|
const result = parseStructure(file)
|
|
82
64
|
if (!result) return
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
plugin: file.replace(dependency, path).replace('.tsx', ''),
|
|
66
|
+
const pluginConfig = {
|
|
67
|
+
plugin: file.replace(dependency, path).replace('.tsx', '').replace('.ts', ''),
|
|
86
68
|
...result,
|
|
87
69
|
enabled: !result.ifConfig || Boolean(get(config, result.ifConfig)),
|
|
88
|
-
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!isPluginConfig(pluginConfig)) {
|
|
73
|
+
if (!isPluginBaseConfig(pluginConfig))
|
|
74
|
+
errors.push(
|
|
75
|
+
`Plugin ${file} is not a valid plugin, make it has "export const exported = '@graphcommerce/my-package"`,
|
|
76
|
+
)
|
|
77
|
+
else if (file.endsWith('.ts')) {
|
|
78
|
+
errors.push(
|
|
79
|
+
`Plugin ${file} is not a valid plugin, please define the method to create a plugin for "export const method = 'someMethod'"`,
|
|
80
|
+
)
|
|
81
|
+
} else if (file.endsWith('.tsx')) {
|
|
82
|
+
errors.push(
|
|
83
|
+
`Plugin ${file} is not a valid plugin, please define the compoennt to create a plugin for "export const component = 'SomeComponent'"`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
plugins.push(pluginConfig)
|
|
88
|
+
}
|
|
89
89
|
} catch (e) {
|
|
90
90
|
console.error(`Error parsing ${file}`, e)
|
|
91
91
|
}
|
|
92
92
|
})
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
if (debug) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
95
|
+
if (process.env.NODE_ENV === 'development' && debug) {
|
|
96
|
+
const byExported = plugins.reduce((acc, plugin) => {
|
|
97
|
+
const componentStr = isReactPluginConfig(plugin) ? plugin.component : ''
|
|
98
|
+
const funcStr = isMethodPluginConfig(plugin) ? plugin.func : ''
|
|
99
|
+
const key = `🔌 ${chalk.greenBright(
|
|
100
|
+
`Plugins loaded for ${plugin.exported}#${componentStr}${funcStr}`,
|
|
101
|
+
)}`
|
|
102
|
+
if (!acc[key]) acc[key] = []
|
|
103
|
+
acc[key].push(plugin)
|
|
104
|
+
return acc
|
|
105
|
+
}, {} as Record<string, Pick<PluginConfig, 'plugin' | 'ifConfig' | 'enabled'>[]>)
|
|
106
|
+
|
|
107
|
+
const toLog: string[] = []
|
|
108
|
+
Object.entries(byExported).forEach(([key, p]) => {
|
|
109
|
+
const logStr = p
|
|
110
|
+
.filter((c) => debug || c.enabled)
|
|
111
|
+
.map(
|
|
112
|
+
(c) =>
|
|
113
|
+
`${c.enabled ? `🟢` : `⚪️`} ${c.plugin} ${
|
|
114
|
+
c.ifConfig ? `(${c.ifConfig}: ${c.enabled ? 'true' : 'false'})` : ''
|
|
115
|
+
}`,
|
|
116
|
+
)
|
|
117
|
+
.join('\n')
|
|
118
|
+
|
|
119
|
+
if (logStr && pluginLogs[key] !== logStr) {
|
|
120
|
+
toLog.push(`${key}\n${logStr}`)
|
|
121
|
+
pluginLogs[key] = logStr
|
|
122
|
+
}
|
|
123
|
+
})
|
|
109
124
|
|
|
110
|
-
console.
|
|
125
|
+
console.log(toLog.join('\n\n'))
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
return plugins
|
|
128
|
+
return [plugins, errors] as const
|
|
114
129
|
}
|
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
+
import { GraphCommerceConfig, GraphCommerceDebugConfig } from '../generated/config'
|
|
2
3
|
import { ResolveDependency, ResolveDependencyReturn } from '../utils/resolveDependency'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exported?: string
|
|
5
|
+
type PluginBaseConfig = {
|
|
6
|
+
exported: string
|
|
7
7
|
plugin: string
|
|
8
|
-
ifConfig?: string
|
|
9
8
|
enabled: boolean
|
|
9
|
+
ifConfig?: string
|
|
10
|
+
}
|
|
11
|
+
export function isPluginBaseConfig(plugin: Partial<PluginBaseConfig>): plugin is PluginBaseConfig {
|
|
12
|
+
return (
|
|
13
|
+
typeof plugin.exported === 'string' &&
|
|
14
|
+
typeof plugin.plugin === 'string' &&
|
|
15
|
+
typeof plugin.enabled === 'boolean'
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ReactPluginConfig = PluginBaseConfig & { component: string }
|
|
20
|
+
type MethodPluginConfig = PluginBaseConfig & { func: string }
|
|
21
|
+
|
|
22
|
+
export function isReactPluginConfig(
|
|
23
|
+
plugin: Partial<PluginBaseConfig>,
|
|
24
|
+
): plugin is ReactPluginConfig {
|
|
25
|
+
if (!isPluginBaseConfig(plugin)) return false
|
|
26
|
+
return (plugin as ReactPluginConfig).component !== undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isMethodPluginConfig(
|
|
30
|
+
plugin: Partial<PluginBaseConfig>,
|
|
31
|
+
): plugin is MethodPluginConfig {
|
|
32
|
+
if (!isPluginBaseConfig(plugin)) return false
|
|
33
|
+
return (plugin as MethodPluginConfig).func !== undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PluginConfig = ReactPluginConfig | MethodPluginConfig
|
|
37
|
+
export function isPluginConfig(plugin: Partial<PluginConfig>): plugin is PluginConfig {
|
|
38
|
+
return isReactPluginConfig(plugin) || isMethodPluginConfig(plugin)
|
|
10
39
|
}
|
|
11
40
|
|
|
12
41
|
type Interceptor = ResolveDependencyReturn & {
|
|
13
|
-
components: Record<string,
|
|
42
|
+
components: Record<string, ReactPluginConfig[]>
|
|
43
|
+
funcs: Record<string, MethodPluginConfig[]>
|
|
14
44
|
target: string
|
|
15
45
|
template?: string
|
|
16
46
|
}
|
|
@@ -25,19 +55,27 @@ function moveRelativeDown(plugins: PluginConfig[]) {
|
|
|
25
55
|
})
|
|
26
56
|
}
|
|
27
57
|
|
|
28
|
-
export function generateInterceptor(
|
|
29
|
-
|
|
58
|
+
export function generateInterceptor(
|
|
59
|
+
interceptor: Interceptor,
|
|
60
|
+
config: GraphCommerceDebugConfig,
|
|
61
|
+
): MaterializedPlugin {
|
|
62
|
+
const { fromModule, dependency, components, funcs } = interceptor
|
|
30
63
|
|
|
31
|
-
const
|
|
64
|
+
const pluginConfigs = [...Object.entries(components), ...Object.entries(funcs)]
|
|
32
65
|
.map(([, plugins]) => plugins)
|
|
33
66
|
.flat()
|
|
67
|
+
|
|
34
68
|
const duplicateImports = new Set()
|
|
35
69
|
|
|
36
70
|
const pluginImports = moveRelativeDown(
|
|
37
|
-
[...
|
|
71
|
+
[...pluginConfigs].sort((a, b) => a.plugin.localeCompare(b.plugin)),
|
|
38
72
|
)
|
|
39
|
-
.map((
|
|
40
|
-
|
|
73
|
+
.map((plugin) => {
|
|
74
|
+
const { plugin: p } = plugin
|
|
75
|
+
if (isReactPluginConfig(plugin))
|
|
76
|
+
return `import { Plugin as ${p.split('/')[p.split('/').length - 1]} } from '${p}'`
|
|
77
|
+
return `import { plugin as ${p.split('/')[p.split('/').length - 1]} } from '${p}'`
|
|
78
|
+
})
|
|
41
79
|
.filter((str) => {
|
|
42
80
|
if (duplicateImports.has(str)) return false
|
|
43
81
|
duplicateImports.add(str)
|
|
@@ -45,9 +83,11 @@ export function generateInterceptor(interceptor: Interceptor): MaterializedPlugi
|
|
|
45
83
|
})
|
|
46
84
|
.join('\n')
|
|
47
85
|
|
|
48
|
-
const imports =
|
|
49
|
-
([component]) => `${component} as ${component}Base
|
|
50
|
-
|
|
86
|
+
const imports = [
|
|
87
|
+
...Object.entries(components).map(([component]) => `${component} as ${component}Base`),
|
|
88
|
+
...Object.entries(funcs).map(([func]) => `${func} as ${func}Base`),
|
|
89
|
+
]
|
|
90
|
+
|
|
51
91
|
const importInjectables =
|
|
52
92
|
imports.length > 1
|
|
53
93
|
? `import {
|
|
@@ -55,43 +95,89 @@ export function generateInterceptor(interceptor: Interceptor): MaterializedPlugi
|
|
|
55
95
|
} from '${fromModule}'`
|
|
56
96
|
: `import { ${imports[0]} } from '${fromModule}'`
|
|
57
97
|
|
|
58
|
-
const
|
|
59
|
-
.
|
|
98
|
+
const entries: [string, PluginConfig[]][] = [
|
|
99
|
+
...Object.entries(components),
|
|
100
|
+
...Object.entries(funcs),
|
|
101
|
+
]
|
|
102
|
+
const pluginExports = entries
|
|
103
|
+
.map(([base, plugins]) => {
|
|
60
104
|
const duplicateInterceptors = new Set()
|
|
105
|
+
const name = (p: PluginConfig) => p.plugin.split('/')[p.plugin.split('/').length - 1]
|
|
106
|
+
|
|
107
|
+
const filterNoDuplicate = (p: PluginConfig) => {
|
|
108
|
+
if (duplicateInterceptors.has(name(p))) return false
|
|
109
|
+
duplicateInterceptors.add(name(p))
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let carry = `${base}Base`
|
|
61
114
|
|
|
62
|
-
let carry = `${component}Base`
|
|
63
115
|
const pluginStr = plugins
|
|
64
116
|
.reverse()
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
.filter(filterNoDuplicate)
|
|
118
|
+
.map((p) => {
|
|
119
|
+
let result
|
|
120
|
+
|
|
121
|
+
if (isReactPluginConfig(p)) {
|
|
122
|
+
const wrapChain = plugins
|
|
123
|
+
.reverse()
|
|
124
|
+
.map((pl) => `<${name(pl)}/>`)
|
|
125
|
+
.join(' wrapping ')
|
|
126
|
+
const debugLog =
|
|
127
|
+
carry === `${base}Base` && config.pluginStatus
|
|
128
|
+
? `\n logInterceptor(\`🔌 Rendering ${base} with plugin(s): ${wrapChain} wrapping <${base}/>\`)`
|
|
129
|
+
: ''
|
|
130
|
+
|
|
131
|
+
result = `function ${name(p)}Interceptor(props: ${base}Props) {${debugLog}
|
|
132
|
+
return <${name(p)} {...props} Prev={${carry}} />
|
|
76
133
|
}`
|
|
77
|
-
|
|
134
|
+
} else {
|
|
135
|
+
const wrapChain = plugins
|
|
136
|
+
.reverse()
|
|
137
|
+
.map((pl) => `${name(pl)}()`)
|
|
138
|
+
.join(' wrapping ')
|
|
139
|
+
|
|
140
|
+
const debugLog =
|
|
141
|
+
carry === `${base}Base` && config.pluginStatus
|
|
142
|
+
? `\n logInterceptor(\`🔌 Calling ${base} with plugin(s): ${wrapChain} wrapping ${base}()\`)`
|
|
143
|
+
: ''
|
|
144
|
+
|
|
145
|
+
result = `const ${name(p)}Interceptor: typeof ${base}Base = (...args) => {${debugLog}
|
|
146
|
+
return ${name(p)}(${carry}, ...args)
|
|
147
|
+
}`
|
|
148
|
+
}
|
|
149
|
+
carry = `${name(p)}Interceptor`
|
|
78
150
|
return result
|
|
79
151
|
})
|
|
80
152
|
.join('\n')
|
|
81
153
|
|
|
154
|
+
const isComponent = plugins.every((p) => isReactPluginConfig(p))
|
|
155
|
+
if (isComponent && plugins.some((p) => isMethodPluginConfig(p))) {
|
|
156
|
+
throw new Error(`Cannot mix React and Method plugins for ${base} in ${dependency}.`)
|
|
157
|
+
}
|
|
158
|
+
|
|
82
159
|
return `
|
|
83
160
|
/**
|
|
84
|
-
* Interceptor for
|
|
161
|
+
* Interceptor for \`${isComponent ? `<${base}/>` : `${base}()`}\` with these plugins:
|
|
85
162
|
*
|
|
86
163
|
${plugins.map((p) => ` * - \`${p.plugin}\``).join('\n')}
|
|
87
164
|
*/
|
|
88
|
-
type ${
|
|
89
|
-
|
|
90
|
-
${pluginStr}
|
|
91
|
-
export const ${component} = ${carry}`
|
|
165
|
+
${isComponent ? `type ${base}Props = ComponentProps<typeof ${base}Base>\n\n` : ``}${pluginStr}
|
|
166
|
+
export const ${base} = ${carry}`
|
|
92
167
|
})
|
|
93
168
|
.join('\n')
|
|
94
169
|
|
|
170
|
+
const logOnce = config.pluginStatus
|
|
171
|
+
? `
|
|
172
|
+
const logged: Set<string> = new Set();
|
|
173
|
+
const logInterceptor = (log: string, ...additional: unknown[]) => {
|
|
174
|
+
if (logged.has(log)) return
|
|
175
|
+
logged.add(log)
|
|
176
|
+
console.log(log, ...additional)
|
|
177
|
+
}
|
|
178
|
+
`
|
|
179
|
+
: ''
|
|
180
|
+
|
|
95
181
|
const componentExports = `export * from '${fromModule}'`
|
|
96
182
|
|
|
97
183
|
const template = `/* This file is automatically generated for ${dependency} */
|
|
@@ -100,7 +186,7 @@ ${componentExports}
|
|
|
100
186
|
${pluginImports}
|
|
101
187
|
import { ComponentProps } from 'react'
|
|
102
188
|
${importInjectables}
|
|
103
|
-
${pluginExports}
|
|
189
|
+
${logOnce}${pluginExports}
|
|
104
190
|
`
|
|
105
191
|
|
|
106
192
|
return { ...interceptor, template }
|
|
@@ -111,11 +197,12 @@ export type GenerateInterceptorsReturn = Record<string, MaterializedPlugin>
|
|
|
111
197
|
export function generateInterceptors(
|
|
112
198
|
plugins: PluginConfig[],
|
|
113
199
|
resolve: ResolveDependency,
|
|
200
|
+
config?: GraphCommerceDebugConfig | null | undefined,
|
|
114
201
|
): GenerateInterceptorsReturn {
|
|
115
202
|
// todo: Do not use reduce as we're passing the accumulator to the next iteration
|
|
116
203
|
const byExportedComponent = moveRelativeDown(plugins).reduce((acc, plug) => {
|
|
117
|
-
const { exported,
|
|
118
|
-
if (!
|
|
204
|
+
const { exported, plugin } = plug
|
|
205
|
+
if (!isPluginConfig(plug) || !plug.enabled) return acc
|
|
119
206
|
|
|
120
207
|
const resolved = resolve(exported)
|
|
121
208
|
|
|
@@ -133,15 +220,28 @@ export function generateInterceptors(
|
|
|
133
220
|
...resolved,
|
|
134
221
|
target: `${resolved.fromRoot}.interceptor`,
|
|
135
222
|
components: {},
|
|
223
|
+
funcs: {},
|
|
136
224
|
} as Interceptor
|
|
137
225
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
226
|
+
if (isReactPluginConfig(plug)) {
|
|
227
|
+
const { component } = plug
|
|
228
|
+
if (!acc[resolved.fromRoot].components[component])
|
|
229
|
+
acc[resolved.fromRoot].components[component] = []
|
|
140
230
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
231
|
+
acc[resolved.fromRoot].components[component].push({
|
|
232
|
+
...plug,
|
|
233
|
+
plugin: pluginPathFromResolved,
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
if (isMethodPluginConfig(plug)) {
|
|
237
|
+
const { func } = plug
|
|
238
|
+
if (!acc[resolved.fromRoot].funcs[func]) acc[resolved.fromRoot].funcs[func] = []
|
|
239
|
+
|
|
240
|
+
acc[resolved.fromRoot].funcs[func].push({
|
|
241
|
+
...plug,
|
|
242
|
+
plugin: pluginPathFromResolved,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
145
245
|
|
|
146
246
|
return acc
|
|
147
247
|
}, {} as Record<string, Interceptor>)
|
|
@@ -149,7 +249,7 @@ export function generateInterceptors(
|
|
|
149
249
|
return Object.fromEntries(
|
|
150
250
|
Object.entries(byExportedComponent).map(([target, interceptor]) => [
|
|
151
251
|
target,
|
|
152
|
-
generateInterceptor(interceptor),
|
|
252
|
+
generateInterceptor(interceptor, config ?? {}),
|
|
153
253
|
]),
|
|
154
254
|
)
|
|
155
255
|
}
|
package/src/withGraphCommerce.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import CircularDependencyPlugin from 'circular-dependency-plugin'
|
|
2
2
|
import { DuplicatesPlugin } from 'inspectpack/plugin'
|
|
3
3
|
import type { NextConfig } from 'next'
|
|
4
|
-
import { Redirect, Rewrite } from 'next/dist/lib/load-custom-routes'
|
|
5
4
|
import { DomainLocale } from 'next/dist/server/config'
|
|
6
5
|
import { RemotePattern } from 'next/dist/shared/lib/image-config'
|
|
7
|
-
import { DefinePlugin, Configuration
|
|
6
|
+
import { DefinePlugin, Configuration } from 'webpack'
|
|
8
7
|
import { loadConfig } from './config/loadConfig'
|
|
9
8
|
import { configToImportMeta } from './config/utils/configToImportMeta'
|
|
10
9
|
import { GraphCommerceConfig } from './generated/config'
|
|
@@ -134,7 +133,7 @@ export function withGraphCommerce(nextConfig: NextConfig, cwd: string): NextConf
|
|
|
134
133
|
config.plugins.push(
|
|
135
134
|
new CircularDependencyPlugin({
|
|
136
135
|
exclude: /readable-stream|duplexer2|node_modules\/next/,
|
|
137
|
-
})
|
|
136
|
+
}),
|
|
138
137
|
)
|
|
139
138
|
}
|
|
140
139
|
if (graphcommerceConfig.debug?.webpackDuplicatesPlugin) {
|