@graphcommerce/next-config 8.1.0-canary.5 → 8.1.0-canary.50
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 +159 -0
- package/Config.graphqls +5 -5
- package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +48 -2
- package/__tests__/config/utils/configToImportMeta.ts +0 -4
- package/__tests__/config/utils/mergeEnvIntoConfig.ts +15 -2
- package/__tests__/config/utils/rewriteLegancyEnv.ts +1 -1
- package/__tests__/interceptors/findPlugins.ts +144 -194
- package/__tests__/interceptors/generateInterceptors.ts +174 -91
- package/__tests__/interceptors/parseStructure.ts +50 -0
- package/__tests__/utils/resolveDependenciesSync.ts +4 -0
- package/dist/config/commands/exportConfig.js +5 -0
- package/dist/config/commands/generateConfig.js +5 -0
- package/dist/config/commands/generateIntercetors.js +9 -0
- package/dist/config/demoConfig.js +5 -0
- package/dist/config/utils/mergeEnvIntoConfig.js +8 -1
- package/dist/generated/config.js +17 -9
- package/dist/index.js +1 -0
- package/dist/interceptors/commands/codegenInterceptors.js +23 -0
- package/dist/interceptors/commands/generateIntercetors.js +9 -0
- package/dist/interceptors/extractExports.js +21 -18
- package/dist/interceptors/findOriginalSource.js +17 -1
- package/dist/interceptors/findPlugins.js +7 -3
- package/dist/interceptors/generateInterceptor.js +32 -15
- package/dist/interceptors/generateInterceptors.js +6 -5
- package/dist/interceptors/parseStructure.js +9 -1
- package/dist/interceptors/writeInterceptors.js +7 -7
- package/dist/utils/resolveDependenciesSync.js +5 -4
- package/dist/utils/resolveDependency.js +5 -0
- package/dist/withGraphCommerce.js +14 -5
- package/package.json +1 -1
- package/src/config/commands/exportConfig.ts +3 -0
- package/src/config/commands/generateConfig.ts +3 -0
- package/src/config/demoConfig.ts +5 -0
- package/src/config/utils/mergeEnvIntoConfig.ts +9 -1
- package/src/generated/config.ts +57 -19
- package/src/index.ts +1 -0
- package/src/interceptors/commands/codegenInterceptors.ts +27 -0
- package/src/interceptors/extractExports.ts +21 -21
- package/src/interceptors/findOriginalSource.ts +16 -1
- package/src/interceptors/findPlugins.ts +7 -3
- package/src/interceptors/generateInterceptor.ts +39 -15
- package/src/interceptors/generateInterceptors.ts +9 -6
- package/src/interceptors/parseStructure.ts +14 -1
- package/src/interceptors/writeInterceptors.ts +7 -7
- package/src/utils/resolveDependenciesSync.ts +11 -3
- package/src/utils/resolveDependency.ts +7 -0
- package/src/withGraphCommerce.ts +15 -6
package/src/generated/config.ts
CHANGED
|
@@ -104,6 +104,8 @@ export type DatalayerConfig = {
|
|
|
104
104
|
* Below is a list of all possible configurations that can be set by GraphCommerce.
|
|
105
105
|
*/
|
|
106
106
|
export type GraphCommerceConfig = {
|
|
107
|
+
/** Configuration for the SidebarGallery component */
|
|
108
|
+
breadcrumbs?: InputMaybe<Scalars['Boolean']['input']>;
|
|
107
109
|
/**
|
|
108
110
|
* The canonical base URL is used for SEO purposes.
|
|
109
111
|
*
|
|
@@ -157,14 +159,17 @@ export type GraphCommerceConfig = {
|
|
|
157
159
|
* Default: 'false'
|
|
158
160
|
*/
|
|
159
161
|
crossSellsRedirectItems?: InputMaybe<Scalars['Boolean']['input']>;
|
|
162
|
+
/** Enables the shipping notes field in the checkout */
|
|
163
|
+
customerAddressNoteEnable?: InputMaybe<Scalars['Boolean']['input']>;
|
|
160
164
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* This value should match Magento 2's configuration value for
|
|
165
|
-
* `customer/create_account/confirm` and should be removed once we can query
|
|
165
|
+
* Enables company fields inside the checkout:
|
|
166
|
+
* - Company name
|
|
167
|
+
* - VAT ID
|
|
166
168
|
*/
|
|
167
|
-
|
|
169
|
+
customerCompanyFieldsEnable?: InputMaybe<Scalars['Boolean']['input']>;
|
|
170
|
+
/** Enable customer account deletion through the account section */
|
|
171
|
+
customerDeleteEnabled?: InputMaybe<Scalars['Boolean']['input']>;
|
|
172
|
+
/** Datalayer config */
|
|
168
173
|
dataLayer?: InputMaybe<DatalayerConfig>;
|
|
169
174
|
/** Debug configuration for GraphCommerce */
|
|
170
175
|
debug?: InputMaybe<GraphCommerceDebugConfig>;
|
|
@@ -275,6 +280,12 @@ export type GraphCommerceConfig = {
|
|
|
275
280
|
* - https://magento2.test/graphql
|
|
276
281
|
*/
|
|
277
282
|
magentoEndpoint: Scalars['String']['input'];
|
|
283
|
+
/**
|
|
284
|
+
* Version of the Magento backend.
|
|
285
|
+
*
|
|
286
|
+
* Values: 245, 246, 247 for Magento 2.4.5, 2.4.6, 2.4.7 respectively.
|
|
287
|
+
*/
|
|
288
|
+
magentoVersion: Scalars['Int']['input'];
|
|
278
289
|
/** To enable next.js' preview mode, configure the secret you'd like to use. */
|
|
279
290
|
previewSecret?: InputMaybe<Scalars['String']['input']>;
|
|
280
291
|
/**
|
|
@@ -285,6 +296,13 @@ export type GraphCommerceConfig = {
|
|
|
285
296
|
productFiltersLayout?: InputMaybe<ProductFiltersLayout>;
|
|
286
297
|
/** Product filters with better UI for mobile and desktop. */
|
|
287
298
|
productFiltersPro?: InputMaybe<Scalars['Boolean']['input']>;
|
|
299
|
+
/**
|
|
300
|
+
* Pagination variant for the product listings.
|
|
301
|
+
*
|
|
302
|
+
* COMPACT means: "< Page X of Y >"
|
|
303
|
+
* EXTENDED means: "< 1 2 ... 4 [5] 6 ... 10 11 >"
|
|
304
|
+
*/
|
|
305
|
+
productListPaginationVariant?: InputMaybe<PaginationVariant>;
|
|
288
306
|
/**
|
|
289
307
|
* By default we route products to /p/[url] but you can change this to /product/[url] if you wish.
|
|
290
308
|
*
|
|
@@ -345,6 +363,12 @@ export type GraphCommerceStorefrontConfig = {
|
|
|
345
363
|
canonicalBaseUrl?: InputMaybe<Scalars['String']['input']>;
|
|
346
364
|
/** Due to a limitation of the GraphQL API it is not possible to determine if a cart should be displayed including or excluding tax. */
|
|
347
365
|
cartDisplayPricesInclTax?: InputMaybe<Scalars['Boolean']['input']>;
|
|
366
|
+
/**
|
|
367
|
+
* Enables company fields inside the checkout:
|
|
368
|
+
* - Company name
|
|
369
|
+
* - VAT ID
|
|
370
|
+
*/
|
|
371
|
+
customerCompanyFieldsEnable?: InputMaybe<Scalars['Boolean']['input']>;
|
|
348
372
|
/**
|
|
349
373
|
* There can only be one entry with defaultLocale set to true.
|
|
350
374
|
* - If there are more, the first one is used.
|
|
@@ -365,11 +389,7 @@ export type GraphCommerceStorefrontConfig = {
|
|
|
365
389
|
googleTagmanagerId?: InputMaybe<Scalars['String']['input']>;
|
|
366
390
|
/** Add a gcms-locales header to make sure queries return in a certain language, can be an array to define fallbacks. */
|
|
367
391
|
hygraphLocales?: InputMaybe<Array<Scalars['String']['input']>>;
|
|
368
|
-
/**
|
|
369
|
-
* Specify a custom locale for to load translations. Must be lowercase valid locale.
|
|
370
|
-
*
|
|
371
|
-
* This value is also used for the Intl.
|
|
372
|
-
*/
|
|
392
|
+
/** Custom locale used to load the .po files. Must be a valid locale, also used for Intl functions. */
|
|
373
393
|
linguiLocale?: InputMaybe<Scalars['String']['input']>;
|
|
374
394
|
/**
|
|
375
395
|
* Must be a [locale string](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers) for automatic redirects to work.
|
|
@@ -388,6 +408,11 @@ export type GraphCommerceStorefrontConfig = {
|
|
|
388
408
|
* - b2b-us
|
|
389
409
|
*/
|
|
390
410
|
magentoStoreCode: Scalars['String']['input'];
|
|
411
|
+
/**
|
|
412
|
+
* Allow the site to be indexed by search engines.
|
|
413
|
+
* If false, the robots.txt file will be set to disallow all.
|
|
414
|
+
*/
|
|
415
|
+
robotsAllow?: InputMaybe<Scalars['Boolean']['input']>;
|
|
391
416
|
};
|
|
392
417
|
|
|
393
418
|
/** Options to configure which values will be replaced when a variant is selected on the product page. */
|
|
@@ -407,6 +432,10 @@ export type MagentoConfigurableVariantValues = {
|
|
|
407
432
|
url?: InputMaybe<Scalars['Boolean']['input']>;
|
|
408
433
|
};
|
|
409
434
|
|
|
435
|
+
export type PaginationVariant =
|
|
436
|
+
| 'COMPACT'
|
|
437
|
+
| 'EXTENDED';
|
|
438
|
+
|
|
410
439
|
export type ProductFiltersLayout =
|
|
411
440
|
| 'DEFAULT'
|
|
412
441
|
| 'SIDEBAR';
|
|
@@ -443,6 +472,8 @@ export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny
|
|
|
443
472
|
|
|
444
473
|
export const CompareVariantSchema = z.enum(['CHECKBOX', 'ICON']);
|
|
445
474
|
|
|
475
|
+
export const PaginationVariantSchema = z.enum(['COMPACT', 'EXTENDED']);
|
|
476
|
+
|
|
446
477
|
export const ProductFiltersLayoutSchema = z.enum(['DEFAULT', 'SIDEBAR']);
|
|
447
478
|
|
|
448
479
|
export const SidebarGalleryPaginationVariantSchema = z.enum(['DOTS', 'THUMBNAILS_BOTTOM']);
|
|
@@ -455,18 +486,21 @@ export function DatalayerConfigSchema(): z.ZodObject<Properties<DatalayerConfig>
|
|
|
455
486
|
|
|
456
487
|
export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerceConfig>> {
|
|
457
488
|
return z.object({
|
|
489
|
+
breadcrumbs: z.boolean().default(false).nullish(),
|
|
458
490
|
canonicalBaseUrl: z.string().min(1),
|
|
459
491
|
cartDisplayPricesInclTax: z.boolean().nullish(),
|
|
460
492
|
compare: z.boolean().nullish(),
|
|
461
|
-
compareVariant: CompareVariantSchema.nullish(),
|
|
462
|
-
configurableVariantForSimple: z.boolean().nullish(),
|
|
493
|
+
compareVariant: CompareVariantSchema.default("ICON").nullish(),
|
|
494
|
+
configurableVariantForSimple: z.boolean().default(false).nullish(),
|
|
463
495
|
configurableVariantValues: MagentoConfigurableVariantValuesSchema().nullish(),
|
|
464
|
-
crossSellsHideCartItems: z.boolean().nullish(),
|
|
465
|
-
crossSellsRedirectItems: z.boolean().nullish(),
|
|
466
|
-
|
|
496
|
+
crossSellsHideCartItems: z.boolean().default(false).nullish(),
|
|
497
|
+
crossSellsRedirectItems: z.boolean().default(false).nullish(),
|
|
498
|
+
customerAddressNoteEnable: z.boolean().nullish(),
|
|
499
|
+
customerCompanyFieldsEnable: z.boolean().nullish(),
|
|
500
|
+
customerDeleteEnabled: z.boolean().nullish(),
|
|
467
501
|
dataLayer: DatalayerConfigSchema().nullish(),
|
|
468
502
|
debug: GraphCommerceDebugConfigSchema().nullish(),
|
|
469
|
-
demoMode: z.boolean().nullish(),
|
|
503
|
+
demoMode: z.boolean().default(true).nullish(),
|
|
470
504
|
enableGuestCheckoutLogin: z.boolean().nullish(),
|
|
471
505
|
googleAnalyticsId: z.string().nullish(),
|
|
472
506
|
googleRecaptchaKey: z.string().nullish(),
|
|
@@ -478,9 +512,11 @@ export function GraphCommerceConfigSchema(): z.ZodObject<Properties<GraphCommerc
|
|
|
478
512
|
hygraphWriteAccessToken: z.string().nullish(),
|
|
479
513
|
limitSsg: z.boolean().nullish(),
|
|
480
514
|
magentoEndpoint: z.string().min(1),
|
|
515
|
+
magentoVersion: z.number(),
|
|
481
516
|
previewSecret: z.string().nullish(),
|
|
482
|
-
productFiltersLayout: ProductFiltersLayoutSchema.nullish(),
|
|
517
|
+
productFiltersLayout: ProductFiltersLayoutSchema.default("DEFAULT").nullish(),
|
|
483
518
|
productFiltersPro: z.boolean().nullish(),
|
|
519
|
+
productListPaginationVariant: PaginationVariantSchema.default("COMPACT").nullish(),
|
|
484
520
|
productRoute: z.string().nullish(),
|
|
485
521
|
recentlyViewedProducts: RecentlyViewedProductsConfigSchema().nullish(),
|
|
486
522
|
robotsAllow: z.boolean().nullish(),
|
|
@@ -504,6 +540,7 @@ export function GraphCommerceStorefrontConfigSchema(): z.ZodObject<Properties<Gr
|
|
|
504
540
|
return z.object({
|
|
505
541
|
canonicalBaseUrl: z.string().nullish(),
|
|
506
542
|
cartDisplayPricesInclTax: z.boolean().nullish(),
|
|
543
|
+
customerCompanyFieldsEnable: z.boolean().nullish(),
|
|
507
544
|
defaultLocale: z.boolean().nullish(),
|
|
508
545
|
domain: z.string().nullish(),
|
|
509
546
|
googleAnalyticsId: z.string().nullish(),
|
|
@@ -512,7 +549,8 @@ export function GraphCommerceStorefrontConfigSchema(): z.ZodObject<Properties<Gr
|
|
|
512
549
|
hygraphLocales: z.array(z.string().min(1)).nullish(),
|
|
513
550
|
linguiLocale: z.string().nullish(),
|
|
514
551
|
locale: z.string().min(1),
|
|
515
|
-
magentoStoreCode: z.string().min(1)
|
|
552
|
+
magentoStoreCode: z.string().min(1),
|
|
553
|
+
robotsAllow: z.boolean().nullish()
|
|
516
554
|
})
|
|
517
555
|
}
|
|
518
556
|
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './withGraphCommerce'
|
|
|
9
9
|
export * from './generated/config'
|
|
10
10
|
export * from './config'
|
|
11
11
|
export * from './runtimeCachingOptimizations'
|
|
12
|
+
export * from './interceptors/commands/codegenInterceptors'
|
|
12
13
|
|
|
13
14
|
export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
|
|
14
15
|
Prev: React.FC<P>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { loadConfig } from '../../config/loadConfig'
|
|
2
|
+
import { resolveDependency } from '../../utils/resolveDependency'
|
|
3
|
+
import { findPlugins } from '../findPlugins'
|
|
4
|
+
import { generateInterceptors } from '../generateInterceptors'
|
|
5
|
+
import { writeInterceptors } from '../writeInterceptors'
|
|
6
|
+
import dotenv from 'dotenv'
|
|
7
|
+
|
|
8
|
+
dotenv.config()
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
11
|
+
export async function codegenInterceptors() {
|
|
12
|
+
const conf = loadConfig(process.cwd())
|
|
13
|
+
|
|
14
|
+
const [plugins, errors] = findPlugins(conf)
|
|
15
|
+
|
|
16
|
+
const generatedInterceptors = await generateInterceptors(
|
|
17
|
+
plugins,
|
|
18
|
+
resolveDependency(),
|
|
19
|
+
conf.debug,
|
|
20
|
+
true,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// const generated = Date.now()
|
|
24
|
+
// console.log('Generated interceptors in', generated - found, 'ms')
|
|
25
|
+
|
|
26
|
+
await writeInterceptors(generatedInterceptors)
|
|
27
|
+
}
|
|
@@ -112,8 +112,8 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
112
112
|
case 'undefined':
|
|
113
113
|
return undefined
|
|
114
114
|
default:
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
return RUNTIME_VALUE
|
|
116
|
+
// throw new UnsupportedValueError(`Unknown identifier "${node.value}"`, path)
|
|
117
117
|
}
|
|
118
118
|
} else if (isArrayExpression(node)) {
|
|
119
119
|
// e.g. [1, 2, 3]
|
|
@@ -123,11 +123,11 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
123
123
|
if (elem) {
|
|
124
124
|
if (elem.spread) {
|
|
125
125
|
// e.g. [ ...a ]
|
|
126
|
-
|
|
127
|
-
throw new UnsupportedValueError(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
126
|
+
return RUNTIME_VALUE
|
|
127
|
+
// throw new UnsupportedValueError(
|
|
128
|
+
// 'Unsupported spread operator in the Array Expression',
|
|
129
|
+
// path,
|
|
130
|
+
// )
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
arr.push(extractValue(elem.expression, path && [...path, `[${i}]`], optional))
|
|
@@ -144,11 +144,11 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
144
144
|
for (const prop of node.properties) {
|
|
145
145
|
if (!isKeyValueProperty(prop)) {
|
|
146
146
|
// e.g. { ...a }
|
|
147
|
-
|
|
148
|
-
throw new UnsupportedValueError(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
147
|
+
return RUNTIME_VALUE
|
|
148
|
+
// throw new UnsupportedValueError(
|
|
149
|
+
// 'Unsupported spread operator in the Object Expression',
|
|
150
|
+
// path,
|
|
151
|
+
// )
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
let key
|
|
@@ -159,11 +159,11 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
159
159
|
// e.g. { "a": 1, "b": 2 }
|
|
160
160
|
key = prop.key.value
|
|
161
161
|
} else {
|
|
162
|
-
|
|
163
|
-
throw new UnsupportedValueError(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
162
|
+
return RUNTIME_VALUE
|
|
163
|
+
// throw new UnsupportedValueError(
|
|
164
|
+
// `Unsupported key type "${prop.key.type}" in the Object Expression`,
|
|
165
|
+
// path,
|
|
166
|
+
// )
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
obj[key] = extractValue(prop.value, path && [...path, key])
|
|
@@ -174,8 +174,8 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
174
174
|
// e.g. `abc`
|
|
175
175
|
if (node.expressions.length !== 0) {
|
|
176
176
|
// TODO: should we add support for `${'e'}d${'g'}'e'`?
|
|
177
|
-
|
|
178
|
-
throw new UnsupportedValueError('Unsupported template literal with expressions', path)
|
|
177
|
+
return RUNTIME_VALUE
|
|
178
|
+
// throw new UnsupportedValueError('Unsupported template literal with expressions', path)
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// When TemplateLiteral has 0 expressions, the length of quasis is always 1.
|
|
@@ -191,8 +191,8 @@ function extractValue(node: Node, path?: string[], optional: boolean = false): a
|
|
|
191
191
|
|
|
192
192
|
return cooked ?? raw
|
|
193
193
|
} else {
|
|
194
|
-
|
|
195
|
-
throw new UnsupportedValueError(`Unsupported node type "${node.type}"`, path)
|
|
194
|
+
return RUNTIME_VALUE
|
|
195
|
+
// throw new UnsupportedValueError(`Unsupported node type "${node.type}"`, path)
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -32,6 +32,21 @@ function parseAndFindExport(
|
|
|
32
32
|
break
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
37
|
+
for (const specifier of node.specifiers) {
|
|
38
|
+
if (specifier.type === 'ExportSpecifier') {
|
|
39
|
+
if (specifier.exported?.value === findExport) return resolved
|
|
40
|
+
} else if (specifier.type === 'ExportDefaultSpecifier') {
|
|
41
|
+
// todo
|
|
42
|
+
} else if (specifier.type === 'ExportNamespaceSpecifier') {
|
|
43
|
+
// todo
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// todo: if (node.type === 'ExportDefaultDeclaration') {}
|
|
49
|
+
// todo: if (node.type === 'ExportDefaultExpression') {}
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
const exports = ast.body
|
|
@@ -95,7 +110,7 @@ export function findOriginalSource(
|
|
|
95
110
|
return {
|
|
96
111
|
resolved: undefined,
|
|
97
112
|
error: new Error(
|
|
98
|
-
`
|
|
113
|
+
`Plugin target not found ${plug.targetModule}#${plug.sourceExport} for plugin ${plug.sourceModule}#${plug.sourceExport}`,
|
|
99
114
|
),
|
|
100
115
|
}
|
|
101
116
|
}
|
|
@@ -17,10 +17,14 @@ export function findPlugins(config: GraphCommerceConfig, cwd: string = process.c
|
|
|
17
17
|
|
|
18
18
|
const errors: string[] = []
|
|
19
19
|
const plugins: PluginConfig[] = []
|
|
20
|
-
dependencies.forEach((
|
|
21
|
-
const files = globSync(`${
|
|
20
|
+
dependencies.forEach((filePath, packageName) => {
|
|
21
|
+
const files = globSync(`${filePath}/plugins/**/*.{ts,tsx}`)
|
|
22
22
|
files.forEach((file) => {
|
|
23
|
-
|
|
23
|
+
let sourceModule = file.replace('.tsx', '').replace('.ts', '')
|
|
24
|
+
if (file.startsWith(filePath))
|
|
25
|
+
sourceModule = `${packageName}/${sourceModule.slice(filePath.length + 1)}`
|
|
26
|
+
|
|
27
|
+
if (packageName === '.' && !sourceModule.startsWith('.')) sourceModule = `./${sourceModule}`
|
|
24
28
|
|
|
25
29
|
try {
|
|
26
30
|
const ast = parseFileSync(file, { syntax: 'typescript', tsx: true })
|
|
@@ -66,11 +66,11 @@ export type Interceptor = ResolveDependencyReturn & {
|
|
|
66
66
|
|
|
67
67
|
export type MaterializedPlugin = Interceptor & { template: string }
|
|
68
68
|
|
|
69
|
-
export const SOURCE_START = '/**
|
|
70
|
-
export const SOURCE_END = '/**
|
|
69
|
+
export const SOURCE_START = '/** Original source starts here (do not modify!): **/'
|
|
70
|
+
export const SOURCE_END = '/** Original source ends here (do not modify!) **/'
|
|
71
71
|
|
|
72
72
|
const originalSuffix = 'Original'
|
|
73
|
-
const sourceSuffix = '
|
|
73
|
+
const sourceSuffix = 'Plugin'
|
|
74
74
|
const interceptorSuffix = 'Interceptor'
|
|
75
75
|
const disabledSuffix = 'Disabled'
|
|
76
76
|
const name = (plugin: PluginConfig) =>
|
|
@@ -81,9 +81,9 @@ const name = (plugin: PluginConfig) =>
|
|
|
81
81
|
const fileName = (plugin: PluginConfig) => `${plugin.sourceModule}#${plugin.sourceExport}`
|
|
82
82
|
|
|
83
83
|
const originalName = (n: string) => `${n}${originalSuffix}`
|
|
84
|
-
const sourceName = (n: string) => `${n}
|
|
84
|
+
const sourceName = (n: string) => `${n}`
|
|
85
85
|
const interceptorName = (n: string) => `${n}${interceptorSuffix}`
|
|
86
|
-
const interceptorPropsName = (n: string) => `${
|
|
86
|
+
const interceptorPropsName = (n: string) => `${n}Props`
|
|
87
87
|
|
|
88
88
|
export function moveRelativeDown(plugins: PluginConfig[]) {
|
|
89
89
|
return [...plugins].sort((a, b) => {
|
|
@@ -153,7 +153,12 @@ export async function generateInterceptor(
|
|
|
153
153
|
const duplicateInterceptors = new Set()
|
|
154
154
|
|
|
155
155
|
let carry = originalName(base)
|
|
156
|
-
|
|
156
|
+
let carryProps: string[] = []
|
|
157
|
+
const pluginSee: string[] = []
|
|
158
|
+
|
|
159
|
+
pluginSee.push(
|
|
160
|
+
`@see {@link file://${interceptor.sourcePathRelative}} for original source file`,
|
|
161
|
+
)
|
|
157
162
|
|
|
158
163
|
const pluginStr = plugins
|
|
159
164
|
.reverse()
|
|
@@ -175,17 +180,20 @@ export async function generateInterceptor(
|
|
|
175
180
|
s.replace(originalSuffix, disabledSuffix),
|
|
176
181
|
).visitModule(ast)
|
|
177
182
|
|
|
178
|
-
carryProps.push(
|
|
183
|
+
carryProps.push(`React.ComponentProps<typeof ${sourceName(name(p))}>`)
|
|
179
184
|
|
|
180
|
-
|
|
185
|
+
pluginSee.push(
|
|
186
|
+
`@see {${sourceName(name(p))}} for replacement of the original source (original source not used)`,
|
|
187
|
+
)
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
if (isReactPluginConfig(p)) {
|
|
184
|
-
|
|
191
|
+
const withBraces = config.pluginStatus || process.env.NODE_ENV === 'development'
|
|
185
192
|
|
|
186
193
|
result = `
|
|
187
|
-
type ${interceptorPropsName(name(p))} =
|
|
188
|
-
|
|
194
|
+
type ${interceptorPropsName(name(p))} = ${carryProps.join(' & ')} & OmitPrev<React.ComponentProps<typeof ${sourceName(name(p))}>, 'Prev'>
|
|
195
|
+
|
|
196
|
+
const ${interceptorName(name(p))} = (props: ${interceptorPropsName(name(p))}) => ${withBraces ? `{` : '('}
|
|
189
197
|
${config.pluginStatus ? `logOnce(\`🔌 Rendering ${base} with plugin(s): ${wrapChain} wrapping <${base}/>\`)` : ''}
|
|
190
198
|
|
|
191
199
|
${
|
|
@@ -194,8 +202,11 @@ export async function generateInterceptor(
|
|
|
194
202
|
logOnce('${fileName(p)} does not spread props to prev: <Prev {...props}/>. This will cause issues if multiple plugins are applied to this component.')`
|
|
195
203
|
: ''
|
|
196
204
|
}
|
|
197
|
-
return <${sourceName(name(p))} {...props} Prev={${carry}
|
|
198
|
-
}`
|
|
205
|
+
${withBraces ? `return` : ''} <${sourceName(name(p))} {...props} Prev={${carry}} />
|
|
206
|
+
${withBraces ? `}` : ')'}`
|
|
207
|
+
|
|
208
|
+
carryProps = [interceptorPropsName(name(p))]
|
|
209
|
+
pluginSee.push(`@see {${sourceName(name(p))}} for source of applied plugin`)
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
if (isMethodPluginConfig(p)) {
|
|
@@ -203,6 +214,7 @@ export async function generateInterceptor(
|
|
|
203
214
|
${config.pluginStatus ? `logOnce(\`🔌 Calling ${base} with plugin(s): ${wrapChain} wrapping ${base}()\`)` : ''}
|
|
204
215
|
return ${sourceName(name(p))}(${carry}, ...args)
|
|
205
216
|
}`
|
|
217
|
+
pluginSee.push(`@see {${sourceName(name(p))}} for source of applied plugin`)
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
carry = p.type === 'replace' ? sourceName(name(p)) : interceptorName(name(p))
|
|
@@ -211,13 +223,23 @@ export async function generateInterceptor(
|
|
|
211
223
|
.filter((v) => !!v)
|
|
212
224
|
.join('\n')
|
|
213
225
|
|
|
214
|
-
const isComponent = plugins.every((p) =>
|
|
226
|
+
const isComponent = plugins.every((p) => isReactPluginConfig(p))
|
|
215
227
|
if (isComponent && plugins.some((p) => isMethodPluginConfig(p))) {
|
|
216
228
|
throw new Error(`Cannot mix React and Method plugins for ${base} in ${dependency}.`)
|
|
217
229
|
}
|
|
218
230
|
|
|
231
|
+
const seeString = `
|
|
232
|
+
/**
|
|
233
|
+
* Here you see the 'interceptor' that is applying all the configured plugins.
|
|
234
|
+
*
|
|
235
|
+
* This file is NOT meant to be modified directly and is auto-generated if the plugins or the original source changes.
|
|
236
|
+
*
|
|
237
|
+
${pluginSee.map((s) => `* ${s}`).join('\n')}
|
|
238
|
+
*/`
|
|
239
|
+
|
|
219
240
|
if (process.env.NODE_ENV === 'development' && isComponent) {
|
|
220
241
|
return `${pluginStr}
|
|
242
|
+
${seeString}
|
|
221
243
|
export const ${base}: typeof ${carry} = (props) => {
|
|
222
244
|
return <${carry} {...props} data-plugin />
|
|
223
245
|
}`
|
|
@@ -225,6 +247,7 @@ export async function generateInterceptor(
|
|
|
225
247
|
|
|
226
248
|
return `
|
|
227
249
|
${pluginStr}
|
|
250
|
+
${seeString}
|
|
228
251
|
export const ${base} = ${carry}
|
|
229
252
|
`
|
|
230
253
|
})
|
|
@@ -247,12 +270,13 @@ export async function generateInterceptor(
|
|
|
247
270
|
/* This file is automatically generated for ${dependency} */
|
|
248
271
|
${
|
|
249
272
|
Object.values(targetExports).some((t) => t.some((p) => p.type === 'component'))
|
|
250
|
-
? `import type { DistributedOmit } from 'type-fest'`
|
|
273
|
+
? `import type { DistributedOmit as OmitPrev } from 'type-fest'`
|
|
251
274
|
: ''
|
|
252
275
|
}
|
|
253
276
|
|
|
254
277
|
${pluginImports}
|
|
255
278
|
|
|
279
|
+
/** @see {@link file://${interceptor.sourcePathRelative}} for source of original */
|
|
256
280
|
${SOURCE_START}
|
|
257
281
|
${printSync(ast).code}
|
|
258
282
|
${SOURCE_END}
|
|
@@ -19,6 +19,7 @@ export async function generateInterceptors(
|
|
|
19
19
|
plugins: PluginConfig[],
|
|
20
20
|
resolve: ResolveDependency,
|
|
21
21
|
config?: GraphCommerceDebugConfig | null | undefined,
|
|
22
|
+
force?: boolean,
|
|
22
23
|
): Promise<GenerateInterceptorsReturn> {
|
|
23
24
|
const byTargetModuleAndExport = moveRelativeDown(plugins).reduce<Record<string, Interceptor>>(
|
|
24
25
|
(acc, plug) => {
|
|
@@ -68,12 +69,14 @@ export async function generateInterceptors(
|
|
|
68
69
|
Object.entries(byTargetModuleAndExport).map(async ([target, interceptor]) => {
|
|
69
70
|
const file = `${interceptor.fromRoot}.interceptor.tsx`
|
|
70
71
|
|
|
71
|
-
const originalSource =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
const originalSource =
|
|
73
|
+
!force &&
|
|
74
|
+
(await fs
|
|
75
|
+
.access(file, fs.constants.F_OK)
|
|
76
|
+
.then(() => true)
|
|
77
|
+
.catch(() => false))
|
|
78
|
+
? (await fs.readFile(file)).toString()
|
|
79
|
+
: undefined
|
|
77
80
|
|
|
78
81
|
return [
|
|
79
82
|
target,
|
|
@@ -34,10 +34,11 @@ export function parseStructure(ast: Module, gcConfig: GraphCommerceConfig, sourc
|
|
|
34
34
|
} = exports
|
|
35
35
|
|
|
36
36
|
const exportVals = Object.keys(rest)
|
|
37
|
+
|
|
37
38
|
if (component && !moduleConfig) exportVals.push('Plugin')
|
|
38
39
|
if (func && !moduleConfig) exportVals.push('plugin')
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
const pluginConfigs = exportVals
|
|
41
42
|
.map((exportVal) => {
|
|
42
43
|
let config = isObject(moduleConfig) ? moduleConfig : {}
|
|
43
44
|
|
|
@@ -49,6 +50,7 @@ export function parseStructure(ast: Module, gcConfig: GraphCommerceConfig, sourc
|
|
|
49
50
|
config = { ...moduleConfig, export: exportVal }
|
|
50
51
|
} else {
|
|
51
52
|
console.error(`Plugin configuration invalid! See ${sourceModule}`)
|
|
53
|
+
return null
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const parsed = pluginConfigParsed.safeParse(config)
|
|
@@ -79,4 +81,15 @@ export function parseStructure(ast: Module, gcConfig: GraphCommerceConfig, sourc
|
|
|
79
81
|
return val
|
|
80
82
|
})
|
|
81
83
|
.filter(nonNullable)
|
|
84
|
+
|
|
85
|
+
const newPluginConfigs = pluginConfigs.reduce<PluginConfig[]>((acc, pluginConfig) => {
|
|
86
|
+
if (
|
|
87
|
+
!acc.find((accPluginConfig) => accPluginConfig.sourceExport === pluginConfig.sourceExport)
|
|
88
|
+
) {
|
|
89
|
+
acc.push(pluginConfig)
|
|
90
|
+
}
|
|
91
|
+
return acc
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
return newPluginConfigs
|
|
82
95
|
}
|
|
@@ -17,24 +17,24 @@ export async function writeInterceptors(
|
|
|
17
17
|
cwd: string = process.cwd(),
|
|
18
18
|
) {
|
|
19
19
|
const dependencies = resolveDependenciesSync(cwd)
|
|
20
|
-
const existing
|
|
20
|
+
const existing = new Set<string>()
|
|
21
21
|
dependencies.forEach((dependency) => {
|
|
22
22
|
const files = globSync(
|
|
23
23
|
[`${dependency}/**/*.interceptor.tsx`, `${dependency}/**/*.interceptor.ts`],
|
|
24
24
|
{ cwd },
|
|
25
25
|
)
|
|
26
|
-
existing.
|
|
26
|
+
files.forEach((file) => existing.add(file))
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
const written = Object.entries(interceptors).map(async ([, plugin]) => {
|
|
30
30
|
const extension = plugin.sourcePath.endsWith('.tsx') ? '.tsx' : '.ts'
|
|
31
31
|
const relativeFile = `${plugin.fromRoot}.interceptor${extension}`
|
|
32
32
|
|
|
33
|
-
if (existing.
|
|
34
|
-
|
|
33
|
+
if (existing.has(relativeFile)) {
|
|
34
|
+
existing.delete(relativeFile)
|
|
35
35
|
}
|
|
36
|
-
if (existing.
|
|
37
|
-
|
|
36
|
+
if (existing.has(`./${relativeFile}`)) {
|
|
37
|
+
existing.delete(`./${relativeFile}`)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const fileToWrite = path.join(cwd, relativeFile)
|
|
@@ -47,7 +47,7 @@ export async function writeInterceptors(
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
// Cleanup unused interceptors
|
|
50
|
-
const cleaned = existing.map(
|
|
50
|
+
const cleaned = [...existing].map(
|
|
51
51
|
async (file) => (await checkFileExists(file)) && (await fs.unlink(file)),
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -12,6 +12,7 @@ function resolveRecursivePackageJson(
|
|
|
12
12
|
dependencyPath: string,
|
|
13
13
|
dependencyStructure: DependencyStructure,
|
|
14
14
|
root: string,
|
|
15
|
+
additionalDependencies: string[] = [],
|
|
15
16
|
) {
|
|
16
17
|
const isRoot = dependencyPath === root
|
|
17
18
|
const fileName = require.resolve(path.join(dependencyPath, 'package.json'))
|
|
@@ -28,8 +29,9 @@ function resolveRecursivePackageJson(
|
|
|
28
29
|
const dependencies = [
|
|
29
30
|
...new Set(
|
|
30
31
|
[
|
|
31
|
-
...Object.keys(packageJson.dependencies ??
|
|
32
|
-
...Object.keys(packageJson.devDependencies ??
|
|
32
|
+
...Object.keys(packageJson.dependencies ?? []),
|
|
33
|
+
...Object.keys(packageJson.devDependencies ?? []),
|
|
34
|
+
...additionalDependencies,
|
|
33
35
|
// ...Object.keys(packageJson.peerDependencies ?? {}),
|
|
34
36
|
].filter((name) => name.includes('graphcommerce')),
|
|
35
37
|
),
|
|
@@ -77,7 +79,13 @@ export function sortDependencies(dependencyStructure: DependencyStructure): Pack
|
|
|
77
79
|
export function resolveDependenciesSync(root = process.cwd()) {
|
|
78
80
|
const cached = resolveCache.get(root)
|
|
79
81
|
if (cached) return cached
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
const dependencyStructure = resolveRecursivePackageJson(
|
|
84
|
+
root,
|
|
85
|
+
{},
|
|
86
|
+
root,
|
|
87
|
+
process.env.PRIVATE_ADDITIONAL_DEPENDENCIES?.split(',') ?? [],
|
|
88
|
+
)
|
|
81
89
|
|
|
82
90
|
const sorted = sortDependencies(dependencyStructure)
|
|
83
91
|
resolveCache.set(root, sorted)
|
|
@@ -11,6 +11,7 @@ export type ResolveDependencyReturn =
|
|
|
11
11
|
fromModule: string
|
|
12
12
|
source: string
|
|
13
13
|
sourcePath: string
|
|
14
|
+
sourcePathRelative: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export type ResolveDependency = (
|
|
@@ -31,6 +32,7 @@ export const resolveDependency = (cwd: string = process.cwd()) => {
|
|
|
31
32
|
root: '.',
|
|
32
33
|
source: '',
|
|
33
34
|
sourcePath: '',
|
|
35
|
+
sourcePathRelative: '',
|
|
34
36
|
dependency,
|
|
35
37
|
fromRoot: dependency,
|
|
36
38
|
fromModule: dependency,
|
|
@@ -73,6 +75,10 @@ export const resolveDependency = (cwd: string = process.cwd()) => {
|
|
|
73
75
|
? '.'
|
|
74
76
|
: `./${relative.split('/')[relative.split('/').length - 1]}`
|
|
75
77
|
|
|
78
|
+
const sourcePathRelative = !sourcePath
|
|
79
|
+
? '.'
|
|
80
|
+
: `./${sourcePath.split('/')[sourcePath.split('/').length - 1]}`
|
|
81
|
+
|
|
76
82
|
if (dependency.startsWith('./')) fromModule = `.${relative}`
|
|
77
83
|
|
|
78
84
|
dependencyPaths = {
|
|
@@ -83,6 +89,7 @@ export const resolveDependency = (cwd: string = process.cwd()) => {
|
|
|
83
89
|
fromModule,
|
|
84
90
|
source,
|
|
85
91
|
sourcePath,
|
|
92
|
+
sourcePathRelative,
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
})
|