@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.
- package/CHANGELOG.md +133 -1
- package/Config.graphqls +4 -2
- package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +19 -2
- package/__tests__/config/utils/replaceConfigInString.ts +4 -0
- package/__tests__/interceptors/findPlugins.ts +473 -113
- package/__tests__/interceptors/generateInterceptors.ts +610 -322
- package/__tests__/interceptors/parseStructure.ts +158 -0
- package/__tests__/interceptors/writeInterceptors.ts +23 -14
- package/__tests__/utils/resolveDependenciesSync.ts +28 -25
- package/dist/config/commands/generateConfig.js +5 -2
- package/dist/config/demoConfig.js +19 -4
- package/dist/generated/config.js +8 -1
- package/dist/interceptors/InterceptorPlugin.js +70 -25
- package/dist/interceptors/RenameVisitor.js +19 -0
- package/dist/interceptors/Visitor.js +1418 -0
- package/dist/interceptors/extractExports.js +201 -0
- package/dist/interceptors/findOriginalSource.js +87 -0
- package/dist/interceptors/findPlugins.js +21 -53
- package/dist/interceptors/generateInterceptor.js +200 -0
- package/dist/interceptors/generateInterceptors.js +38 -179
- package/dist/interceptors/parseStructure.js +71 -0
- package/dist/interceptors/swc.js +16 -0
- package/dist/interceptors/writeInterceptors.js +19 -10
- package/dist/utils/resolveDependency.js +27 -5
- package/dist/withGraphCommerce.js +2 -1
- package/package.json +4 -1
- package/src/config/commands/generateConfig.ts +5 -2
- package/src/config/demoConfig.ts +19 -4
- package/src/config/index.ts +4 -2
- package/src/generated/config.ts +25 -3
- package/src/index.ts +16 -6
- package/src/interceptors/InterceptorPlugin.ts +90 -32
- package/src/interceptors/RenameVisitor.ts +17 -0
- package/src/interceptors/Visitor.ts +1847 -0
- package/src/interceptors/extractExports.ts +230 -0
- package/src/interceptors/findOriginalSource.ts +105 -0
- package/src/interceptors/findPlugins.ts +36 -87
- package/src/interceptors/generateInterceptor.ts +271 -0
- package/src/interceptors/generateInterceptors.ts +67 -237
- package/src/interceptors/parseStructure.ts +82 -0
- package/src/interceptors/swc.ts +13 -0
- package/src/interceptors/writeInterceptors.ts +26 -10
- package/src/utils/resolveDependency.ts +51 -12
- package/src/withGraphCommerce.ts +2 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractExports = exports.RUNTIME_VALUE = exports.UnsupportedValueError = exports.NoSuchDeclarationError = void 0;
|
|
4
|
+
class NoSuchDeclarationError extends Error {
|
|
5
|
+
}
|
|
6
|
+
exports.NoSuchDeclarationError = NoSuchDeclarationError;
|
|
7
|
+
function isIdentifier(node) {
|
|
8
|
+
return node.type === 'Identifier';
|
|
9
|
+
}
|
|
10
|
+
function isBooleanLiteral(node) {
|
|
11
|
+
return node.type === 'BooleanLiteral';
|
|
12
|
+
}
|
|
13
|
+
function isNullLiteral(node) {
|
|
14
|
+
return node.type === 'NullLiteral';
|
|
15
|
+
}
|
|
16
|
+
function isStringLiteral(node) {
|
|
17
|
+
return node.type === 'StringLiteral';
|
|
18
|
+
}
|
|
19
|
+
function isNumericLiteral(node) {
|
|
20
|
+
return node.type === 'NumericLiteral';
|
|
21
|
+
}
|
|
22
|
+
function isArrayExpression(node) {
|
|
23
|
+
return node.type === 'ArrayExpression';
|
|
24
|
+
}
|
|
25
|
+
function isObjectExpression(node) {
|
|
26
|
+
return node.type === 'ObjectExpression';
|
|
27
|
+
}
|
|
28
|
+
function isKeyValueProperty(node) {
|
|
29
|
+
return node.type === 'KeyValueProperty';
|
|
30
|
+
}
|
|
31
|
+
function isRegExpLiteral(node) {
|
|
32
|
+
return node.type === 'RegExpLiteral';
|
|
33
|
+
}
|
|
34
|
+
function isTemplateLiteral(node) {
|
|
35
|
+
return node.type === 'TemplateLiteral';
|
|
36
|
+
}
|
|
37
|
+
class UnsupportedValueError extends Error {
|
|
38
|
+
/** @example `config.runtime[0].value` */
|
|
39
|
+
path;
|
|
40
|
+
constructor(message, paths) {
|
|
41
|
+
super(message);
|
|
42
|
+
// Generating "path" that looks like "config.runtime[0].value"
|
|
43
|
+
let codePath;
|
|
44
|
+
if (Array.isArray(paths)) {
|
|
45
|
+
codePath = '';
|
|
46
|
+
for (const path of paths) {
|
|
47
|
+
if (path[0] === '[') {
|
|
48
|
+
// "array" + "[0]"
|
|
49
|
+
codePath += path;
|
|
50
|
+
}
|
|
51
|
+
else if (codePath === '') {
|
|
52
|
+
codePath = path;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// "object" + ".key"
|
|
56
|
+
codePath += `.${path}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.path = codePath;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.UnsupportedValueError = UnsupportedValueError;
|
|
64
|
+
exports.RUNTIME_VALUE = Symbol('RUNTIME_VALUE');
|
|
65
|
+
function extractValue(node, path, optional = false) {
|
|
66
|
+
if (isNullLiteral(node)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (isBooleanLiteral(node)) {
|
|
70
|
+
// e.g. true / false
|
|
71
|
+
return node.value;
|
|
72
|
+
}
|
|
73
|
+
if (isStringLiteral(node)) {
|
|
74
|
+
// e.g. "abc"
|
|
75
|
+
return node.value;
|
|
76
|
+
}
|
|
77
|
+
if (isNumericLiteral(node)) {
|
|
78
|
+
// e.g. 123
|
|
79
|
+
return node.value;
|
|
80
|
+
}
|
|
81
|
+
if (isRegExpLiteral(node)) {
|
|
82
|
+
// e.g. /abc/i
|
|
83
|
+
return new RegExp(node.pattern, node.flags);
|
|
84
|
+
}
|
|
85
|
+
if (isIdentifier(node)) {
|
|
86
|
+
switch (node.value) {
|
|
87
|
+
case 'undefined':
|
|
88
|
+
return undefined;
|
|
89
|
+
default:
|
|
90
|
+
if (optional)
|
|
91
|
+
return exports.RUNTIME_VALUE;
|
|
92
|
+
throw new UnsupportedValueError(`Unknown identifier "${node.value}"`, path);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (isArrayExpression(node)) {
|
|
96
|
+
// e.g. [1, 2, 3]
|
|
97
|
+
const arr = [];
|
|
98
|
+
for (let i = 0, len = node.elements.length; i < len; i++) {
|
|
99
|
+
const elem = node.elements[i];
|
|
100
|
+
if (elem) {
|
|
101
|
+
if (elem.spread) {
|
|
102
|
+
// e.g. [ ...a ]
|
|
103
|
+
if (optional)
|
|
104
|
+
return exports.RUNTIME_VALUE;
|
|
105
|
+
throw new UnsupportedValueError('Unsupported spread operator in the Array Expression', path);
|
|
106
|
+
}
|
|
107
|
+
arr.push(extractValue(elem.expression, path && [...path, `[${i}]`], optional));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// e.g. [1, , 2]
|
|
111
|
+
// ^^
|
|
112
|
+
arr.push(undefined);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return arr;
|
|
116
|
+
}
|
|
117
|
+
else if (isObjectExpression(node)) {
|
|
118
|
+
// e.g. { a: 1, b: 2 }
|
|
119
|
+
const obj = {};
|
|
120
|
+
for (const prop of node.properties) {
|
|
121
|
+
if (!isKeyValueProperty(prop)) {
|
|
122
|
+
// e.g. { ...a }
|
|
123
|
+
if (optional)
|
|
124
|
+
return exports.RUNTIME_VALUE;
|
|
125
|
+
throw new UnsupportedValueError('Unsupported spread operator in the Object Expression', path);
|
|
126
|
+
}
|
|
127
|
+
let key;
|
|
128
|
+
if (isIdentifier(prop.key)) {
|
|
129
|
+
// e.g. { a: 1, b: 2 }
|
|
130
|
+
key = prop.key.value;
|
|
131
|
+
}
|
|
132
|
+
else if (isStringLiteral(prop.key)) {
|
|
133
|
+
// e.g. { "a": 1, "b": 2 }
|
|
134
|
+
key = prop.key.value;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (optional)
|
|
138
|
+
return exports.RUNTIME_VALUE;
|
|
139
|
+
throw new UnsupportedValueError(`Unsupported key type "${prop.key.type}" in the Object Expression`, path);
|
|
140
|
+
}
|
|
141
|
+
obj[key] = extractValue(prop.value, path && [...path, key]);
|
|
142
|
+
}
|
|
143
|
+
return obj;
|
|
144
|
+
}
|
|
145
|
+
else if (isTemplateLiteral(node)) {
|
|
146
|
+
// e.g. `abc`
|
|
147
|
+
if (node.expressions.length !== 0) {
|
|
148
|
+
// TODO: should we add support for `${'e'}d${'g'}'e'`?
|
|
149
|
+
if (optional)
|
|
150
|
+
return exports.RUNTIME_VALUE;
|
|
151
|
+
throw new UnsupportedValueError('Unsupported template literal with expressions', path);
|
|
152
|
+
}
|
|
153
|
+
// When TemplateLiteral has 0 expressions, the length of quasis is always 1.
|
|
154
|
+
// Because when parsing TemplateLiteral, the parser yields the first quasi,
|
|
155
|
+
// then the first expression, then the next quasi, then the next expression, etc.,
|
|
156
|
+
// until the last quasi.
|
|
157
|
+
// Thus if there is no expression, the parser ends at the frst and also last quasis
|
|
158
|
+
//
|
|
159
|
+
// A "cooked" interpretation where backslashes have special meaning, while a
|
|
160
|
+
// "raw" interpretation where backslashes do not have special meaning
|
|
161
|
+
// https://exploringjs.com/impatient-js/ch_template-literals.html#template-strings-cooked-vs-raw
|
|
162
|
+
const [{ cooked, raw }] = node.quasis;
|
|
163
|
+
return cooked ?? raw;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
if (optional)
|
|
167
|
+
return exports.RUNTIME_VALUE;
|
|
168
|
+
throw new UnsupportedValueError(`Unsupported node type "${node.type}"`, path);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function extractExports(module) {
|
|
172
|
+
const exports = {};
|
|
173
|
+
const errors = [];
|
|
174
|
+
for (const moduleItem of module.body) {
|
|
175
|
+
switch (moduleItem.type) {
|
|
176
|
+
case 'ExportAllDeclaration':
|
|
177
|
+
errors.push('You can not use export * from a plugin, exports must be explicit');
|
|
178
|
+
break;
|
|
179
|
+
case 'ExportDefaultDeclaration':
|
|
180
|
+
errors.push('You can not use default exports from a plugin, exports must be explicit');
|
|
181
|
+
break;
|
|
182
|
+
case 'ExportDeclaration':
|
|
183
|
+
switch (moduleItem.declaration.type) {
|
|
184
|
+
case 'ClassDeclaration':
|
|
185
|
+
case 'FunctionDeclaration':
|
|
186
|
+
exports[moduleItem.declaration.identifier.value] = exports.RUNTIME_VALUE;
|
|
187
|
+
// node.identifier.value
|
|
188
|
+
break;
|
|
189
|
+
case 'VariableDeclaration':
|
|
190
|
+
moduleItem.declaration.declarations.forEach((decl) => {
|
|
191
|
+
if (isIdentifier(decl.id) && decl.init) {
|
|
192
|
+
exports[decl.id.value] = extractValue(decl.init, undefined, true);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return [exports, errors];
|
|
200
|
+
}
|
|
201
|
+
exports.extractExports = extractExports;
|
|
@@ -0,0 +1,87 @@
|
|
|
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.findOriginalSource = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const swc_1 = require("./swc");
|
|
9
|
+
function parseAndFindExport(resolved, findExport, resolve) {
|
|
10
|
+
if (!resolved?.source)
|
|
11
|
+
return undefined;
|
|
12
|
+
const ast = (0, swc_1.parseSync)(resolved.source);
|
|
13
|
+
for (const node of ast.body) {
|
|
14
|
+
if (node.type === 'ExportDeclaration') {
|
|
15
|
+
switch (node.declaration.type) {
|
|
16
|
+
case 'ClassDeclaration':
|
|
17
|
+
case 'FunctionDeclaration':
|
|
18
|
+
if (node.declaration.identifier.value === findExport)
|
|
19
|
+
return resolved;
|
|
20
|
+
break;
|
|
21
|
+
case 'VariableDeclaration':
|
|
22
|
+
for (const declaration of node.declaration.declarations) {
|
|
23
|
+
if (declaration.type === 'VariableDeclarator') {
|
|
24
|
+
if (declaration.id.type === 'Identifier') {
|
|
25
|
+
if (declaration.id.value === findExport)
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(declaration);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const exports = ast.body
|
|
38
|
+
.filter((node) => node.type === 'ExportAllDeclaration')
|
|
39
|
+
.sort((a, b) => {
|
|
40
|
+
const probablyA = a.source.value.includes(findExport);
|
|
41
|
+
const probablyB = b.source.value.includes(findExport);
|
|
42
|
+
// eslint-disable-next-line no-nested-ternary
|
|
43
|
+
return probablyA === probablyB ? 0 : probablyA ? -1 : 1;
|
|
44
|
+
});
|
|
45
|
+
for (const node of exports) {
|
|
46
|
+
const isRelative = node.source.value.startsWith('.');
|
|
47
|
+
if (isRelative) {
|
|
48
|
+
const d = resolved.dependency === resolved.denormalized
|
|
49
|
+
? resolved.dependency.substring(0, resolved.dependency.lastIndexOf('/'))
|
|
50
|
+
: resolved.dependency;
|
|
51
|
+
const newPath = path_1.default.join(d, node.source.value);
|
|
52
|
+
const resolveResult = resolve(newPath, { includeSources: true });
|
|
53
|
+
// eslint-disable-next-line no-continue
|
|
54
|
+
if (!resolveResult)
|
|
55
|
+
continue;
|
|
56
|
+
const newResolved = parseAndFindExport(resolveResult, findExport, resolve);
|
|
57
|
+
if (newResolved && resolved.dependency !== newResolved.dependency)
|
|
58
|
+
return newResolved;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const cachedResults = new Map();
|
|
64
|
+
function findOriginalSource(plug, resolved, resolve) {
|
|
65
|
+
if (!resolved?.source)
|
|
66
|
+
return {
|
|
67
|
+
resolved: undefined,
|
|
68
|
+
error: new Error(`Could not resolve ${plug.targetModule}`),
|
|
69
|
+
};
|
|
70
|
+
// const cacheKey = `${plug.targetModule}#${plug.targetExport}`
|
|
71
|
+
// if (cachedResults.has(cacheKey)) {
|
|
72
|
+
// return {
|
|
73
|
+
// resolved: cachedResults.get(cacheKey) as NonNullable<ResolveDependencyReturn>,
|
|
74
|
+
// error: undefined,
|
|
75
|
+
// }
|
|
76
|
+
// }
|
|
77
|
+
const newResolved = parseAndFindExport(resolved, plug.targetExport, resolve);
|
|
78
|
+
if (!newResolved) {
|
|
79
|
+
return {
|
|
80
|
+
resolved: undefined,
|
|
81
|
+
error: new Error(`Can not find ${plug.targetModule}#${plug.sourceExport} for plugin ${plug.sourceModule}`),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// cachedResults.set(cacheKey, newResolved)
|
|
85
|
+
return { resolved: newResolved, error: undefined };
|
|
86
|
+
}
|
|
87
|
+
exports.findOriginalSource = findOriginalSource;
|
|
@@ -4,36 +4,13 @@ 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 core_1 = require("@swc/core");
|
|
8
7
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
8
|
+
const core_1 = require("@swc/core");
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
|
-
const glob_1 =
|
|
12
|
-
const get_1 = __importDefault(require("lodash/get"));
|
|
11
|
+
const glob_1 = require("glob");
|
|
13
12
|
const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
|
|
14
|
-
const
|
|
15
|
-
function parseStructure(file) {
|
|
16
|
-
const ast = (0, core_1.parseFileSync)(file, { syntax: 'typescript', tsx: true });
|
|
17
|
-
const imports = {};
|
|
18
|
-
const exports = {};
|
|
19
|
-
ast.body.forEach((node) => {
|
|
20
|
-
if (node.type === 'ImportDeclaration') {
|
|
21
|
-
node.specifiers.forEach((s) => {
|
|
22
|
-
if (s.type === 'ImportSpecifier') {
|
|
23
|
-
imports[s.local.value] = node.source.value;
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (node.type === 'ExportDeclaration' && node.declaration.type === 'VariableDeclaration') {
|
|
28
|
-
node.declaration.declarations.forEach((declaration) => {
|
|
29
|
-
if (declaration.init?.type === 'StringLiteral' && declaration.id.type === 'Identifier') {
|
|
30
|
-
exports[declaration.id.value] = declaration.init.value;
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
return exports;
|
|
36
|
-
}
|
|
13
|
+
const parseStructure_1 = require("./parseStructure");
|
|
37
14
|
const pluginLogs = {};
|
|
38
15
|
function findPlugins(config, cwd = process.cwd()) {
|
|
39
16
|
const dependencies = (0, resolveDependenciesSync_1.resolveDependenciesSync)(cwd);
|
|
@@ -41,30 +18,14 @@ function findPlugins(config, cwd = process.cwd()) {
|
|
|
41
18
|
const errors = [];
|
|
42
19
|
const plugins = [];
|
|
43
20
|
dependencies.forEach((dependency, path) => {
|
|
44
|
-
const files = glob_1.
|
|
21
|
+
const files = (0, glob_1.sync)(`${dependency}/plugins/**/*.{ts,tsx}`, { dotRelative: true });
|
|
45
22
|
files.forEach((file) => {
|
|
23
|
+
const sourceModule = file.replace(dependency, path).replace('.tsx', '').replace('.ts', '');
|
|
46
24
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
plugin: file.replace(dependency, path).replace('.tsx', '').replace('.ts', ''),
|
|
52
|
-
...result,
|
|
53
|
-
enabled: !result.ifConfig || Boolean((0, get_1.default)(config, result.ifConfig)),
|
|
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
|
-
}
|
|
25
|
+
const ast = (0, core_1.parseFileSync)(file, { syntax: 'typescript', tsx: true });
|
|
26
|
+
(0, parseStructure_1.parseStructure)(ast, config, sourceModule).forEach((result) => {
|
|
27
|
+
plugins.push(result);
|
|
28
|
+
});
|
|
68
29
|
}
|
|
69
30
|
catch (e) {
|
|
70
31
|
console.error(`Error parsing ${file}`, e);
|
|
@@ -73,9 +34,7 @@ function findPlugins(config, cwd = process.cwd()) {
|
|
|
73
34
|
});
|
|
74
35
|
if (process.env.NODE_ENV === 'development' && debug) {
|
|
75
36
|
const byExported = plugins.reduce((acc, plugin) => {
|
|
76
|
-
const
|
|
77
|
-
const funcStr = (0, generateInterceptors_1.isMethodPluginConfig)(plugin) ? plugin.func : '';
|
|
78
|
-
const key = `🔌 ${chalk_1.default.greenBright(`Plugins loaded for ${plugin.exported}#${componentStr}${funcStr}`)}`;
|
|
37
|
+
const key = `🔌 ${chalk_1.default.greenBright(`Plugins loaded for ${plugin.targetModule}#${plugin.targetExport}`)}`;
|
|
79
38
|
if (!acc[key])
|
|
80
39
|
acc[key] = [];
|
|
81
40
|
acc[key].push(plugin);
|
|
@@ -85,7 +44,15 @@ function findPlugins(config, cwd = process.cwd()) {
|
|
|
85
44
|
Object.entries(byExported).forEach(([key, p]) => {
|
|
86
45
|
const logStr = p
|
|
87
46
|
.filter((c) => debug || c.enabled)
|
|
88
|
-
.map((c) =>
|
|
47
|
+
.map((c) => {
|
|
48
|
+
// eslint-disable-next-line no-nested-ternary
|
|
49
|
+
const ifConfigStr = c.ifConfig
|
|
50
|
+
? Array.isArray(c.ifConfig)
|
|
51
|
+
? `${c.ifConfig[0]}=${c.ifConfig[1]}`
|
|
52
|
+
: `${c.ifConfig}`
|
|
53
|
+
: '';
|
|
54
|
+
return `${c.enabled ? `🟢` : `⚪️`} ${c.sourceModule} ${ifConfigStr}`;
|
|
55
|
+
})
|
|
89
56
|
.join('\n');
|
|
90
57
|
if (logStr && pluginLogs[key] !== logStr) {
|
|
91
58
|
toLog.push(`${key}\n${logStr}`);
|
|
@@ -93,7 +60,8 @@ function findPlugins(config, cwd = process.cwd()) {
|
|
|
93
60
|
}
|
|
94
61
|
});
|
|
95
62
|
// eslint-disable-next-line no-console
|
|
96
|
-
|
|
63
|
+
if (toLog.length)
|
|
64
|
+
console.log(toLog.join('\n\n'));
|
|
97
65
|
}
|
|
98
66
|
return [plugins, errors];
|
|
99
67
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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.generateInterceptor = exports.moveRelativeDown = exports.SOURCE_END = exports.SOURCE_START = exports.isPluginConfig = exports.isReplacePluginConfig = exports.isMethodPluginConfig = exports.isReactPluginConfig = exports.isPluginBaseConfig = void 0;
|
|
7
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
8
|
+
const prettier_config_pwa_1 = __importDefault(require("@graphcommerce/prettier-config-pwa"));
|
|
9
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
10
|
+
const prettier_1 = __importDefault(require("prettier"));
|
|
11
|
+
const RenameVisitor_1 = require("./RenameVisitor");
|
|
12
|
+
const swc_1 = require("./swc");
|
|
13
|
+
function isPluginBaseConfig(plugin) {
|
|
14
|
+
return (typeof plugin.type === 'string' &&
|
|
15
|
+
typeof plugin.sourceModule === 'string' &&
|
|
16
|
+
typeof plugin.enabled === 'boolean' &&
|
|
17
|
+
typeof plugin.targetExport === 'string');
|
|
18
|
+
}
|
|
19
|
+
exports.isPluginBaseConfig = isPluginBaseConfig;
|
|
20
|
+
function isReactPluginConfig(plugin) {
|
|
21
|
+
if (!isPluginBaseConfig(plugin))
|
|
22
|
+
return false;
|
|
23
|
+
return plugin.type === 'component';
|
|
24
|
+
}
|
|
25
|
+
exports.isReactPluginConfig = isReactPluginConfig;
|
|
26
|
+
function isMethodPluginConfig(plugin) {
|
|
27
|
+
if (!isPluginBaseConfig(plugin))
|
|
28
|
+
return false;
|
|
29
|
+
return plugin.type === 'function';
|
|
30
|
+
}
|
|
31
|
+
exports.isMethodPluginConfig = isMethodPluginConfig;
|
|
32
|
+
function isReplacePluginConfig(plugin) {
|
|
33
|
+
if (!isPluginBaseConfig(plugin))
|
|
34
|
+
return false;
|
|
35
|
+
return plugin.type === 'replace';
|
|
36
|
+
}
|
|
37
|
+
exports.isReplacePluginConfig = isReplacePluginConfig;
|
|
38
|
+
function isPluginConfig(plugin) {
|
|
39
|
+
return isPluginBaseConfig(plugin);
|
|
40
|
+
}
|
|
41
|
+
exports.isPluginConfig = isPluginConfig;
|
|
42
|
+
exports.SOURCE_START = '/** ❗️ Original (modified) source starts here **/';
|
|
43
|
+
exports.SOURCE_END = '/** ❗️ Original (modified) source ends here **/';
|
|
44
|
+
const originalSuffix = 'Original';
|
|
45
|
+
const sourceSuffix = 'Source';
|
|
46
|
+
const interceptorSuffix = 'Interceptor';
|
|
47
|
+
const disabledSuffix = 'Disabled';
|
|
48
|
+
const name = (plugin) => `${plugin.sourceExport}${plugin.sourceModule
|
|
49
|
+
.split('/')[plugin.sourceModule.split('/').length - 1].replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
50
|
+
const fileName = (plugin) => `${plugin.sourceModule}#${plugin.sourceExport}`;
|
|
51
|
+
const originalName = (n) => `${n}${originalSuffix}`;
|
|
52
|
+
const sourceName = (n) => `${n}${sourceSuffix}`;
|
|
53
|
+
const interceptorName = (n) => `${n}${interceptorSuffix}`;
|
|
54
|
+
const interceptorPropsName = (n) => `${interceptorName(n)}Props`;
|
|
55
|
+
function moveRelativeDown(plugins) {
|
|
56
|
+
return [...plugins].sort((a, b) => {
|
|
57
|
+
if (a.sourceModule.startsWith('.') && !b.sourceModule.startsWith('.'))
|
|
58
|
+
return 1;
|
|
59
|
+
if (!a.sourceModule.startsWith('.') && b.sourceModule.startsWith('.'))
|
|
60
|
+
return -1;
|
|
61
|
+
return 0;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
exports.moveRelativeDown = moveRelativeDown;
|
|
65
|
+
const generateIdentifyer = (s) => Math.abs(s.split('').reduce((a, b) => {
|
|
66
|
+
// eslint-disable-next-line no-param-reassign, no-bitwise
|
|
67
|
+
a = (a << 5) - a + b.charCodeAt(0);
|
|
68
|
+
// eslint-disable-next-line no-bitwise
|
|
69
|
+
return a & a;
|
|
70
|
+
}, 0)).toString();
|
|
71
|
+
/**
|
|
72
|
+
* The is on the first line, with the format: \/* hash:${identifer} *\/
|
|
73
|
+
*/
|
|
74
|
+
function extractIdentifier(source) {
|
|
75
|
+
if (!source)
|
|
76
|
+
return null;
|
|
77
|
+
const match = source.match(/\/\* hash:(\d+) \*\//);
|
|
78
|
+
if (!match)
|
|
79
|
+
return null;
|
|
80
|
+
return match[1];
|
|
81
|
+
}
|
|
82
|
+
async function generateInterceptor(interceptor, config, oldInterceptorSource) {
|
|
83
|
+
const identifer = generateIdentifyer(JSON.stringify(interceptor) + JSON.stringify(config));
|
|
84
|
+
const { dependency, targetExports, source } = interceptor;
|
|
85
|
+
if (oldInterceptorSource && identifer === extractIdentifier(oldInterceptorSource))
|
|
86
|
+
return { ...interceptor, template: oldInterceptorSource };
|
|
87
|
+
const pluginConfigs = [...Object.entries(targetExports)].map(([, plugins]) => plugins).flat();
|
|
88
|
+
// console.log('pluginConfigs', pluginConfigs)
|
|
89
|
+
const duplicateImports = new Set();
|
|
90
|
+
const pluginImports = moveRelativeDown([...pluginConfigs].sort((a, b) => a.sourceModule.localeCompare(b.sourceModule)))
|
|
91
|
+
.map((plugin) => `import { ${plugin.sourceExport} as ${sourceName(name(plugin))} } from '${plugin.sourceModule}'`)
|
|
92
|
+
.filter((str) => {
|
|
93
|
+
if (duplicateImports.has(str))
|
|
94
|
+
return false;
|
|
95
|
+
duplicateImports.add(str);
|
|
96
|
+
return true;
|
|
97
|
+
})
|
|
98
|
+
.join('\n');
|
|
99
|
+
const ast = (0, swc_1.parseSync)(source);
|
|
100
|
+
new RenameVisitor_1.RenameVisitor(Object.keys(targetExports), (s) => originalName(s)).visitModule(ast);
|
|
101
|
+
const pluginExports = Object.entries(targetExports)
|
|
102
|
+
.map(([base, plugins]) => {
|
|
103
|
+
const duplicateInterceptors = new Set();
|
|
104
|
+
let carry = originalName(base);
|
|
105
|
+
const carryProps = [];
|
|
106
|
+
const pluginStr = plugins
|
|
107
|
+
.reverse()
|
|
108
|
+
.filter((p) => {
|
|
109
|
+
if (duplicateInterceptors.has(name(p)))
|
|
110
|
+
return false;
|
|
111
|
+
duplicateInterceptors.add(name(p));
|
|
112
|
+
return true;
|
|
113
|
+
})
|
|
114
|
+
.map((p) => {
|
|
115
|
+
let result;
|
|
116
|
+
const wrapChain = plugins
|
|
117
|
+
.reverse()
|
|
118
|
+
.map((pl) => name(pl))
|
|
119
|
+
.join(' wrapping ');
|
|
120
|
+
if (isReplacePluginConfig(p)) {
|
|
121
|
+
new RenameVisitor_1.RenameVisitor([originalName(p.targetExport)], (s) => s.replace(originalSuffix, disabledSuffix)).visitModule(ast);
|
|
122
|
+
carryProps.push(interceptorPropsName(name(p)));
|
|
123
|
+
result = `type ${interceptorPropsName(name(p))} = React.ComponentProps<typeof ${sourceName(name(p))}>`;
|
|
124
|
+
}
|
|
125
|
+
if (isReactPluginConfig(p)) {
|
|
126
|
+
carryProps.push(interceptorPropsName(name(p)));
|
|
127
|
+
result = `
|
|
128
|
+
type ${interceptorPropsName(name(p))} = DistributedOmit<React.ComponentProps<typeof ${sourceName(name(p))}>, 'Prev'>
|
|
129
|
+
const ${interceptorName(name(p))} = (props: ${carryProps.join(' & ')}) => {
|
|
130
|
+
${config.pluginStatus ? `logOnce(\`🔌 Rendering ${base} with plugin(s): ${wrapChain} wrapping <${base}/>\`)` : ''}
|
|
131
|
+
|
|
132
|
+
${process.env.NODE_ENV === 'development'
|
|
133
|
+
? `if(!props['data-plugin'])
|
|
134
|
+
logOnce('${fileName(p)} does not spread props to prev: <Prev {...props}/>. This will cause issues if multiple plugins are applied to this component.')`
|
|
135
|
+
: ''}
|
|
136
|
+
return <${sourceName(name(p))} {...props} Prev={${carry} as React.FC} />
|
|
137
|
+
}`;
|
|
138
|
+
}
|
|
139
|
+
if (isMethodPluginConfig(p)) {
|
|
140
|
+
result = `const ${interceptorName(name(p))}: typeof ${carry} = (...args) => {
|
|
141
|
+
${config.pluginStatus ? `logOnce(\`🔌 Calling ${base} with plugin(s): ${wrapChain} wrapping ${base}()\`)` : ''}
|
|
142
|
+
return ${sourceName(name(p))}(${carry}, ...args)
|
|
143
|
+
}`;
|
|
144
|
+
}
|
|
145
|
+
carry = p.type === 'replace' ? sourceName(name(p)) : interceptorName(name(p));
|
|
146
|
+
return result;
|
|
147
|
+
})
|
|
148
|
+
.filter((v) => !!v)
|
|
149
|
+
.join('\n');
|
|
150
|
+
const isComponent = plugins.every((p) => isReplacePluginConfig(p) || isReactPluginConfig(p));
|
|
151
|
+
if (isComponent && plugins.some((p) => isMethodPluginConfig(p))) {
|
|
152
|
+
throw new Error(`Cannot mix React and Method plugins for ${base} in ${dependency}.`);
|
|
153
|
+
}
|
|
154
|
+
if (process.env.NODE_ENV === 'development' && isComponent) {
|
|
155
|
+
return `${pluginStr}
|
|
156
|
+
export const ${base}: typeof ${carry} = (props) => {
|
|
157
|
+
return <${carry} {...props} data-plugin />
|
|
158
|
+
}`;
|
|
159
|
+
}
|
|
160
|
+
return `
|
|
161
|
+
${pluginStr}
|
|
162
|
+
export const ${base} = ${carry}
|
|
163
|
+
`;
|
|
164
|
+
})
|
|
165
|
+
.join('\n');
|
|
166
|
+
const logOnce = config.pluginStatus || process.env.NODE_ENV === 'development'
|
|
167
|
+
? `
|
|
168
|
+
const logged: Set<string> = new Set();
|
|
169
|
+
const logOnce = (log: string, ...additional: unknown[]) => {
|
|
170
|
+
if (logged.has(log)) return
|
|
171
|
+
logged.add(log)
|
|
172
|
+
console.warn(log, ...additional)
|
|
173
|
+
}
|
|
174
|
+
`
|
|
175
|
+
: '';
|
|
176
|
+
const template = `/* hash:${identifer} */
|
|
177
|
+
/* eslint-disable */
|
|
178
|
+
/* This file is automatically generated for ${dependency} */
|
|
179
|
+
${Object.values(targetExports).some((t) => t.some((p) => p.type === 'component'))
|
|
180
|
+
? `import type { DistributedOmit } from 'type-fest'`
|
|
181
|
+
: ''}
|
|
182
|
+
|
|
183
|
+
${pluginImports}
|
|
184
|
+
|
|
185
|
+
${exports.SOURCE_START}
|
|
186
|
+
${(0, swc_1.printSync)(ast).code}
|
|
187
|
+
${exports.SOURCE_END}
|
|
188
|
+
${logOnce}${pluginExports}
|
|
189
|
+
`;
|
|
190
|
+
let templateFormatted;
|
|
191
|
+
try {
|
|
192
|
+
templateFormatted = await prettier_1.default.format(template, { ...prettier_config_pwa_1.default, parser: 'typescript' });
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
console.log('Error formatting interceptor: ', e, 'using raw template.');
|
|
196
|
+
templateFormatted = template;
|
|
197
|
+
}
|
|
198
|
+
return { ...interceptor, template: templateFormatted };
|
|
199
|
+
}
|
|
200
|
+
exports.generateInterceptor = generateInterceptor;
|