@graphcommerce/next-config 4.30.0 → 4.31.0-canary.1
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/dist/index.d.ts +4 -0
- package/dist/interceptors/InterceptorPlugin.d.ts +9 -0
- package/dist/interceptors/InterceptorPlugin.js +62 -0
- package/dist/interceptors/findPlugins.d.ts +2 -0
- package/dist/interceptors/findPlugins.js +73 -0
- package/dist/interceptors/generateInterceptors.d.ts +18 -0
- package/dist/interceptors/generateInterceptors.js +87 -0
- package/dist/interceptors/rmInterceptors.d.ts +1 -0
- package/dist/interceptors/rmInterceptors.js +22 -0
- package/dist/interceptors/writeInterceptors.d.ts +2 -0
- package/dist/interceptors/writeInterceptors.js +19 -0
- package/dist/utils/resolveDependenciesSync.js +7 -1
- package/dist/utils/resolveDependency.d.ts +8 -0
- package/dist/utils/resolveDependency.js +24 -0
- package/dist/withGraphCommerce.js +3 -1
- package/package.json +3 -2
- package/src/index.ts +5 -0
- package/src/interceptors/InterceptorPlugin.ts +76 -0
- package/src/interceptors/findPlugins.ts +72 -0
- package/src/interceptors/generateInterceptors.ts +123 -0
- package/src/interceptors/rmInterceptors.ts +17 -0
- package/src/interceptors/writeInterceptors.ts +20 -0
- package/src/utils/resolveDependenciesSync.ts +7 -1
- package/src/utils/resolveDependency.ts +32 -0
- package/src/withGraphCommerce.ts +4 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { NextConfig } from 'next/dist/server/config-shared';
|
|
2
|
+
import React from 'react';
|
|
2
3
|
export * from './utils/isMonorepo';
|
|
3
4
|
export * from './utils/resolveDependenciesSync';
|
|
4
5
|
export * from './withGraphCommerce';
|
|
5
6
|
export declare function withYarn1Workspaces(packages?: string[]): (config: NextConfig) => NextConfig;
|
|
6
7
|
export declare function withYarn1Scopes(packages?: string[]): (config: NextConfig) => NextConfig;
|
|
8
|
+
export declare type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
|
|
9
|
+
Component: React.FC<P>;
|
|
10
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
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.InterceptorPlugin = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const resolveDependency_1 = require("../utils/resolveDependency");
|
|
9
|
+
const findPlugins_1 = require("./findPlugins");
|
|
10
|
+
const generateInterceptors_1 = require("./generateInterceptors");
|
|
11
|
+
const writeInterceptors_1 = require("./writeInterceptors");
|
|
12
|
+
class InterceptorPlugin {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.watched = [];
|
|
15
|
+
this.resolveDependency = (0, resolveDependency_1.resolveDependency)();
|
|
16
|
+
this.watched = this.watchList();
|
|
17
|
+
this.interceptors = (0, generateInterceptors_1.generateInterceptors)((0, findPlugins_1.findPlugins)(), this.resolveDependency);
|
|
18
|
+
(0, writeInterceptors_1.writeInterceptors)(this.interceptors);
|
|
19
|
+
}
|
|
20
|
+
watchList() {
|
|
21
|
+
return [
|
|
22
|
+
...new Set((0, findPlugins_1.findPlugins)()
|
|
23
|
+
.map((p) => this.resolveDependency(p.plugin))
|
|
24
|
+
.map((p) => p.fromRoot)),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
apply(compiler) {
|
|
28
|
+
const logger = compiler.getInfrastructureLogger('InterceptorPlugin');
|
|
29
|
+
// After the compilation has succeeded we watch all possible plugin locations.
|
|
30
|
+
compiler.hooks.afterCompile.tap('InterceptorPlugin', (compilation) => {
|
|
31
|
+
const watchList = this.watchList();
|
|
32
|
+
const added = watchList.filter((d) => !this.watched.includes(d));
|
|
33
|
+
const removed = this.watched.filter((d) => !watchList.includes(d));
|
|
34
|
+
this.watched = watchList;
|
|
35
|
+
if (added.length > 0) {
|
|
36
|
+
added.forEach((context) => compilation.contextDependencies.add(context));
|
|
37
|
+
}
|
|
38
|
+
if (removed.length > 0) {
|
|
39
|
+
removed.forEach((context) => compilation.contextDependencies.delete(context));
|
|
40
|
+
}
|
|
41
|
+
if (added.length > 0 || removed.length > 0) {
|
|
42
|
+
this.interceptors = (0, generateInterceptors_1.generateInterceptors)((0, findPlugins_1.findPlugins)(), this.resolveDependency);
|
|
43
|
+
(0, writeInterceptors_1.writeInterceptors)(this.interceptors);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
compiler.hooks.normalModuleFactory.tap('InterceptorPlugin', (nmf) => {
|
|
47
|
+
nmf.hooks.beforeResolve.tap('InterceptorPlugin', (resource) => {
|
|
48
|
+
const issuer = resource.contextInfo.issuer ?? '';
|
|
49
|
+
const requestPath = path_1.default.relative(process.cwd(), path_1.default.resolve(resource.context, resource.request));
|
|
50
|
+
if (issuer.endsWith('interceptor.tsx') && this.interceptors[requestPath]) {
|
|
51
|
+
logger.log(`Interceptor ${issuer} is requesting the original ${requestPath}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.interceptors[requestPath]) {
|
|
55
|
+
logger.log(`Intercepting... ${this.interceptors[requestPath].fromRoot}}`);
|
|
56
|
+
resource.request = `${resource.request}.interceptor.tsx`;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.InterceptorPlugin = InterceptorPlugin;
|
|
@@ -0,0 +1,73 @@
|
|
|
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.findPlugins = void 0;
|
|
7
|
+
const core_1 = require("@swc/core");
|
|
8
|
+
const glob_1 = __importDefault(require("glob"));
|
|
9
|
+
const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
|
|
10
|
+
function parseStructure(file) {
|
|
11
|
+
const ast = (0, core_1.parseFileSync)(file, { syntax: 'typescript', tsx: true });
|
|
12
|
+
// const ast = swc.parseFileSync(file, { syntax: 'typescript' })
|
|
13
|
+
const imports = {};
|
|
14
|
+
const exports = {};
|
|
15
|
+
ast.body.forEach((node) => {
|
|
16
|
+
switch (node.type) {
|
|
17
|
+
case 'ImportDeclaration':
|
|
18
|
+
node.specifiers.forEach((s) => {
|
|
19
|
+
if (s.type === 'ImportSpecifier') {
|
|
20
|
+
imports[s.local.value] = node.source.value;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
break;
|
|
24
|
+
case 'ExportDeclaration':
|
|
25
|
+
switch (node.declaration.type) {
|
|
26
|
+
case 'VariableDeclaration':
|
|
27
|
+
node.declaration.declarations.forEach((declaration) => {
|
|
28
|
+
if (declaration.id.type !== 'Identifier')
|
|
29
|
+
return;
|
|
30
|
+
switch (declaration.init?.type) {
|
|
31
|
+
case 'StringLiteral':
|
|
32
|
+
exports[declaration.id.value] = declaration.init.value;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
break;
|
|
37
|
+
case 'FunctionDeclaration':
|
|
38
|
+
if (node.declaration.type === 'FunctionDeclaration') {
|
|
39
|
+
// console.log('func', node.declaration)
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
console.log('unknown', node.declaration);
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
// default:
|
|
47
|
+
// console.log('hallo', node)
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return exports.component && exports.exported
|
|
51
|
+
? exports
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
function findPlugins(cwd = process.cwd()) {
|
|
55
|
+
const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
|
|
56
|
+
const plugins = [];
|
|
57
|
+
dependencies.forEach((dependency, path) => {
|
|
58
|
+
const files = glob_1.default.sync(`${dependency}/plugins/**/*.tsx`);
|
|
59
|
+
files.forEach((file) => {
|
|
60
|
+
try {
|
|
61
|
+
const result = parseStructure(file);
|
|
62
|
+
if (!result)
|
|
63
|
+
return;
|
|
64
|
+
plugins.push({ ...result, plugin: file.replace(dependency, path).replace('.tsx', '') });
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
console.error(`Error parsing ${file}`, e);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return plugins;
|
|
72
|
+
}
|
|
73
|
+
exports.findPlugins = findPlugins;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ResolveDependency, ResolveDependencyReturn } from '../utils/resolveDependency';
|
|
2
|
+
export declare type PluginConfig = {
|
|
3
|
+
component: string;
|
|
4
|
+
exported: string;
|
|
5
|
+
plugin: string;
|
|
6
|
+
};
|
|
7
|
+
declare type Plugin = ResolveDependencyReturn & {
|
|
8
|
+
components: Record<string, PluginConfig[]>;
|
|
9
|
+
target: string;
|
|
10
|
+
template?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare type MaterializedPlugin = Plugin & {
|
|
13
|
+
template: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function generateInterceptor(plugin: Plugin): MaterializedPlugin;
|
|
16
|
+
export declare type GenerateInterceptorsReturn = Record<string, MaterializedPlugin>;
|
|
17
|
+
export declare function generateInterceptors(plugins: PluginConfig[], resolve: ResolveDependency): GenerateInterceptorsReturn;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateInterceptors = exports.generateInterceptor = void 0;
|
|
4
|
+
function generateInterceptor(plugin) {
|
|
5
|
+
const { fromModule, components } = plugin;
|
|
6
|
+
const pluginImports = Object.entries(components)
|
|
7
|
+
.map(([_, plugins]) => {
|
|
8
|
+
const duplicateImports = new Set();
|
|
9
|
+
return plugins
|
|
10
|
+
.sort((a, b) => a.plugin.localeCompare(b.plugin))
|
|
11
|
+
.map((p) => `import { Plugin as ${p.plugin.split('/')[p.plugin.split('/').length - 1]} } from '${p.plugin}'`)
|
|
12
|
+
.filter((importStr) => {
|
|
13
|
+
if (duplicateImports.has(importStr)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
duplicateImports.add(importStr);
|
|
17
|
+
return true;
|
|
18
|
+
})
|
|
19
|
+
.join('\n');
|
|
20
|
+
})
|
|
21
|
+
.join('\n');
|
|
22
|
+
const importInjectables = `import { ${Object.entries(components)
|
|
23
|
+
.map(([component]) => `${component} as ${component}Base`)
|
|
24
|
+
.join(', ')} } from '${fromModule}'`;
|
|
25
|
+
const pluginExports = Object.entries(components)
|
|
26
|
+
.map(([component, plugins]) => {
|
|
27
|
+
const duplicateImports = new Set();
|
|
28
|
+
let carry = `${component}Base`;
|
|
29
|
+
const pluginStr = plugins
|
|
30
|
+
.reverse()
|
|
31
|
+
.map((p) => p.plugin.split('/')[p.plugin.split('/').length - 1])
|
|
32
|
+
.filter((importStr) => {
|
|
33
|
+
if (duplicateImports.has(importStr)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
duplicateImports.add(importStr);
|
|
37
|
+
return true;
|
|
38
|
+
})
|
|
39
|
+
.map((name) => {
|
|
40
|
+
const result = `const ${name}Interceptor = (props: ${component}BaseProps) => (
|
|
41
|
+
<${name} {...props} Component={${carry}} />
|
|
42
|
+
)`;
|
|
43
|
+
carry = `${name}Interceptor`;
|
|
44
|
+
return result;
|
|
45
|
+
})
|
|
46
|
+
.join('\n');
|
|
47
|
+
return `type ${component}BaseProps = React.ComponentProps<typeof ${component}Base>
|
|
48
|
+
|
|
49
|
+
${pluginStr}
|
|
50
|
+
export const ${component} = ${carry}`;
|
|
51
|
+
})
|
|
52
|
+
.join('\n');
|
|
53
|
+
const componentExports = `export * from '${fromModule}'`;
|
|
54
|
+
const template = `/* This file is automatically generated. */
|
|
55
|
+
|
|
56
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
57
|
+
/* eslint-disable import/export */
|
|
58
|
+
|
|
59
|
+
${pluginImports}
|
|
60
|
+
${importInjectables}
|
|
61
|
+
|
|
62
|
+
${componentExports}
|
|
63
|
+
|
|
64
|
+
${pluginExports}
|
|
65
|
+
`;
|
|
66
|
+
return { ...plugin, template };
|
|
67
|
+
}
|
|
68
|
+
exports.generateInterceptor = generateInterceptor;
|
|
69
|
+
function generateInterceptors(plugins, resolve) {
|
|
70
|
+
// todo: Do not use reduce as we're passing the accumulator to the next iteration
|
|
71
|
+
const byExportedComponent = plugins.reduce((acc, plug) => {
|
|
72
|
+
const { exported } = plug;
|
|
73
|
+
const resolved = resolve(exported);
|
|
74
|
+
if (!acc[resolved.fromRoot])
|
|
75
|
+
acc[resolved.fromRoot] = {
|
|
76
|
+
...resolved,
|
|
77
|
+
target: `${resolved.fromRoot}.interceptor`,
|
|
78
|
+
components: {},
|
|
79
|
+
};
|
|
80
|
+
if (!acc[resolved.fromRoot].components[plug.component])
|
|
81
|
+
acc[resolved.fromRoot].components[plug.component] = [];
|
|
82
|
+
acc[resolved.fromRoot].components[plug.component].push(plug);
|
|
83
|
+
return acc;
|
|
84
|
+
}, {});
|
|
85
|
+
return Object.fromEntries(Object.entries(byExportedComponent).map(([target, plg]) => [target, generateInterceptor(plg)]));
|
|
86
|
+
}
|
|
87
|
+
exports.generateInterceptors = generateInterceptors;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function rmInterceptors(cwd?: string): string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
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.rmInterceptors = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const glob_1 = __importDefault(require("glob"));
|
|
9
|
+
const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
|
|
10
|
+
function rmInterceptors(cwd = process.cwd()) {
|
|
11
|
+
const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
|
|
12
|
+
const removed = [];
|
|
13
|
+
dependencies.forEach((dependency) => {
|
|
14
|
+
const files = glob_1.default.sync(`${dependency}/**/*.interceptor.tsx`, { cwd });
|
|
15
|
+
files.forEach((file) => {
|
|
16
|
+
node_fs_1.default.unlinkSync(file);
|
|
17
|
+
removed.push(file);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
return removed;
|
|
21
|
+
}
|
|
22
|
+
exports.rmInterceptors = rmInterceptors;
|
|
@@ -0,0 +1,19 @@
|
|
|
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.writeInterceptors = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function writeInterceptors(interceptors, cwd = process.cwd()) {
|
|
10
|
+
Object.entries(interceptors).forEach(([target, plugin]) => {
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
const fileToWrite = `${path_1.default.join(cwd, plugin.fromRoot)}.interceptor.tsx`;
|
|
13
|
+
if (!node_fs_1.default.existsSync(fileToWrite) ||
|
|
14
|
+
node_fs_1.default.readFileSync(fileToWrite, 'utf8').toString() !== plugin.template) {
|
|
15
|
+
node_fs_1.default.writeFileSync(fileToWrite, plugin.template);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
exports.writeInterceptors = writeInterceptors;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.resolveDependenciesSync = void 0;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const resolveCache = new Map();
|
|
9
10
|
function resolveRecursivePackageJson(packageJsonFilename, packageNames) {
|
|
10
11
|
try {
|
|
11
12
|
const packageJsonFile = node_fs_1.default.readFileSync(packageJsonFilename, 'utf-8').toString();
|
|
@@ -52,6 +53,11 @@ function resolveRecursivePackageJson(packageJsonFilename, packageNames) {
|
|
|
52
53
|
* and stop there, not checking children.
|
|
53
54
|
*/
|
|
54
55
|
function resolveDependenciesSync(root = process.cwd()) {
|
|
55
|
-
|
|
56
|
+
const cached = resolveCache.get(root);
|
|
57
|
+
if (cached)
|
|
58
|
+
return cached;
|
|
59
|
+
const result = resolveRecursivePackageJson(node_path_1.default.join(root, 'package.json'), new Map());
|
|
60
|
+
resolveCache.set(root, result);
|
|
61
|
+
return result;
|
|
56
62
|
}
|
|
57
63
|
exports.resolveDependenciesSync = resolveDependenciesSync;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare type ResolveDependencyReturn = {
|
|
2
|
+
dependency: string;
|
|
3
|
+
root: string;
|
|
4
|
+
fromRoot: string;
|
|
5
|
+
fromModule: string;
|
|
6
|
+
};
|
|
7
|
+
export declare type ResolveDependency = (req: string) => ResolveDependencyReturn;
|
|
8
|
+
export declare const resolveDependency: (cwd?: string) => (dependency: string) => ResolveDependencyReturn;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveDependency = void 0;
|
|
4
|
+
const resolveDependenciesSync_1 = require("./resolveDependenciesSync");
|
|
5
|
+
const resolveDependency = (cwd = process.cwd()) => {
|
|
6
|
+
const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
|
|
7
|
+
return (dependency) => {
|
|
8
|
+
let dependencyPaths = { root: '.', dependency, fromRoot: dependency, fromModule: dependency };
|
|
9
|
+
dependencies.forEach((root, depCandidate) => {
|
|
10
|
+
if (dependency.startsWith(depCandidate)) {
|
|
11
|
+
const relative = dependency.replace(depCandidate, '');
|
|
12
|
+
const fromRoot = dependency.replace(depCandidate, root);
|
|
13
|
+
dependencyPaths = {
|
|
14
|
+
root,
|
|
15
|
+
dependency,
|
|
16
|
+
fromRoot,
|
|
17
|
+
fromModule: !relative ? '.' : `./${relative.split('/')[relative.split('/').length - 1]}`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return dependencyPaths;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
exports.resolveDependency = resolveDependency;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.withGraphCommerce = void 0;
|
|
7
7
|
const next_transpile_modules_1 = __importDefault(require("next-transpile-modules"));
|
|
8
8
|
const webpack_1 = require("webpack");
|
|
9
|
+
const InterceptorPlugin_1 = require("./interceptors/InterceptorPlugin");
|
|
9
10
|
const resolveDependenciesSync_1 = require("./utils/resolveDependenciesSync");
|
|
10
11
|
function extendConfig(nextConfig, modules) {
|
|
11
12
|
return {
|
|
@@ -42,13 +43,14 @@ function extendConfig(nextConfig, modules) {
|
|
|
42
43
|
'@mui/styled-engine': '@mui/styled-engine/modern',
|
|
43
44
|
'@mui/system': '@mui/system/modern',
|
|
44
45
|
};
|
|
46
|
+
config.plugins = [...(config.plugins ?? []), new InterceptorPlugin_1.InterceptorPlugin()];
|
|
45
47
|
return typeof nextConfig.webpack === 'function' ? nextConfig.webpack(config, options) : config;
|
|
46
48
|
},
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
function withGraphCommerce(conf = {}) {
|
|
50
52
|
const { packages = [] } = conf;
|
|
51
|
-
const dependencies = [...(0, resolveDependenciesSync_1.resolveDependenciesSync)().keys()];
|
|
53
|
+
const dependencies = [...(0, resolveDependenciesSync_1.resolveDependenciesSync)().keys()].slice(1);
|
|
52
54
|
const modules = [...dependencies, ...packages];
|
|
53
55
|
return (config) => extendConfig((0, next_transpile_modules_1.default)(modules)(config), modules);
|
|
54
56
|
}
|
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": "4.
|
|
5
|
+
"version": "4.31.0-canary.1",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"typings": "dist/index.d.ts",
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
"prepack": "yarn build"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@graphcommerce/cli": "4.
|
|
16
|
+
"@graphcommerce/cli": "4.31.0-canary.1",
|
|
17
17
|
"@lingui/loader": "^3.14.0",
|
|
18
|
+
"@swc/core": "^1.3.10",
|
|
18
19
|
"js-yaml-loader": "^1.2.2",
|
|
19
20
|
"next-transpile-modules": "^9.0.0",
|
|
20
21
|
"webpack": "^5.73.0"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextConfig } from 'next/dist/server/config-shared'
|
|
2
|
+
import React from 'react'
|
|
2
3
|
import { withGraphCommerce } from './withGraphCommerce'
|
|
3
4
|
|
|
4
5
|
export * from './utils/isMonorepo'
|
|
@@ -12,3 +13,7 @@ export function withYarn1Workspaces(packages: string[] = []): (config: NextConfi
|
|
|
12
13
|
export function withYarn1Scopes(packages?: string[]): (config: NextConfig) => NextConfig {
|
|
13
14
|
return withGraphCommerce({ packages })
|
|
14
15
|
}
|
|
16
|
+
|
|
17
|
+
export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
|
|
18
|
+
Component: React.FC<P>
|
|
19
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { Compiler } from 'webpack'
|
|
3
|
+
import { ResolveDependency, resolveDependency } from '../utils/resolveDependency'
|
|
4
|
+
import { findPlugins } from './findPlugins'
|
|
5
|
+
import { generateInterceptors, GenerateInterceptorsReturn } from './generateInterceptors'
|
|
6
|
+
import { writeInterceptors } from './writeInterceptors'
|
|
7
|
+
|
|
8
|
+
export class InterceptorPlugin {
|
|
9
|
+
private interceptors: GenerateInterceptorsReturn
|
|
10
|
+
|
|
11
|
+
private resolveDependency: ResolveDependency
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.resolveDependency = resolveDependency()
|
|
15
|
+
|
|
16
|
+
this.watched = this.watchList()
|
|
17
|
+
this.interceptors = generateInterceptors(findPlugins(), this.resolveDependency)
|
|
18
|
+
writeInterceptors(this.interceptors)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private watched: string[] = []
|
|
22
|
+
|
|
23
|
+
watchList() {
|
|
24
|
+
return [
|
|
25
|
+
...new Set(
|
|
26
|
+
findPlugins()
|
|
27
|
+
.map((p) => this.resolveDependency(p.plugin))
|
|
28
|
+
.map((p) => p.fromRoot),
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
apply(compiler: Compiler): void {
|
|
34
|
+
const logger = compiler.getInfrastructureLogger('InterceptorPlugin')
|
|
35
|
+
|
|
36
|
+
// After the compilation has succeeded we watch all possible plugin locations.
|
|
37
|
+
compiler.hooks.afterCompile.tap('InterceptorPlugin', (compilation) => {
|
|
38
|
+
const watchList = this.watchList()
|
|
39
|
+
const added = watchList.filter((d) => !this.watched.includes(d))
|
|
40
|
+
const removed = this.watched.filter((d) => !watchList.includes(d))
|
|
41
|
+
this.watched = watchList
|
|
42
|
+
|
|
43
|
+
if (added.length > 0) {
|
|
44
|
+
added.forEach((context) => compilation.contextDependencies.add(context))
|
|
45
|
+
}
|
|
46
|
+
if (removed.length > 0) {
|
|
47
|
+
removed.forEach((context) => compilation.contextDependencies.delete(context))
|
|
48
|
+
}
|
|
49
|
+
if (added.length > 0 || removed.length > 0) {
|
|
50
|
+
this.interceptors = generateInterceptors(findPlugins(), this.resolveDependency)
|
|
51
|
+
writeInterceptors(this.interceptors)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
compiler.hooks.normalModuleFactory.tap('InterceptorPlugin', (nmf) => {
|
|
56
|
+
nmf.hooks.beforeResolve.tap('InterceptorPlugin', (resource) => {
|
|
57
|
+
const issuer = resource.contextInfo.issuer ?? ''
|
|
58
|
+
|
|
59
|
+
const requestPath = path.relative(
|
|
60
|
+
process.cwd(),
|
|
61
|
+
path.resolve(resource.context, resource.request),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if (issuer.endsWith('interceptor.tsx') && this.interceptors[requestPath]) {
|
|
65
|
+
logger.log(`Interceptor ${issuer} is requesting the original ${requestPath}`)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.interceptors[requestPath]) {
|
|
70
|
+
logger.log(`Intercepting... ${this.interceptors[requestPath].fromRoot}}`)
|
|
71
|
+
resource.request = `${resource.request}.interceptor.tsx`
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseFileSync } from '@swc/core'
|
|
2
|
+
import glob from 'glob'
|
|
3
|
+
import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
|
|
4
|
+
import type { PluginConfig } from './generateInterceptors'
|
|
5
|
+
|
|
6
|
+
function parseStructure(file: string): { component: string; exported: string } | undefined {
|
|
7
|
+
const ast = parseFileSync(file, { syntax: 'typescript', tsx: true })
|
|
8
|
+
// const ast = swc.parseFileSync(file, { syntax: 'typescript' })
|
|
9
|
+
|
|
10
|
+
const imports: Record<string, string> = {}
|
|
11
|
+
const exports: Record<string, string> = {}
|
|
12
|
+
|
|
13
|
+
ast.body.forEach((node) => {
|
|
14
|
+
switch (node.type) {
|
|
15
|
+
case 'ImportDeclaration':
|
|
16
|
+
node.specifiers.forEach((s) => {
|
|
17
|
+
if (s.type === 'ImportSpecifier') {
|
|
18
|
+
imports[s.local.value] = node.source.value
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
break
|
|
22
|
+
case 'ExportDeclaration':
|
|
23
|
+
switch (node.declaration.type) {
|
|
24
|
+
case 'VariableDeclaration':
|
|
25
|
+
node.declaration.declarations.forEach((declaration) => {
|
|
26
|
+
if (declaration.id.type !== 'Identifier') return
|
|
27
|
+
|
|
28
|
+
switch (declaration.init?.type) {
|
|
29
|
+
case 'StringLiteral':
|
|
30
|
+
exports[declaration.id.value] = declaration.init.value
|
|
31
|
+
break
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
break
|
|
35
|
+
case 'FunctionDeclaration':
|
|
36
|
+
if (node.declaration.type === 'FunctionDeclaration') {
|
|
37
|
+
// console.log('func', node.declaration)
|
|
38
|
+
}
|
|
39
|
+
break
|
|
40
|
+
default:
|
|
41
|
+
console.log('unknown', node.declaration)
|
|
42
|
+
}
|
|
43
|
+
break
|
|
44
|
+
// default:
|
|
45
|
+
// console.log('hallo', node)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return exports.component && exports.exported
|
|
50
|
+
? (exports as { component: string; exported: string })
|
|
51
|
+
: undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function findPlugins(cwd: string = process.cwd()) {
|
|
55
|
+
const dependencies = resolveDependenciesSync(cwd)
|
|
56
|
+
|
|
57
|
+
const plugins: PluginConfig[] = []
|
|
58
|
+
dependencies.forEach((dependency, path) => {
|
|
59
|
+
const files = glob.sync(`${dependency}/plugins/**/*.tsx`)
|
|
60
|
+
files.forEach((file) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = parseStructure(file)
|
|
63
|
+
if (!result) return
|
|
64
|
+
|
|
65
|
+
plugins.push({ ...result, plugin: file.replace(dependency, path).replace('.tsx', '') })
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error(`Error parsing ${file}`, e)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
return plugins
|
|
72
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ResolveDependency, ResolveDependencyReturn } from '../utils/resolveDependency'
|
|
2
|
+
|
|
3
|
+
export type PluginConfig = {
|
|
4
|
+
component: string
|
|
5
|
+
exported: string
|
|
6
|
+
plugin: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type Plugin = ResolveDependencyReturn & {
|
|
10
|
+
components: Record<string, PluginConfig[]>
|
|
11
|
+
target: string
|
|
12
|
+
template?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type MaterializedPlugin = Plugin & { template: string }
|
|
16
|
+
|
|
17
|
+
export function generateInterceptor(plugin: Plugin): MaterializedPlugin {
|
|
18
|
+
const { fromModule, components } = plugin
|
|
19
|
+
|
|
20
|
+
const pluginImports = Object.entries(components)
|
|
21
|
+
.map(([_, plugins]) => {
|
|
22
|
+
const duplicateImports = new Set()
|
|
23
|
+
return plugins
|
|
24
|
+
.sort((a, b) => a.plugin.localeCompare(b.plugin))
|
|
25
|
+
.map(
|
|
26
|
+
(p) =>
|
|
27
|
+
`import { Plugin as ${p.plugin.split('/')[p.plugin.split('/').length - 1]} } from '${
|
|
28
|
+
p.plugin
|
|
29
|
+
}'`,
|
|
30
|
+
)
|
|
31
|
+
.filter((importStr) => {
|
|
32
|
+
if (duplicateImports.has(importStr)) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
duplicateImports.add(importStr)
|
|
36
|
+
return true
|
|
37
|
+
})
|
|
38
|
+
.join('\n')
|
|
39
|
+
})
|
|
40
|
+
.join('\n')
|
|
41
|
+
|
|
42
|
+
const importInjectables = `import { ${Object.entries(components)
|
|
43
|
+
.map(([component]) => `${component} as ${component}Base`)
|
|
44
|
+
.join(', ')} } from '${fromModule}'`
|
|
45
|
+
|
|
46
|
+
const pluginExports = Object.entries(components)
|
|
47
|
+
.map(([component, plugins]) => {
|
|
48
|
+
const duplicateImports = new Set()
|
|
49
|
+
|
|
50
|
+
let carry = `${component}Base`
|
|
51
|
+
const pluginStr = plugins
|
|
52
|
+
.reverse()
|
|
53
|
+
.map((p) => p.plugin.split('/')[p.plugin.split('/').length - 1])
|
|
54
|
+
.filter((importStr) => {
|
|
55
|
+
if (duplicateImports.has(importStr)) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
duplicateImports.add(importStr)
|
|
59
|
+
return true
|
|
60
|
+
})
|
|
61
|
+
.map((name) => {
|
|
62
|
+
const result = `const ${name}Interceptor = (props: ${component}BaseProps) => (
|
|
63
|
+
<${name} {...props} Component={${carry}} />
|
|
64
|
+
)`
|
|
65
|
+
carry = `${name}Interceptor`
|
|
66
|
+
return result
|
|
67
|
+
})
|
|
68
|
+
.join('\n')
|
|
69
|
+
|
|
70
|
+
return `type ${component}BaseProps = React.ComponentProps<typeof ${component}Base>
|
|
71
|
+
|
|
72
|
+
${pluginStr}
|
|
73
|
+
export const ${component} = ${carry}`
|
|
74
|
+
})
|
|
75
|
+
.join('\n')
|
|
76
|
+
|
|
77
|
+
const componentExports = `export * from '${fromModule}'`
|
|
78
|
+
|
|
79
|
+
const template = `/* This file is automatically generated. */
|
|
80
|
+
|
|
81
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
82
|
+
/* eslint-disable import/export */
|
|
83
|
+
|
|
84
|
+
${pluginImports}
|
|
85
|
+
${importInjectables}
|
|
86
|
+
|
|
87
|
+
${componentExports}
|
|
88
|
+
|
|
89
|
+
${pluginExports}
|
|
90
|
+
`
|
|
91
|
+
|
|
92
|
+
return { ...plugin, template }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type GenerateInterceptorsReturn = Record<string, MaterializedPlugin>
|
|
96
|
+
|
|
97
|
+
export function generateInterceptors(
|
|
98
|
+
plugins: PluginConfig[],
|
|
99
|
+
resolve: ResolveDependency,
|
|
100
|
+
): GenerateInterceptorsReturn {
|
|
101
|
+
// todo: Do not use reduce as we're passing the accumulator to the next iteration
|
|
102
|
+
const byExportedComponent = plugins.reduce((acc, plug) => {
|
|
103
|
+
const { exported } = plug
|
|
104
|
+
const resolved = resolve(exported)
|
|
105
|
+
|
|
106
|
+
if (!acc[resolved.fromRoot])
|
|
107
|
+
acc[resolved.fromRoot] = {
|
|
108
|
+
...resolved,
|
|
109
|
+
target: `${resolved.fromRoot}.interceptor`,
|
|
110
|
+
components: {},
|
|
111
|
+
} as Plugin
|
|
112
|
+
|
|
113
|
+
if (!acc[resolved.fromRoot].components[plug.component])
|
|
114
|
+
acc[resolved.fromRoot].components[plug.component] = []
|
|
115
|
+
|
|
116
|
+
acc[resolved.fromRoot].components[plug.component].push(plug)
|
|
117
|
+
return acc
|
|
118
|
+
}, {} as Record<string, Plugin>)
|
|
119
|
+
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
Object.entries(byExportedComponent).map(([target, plg]) => [target, generateInterceptor(plg)]),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import glob from 'glob'
|
|
3
|
+
import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
|
|
4
|
+
|
|
5
|
+
export function rmInterceptors(cwd: string = process.cwd()): string[] {
|
|
6
|
+
const dependencies = resolveDependenciesSync(cwd)
|
|
7
|
+
|
|
8
|
+
const removed: string[] = []
|
|
9
|
+
dependencies.forEach((dependency) => {
|
|
10
|
+
const files = glob.sync(`${dependency}/**/*.interceptor.tsx`, { cwd })
|
|
11
|
+
files.forEach((file) => {
|
|
12
|
+
fs.unlinkSync(file)
|
|
13
|
+
removed.push(file)
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
return removed
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { GenerateInterceptorsReturn } from './generateInterceptors'
|
|
4
|
+
|
|
5
|
+
export function writeInterceptors(
|
|
6
|
+
interceptors: GenerateInterceptorsReturn,
|
|
7
|
+
cwd: string = process.cwd(),
|
|
8
|
+
) {
|
|
9
|
+
Object.entries(interceptors).forEach(([target, plugin]) => {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
const fileToWrite = `${path.join(cwd, plugin.fromRoot)}.interceptor.tsx`
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
!fs.existsSync(fileToWrite) ||
|
|
15
|
+
fs.readFileSync(fileToWrite, 'utf8').toString() !== plugin.template
|
|
16
|
+
) {
|
|
17
|
+
fs.writeFileSync(fileToWrite, plugin.template)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -2,6 +2,8 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import type { PackageJson } from 'type-fest'
|
|
4
4
|
|
|
5
|
+
const resolveCache: Map<string, Map<string, string>> = new Map<string, Map<string, string>>()
|
|
6
|
+
|
|
5
7
|
function resolveRecursivePackageJson(
|
|
6
8
|
packageJsonFilename: string,
|
|
7
9
|
packageNames: Map<string, string>,
|
|
@@ -55,5 +57,9 @@ function resolveRecursivePackageJson(
|
|
|
55
57
|
* and stop there, not checking children.
|
|
56
58
|
*/
|
|
57
59
|
export function resolveDependenciesSync(root = process.cwd()) {
|
|
58
|
-
|
|
60
|
+
const cached = resolveCache.get(root)
|
|
61
|
+
if (cached) return cached
|
|
62
|
+
const result = resolveRecursivePackageJson(path.join(root, 'package.json'), new Map())
|
|
63
|
+
resolveCache.set(root, result)
|
|
64
|
+
return result
|
|
59
65
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { resolveDependenciesSync } from './resolveDependenciesSync'
|
|
2
|
+
|
|
3
|
+
export type ResolveDependencyReturn = {
|
|
4
|
+
dependency: string
|
|
5
|
+
root: string
|
|
6
|
+
fromRoot: string
|
|
7
|
+
fromModule: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ResolveDependency = (req: string) => ResolveDependencyReturn
|
|
11
|
+
|
|
12
|
+
export const resolveDependency = (cwd: string = process.cwd()) => {
|
|
13
|
+
const dependencies = resolveDependenciesSync(cwd)
|
|
14
|
+
return (dependency: string): ResolveDependencyReturn => {
|
|
15
|
+
let dependencyPaths = { root: '.', dependency, fromRoot: dependency, fromModule: dependency }
|
|
16
|
+
|
|
17
|
+
dependencies.forEach((root, depCandidate) => {
|
|
18
|
+
if (dependency.startsWith(depCandidate)) {
|
|
19
|
+
const relative = dependency.replace(depCandidate, '')
|
|
20
|
+
const fromRoot = dependency.replace(depCandidate, root)
|
|
21
|
+
|
|
22
|
+
dependencyPaths = {
|
|
23
|
+
root,
|
|
24
|
+
dependency,
|
|
25
|
+
fromRoot,
|
|
26
|
+
fromModule: !relative ? '.' : `./${relative.split('/')[relative.split('/').length - 1]}`,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
return dependencyPaths
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/withGraphCommerce.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NextConfig } from 'next'
|
|
2
2
|
import withTranspileModules from 'next-transpile-modules'
|
|
3
3
|
import { DefinePlugin, Configuration } from 'webpack'
|
|
4
|
+
import { InterceptorPlugin } from './interceptors/InterceptorPlugin'
|
|
4
5
|
import { resolveDependenciesSync } from './utils/resolveDependenciesSync'
|
|
5
6
|
|
|
6
7
|
function extendConfig(nextConfig: NextConfig, modules: string[]): NextConfig {
|
|
@@ -44,6 +45,8 @@ function extendConfig(nextConfig: NextConfig, modules: string[]): NextConfig {
|
|
|
44
45
|
'@mui/system': '@mui/system/modern',
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
config.plugins = [...(config.plugins ?? []), new InterceptorPlugin()]
|
|
49
|
+
|
|
47
50
|
return typeof nextConfig.webpack === 'function' ? nextConfig.webpack(config, options) : config
|
|
48
51
|
},
|
|
49
52
|
}
|
|
@@ -58,7 +61,7 @@ export function withGraphCommerce(
|
|
|
58
61
|
conf: GraphCommerceConfig = {},
|
|
59
62
|
): (config: NextConfig) => NextConfig {
|
|
60
63
|
const { packages = [] } = conf
|
|
61
|
-
const dependencies = [...resolveDependenciesSync().keys()]
|
|
64
|
+
const dependencies = [...resolveDependenciesSync().keys()].slice(1)
|
|
62
65
|
|
|
63
66
|
const modules = [...dependencies, ...packages]
|
|
64
67
|
return (config) => extendConfig(withTranspileModules(modules)(config), modules)
|