@graphcommerce/next-config 9.0.0-canary.104 → 9.0.0-canary.106

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 9.0.0-canary.106
4
+
5
+ ### Minor Changes
6
+
7
+ - [#2385](https://github.com/graphcommerce-org/graphcommerce/pull/2385) [`3434ede`](https://github.com/graphcommerce-org/graphcommerce/commit/3434ede37855c36949711d05d13eb01906b29ad0) - Added a functionality to copy directories from packages to the project and keep them managed with GraphCommerce ([@paales](https://github.com/paales))
8
+
9
+ ## 9.0.0-canary.105
10
+
3
11
  ## 9.0.0-canary.104
4
12
 
5
13
  ### Patch Changes
@@ -121,14 +121,12 @@ it("resolves a 'root plugin' to be relative to the interceptor", async () => {
121
121
  expect(Object.keys(interceptors)[0]).toMatchInlineSnapshot(
122
122
  '"packages/magento-cart-payment-method/PaymentMethodContext/PaymentMethodContext"',
123
123
  )
124
- expectImport(
124
+ expect(
125
125
  interceptors['packages/magento-cart-payment-method/PaymentMethodContext/PaymentMethodContext']
126
126
  ?.template,
127
- ).toMatchInlineSnapshot(`
128
- "import type { DistributedOmit as OmitPrev } from 'type-fest'
129
-
130
- import { Plugin as PluginAddPaymentMethodEnhancer } from '../../../plugins/AddPaymentMethodEnhancer'"
131
- `)
127
+ ).toContain(
128
+ "import { Plugin as PluginAddPaymentMethodEnhancer } from '../../../plugins/AddPaymentMethodEnhancer'",
129
+ )
132
130
  })
133
131
 
134
132
  it('it can apply multiple plugins to a single export', async () => {
@@ -157,13 +155,12 @@ it('it can apply multiple plugins to a single export', async () => {
157
155
  const result =
158
156
  interceptors['packages/magento-cart-payment-method/PaymentMethodContext/PaymentMethodContext']
159
157
  ?.template
160
- expectImport(result).toMatchInlineSnapshot(`
161
- "import type { DistributedOmit as OmitPrev } from 'type-fest'
162
-
163
- import { Plugin as PluginAddAdyenMethods } from '@graphcommerce/magento-payment-adyen/plugins/AddAdyenMethods'
164
- import { Plugin as PluginAddMollieMethods } from '@graphcommerce/mollie-magento-payment/plugins/AddMollieMethods'"
165
- `)
166
-
158
+ expect(result).toContain(
159
+ "import { Plugin as PluginAddAdyenMethods } from '@graphcommerce/magento-payment-adyen/plugins/AddAdyenMethods'",
160
+ )
161
+ expect(result).toContain(
162
+ "import { Plugin as PluginAddMollieMethods } from '@graphcommerce/mollie-magento-payment/plugins/AddMollieMethods'",
163
+ )
167
164
  expectOriginal(result).toContain('PaymentMethodContextProviderOriginal')
168
165
 
169
166
  expectInterceptor(result).toMatchInlineSnapshot(`
@@ -351,12 +348,15 @@ it('generates method interceptors alognside component interceptors', async () =>
351
348
  resolve,
352
349
  )
353
350
 
354
- expect(Object.keys(interceptors)).toMatchInlineSnapshot(`
351
+ expect(Object.keys(interceptors)).toMatchInlineSnapshot(
352
+ `
355
353
  [
356
354
  "packages/graphql/components/GraphQLProvider/GraphQLProvider",
357
355
  "packages/graphql/config",
358
356
  ]
359
- `)
357
+ `,
358
+ )
359
+
360
360
  expect(
361
361
  interceptors['packages/graphql/components/GraphQLProvider/GraphQLProvider']?.template,
362
362
  ).toContain('MagentoGraphqlGraphqlProvider')
@@ -403,10 +403,11 @@ it('adds debug logging to interceptors for components', async () => {
403
403
  `)
404
404
 
405
405
  expectOriginal(interceptors['packages/graphql/config']?.template).toMatchInlineSnapshot(`
406
- "import type { ApolloLink, TypePolicies } from '@apollo/client'
407
- import type { GraphCommerceStorefrontConfig } from '@graphcommerce/next-config'
406
+ "import type { GraphCommerceStorefrontConfig } from '@graphcommerce/next-config'
407
+ import type { ApolloLink, TypePolicies } from '@apollo/client'
408
408
  import type { SetRequired } from 'type-fest'
409
409
  import type { MigrateCache } from './components/GraphQLProvider/migrateCache'
410
+
410
411
  export interface PreviewData {}
411
412
  export type PreviewConfig = {
412
413
  preview?: boolean
@@ -433,7 +434,6 @@ it('adds debug logging to interceptors for components', async () => {
433
434
  }
434
435
  }"
435
436
  `)
436
-
437
437
  expectInterceptor(interceptors['packages/graphql/config']?.template).toMatchInlineSnapshot(`
438
438
  "const logged: Set<string> = new Set()
439
439
  const logOnce = (log: string, ...additional: unknown[]) => {
@@ -535,8 +535,8 @@ it('Should apply overrides to the correct file', async () => {
535
535
  const result =
536
536
  interceptors['packages/magento-product/components/ProductStaticPaths/getProductStaticPaths']
537
537
  ?.template
538
- expectImport(result).toMatchInlineSnapshot(
539
- '"import { getProductStaticPaths as getProductStaticPathsreplaceGetProductStaticPaths } from \'../../../../plugins/replaceGetProductStaticPaths\'"',
538
+ expect(result).toContain(
539
+ "import { getProductStaticPaths as getProductStaticPathsreplaceGetProductStaticPaths } from '../../../../plugins/replaceGetProductStaticPaths'",
540
540
  )
541
541
 
542
542
  expectOriginal(result).toContain('getProductStaticPathsDisabled')
@@ -626,8 +626,8 @@ export const Plugin = ConfigurableProductPageName
626
626
  )
627
627
 
628
628
  const plugins = [...firstFile, ...secondFile]
629
-
630
- expect(plugins).toMatchInlineSnapshot(`
629
+ expect(plugins).toMatchInlineSnapshot(
630
+ `
631
631
  [
632
632
  {
633
633
  "enabled": true,
@@ -648,8 +648,8 @@ export const Plugin = ConfigurableProductPageName
648
648
  "type": "component",
649
649
  },
650
650
  ]
651
- `)
652
-
651
+ `,
652
+ )
653
653
  const interceptors = await generateInterceptors(plugins, resolveDependency(projectRoot))
654
654
 
655
655
  expect(Object.keys(interceptors)[0]).toMatchInlineSnapshot(
@@ -660,14 +660,14 @@ export const Plugin = ConfigurableProductPageName
660
660
  interceptors['packages/magento-product/components/ProductPageName/ProductPageName']?.template
661
661
 
662
662
  expectImport(result).toMatchInlineSnapshot(`
663
- "import type { DistributedOmit as OmitPrev } from 'type-fest'
664
-
665
- import { Plugin as PluginConfigurableProductPageName } from '@graphcommerce/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageName'
663
+ "import { Plugin as PluginConfigurableProductPageName } from '@graphcommerce/magento-product-configurable/plugins/ConfigurableProductPage/ConfigurableProductPageName'
664
+ import type { DistributedOmit as OmitPrev } from 'type-fest'
666
665
  import { ProductPageName as ProductPageNameMyPlugin } from '../../../../plugins/MyPlugin'"
667
666
  `)
668
667
 
669
668
  expectOriginal(result).toMatchInlineSnapshot(`
670
669
  "import type { ProductPageNameFragment } from './ProductPageName.gql'
670
+
671
671
  export type ProductPageNameProps = {
672
672
  product: ProductPageNameFragment
673
673
  }
@@ -736,7 +736,6 @@ it('Can correctly find exports that are default exports', async () => {
736
736
  export const iconChevronLeft = accessibilityHuman
737
737
  export const iconChevronRight = alarm
738
738
  `
739
-
740
739
  const config = {
741
740
  demoMode: true,
742
741
  configurableVariantForSimple: true,
@@ -751,8 +750,10 @@ it('Can correctly find exports that are default exports', async () => {
751
750
  const result = interceptors['packages/next-ui/icons']?.template
752
751
 
753
752
  expectImport(result).toMatchInlineSnapshot(`
754
- "import { iconChevronLeft as iconChevronLeftMyProjectIcon } from '../../plugins/MyProjectIcon'
755
- import { iconChevronRight as iconChevronRightMyProjectIcon } from '../../plugins/MyProjectIcon'"
753
+ "import {
754
+ iconChevronLeft as iconChevronLeftMyProjectIcon,
755
+ iconChevronRight as iconChevronRightMyProjectIcon,
756
+ } from '../../plugins/MyProjectIcon'"
756
757
  `)
757
758
 
758
759
  expectOriginal(result).toContain('iconChevronLeftDisabled')
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.codegen = codegen;
4
+ const generateConfig_1 = require("../config/commands/generateConfig");
5
+ const codegenInterceptors_1 = require("../interceptors/commands/codegenInterceptors");
6
+ const copyFiles_1 = require("./copyFiles");
7
+ /** Run all code generation steps in sequence */
8
+ async function codegen() {
9
+ // Copy files from packages to project
10
+ console.log('🔄 Copying files from packages to project...');
11
+ await (0, copyFiles_1.copyFiles)();
12
+ // Generate GraphCommerce config types
13
+ console.log('⚙️ Generating GraphCommerce config types...');
14
+ await (0, generateConfig_1.generateConfig)();
15
+ // Generate interceptors
16
+ console.log('🔌 Generating interceptors...');
17
+ await (0, codegenInterceptors_1.codegenInterceptors)();
18
+ }
@@ -0,0 +1,201 @@
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.copyFiles = copyFiles;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const glob_1 = require("glob");
10
+ const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
11
+ // Add debug logging helper
12
+ const debug = (...args) => {
13
+ if (process.env.DEBUG)
14
+ console.log('[copyFiles]', ...args);
15
+ };
16
+ const createManagementComment = (type) => `// managed by: ${type}`;
17
+ const MANAGED_BY_GC = createManagementComment('graphcommerce');
18
+ const MANAGED_LOCALLY = createManagementComment('local');
19
+ const GITIGNORE_SECTION_START = '# managed by: graphcommerce';
20
+ const GITIGNORE_SECTION_END = '# end managed by: graphcommerce';
21
+ /**
22
+ * Updates the .gitignore file with a list of GraphCommerce managed files
23
+ *
24
+ * - Removes any existing GraphCommerce managed files section
25
+ * - If managedFiles is not empty, adds a new section with the files
26
+ * - If managedFiles is empty, just cleans up the existing section
27
+ * - Ensures the file ends with a newline
28
+ */
29
+ async function updateGitignore(managedFiles) {
30
+ const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
31
+ let content;
32
+ debug('Updating .gitignore with managed files:', managedFiles);
33
+ try {
34
+ content = await promises_1.default.readFile(gitignorePath, 'utf-8');
35
+ debug('Existing .gitignore content:', content);
36
+ }
37
+ catch (err) {
38
+ debug('.gitignore not found, creating new file');
39
+ content = '';
40
+ }
41
+ // Remove existing GraphCommerce section if it exists
42
+ const sectionRegex = new RegExp(`${GITIGNORE_SECTION_START}[\\s\\S]*?${GITIGNORE_SECTION_END}\\n?`, 'g');
43
+ content = content.replace(sectionRegex, '');
44
+ debug('Content after removing existing section:', content);
45
+ // Only add new section if there are files to manage
46
+ if (managedFiles.length > 0) {
47
+ const newSection = [
48
+ GITIGNORE_SECTION_START,
49
+ ...managedFiles.sort(),
50
+ GITIGNORE_SECTION_END,
51
+ '', // Empty line at the end
52
+ ].join('\n');
53
+ debug('New section to add:', newSection);
54
+ // Append the new section
55
+ content = `${content.trim()}\n\n${newSection}`;
56
+ }
57
+ else {
58
+ // Just trim the content when no files to manage
59
+ content = `${content.trim()}\n`;
60
+ }
61
+ debug('Final content:', content);
62
+ try {
63
+ await promises_1.default.writeFile(gitignorePath, content);
64
+ debug('Successfully wrote .gitignore file');
65
+ }
66
+ catch (err) {
67
+ console.error('Error writing .gitignore:', err);
68
+ }
69
+ }
70
+ /** Determines how a file should be managed based on its content */
71
+ function getFileManagement(content) {
72
+ if (!content)
73
+ return 'graphcommerce';
74
+ const contentStr = content.toString();
75
+ if (contentStr.startsWith(MANAGED_LOCALLY))
76
+ return 'local';
77
+ if (contentStr.startsWith(MANAGED_BY_GC))
78
+ return 'graphcommerce';
79
+ return 'unmanaged';
80
+ }
81
+ /**
82
+ * The packages are @graphcommerce/* packages and have special treatment.
83
+ *
84
+ * 1. Glob the `copy/**` directory for each package and generate a list of files that need to be
85
+ * copied. Error if a file with the same path exists in another package.
86
+ * 2. Copy the files to the project directory (cwd).
87
+ *
88
+ * 1. If the file doesn't exist: Create directories and the file with "managed by: graphcommerce"
89
+ * 2. If the file exists and starts with "managed by: local": Skip the file
90
+ * 3. If the file exists but doesn't have a management comment: Suggest adding "managed by: local"
91
+ * 4. If the file is managed by graphcommerce: Update if content differs
92
+ */
93
+ async function copyFiles() {
94
+ debug('Starting copyFiles');
95
+ const cwd = process.cwd();
96
+ const deps = (0, resolveDependenciesSync_1.resolveDependenciesSync)();
97
+ const packages = [...deps.values()].filter((p) => p !== '.');
98
+ debug('Found packages:', packages);
99
+ // Track files and their source packages to detect conflicts
100
+ const fileMap = new Map();
101
+ // Track which files are managed by GraphCommerce
102
+ const managedFiles = new Set();
103
+ // First pass: collect all files and check for conflicts
104
+ await Promise.all(packages.map(async (pkg) => {
105
+ const copyDir = path_1.default.join(pkg, 'copy');
106
+ try {
107
+ const files = await (0, glob_1.glob)('**/*', { cwd: copyDir, nodir: true, dot: true });
108
+ debug(`Found files in ${pkg}:`, files);
109
+ for (const file of files) {
110
+ const sourcePath = path_1.default.join(copyDir, file);
111
+ const existing = fileMap.get(file);
112
+ if (existing) {
113
+ console.error(`Error: File conflict detected for '${file}'
114
+ Found in packages:
115
+ - ${existing.packagePath} -> ${existing.sourcePath}
116
+ - ${pkg} -> ${sourcePath}`);
117
+ process.exit(1);
118
+ }
119
+ fileMap.set(file, { sourcePath, packagePath: pkg });
120
+ }
121
+ }
122
+ catch (err) {
123
+ // Skip if copy directory doesn't exist
124
+ if (err.code !== 'ENOENT') {
125
+ console.error(`Error scanning directory ${copyDir}: ${err.message}
126
+ Path: ${copyDir}`);
127
+ process.exit(1);
128
+ }
129
+ }
130
+ }));
131
+ // Second pass: copy files
132
+ await Promise.all(Array.from(fileMap.entries()).map(async ([file, { sourcePath }]) => {
133
+ const targetPath = path_1.default.join(cwd, file);
134
+ debug(`Processing file: ${file}`);
135
+ try {
136
+ await promises_1.default.mkdir(path_1.default.dirname(targetPath), { recursive: true });
137
+ const sourceContent = await promises_1.default.readFile(sourcePath);
138
+ const contentWithComment = Buffer.concat([
139
+ Buffer.from(`${MANAGED_BY_GC}\n`),
140
+ Buffer.from('// to modify this file, change it to managed by: local\n\n'),
141
+ sourceContent,
142
+ ]);
143
+ let targetContent;
144
+ try {
145
+ targetContent = await promises_1.default.readFile(targetPath);
146
+ const management = getFileManagement(targetContent);
147
+ if (management === 'local') {
148
+ debug(`File ${file} is managed locally, skipping`);
149
+ return;
150
+ }
151
+ if (management === 'unmanaged') {
152
+ console.log(`Note: File ${file} has been modified. Add '${MANAGED_LOCALLY.trim()}' at the top to manage it locally.`);
153
+ debug(`File ${file} doesn't have management comment, skipping`);
154
+ return;
155
+ }
156
+ debug(`File ${file} is managed by graphcommerce, will update if needed`);
157
+ }
158
+ catch (err) {
159
+ if (err.code !== 'ENOENT') {
160
+ console.error(`Error reading file ${file}: ${err.message}
161
+ Source: ${sourcePath}`);
162
+ process.exit(1);
163
+ }
164
+ console.log(`Creating new file: ${file}
165
+ Source: ${sourcePath}`);
166
+ debug('File does not exist yet');
167
+ }
168
+ // Skip if content is identical (including magic comment)
169
+ if (targetContent && Buffer.compare(contentWithComment, targetContent) === 0) {
170
+ debug(`File ${file} content is identical to source, skipping`);
171
+ managedFiles.add(file);
172
+ return;
173
+ }
174
+ // Copy the file with magic comment
175
+ await promises_1.default.writeFile(targetPath, contentWithComment);
176
+ if (targetContent) {
177
+ console.log(`Updated managed file: ${file}`);
178
+ debug(`Overwrote existing file: ${file}`);
179
+ }
180
+ // If the file is managed by GraphCommerce (new or updated), add it to managedFiles
181
+ if (!targetContent || targetContent.toString().startsWith(MANAGED_BY_GC)) {
182
+ managedFiles.add(file);
183
+ debug('Added managed file:', file);
184
+ }
185
+ }
186
+ catch (err) {
187
+ console.error(`Error copying file ${file}: ${err.message}
188
+ Source: ${sourcePath}`);
189
+ process.exit(1);
190
+ }
191
+ }));
192
+ // Update .gitignore with the list of managed files
193
+ if (managedFiles.size > 0) {
194
+ debug('Found managed files:', Array.from(managedFiles));
195
+ await updateGitignore(Array.from(managedFiles));
196
+ }
197
+ else {
198
+ debug('No managed files found, cleaning up .gitignore section');
199
+ await updateGitignore([]); // Pass empty array to clean up the section
200
+ }
201
+ }
@@ -0,0 +1,20 @@
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
+ const promises_1 = __importDefault(require("fs/promises"));
7
+ // ... earlier code remains the same ...
8
+ try {
9
+ targetContent = await promises_1.default.readFile(targetPath);
10
+ }
11
+ catch (err) {
12
+ if (err.code !== 'ENOENT')
13
+ throw err;
14
+ // File doesn't exist, log that we're creating it
15
+ console.log(`Creating new file: ${file}`);
16
+ }
17
+ // Skip if content is identical
18
+ if (targetContent && Buffer.compare(sourceContent, targetContent) === 0)
19
+ return;
20
+ // ... rest of the code remains the same ...
package/dist/index.js CHANGED
@@ -17,8 +17,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./utils/isMonorepo"), exports);
18
18
  __exportStar(require("./utils/resolveDependenciesSync"), exports);
19
19
  __exportStar(require("./utils/packageRoots"), exports);
20
+ __exportStar(require("./utils/sig"), exports);
20
21
  __exportStar(require("./withGraphCommerce"), exports);
21
22
  __exportStar(require("./generated/config"), exports);
22
23
  __exportStar(require("./config"), exports);
23
24
  __exportStar(require("./runtimeCachingOptimizations"), exports);
24
25
  __exportStar(require("./interceptors/commands/codegenInterceptors"), exports);
26
+ __exportStar(require("./commands/copyFiles"), exports);
27
+ __exportStar(require("./commands/codegen"), exports);
@@ -8,12 +8,14 @@ exports.resolveDependenciesSync = resolveDependenciesSync;
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const PackagesSort_1 = require("./PackagesSort");
11
+ const sig_1 = require("./sig");
11
12
  const resolveCache = new Map();
12
13
  function resolveRecursivePackageJson(dependencyPath, dependencyStructure, root, additionalDependencies = []) {
13
14
  const isRoot = dependencyPath === root;
14
15
  const fileName = require.resolve(node_path_1.default.join(dependencyPath, 'package.json'));
15
16
  const packageJsonFile = node_fs_1.default.readFileSync(fileName, 'utf-8').toString();
16
17
  const packageJson = JSON.parse(packageJsonFile);
18
+ const e = [atob('QGdyYXBoY29tbWVyY2UvYWRvYmUtY29tbWVyY2U=')].filter((n) => !globalThis.gcl ? true : !globalThis.gcl.includes(n));
17
19
  if (!packageJson.name)
18
20
  throw Error(`Package ${packageJsonFile} does not have a name field`);
19
21
  // Previously processed
@@ -29,7 +31,9 @@ function resolveRecursivePackageJson(dependencyPath, dependencyStructure, root,
29
31
  ...Object.keys(packageJson.devDependencies ?? []),
30
32
  ...additionalDependencies,
31
33
  ...Object.keys(packageJson.peerDependencies ?? {}),
32
- ].filter((name) => name.includes('graphcommerce'))),
34
+ ].filter((name) => name.includes('graphcommerce')
35
+ ? !(e.length >= 0 && e.some((v) => name.startsWith(v)))
36
+ : false)),
33
37
  ];
34
38
  const name = isRoot ? '.' : packageJson.name;
35
39
  dependencyStructure[name] = {
@@ -66,6 +70,7 @@ function resolveDependenciesSync(root = process.cwd()) {
66
70
  const cached = resolveCache.get(root);
67
71
  if (cached)
68
72
  return cached;
73
+ (0, sig_1.sig)();
69
74
  const dependencyStructure = resolveRecursivePackageJson(root, {}, root, process.env.PRIVATE_ADDITIONAL_DEPENDENCIES?.split(',') ?? []);
70
75
  const sorted = sortDependencies(dependencyStructure);
71
76
  resolveCache.set(root, sorted);
@@ -0,0 +1,34 @@
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.g = g;
7
+ exports.sig = sig;
8
+ // import necessary modules
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ // Function to generate a license key based on input data
11
+ function g(data) {
12
+ const iv = crypto_1.default.randomBytes(16); // Initialization vector
13
+ const cipher = crypto_1.default.createCipheriv('aes-256-cbc', 'BbcFEkUydGw3nE9ZPm7gbxTIIBQ9IiKN', iv);
14
+ let encrypted = cipher.update(JSON.stringify(data), 'utf-8', 'hex');
15
+ encrypted += cipher.final('hex');
16
+ // Return the IV and the encrypted data as a single string, encoded in base64
17
+ return Buffer.from(`${iv.toString('hex')}:${encrypted}`).toString();
18
+ }
19
+ // Function to validate and decode the license key
20
+ function sig() {
21
+ const l = process.env[atob('R0NfTElDRU5TRQ==')];
22
+ if (!l)
23
+ return;
24
+ if (!globalThis.gcl)
25
+ try {
26
+ const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', 'BbcFEkUydGw3nE9ZPm7gbxTIIBQ9IiKN', Buffer.from(l.split(':')[0], 'hex'));
27
+ let decrypted = decipher.update(l.split(':')[1], 'hex', 'utf-8');
28
+ decrypted += decipher.final('utf-8');
29
+ globalThis.gcl = JSON.parse(decrypted); // Parse and return the decoded data
30
+ }
31
+ catch (error) {
32
+ // Silent
33
+ }
34
+ }
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": "9.0.0-canary.104",
5
+ "version": "9.0.0-canary.106",
6
6
  "type": "commonjs",
7
7
  "main": "dist/index.js",
8
8
  "types": "src/index.ts",
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@graphql-mesh/cli": "latest",
16
- "@lingui/loader": "4.11.4",
17
- "@lingui/macro": "4.11.4",
18
- "@lingui/react": "4.11.4",
19
- "@lingui/swc-plugin": "4.0.8",
20
- "@swc/core": "1.7.26",
21
- "@swc/wasm-web": "^1.7.28",
16
+ "@lingui/loader": "4.14.0",
17
+ "@lingui/macro": "4.14.0",
18
+ "@lingui/react": "4.14.0",
19
+ "@lingui/swc-plugin": "4.1.0",
20
+ "@swc/core": "1.9.3",
21
+ "@swc/wasm-web": "^1.9.3",
22
22
  "@types/circular-dependency-plugin": "^5.0.8",
23
- "@types/lodash": "^4.17.10",
23
+ "@types/lodash": "^4.17.13",
24
24
  "babel-plugin-macros": "^3.1.0",
25
25
  "circular-dependency-plugin": "^5.2.2",
26
26
  "glob": "^10.4.5",
@@ -29,8 +29,8 @@
29
29
  "js-yaml-loader": "^1.2.2",
30
30
  "lodash": "^4.17.21",
31
31
  "react": "^18.3.1",
32
- "typescript": "5.6.2",
33
- "webpack": "~5.93.0",
32
+ "typescript": "5.7.2",
33
+ "webpack": "^5.96.1",
34
34
  "znv": "^0.4.0",
35
35
  "zod": "^3.23.8"
36
36
  },
@@ -0,0 +1,18 @@
1
+ import { generateConfig } from '../config/commands/generateConfig'
2
+ import { codegenInterceptors } from '../interceptors/commands/codegenInterceptors'
3
+ import { copyFiles } from './copyFiles'
4
+
5
+ /** Run all code generation steps in sequence */
6
+ export async function codegen() {
7
+ // Copy files from packages to project
8
+ console.log('🔄 Copying files from packages to project...')
9
+ await copyFiles()
10
+
11
+ // Generate GraphCommerce config types
12
+ console.log('⚙️ Generating GraphCommerce config types...')
13
+ await generateConfig()
14
+
15
+ // Generate interceptors
16
+ console.log('🔌 Generating interceptors...')
17
+ await codegenInterceptors()
18
+ }
@@ -0,0 +1,227 @@
1
+ import fs from 'fs/promises'
2
+ import path from 'path'
3
+ import { glob } from 'glob'
4
+ import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
5
+
6
+ // Add debug logging helper
7
+ const debug = (...args: unknown[]) => {
8
+ if (process.env.DEBUG) console.log('[copyFiles]', ...args)
9
+ }
10
+
11
+ // Add constants for the magic comments
12
+ type FileManagement = 'graphcommerce' | 'local'
13
+ const createManagementComment = (type: FileManagement) => `// managed by: ${type}`
14
+
15
+ const MANAGED_BY_GC = createManagementComment('graphcommerce')
16
+ const MANAGED_LOCALLY = createManagementComment('local')
17
+
18
+ const GITIGNORE_SECTION_START = '# managed by: graphcommerce'
19
+ const GITIGNORE_SECTION_END = '# end managed by: graphcommerce'
20
+
21
+ /**
22
+ * Updates the .gitignore file with a list of GraphCommerce managed files
23
+ *
24
+ * - Removes any existing GraphCommerce managed files section
25
+ * - If managedFiles is not empty, adds a new section with the files
26
+ * - If managedFiles is empty, just cleans up the existing section
27
+ * - Ensures the file ends with a newline
28
+ */
29
+ async function updateGitignore(managedFiles: string[]) {
30
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
31
+ let content: string
32
+
33
+ debug('Updating .gitignore with managed files:', managedFiles)
34
+
35
+ try {
36
+ content = await fs.readFile(gitignorePath, 'utf-8')
37
+ debug('Existing .gitignore content:', content)
38
+ } catch (err) {
39
+ debug('.gitignore not found, creating new file')
40
+ content = ''
41
+ }
42
+
43
+ // Remove existing GraphCommerce section if it exists
44
+ const sectionRegex = new RegExp(
45
+ `${GITIGNORE_SECTION_START}[\\s\\S]*?${GITIGNORE_SECTION_END}\\n?`,
46
+ 'g',
47
+ )
48
+ content = content.replace(sectionRegex, '')
49
+ debug('Content after removing existing section:', content)
50
+
51
+ // Only add new section if there are files to manage
52
+ if (managedFiles.length > 0) {
53
+ const newSection = [
54
+ GITIGNORE_SECTION_START,
55
+ ...managedFiles.sort(),
56
+ GITIGNORE_SECTION_END,
57
+ '', // Empty line at the end
58
+ ].join('\n')
59
+ debug('New section to add:', newSection)
60
+
61
+ // Append the new section
62
+ content = `${content.trim()}\n\n${newSection}`
63
+ } else {
64
+ // Just trim the content when no files to manage
65
+ content = `${content.trim()}\n`
66
+ }
67
+
68
+ debug('Final content:', content)
69
+
70
+ try {
71
+ await fs.writeFile(gitignorePath, content)
72
+ debug('Successfully wrote .gitignore file')
73
+ } catch (err) {
74
+ console.error('Error writing .gitignore:', err)
75
+ }
76
+ }
77
+
78
+ /** Determines how a file should be managed based on its content */
79
+ function getFileManagement(content: Buffer | undefined): 'local' | 'graphcommerce' | 'unmanaged' {
80
+ if (!content) return 'graphcommerce'
81
+ const contentStr = content.toString()
82
+ if (contentStr.startsWith(MANAGED_LOCALLY)) return 'local'
83
+ if (contentStr.startsWith(MANAGED_BY_GC)) return 'graphcommerce'
84
+ return 'unmanaged'
85
+ }
86
+
87
+ /**
88
+ * The packages are @graphcommerce/* packages and have special treatment.
89
+ *
90
+ * 1. Glob the `copy/**` directory for each package and generate a list of files that need to be
91
+ * copied. Error if a file with the same path exists in another package.
92
+ * 2. Copy the files to the project directory (cwd).
93
+ *
94
+ * 1. If the file doesn't exist: Create directories and the file with "managed by: graphcommerce"
95
+ * 2. If the file exists and starts with "managed by: local": Skip the file
96
+ * 3. If the file exists but doesn't have a management comment: Suggest adding "managed by: local"
97
+ * 4. If the file is managed by graphcommerce: Update if content differs
98
+ */
99
+ export async function copyFiles() {
100
+ debug('Starting copyFiles')
101
+
102
+ const cwd = process.cwd()
103
+ const deps = resolveDependenciesSync()
104
+ const packages = [...deps.values()].filter((p) => p !== '.')
105
+ debug('Found packages:', packages)
106
+
107
+ // Track files and their source packages to detect conflicts
108
+ const fileMap = new Map<string, { sourcePath: string; packagePath: string }>()
109
+ // Track which files are managed by GraphCommerce
110
+ const managedFiles = new Set<string>()
111
+
112
+ // First pass: collect all files and check for conflicts
113
+ await Promise.all(
114
+ packages.map(async (pkg) => {
115
+ const copyDir = path.join(pkg, 'copy')
116
+
117
+ try {
118
+ const files = await glob('**/*', { cwd: copyDir, nodir: true, dot: true })
119
+ debug(`Found files in ${pkg}:`, files)
120
+
121
+ for (const file of files) {
122
+ const sourcePath = path.join(copyDir, file)
123
+ const existing = fileMap.get(file)
124
+
125
+ if (existing) {
126
+ console.error(`Error: File conflict detected for '${file}'
127
+ Found in packages:
128
+ - ${existing.packagePath} -> ${existing.sourcePath}
129
+ - ${pkg} -> ${sourcePath}`)
130
+ process.exit(1)
131
+ }
132
+
133
+ fileMap.set(file, { sourcePath, packagePath: pkg })
134
+ }
135
+ } catch (err) {
136
+ // Skip if copy directory doesn't exist
137
+ if ((err as { code?: string }).code !== 'ENOENT') {
138
+ console.error(`Error scanning directory ${copyDir}: ${(err as Error).message}
139
+ Path: ${copyDir}`)
140
+ process.exit(1)
141
+ }
142
+ }
143
+ }),
144
+ )
145
+
146
+ // Second pass: copy files
147
+ await Promise.all(
148
+ Array.from(fileMap.entries()).map(async ([file, { sourcePath }]) => {
149
+ const targetPath = path.join(cwd, file)
150
+ debug(`Processing file: ${file}`)
151
+
152
+ try {
153
+ await fs.mkdir(path.dirname(targetPath), { recursive: true })
154
+
155
+ const sourceContent = await fs.readFile(sourcePath)
156
+ const contentWithComment = Buffer.concat([
157
+ Buffer.from(`${MANAGED_BY_GC}\n`),
158
+ Buffer.from('// to modify this file, change it to managed by: local\n\n'),
159
+ sourceContent,
160
+ ])
161
+
162
+ let targetContent: Buffer | undefined
163
+
164
+ try {
165
+ targetContent = await fs.readFile(targetPath)
166
+
167
+ const management = getFileManagement(targetContent)
168
+ if (management === 'local') {
169
+ debug(`File ${file} is managed locally, skipping`)
170
+ return
171
+ }
172
+ if (management === 'unmanaged') {
173
+ console.log(
174
+ `Note: File ${file} has been modified. Add '${MANAGED_LOCALLY.trim()}' at the top to manage it locally.`,
175
+ )
176
+ debug(`File ${file} doesn't have management comment, skipping`)
177
+ return
178
+ }
179
+
180
+ debug(`File ${file} is managed by graphcommerce, will update if needed`)
181
+ } catch (err) {
182
+ if ((err as { code?: string }).code !== 'ENOENT') {
183
+ console.error(`Error reading file ${file}: ${(err as Error).message}
184
+ Source: ${sourcePath}`)
185
+ process.exit(1)
186
+ }
187
+ console.log(`Creating new file: ${file}
188
+ Source: ${sourcePath}`)
189
+ debug('File does not exist yet')
190
+ }
191
+
192
+ // Skip if content is identical (including magic comment)
193
+ if (targetContent && Buffer.compare(contentWithComment, targetContent) === 0) {
194
+ debug(`File ${file} content is identical to source, skipping`)
195
+ managedFiles.add(file)
196
+ return
197
+ }
198
+
199
+ // Copy the file with magic comment
200
+ await fs.writeFile(targetPath, contentWithComment)
201
+ if (targetContent) {
202
+ console.log(`Updated managed file: ${file}`)
203
+ debug(`Overwrote existing file: ${file}`)
204
+ }
205
+
206
+ // If the file is managed by GraphCommerce (new or updated), add it to managedFiles
207
+ if (!targetContent || targetContent.toString().startsWith(MANAGED_BY_GC)) {
208
+ managedFiles.add(file)
209
+ debug('Added managed file:', file)
210
+ }
211
+ } catch (err) {
212
+ console.error(`Error copying file ${file}: ${(err as Error).message}
213
+ Source: ${sourcePath}`)
214
+ process.exit(1)
215
+ }
216
+ }),
217
+ )
218
+
219
+ // Update .gitignore with the list of managed files
220
+ if (managedFiles.size > 0) {
221
+ debug('Found managed files:', Array.from(managedFiles))
222
+ await updateGitignore(Array.from(managedFiles))
223
+ } else {
224
+ debug('No managed files found, cleaning up .gitignore section')
225
+ await updateGitignore([]) // Pass empty array to clean up the section
226
+ }
227
+ }
@@ -1,9 +1,9 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
2
  import type { PartialDeep } from 'type-fest'
3
- import type { GraphCommerceConfig } from '../generated/config'
3
+ import type { GraphCommerceConfig, GraphCommerceStorefrontConfig } from '../generated/config'
4
4
 
5
5
  export const demoConfig: PartialDeep<GraphCommerceConfig, { recurseIntoArrays: true }> &
6
- Record<string, unknown> = {
6
+ Record<string, unknown> & { storefront: PartialDeep<GraphCommerceStorefrontConfig>[] } = {
7
7
  canonicalBaseUrl: 'https://graphcommerce.vercel.app',
8
8
  hygraphEndpoint: 'https://eu-central-1.cdn.hygraph.com/content/ckhx7xadya6xs01yxdujt8i80/master',
9
9
  magentoEndpoint: 'https://backend.reachdigital.dev/graphql',
package/src/index.ts CHANGED
@@ -6,11 +6,14 @@ import type { GraphCommerceConfig } from './generated/config'
6
6
  export * from './utils/isMonorepo'
7
7
  export * from './utils/resolveDependenciesSync'
8
8
  export * from './utils/packageRoots'
9
+ export * from './utils/sig'
9
10
  export * from './withGraphCommerce'
10
11
  export * from './generated/config'
11
12
  export * from './config'
12
13
  export * from './runtimeCachingOptimizations'
13
14
  export * from './interceptors/commands/codegenInterceptors'
15
+ export * from './commands/copyFiles'
16
+ export * from './commands/codegen'
14
17
 
15
18
  export type PluginProps<P extends Record<string, unknown> = Record<string, unknown>> = P & {
16
19
  Prev: React.FC<P>
@@ -21,9 +24,7 @@ export type FunctionPlugin<T extends (...args: any[]) => any> = (
21
24
  ...args: Parameters<T>
22
25
  ) => ReturnType<T>
23
26
 
24
- /**
25
- * @deprecated use FunctionPlugin instead
26
- */
27
+ /** @deprecated Use FunctionPlugin instead */
27
28
  export type MethodPlugin<T extends (...args: any[]) => any> = (
28
29
  prev: T,
29
30
  ...args: Parameters<T>
@@ -2,6 +2,7 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import type { PackageJson } from 'type-fest'
4
4
  import { PackagesSort } from './PackagesSort'
5
+ import { g, sig } from './sig'
5
6
 
6
7
  type PackageNames = Map<string, string>
7
8
  type DependencyStructure = Record<string, { dirName: string; dependencies: string[] }>
@@ -18,6 +19,9 @@ function resolveRecursivePackageJson(
18
19
  const fileName = require.resolve(path.join(dependencyPath, 'package.json'))
19
20
  const packageJsonFile = fs.readFileSync(fileName, 'utf-8').toString()
20
21
  const packageJson = JSON.parse(packageJsonFile) as PackageJson
22
+ const e = [atob('QGdyYXBoY29tbWVyY2UvYWRvYmUtY29tbWVyY2U=')].filter((n) =>
23
+ !globalThis.gcl ? true : !globalThis.gcl.includes(n),
24
+ )
21
25
 
22
26
  if (!packageJson.name) throw Error(`Package ${packageJsonFile} does not have a name field`)
23
27
 
@@ -36,7 +40,11 @@ function resolveRecursivePackageJson(
36
40
  ...Object.keys(packageJson.devDependencies ?? []),
37
41
  ...additionalDependencies,
38
42
  ...Object.keys(packageJson.peerDependencies ?? {}),
39
- ].filter((name) => name.includes('graphcommerce')),
43
+ ].filter((name) =>
44
+ name.includes('graphcommerce')
45
+ ? !(e.length >= 0 && e.some((v) => name.startsWith(v)))
46
+ : false,
47
+ ),
40
48
  ),
41
49
  ]
42
50
 
@@ -82,6 +90,7 @@ export function sortDependencies(dependencyStructure: DependencyStructure): Pack
82
90
  export function resolveDependenciesSync(root = process.cwd()) {
83
91
  const cached = resolveCache.get(root)
84
92
  if (cached) return cached
93
+ sig()
85
94
 
86
95
  const dependencyStructure = resolveRecursivePackageJson(
87
96
  root,
@@ -0,0 +1,37 @@
1
+ // import necessary modules
2
+ import crypto from 'crypto'
3
+
4
+ declare global {
5
+ // eslint-disable-next-line vars-on-top, no-var
6
+ var gcl: string[] | undefined
7
+ }
8
+
9
+ // Function to generate a license key based on input data
10
+ export function g(data: string[]) {
11
+ const iv = crypto.randomBytes(16) // Initialization vector
12
+ const cipher = crypto.createCipheriv('aes-256-cbc', 'BbcFEkUydGw3nE9ZPm7gbxTIIBQ9IiKN', iv)
13
+ let encrypted = cipher.update(JSON.stringify(data), 'utf-8', 'hex')
14
+ encrypted += cipher.final('hex')
15
+ // Return the IV and the encrypted data as a single string, encoded in base64
16
+ return Buffer.from(`${iv.toString('hex')}:${encrypted}`).toString()
17
+ }
18
+
19
+ // Function to validate and decode the license key
20
+ export function sig() {
21
+ const l = process.env[atob('R0NfTElDRU5TRQ==')]
22
+ if (!l) return
23
+
24
+ if (!globalThis.gcl)
25
+ try {
26
+ const decipher = crypto.createDecipheriv(
27
+ 'aes-256-cbc',
28
+ 'BbcFEkUydGw3nE9ZPm7gbxTIIBQ9IiKN',
29
+ Buffer.from(l.split(':')[0], 'hex'),
30
+ )
31
+ let decrypted = decipher.update(l.split(':')[1], 'hex', 'utf-8')
32
+ decrypted += decipher.final('utf-8')
33
+ globalThis.gcl = JSON.parse(decrypted) // Parse and return the decoded data
34
+ } catch (error) {
35
+ // Silent
36
+ }
37
+ }
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.exportConfig = void 0;
4
- const loadConfig_1 = require("../loadConfig");
5
- // eslint-disable-next-line @typescript-eslint/require-await
6
- async function exportConfig() {
7
- const conf = (0, loadConfig_1.loadConfig)(process.cwd());
8
- }
9
- exports.exportConfig = exportConfig;
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.exportConfig = void 0;
4
- const loadConfig_1 = require("../../config/loadConfig");
5
- // eslint-disable-next-line @typescript-eslint/require-await
6
- async function exportConfig() {
7
- const conf = (0, loadConfig_1.loadConfig)(process.cwd());
8
- }
9
- exports.exportConfig = exportConfig;