@graphcommerce/next-config 9.0.4-canary.0 → 9.0.4-canary.10

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +28 -8
  2. package/__tests__/commands/copyFiles.ts +8 -5
  3. package/__tests__/config/utils/__snapshots__/mergeEnvIntoConfig.ts.snap +0 -905
  4. package/__tests__/config/utils/mergeEnvIntoConfig.ts +21 -3
  5. package/__tests__/config/utils/replaceConfigInString.ts +0 -1
  6. package/__tests__/interceptors/findPlugins.ts +270 -272
  7. package/__tests__/interceptors/generateInterceptors.ts +1 -0
  8. package/__tests__/utils/resolveDependenciesSync.ts +45 -44
  9. package/dist/generated/config.js +110 -116
  10. package/dist/index.js +3359 -26
  11. package/package.json +34 -8
  12. package/src/commands/codegen.ts +3 -3
  13. package/src/commands/copyFiles.ts +14 -7
  14. package/src/config/commands/generateConfig.ts +17 -6
  15. package/src/config/demoConfig.ts +0 -1
  16. package/src/config/loadConfig.ts +3 -9
  17. package/src/config/utils/mergeEnvIntoConfig.ts +9 -9
  18. package/src/generated/config.ts +305 -245
  19. package/src/interceptors/Visitor.ts +1 -1
  20. package/src/interceptors/findOriginalSource.ts +1 -1
  21. package/src/interceptors/generateInterceptor.ts +0 -2
  22. package/src/interceptors/parseStructure.ts +3 -3
  23. package/src/interceptors/writeInterceptors.ts +1 -1
  24. package/src/utils/resolveDependenciesSync.ts +44 -7
  25. package/src/withGraphCommerce.ts +30 -42
  26. package/tsconfig.json +1 -1
  27. package/__tests__/config/utils/rewriteLegancyEnv.ts +0 -78
  28. package/dist/commands/codegen.js +0 -18
  29. package/dist/commands/copyFiles.js +0 -292
  30. package/dist/config/commands/exportConfig.js +0 -16
  31. package/dist/config/commands/generateConfig.js +0 -57
  32. package/dist/config/demoConfig.js +0 -52
  33. package/dist/config/index.js +0 -19
  34. package/dist/config/loadConfig.js +0 -62
  35. package/dist/config/utils/configToImportMeta.js +0 -39
  36. package/dist/config/utils/diff.js +0 -33
  37. package/dist/config/utils/exportConfigToEnv.js +0 -31
  38. package/dist/config/utils/mergeEnvIntoConfig.js +0 -184
  39. package/dist/config/utils/replaceConfigInString.js +0 -12
  40. package/dist/config/utils/rewriteLegacyEnv.js +0 -115
  41. package/dist/interceptors/InterceptorPlugin.js +0 -108
  42. package/dist/interceptors/RenameVisitor.js +0 -19
  43. package/dist/interceptors/Visitor.js +0 -1414
  44. package/dist/interceptors/commands/codegenInterceptors.js +0 -22
  45. package/dist/interceptors/extractExports.js +0 -159
  46. package/dist/interceptors/findOriginalSource.js +0 -103
  47. package/dist/interceptors/findPlugins.js +0 -68
  48. package/dist/interceptors/generateInterceptor.js +0 -219
  49. package/dist/interceptors/generateInterceptors.js +0 -56
  50. package/dist/interceptors/parseStructure.js +0 -84
  51. package/dist/interceptors/swc.js +0 -15
  52. package/dist/interceptors/writeInterceptors.js +0 -44
  53. package/dist/utils/PackagesSort.js +0 -7
  54. package/dist/utils/TopologicalSort.js +0 -87
  55. package/dist/utils/isMonorepo.js +0 -47
  56. package/dist/utils/packageRoots.js +0 -31
  57. package/dist/utils/resolveDependenciesSync.js +0 -78
  58. package/dist/utils/resolveDependency.js +0 -70
  59. package/dist/utils/sig.js +0 -34
  60. package/dist/withGraphCommerce.js +0 -162
  61. package/src/config/utils/rewriteLegacyEnv.ts +0 -125
@@ -499,7 +499,7 @@ export class Visitor {
499
499
  return this.visitExpressionStatement(stmt)
500
500
 
501
501
  default:
502
- throw new Error(`Unknown statement type: ${(stmt as any).type}`)
502
+ throw new Error(`Unknown statement type: ${(stmt as { type: string }).type}`)
503
503
  }
504
504
  }
505
505
 
@@ -1,5 +1,5 @@
1
- import type { ExportAllDeclaration } from '@swc/core'
2
1
  import path from 'path'
2
+ import type { ExportAllDeclaration } from '@swc/core'
3
3
  import type { ResolveDependency, ResolveDependencyReturn } from '../utils/resolveDependency'
4
4
  import type { PluginConfig } from './generateInterceptor'
5
5
  import { parseSync } from './swc'
@@ -1,6 +1,4 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
1
  import prettierConf from '@graphcommerce/prettier-config-pwa'
3
- // eslint-disable-next-line import/no-extraneous-dependencies
4
2
  import prettier from 'prettier'
5
3
  import type { GraphCommerceDebugConfig } from '../generated/config'
6
4
  import type { ResolveDependencyReturn } from '../utils/resolveDependency'
@@ -1,5 +1,5 @@
1
1
  import type { Module } from '@swc/core'
2
- import get from 'lodash/get'
2
+ import lodash from 'lodash'
3
3
  import { z } from 'zod'
4
4
  import type { GraphCommerceConfig } from '../generated/config'
5
5
  import { extractExports } from './extractExports'
@@ -66,12 +66,12 @@ export function parseStructure(ast: Module, gcConfig: GraphCommerceConfig, sourc
66
66
  if (parsed.data.ifConfig) {
67
67
  if (Array.isArray(parsed.data.ifConfig)) {
68
68
  const isBoolean = typeof parsed.data.ifConfig[1] === 'boolean'
69
- let confValue = get(gcConfig, parsed.data.ifConfig[0])
69
+ let confValue = lodash.get(gcConfig, parsed.data.ifConfig[0])
70
70
  confValue = isBoolean ? Boolean(confValue) : confValue
71
71
 
72
72
  enabled = confValue === parsed.data.ifConfig[1]
73
73
  } else {
74
- enabled = Boolean(get(gcConfig, parsed.data.ifConfig))
74
+ enabled = Boolean(lodash.get(gcConfig, parsed.data.ifConfig))
75
75
  }
76
76
  }
77
77
 
@@ -1,7 +1,7 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
- import { sync as globSync } from 'glob'
3
2
  import fs from 'node:fs/promises'
4
3
  import path from 'path'
4
+ import { sync as globSync } from 'glob'
5
5
  import { resolveDependenciesSync } from '../utils/resolveDependenciesSync'
6
6
  import type { GenerateInterceptorsReturn } from './generateInterceptors'
7
7
 
@@ -2,13 +2,24 @@ 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
+ import { sig } from './sig'
6
6
 
7
7
  type PackageNames = Map<string, string>
8
8
  type DependencyStructure = Record<string, { dirName: string; dependencies: string[] }>
9
9
 
10
10
  const resolveCache: Map<string, PackageNames> = new Map<string, PackageNames>()
11
11
 
12
+ function findPackageJson(id: string, root: string) {
13
+ let dir = id.startsWith('/') ? id : require.resolve(id)
14
+ let packageJsonLocation = path.join(dir, 'package.json')
15
+ while (!fs.existsSync(packageJsonLocation)) {
16
+ dir = path.dirname(dir)
17
+ if (dir === root) throw Error(`Can't find package.json for ${id}`)
18
+ packageJsonLocation = path.join(dir, 'package.json')
19
+ }
20
+ return packageJsonLocation
21
+ }
22
+
12
23
  function resolveRecursivePackageJson(
13
24
  dependencyPath: string,
14
25
  dependencyStructure: DependencyStructure,
@@ -16,7 +27,15 @@ function resolveRecursivePackageJson(
16
27
  additionalDependencies: string[] = [],
17
28
  ) {
18
29
  const isRoot = dependencyPath === root
19
- const fileName = require.resolve(path.join(dependencyPath, 'package.json'))
30
+
31
+ let fileName: string
32
+ try {
33
+ fileName = require.resolve(path.join(dependencyPath, 'package.json'))
34
+ } catch (e) {
35
+ fileName = findPackageJson(dependencyPath, root)
36
+ }
37
+ if (!fileName) throw Error(`Can't find package.json for ${dependencyPath}`)
38
+
20
39
  const packageJsonFile = fs.readFileSync(fileName, 'utf-8').toString()
21
40
  const packageJson = JSON.parse(packageJsonFile) as PackageJson
22
41
  const e = [atob('QGdyYXBoY29tbWVyY2UvYWRvYmUtY29tbWVyY2U=')].filter((n) =>
@@ -48,16 +67,34 @@ function resolveRecursivePackageJson(
48
67
  ),
49
68
  ]
50
69
 
70
+ const optionalPeerDependencies = Object.entries(packageJson.peerDependenciesMeta ?? {})
71
+ .filter(([_, v]) => v?.optional)
72
+ .map(([key]) => key)
73
+
74
+ const optionalDependencies = Object.keys(packageJson.optionalDependencies ?? {})
75
+ const optional = new Set([...optionalPeerDependencies, ...optionalDependencies])
76
+
77
+ const availableDependencies = dependencies.filter((dep) => {
78
+ if (optional.has(dep)) {
79
+ try {
80
+ resolveRecursivePackageJson(dep, dependencyStructure, root)
81
+ return true
82
+ } catch (resolveError) {
83
+ // Dependency is optional, so we don't care if it is not found.
84
+ return false
85
+ }
86
+ } else {
87
+ resolveRecursivePackageJson(dep, dependencyStructure, root)
88
+ return true
89
+ }
90
+ })
91
+
51
92
  const name = isRoot ? '.' : packageJson.name
52
93
  dependencyStructure[name] = {
53
94
  dirName: path.dirname(path.relative(process.cwd(), fileName)),
54
- dependencies,
95
+ dependencies: availableDependencies,
55
96
  }
56
97
 
57
- dependencies.forEach((dep) => {
58
- resolveRecursivePackageJson(dep, dependencyStructure, root)
59
- })
60
-
61
98
  return dependencyStructure
62
99
  }
63
100
 
@@ -1,9 +1,9 @@
1
1
  // import CircularDependencyPlugin from 'circular-dependency-plugin'
2
- import { DuplicatesPlugin } from 'inspectpack/plugin'
2
+ // import { DuplicatesPlugin } from 'inspectpack/plugin'
3
3
  import type { NextConfig } from 'next'
4
4
  import type { DomainLocale } from 'next/dist/server/config'
5
5
  import type { Configuration } from 'webpack'
6
- import { DefinePlugin } from 'webpack'
6
+ import webpack from 'webpack'
7
7
  import { loadConfig } from './config/loadConfig'
8
8
  import { configToImportMeta } from './config/utils/configToImportMeta'
9
9
  import type { GraphCommerceConfig } from './generated/config'
@@ -70,31 +70,15 @@ export function withGraphCommerce(nextConfig: NextConfig, cwd: string = process.
70
70
  images: {
71
71
  ...nextConfig.images,
72
72
  remotePatterns: [
73
- { hostname: new URL(graphcommerceConfig.magentoEndpoint).hostname },
73
+ 'magentoEndpoint' in graphcommerceConfig
74
+ ? {
75
+ hostname: new URL(graphcommerceConfig.magentoEndpoint).hostname,
76
+ }
77
+ : undefined,
74
78
  { hostname: '**.graphassets.com' },
75
79
  { hostname: '*.graphcommerce.org' },
76
80
  ...(nextConfig.images?.remotePatterns ?? []),
77
- ],
78
- },
79
- redirects: async () => {
80
- const redirects = (await nextConfig.redirects?.()) ?? []
81
-
82
- const destination = `${graphcommerceConfig.productRoute ?? '/p/'}:url*`
83
-
84
- redirects.push(
85
- ...[
86
- { source: '/product/bundle/:url*', destination, permanent: true },
87
- { source: '/product/configurable/:url*', destination, permanent: true },
88
- { source: '/product/downloadable/:url*', destination, permanent: true },
89
- { source: '/product/grouped/:url*', destination, permanent: true },
90
- { source: '/product/virtual/:url*', destination, permanent: true },
91
- { source: '/customer/account', destination: '/account', permanent: true },
92
- ],
93
- )
94
- if (destination !== '/product/:url*')
95
- redirects.push({ source: '/product/:url*', destination, permanent: true })
96
-
97
- return redirects
81
+ ].filter((v) => !!v),
98
82
  },
99
83
  rewrites: async () => {
100
84
  let rewrites = (await nextConfig.rewrites?.()) ?? []
@@ -103,7 +87,11 @@ export function withGraphCommerce(nextConfig: NextConfig, cwd: string = process.
103
87
  rewrites = { beforeFiles: rewrites, afterFiles: [], fallback: [] }
104
88
  }
105
89
 
106
- if (graphcommerceConfig.productRoute && graphcommerceConfig.productRoute !== '/p/') {
90
+ if (
91
+ 'productRoute' in graphcommerceConfig &&
92
+ typeof graphcommerceConfig.productRoute === 'string' &&
93
+ graphcommerceConfig.productRoute !== '/p/'
94
+ ) {
107
95
  rewrites.beforeFiles.push({
108
96
  source: `${graphcommerceConfig.productRoute ?? '/p/'}:path*`,
109
97
  destination: '/p/:path*',
@@ -131,10 +119,10 @@ export function withGraphCommerce(nextConfig: NextConfig, cwd: string = process.
131
119
  if (!config.plugins) config.plugins = []
132
120
 
133
121
  // Make import.meta.graphCommerce available for usage.
134
- config.plugins.push(new DefinePlugin(importMetaPaths))
122
+ config.plugins.push(new webpack.DefinePlugin(importMetaPaths))
135
123
 
136
124
  // To properly properly treeshake @apollo/client we need to define the __DEV__ property
137
- config.plugins.push(new DefinePlugin({ 'globalThis.__DEV__': options.dev }))
125
+ config.plugins.push(new webpack.DefinePlugin({ 'globalThis.__DEV__': options.dev }))
138
126
 
139
127
  if (!options.isServer) {
140
128
  // if (graphcommerceConfig.debug?.webpackCircularDependencyPlugin) {
@@ -144,21 +132,21 @@ export function withGraphCommerce(nextConfig: NextConfig, cwd: string = process.
144
132
  // }),
145
133
  // )
146
134
  // }
147
- if (graphcommerceConfig.debug?.webpackDuplicatesPlugin) {
148
- config.plugins.push(
149
- new DuplicatesPlugin({
150
- ignoredPackages: [
151
- // very small
152
- 'react-is',
153
- // build issue
154
- 'tslib',
155
- // server
156
- 'isarray',
157
- 'readable-stream',
158
- ],
159
- }),
160
- )
161
- }
135
+ // if (graphcommerceConfig.debug?.webpackDuplicatesPlugin) {
136
+ // config.plugins.push(
137
+ // new DuplicatesPlugin({
138
+ // ignoredPackages: [
139
+ // // very small
140
+ // 'react-is',
141
+ // // build issue
142
+ // 'tslib',
143
+ // // server
144
+ // 'isarray',
145
+ // 'readable-stream',
146
+ // ],
147
+ // }),
148
+ // )
149
+ // }
162
150
  }
163
151
 
164
152
  config.snapshot = {
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "exclude": ["**/node_modules", "example", "dist", "**/.*/", "__tests__", "__mocks__"],
3
- "include": ["**/*.ts", "**/*.tsx"],
3
+ "include": ["src/index.ts"],
4
4
  "extends": "@graphcommerce/typescript-config-pwa/node.json",
5
5
  "compilerOptions": {
6
6
  "rootDir": "src",
@@ -1,78 +0,0 @@
1
- import { formatAppliedEnv } from '../../../src/config/utils/mergeEnvIntoConfig'
2
- import { rewriteLegacyEnv } from '../../../src/config/utils/rewriteLegacyEnv'
3
- import type { GraphCommerceConfig } from '../../../src/generated/config'
4
- import { GraphCommerceConfigSchema } from '../../../src/generated/config'
5
- export const removeColor = (str: string) =>
6
- str.replace(
7
- new RegExp(
8
- [
9
- '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
10
- '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))',
11
- ].join('|'),
12
- 'g',
13
- ),
14
- '',
15
- )
16
- it('rewrites legacy env', () => {
17
- const configFile: GraphCommerceConfig = {
18
- storefront: [{ locale: 'en', hygraphLocales: ['en'], magentoStoreCode: 'en_us' }],
19
- productFiltersPro: false,
20
- canonicalBaseUrl: 'https://example.com',
21
- hygraphEndpoint: 'https://example.com',
22
- magentoEndpoint: 'https://example.com',
23
- previewSecret: 'secret',
24
- robotsAllow: true,
25
- magentoVersion: 246,
26
- }
27
- const legacyEnv = {
28
- GRAPHCMS_URL: 'https://api-eu-central-1.graphcms.com/v2/ckhx7xadya6xs01yxdujt8i80/master',
29
- MAGENTO_ENDPOINT: 'https://backend.reachdigital.dev/graphql',
30
- NEXT_PUBLIC_GRAPHQL_ENDPOINT: 'http://localhost:3000/api/graphql',
31
- IMAGE_DOMAINS: 'backend.reachdigital.dev,media.graphcms.com,media.graphassets.com',
32
- NEXT_PUBLIC_LOCALE_STORES:
33
- '{"en-us": "en_US", "nl-nl": "nl_NL", "fr-be": "fr_BE", "nl-be": "nl_BE", "en-gb": "en_GB", "en-ca": "en_CA"}',
34
- NEXT_PUBLIC_SITE_URL: 'https://graphcommerce.vercel.app/',
35
- NEXT_PUBLIC_GTM_ID: '123',
36
- NEXT_PUBLIC_GOOGLE_ANALYTICS:
37
- '{"en-us": "G-111", "nl-nl": "G-222", "fr-be": "G-333", "nl-be": "G-444", "en-gb":"G-555", "en-ca": "G-666"}',
38
- NEXT_PUBLIC_GOOGLE_RECAPTCHA_V3_SITE_KEY: '',
39
- NEXT_PUBLIC_DISPLAY_INCL_TAX: 'nl,fr-be,nl-be,en-gb,eu',
40
- PREVIEW_SECRET: 'dya6xs01y',
41
- DEMO_MAGENTO_GRAPHCOMMERCE: '1',
42
- SOME_KEY: 'bla',
43
- }
44
- const [, appliedRewrite] = rewriteLegacyEnv(GraphCommerceConfigSchema(), legacyEnv, configFile)
45
- expect(removeColor(formatAppliedEnv(appliedRewrite))).toMatchInlineSnapshot(`
46
- "warning - Loaded GraphCommerce env variables
47
- ‼ GRAPHCMS_URL => should be renamed to GC_HYGRAPH_ENDPOINT='https://api-eu-central-1.graphcms.com/v2/ckhx7xadya6xs01yxdujt8i80/master'
48
- ‼ MAGENTO_ENDPOINT => should be renamed to GC_MAGENTO_ENDPOINT='https://backend.reachdigital.dev/graphql'
49
- ‼ NEXT_PUBLIC_GRAPHQL_ENDPOINT => should be removed
50
- ‼ IMAGE_DOMAINS => should be removed: will automatically add the Magento/Hygraph URL. For more advanced configurations, see: https://nextjs.org/docs/api-reference/next/image#configuration-options
51
- ‼ NEXT_PUBLIC_LOCALE_STORES => env variable is is modified, rewritten to GC_STOREFRONT.
52
- ‼ NEXT_PUBLIC_SITE_URL => should be renamed to GC_CANONICAL_BASE_URL='https://graphcommerce.vercel.app/'
53
- ‼ NEXT_PUBLIC_GTM_ID => should be renamed to GC_GOOGLE_TAGMANAGER_ID='123'
54
- ‼ NEXT_PUBLIC_GOOGLE_ANALYTICS => should be rewritten to GC_STOREFRONT_*_GOOGLE_ANALYTICS_ID
55
- ‼ NEXT_PUBLIC_GOOGLE_RECAPTCHA_V3_SITE_KEY => should be renamed to GC_GOOGLE_RECAPTCHA_KEY=''
56
- ‼ NEXT_PUBLIC_DISPLAY_INCL_TAX => env variable is renamed, move to configuration: cartDisplayPricesInclTax
57
- ‼ PREVIEW_SECRET => should be renamed to GC_PREVIEW_SECRET='dya6xs01y'
58
- ‼ DEMO_MAGENTO_GRAPHCOMMERCE => should be renamed to GC_DEMO_MODE='1'
59
- ~ GC_CANONICAL_BASE_URL => canonicalBaseUrl
60
- + GC_DEMO_MODE => demoMode
61
- + GC_GOOGLE_ANALYTICS_ID => googleAnalyticsId
62
- + GC_GOOGLE_RECAPTCHA_KEY => googleRecaptchaKey
63
- + GC_GOOGLE_TAGMANAGER_ID => googleTagmanagerId
64
- ~ GC_HYGRAPH_ENDPOINT => hygraphEndpoint
65
- ~ GC_MAGENTO_ENDPOINT => magentoEndpoint
66
- ~ GC_PREVIEW_SECRET => previewSecret
67
- = GC_STOREFRONT => storefront: (ignored)
68
- + GC_STOREFRONT_0_GOOGLE_ANALYTICS_ID => storefront.[0].googleAnalyticsId
69
- + GC_STOREFRONT_1_GOOGLE_ANALYTICS_ID => storefront.[1].googleAnalyticsId
70
- + GC_STOREFRONT_2_CART_DISPLAY_PRICES_INCL_TAX => storefront.[2].cartDisplayPricesInclTax
71
- + GC_STOREFRONT_2_GOOGLE_ANALYTICS_ID => storefront.[2].googleAnalyticsId
72
- + GC_STOREFRONT_3_CART_DISPLAY_PRICES_INCL_TAX => storefront.[3].cartDisplayPricesInclTax
73
- + GC_STOREFRONT_3_GOOGLE_ANALYTICS_ID => storefront.[3].googleAnalyticsId
74
- + GC_STOREFRONT_4_CART_DISPLAY_PRICES_INCL_TAX => storefront.[4].cartDisplayPricesInclTax
75
- + GC_STOREFRONT_4_GOOGLE_ANALYTICS_ID => storefront.[4].googleAnalyticsId
76
- + GC_STOREFRONT_5_GOOGLE_ANALYTICS_ID => storefront.[5].googleAnalyticsId"
77
- `)
78
- })
@@ -1,18 +0,0 @@
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
- }
@@ -1,292 +0,0 @@
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
- /* eslint-disable no-await-in-loop */
8
- const promises_1 = __importDefault(require("fs/promises"));
9
- const path_1 = __importDefault(require("path"));
10
- const fast_glob_1 = __importDefault(require("fast-glob"));
11
- const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
12
- // Add debug logging helper
13
- const debug = (...args) => {
14
- if (process.env.DEBUG)
15
- console.log('[copy-files]', ...args);
16
- };
17
- // Add constants for the magic comments
18
- const MANAGED_BY_GC = '// managed by: graphcommerce';
19
- const MANAGED_LOCALLY = '// managed by: local';
20
- const GITIGNORE_SECTION_START = '# managed by: graphcommerce';
21
- const GITIGNORE_SECTION_END = '# end managed by: graphcommerce';
22
- /**
23
- * Updates the .gitignore file with a list of GraphCommerce managed files
24
- *
25
- * - Removes any existing GraphCommerce managed files section
26
- * - If managedFiles is not empty, adds a new section with the files
27
- * - If managedFiles is empty, just cleans up the existing section
28
- * - Ensures the file ends with a newline
29
- */
30
- async function updateGitignore(managedFiles) {
31
- const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
32
- let content;
33
- try {
34
- content = await promises_1.default.readFile(gitignorePath, 'utf-8');
35
- debug('Reading existing .gitignore');
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
- // Only add new section if there are files to manage
45
- if (managedFiles.length > 0) {
46
- const newSection = [
47
- GITIGNORE_SECTION_START,
48
- ...managedFiles.sort(),
49
- GITIGNORE_SECTION_END,
50
- '', // Empty line at the end
51
- ].join('\n');
52
- // Append the new section
53
- content = `${content.trim()}\n\n${newSection}`;
54
- debug(`Updated .gitignore with ${managedFiles.length} managed files`);
55
- }
56
- else {
57
- content = `${content.trim()}\n`;
58
- debug('Cleaned up .gitignore managed section');
59
- }
60
- await promises_1.default.writeFile(gitignorePath, content);
61
- }
62
- /** Determines how a file should be managed based on its content */
63
- function getFileManagement(content) {
64
- if (!content)
65
- return 'graphcommerce';
66
- const contentStr = content.toString();
67
- if (contentStr.startsWith(MANAGED_LOCALLY))
68
- return 'local';
69
- if (contentStr.startsWith(MANAGED_BY_GC))
70
- return 'graphcommerce';
71
- return 'unmanaged';
72
- }
73
- /**
74
- * The packages are @graphcommerce/* packages and have special treatment.
75
- *
76
- * 1. Glob the `copy/**` directory for each package and generate a list of files that need to be
77
- * copied. Error if a file with the same path exists in another package.
78
- * 2. Copy the files to the project directory (cwd).
79
- *
80
- * 1. If the file doesn't exist: Create directories and the file with "managed by: graphcommerce"
81
- * 2. If the file exists and starts with "managed by: local": Skip the file
82
- * 3. If the file exists but doesn't have a management comment: Suggest adding "managed by: local"
83
- * 4. If the file is managed by graphcommerce: Update if content differs
84
- */
85
- async function copyFiles() {
86
- const startTime = performance.now();
87
- debug('Starting copyFiles');
88
- const cwd = process.cwd();
89
- const deps = (0, resolveDependenciesSync_1.resolveDependenciesSync)();
90
- const packages = [...deps.values()].filter((p) => p !== '.');
91
- // Track files and their source packages to detect conflicts
92
- const fileMap = new Map();
93
- const managedFiles = new Set();
94
- const existingManagedFiles = new Set();
95
- // First scan existing files to find GraphCommerce managed ones
96
- const scanStart = performance.now();
97
- try {
98
- // Use only default patterns for testing
99
- const gitignorePatterns = [
100
- '**/dist/**',
101
- '**/build/**',
102
- '**/.next/**',
103
- '**/.git/**',
104
- '**/node_modules/**',
105
- ];
106
- const allFiles = await (0, fast_glob_1.default)('**/*', {
107
- cwd,
108
- dot: true,
109
- ignore: gitignorePatterns,
110
- onlyFiles: true,
111
- });
112
- debug(`Found ${allFiles.length} project files in ${(performance.now() - scanStart).toFixed(0)}ms`);
113
- const readStart = performance.now();
114
- await Promise.all(allFiles.map(async (file) => {
115
- const filePath = path_1.default.join(cwd, file);
116
- try {
117
- const content = await promises_1.default.readFile(filePath);
118
- if (getFileManagement(content) === 'graphcommerce') {
119
- existingManagedFiles.add(file);
120
- debug(`Found existing managed file: ${file}`);
121
- }
122
- }
123
- catch (err) {
124
- debug(`Error reading file ${file}:`, err);
125
- }
126
- }));
127
- debug(`Read ${existingManagedFiles.size} managed files in ${(performance.now() - readStart).toFixed(0)}ms`);
128
- }
129
- catch (err) {
130
- debug('Error scanning project files:', err);
131
- }
132
- // First pass: collect all files and check for conflicts
133
- const collectStart = performance.now();
134
- await Promise.all(packages.map(async (pkg) => {
135
- const copyDir = path_1.default.join(pkg, 'copy');
136
- try {
137
- const files = await (0, fast_glob_1.default)('**/*', { cwd: copyDir, dot: true, suppressErrors: true });
138
- if (files.length > 0) {
139
- debug(`Found files in ${pkg}:`, files);
140
- for (const file of files) {
141
- const sourcePath = path_1.default.join(copyDir, file);
142
- const existing = fileMap.get(file);
143
- if (existing) {
144
- console.error(`Error: File conflict detected for '${file}'
145
- Found in packages:
146
- - ${existing.packagePath} -> ${existing.sourcePath}
147
- - ${pkg} -> ${sourcePath}`);
148
- process.exit(1);
149
- }
150
- fileMap.set(file, { sourcePath, packagePath: pkg });
151
- }
152
- }
153
- }
154
- catch (err) {
155
- if (err.code === 'ENOENT')
156
- return;
157
- console.error(`Error scanning directory ${copyDir}: ${err.message}\nPath: ${copyDir}`);
158
- process.exit(1);
159
- }
160
- }));
161
- debug(`Collected ${fileMap.size} files in ${(performance.now() - collectStart).toFixed(0)}ms`);
162
- // Second pass: copy files and handle removals
163
- const copyStart = performance.now();
164
- await Promise.all(Array.from(fileMap.entries()).map(async ([file, { sourcePath }]) => {
165
- const targetPath = path_1.default.join(cwd, file);
166
- debug(`Processing file: ${file}`);
167
- try {
168
- await promises_1.default.mkdir(path_1.default.dirname(targetPath), { recursive: true });
169
- const sourceContent = await promises_1.default.readFile(sourcePath);
170
- const contentWithComment = Buffer.concat([
171
- Buffer.from(`${MANAGED_BY_GC}\n// to modify this file, change it to managed by: local\n\n`),
172
- sourceContent,
173
- ]);
174
- let targetContent;
175
- try {
176
- targetContent = await promises_1.default.readFile(targetPath);
177
- const management = getFileManagement(targetContent);
178
- if (management === 'local') {
179
- debug(`File ${file} is managed locally, skipping`);
180
- return;
181
- }
182
- if (management === 'unmanaged') {
183
- console.log(`Note: File ${file} has been modified. Add '${MANAGED_LOCALLY.trim()}' at the top to manage it locally.`);
184
- debug(`File ${file} doesn't have management comment, skipping`);
185
- return;
186
- }
187
- debug(`File ${file} is managed by graphcommerce, will update if needed`);
188
- }
189
- catch (err) {
190
- if (err.code !== 'ENOENT') {
191
- console.error(`Error reading file ${file}: ${err.message}
192
- Source: ${sourcePath}`);
193
- process.exit(1);
194
- }
195
- console.log(`Creating new file: ${file}\nSource: ${sourcePath}`);
196
- debug('File does not exist yet');
197
- }
198
- // Skip if content is identical (including magic comment)
199
- if (targetContent && Buffer.compare(contentWithComment, targetContent) === 0) {
200
- debug(`File ${file} content is identical to source, skipping`);
201
- managedFiles.add(file);
202
- return;
203
- }
204
- // Copy the file with magic comment
205
- await promises_1.default.writeFile(targetPath, contentWithComment);
206
- if (targetContent) {
207
- console.log(`Updated managed file: ${file}`);
208
- debug(`Overwrote existing file: ${file}`);
209
- }
210
- // If the file is managed by GraphCommerce (new or updated), add it to managedFiles
211
- if (!targetContent || targetContent.toString().startsWith(MANAGED_BY_GC)) {
212
- managedFiles.add(file);
213
- debug('Added managed file:', file);
214
- }
215
- }
216
- catch (err) {
217
- console.error(`Error copying file ${file}: ${err.message}
218
- Source: ${sourcePath}`);
219
- process.exit(1);
220
- }
221
- }));
222
- debug(`Copied ${managedFiles.size} files in ${(performance.now() - copyStart).toFixed(0)}ms`);
223
- // Remove files that are no longer provided
224
- const removeStart = performance.now();
225
- const filesToRemove = Array.from(existingManagedFiles).filter((file) => !managedFiles.has(file));
226
- debug(`Files to remove: ${filesToRemove.length}`);
227
- // Helper function to recursively clean up empty directories
228
- async function cleanupEmptyDirs(startPath) {
229
- let currentDir = startPath;
230
- while (currentDir !== cwd) {
231
- try {
232
- const dirContents = await promises_1.default.readdir(currentDir);
233
- if (dirContents.length === 0) {
234
- await promises_1.default.rmdir(currentDir);
235
- debug(`Removed empty directory: ${currentDir}`);
236
- currentDir = path_1.default.dirname(currentDir);
237
- }
238
- else {
239
- break; // Stop if directory is not empty
240
- }
241
- }
242
- catch (err) {
243
- if (err.code === 'EACCES') {
244
- console.error(`Error cleaning up directory ${currentDir}: ${err.message}`);
245
- process.exit(1);
246
- }
247
- break; // Stop on other errors (like ENOENT)
248
- }
249
- }
250
- }
251
- // Process file removals in parallel
252
- await Promise.all(filesToRemove.map(async (file) => {
253
- const filePath = path_1.default.join(cwd, file);
254
- const dirPath = path_1.default.dirname(filePath);
255
- try {
256
- // First check if the directory exists and is accessible
257
- await promises_1.default.readdir(dirPath);
258
- // Then try to remove the file
259
- try {
260
- await promises_1.default.unlink(filePath);
261
- console.log(`Removed managed file: ${file}`);
262
- debug(`Removed file: ${file}`);
263
- }
264
- catch (err) {
265
- if (err.code !== 'ENOENT') {
266
- console.error(`Error removing file ${file}: ${err.message}`);
267
- process.exit(1);
268
- }
269
- }
270
- // Finally, try to clean up empty directories
271
- await cleanupEmptyDirs(dirPath);
272
- }
273
- catch (err) {
274
- if (err.code === 'EACCES') {
275
- console.error(`Error accessing directory ${dirPath}: ${err.message}`);
276
- process.exit(1);
277
- }
278
- // Ignore ENOENT errors for directories that don't exist
279
- }
280
- }));
281
- debug(`Removed files in ${(performance.now() - removeStart).toFixed(0)}ms`);
282
- // Update .gitignore with current list of managed files
283
- if (managedFiles.size > 0) {
284
- debug('Found managed files:', Array.from(managedFiles));
285
- await updateGitignore(Array.from(managedFiles));
286
- }
287
- else {
288
- debug('No managed files found, cleaning up .gitignore section');
289
- await updateGitignore([]);
290
- }
291
- debug(`Total execution time: ${(performance.now() - startTime).toFixed(0)}ms`);
292
- }
@@ -1,16 +0,0 @@
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.exportConfig = exportConfig;
7
- const dotenv_1 = __importDefault(require("dotenv"));
8
- const loadConfig_1 = require("../loadConfig");
9
- const exportConfigToEnv_1 = require("../utils/exportConfigToEnv");
10
- dotenv_1.default.config();
11
- // eslint-disable-next-line @typescript-eslint/require-await
12
- async function exportConfig() {
13
- const conf = (0, loadConfig_1.loadConfig)(process.cwd());
14
- // eslint-disable-next-line no-console
15
- console.log((0, exportConfigToEnv_1.exportConfigToEnv)(conf));
16
- }