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

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.
@@ -4,18 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.copyFiles = copyFiles;
7
+ /* eslint-disable no-await-in-loop */
7
8
  const promises_1 = __importDefault(require("fs/promises"));
8
9
  const path_1 = __importDefault(require("path"));
9
- const glob_1 = require("glob");
10
+ const fast_glob_1 = __importDefault(require("fast-glob"));
10
11
  const resolveDependenciesSync_1 = require("../utils/resolveDependenciesSync");
11
12
  // Add debug logging helper
12
13
  const debug = (...args) => {
13
14
  if (process.env.DEBUG)
14
- console.log('[copyFiles]', ...args);
15
+ console.log('[copy-files]', ...args);
15
16
  };
16
- const createManagementComment = (type) => `// managed by: ${type}`;
17
- const MANAGED_BY_GC = createManagementComment('graphcommerce');
18
- const MANAGED_LOCALLY = createManagementComment('local');
17
+ // Add constants for the magic comments
18
+ const MANAGED_BY_GC = '// managed by: graphcommerce';
19
+ const MANAGED_LOCALLY = '// managed by: local';
19
20
  const GITIGNORE_SECTION_START = '# managed by: graphcommerce';
20
21
  const GITIGNORE_SECTION_END = '# end managed by: graphcommerce';
21
22
  /**
@@ -29,10 +30,9 @@ const GITIGNORE_SECTION_END = '# end managed by: graphcommerce';
29
30
  async function updateGitignore(managedFiles) {
30
31
  const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
31
32
  let content;
32
- debug('Updating .gitignore with managed files:', managedFiles);
33
33
  try {
34
34
  content = await promises_1.default.readFile(gitignorePath, 'utf-8');
35
- debug('Existing .gitignore content:', content);
35
+ debug('Reading existing .gitignore');
36
36
  }
37
37
  catch (err) {
38
38
  debug('.gitignore not found, creating new file');
@@ -41,7 +41,6 @@ async function updateGitignore(managedFiles) {
41
41
  // Remove existing GraphCommerce section if it exists
42
42
  const sectionRegex = new RegExp(`${GITIGNORE_SECTION_START}[\\s\\S]*?${GITIGNORE_SECTION_END}\\n?`, 'g');
43
43
  content = content.replace(sectionRegex, '');
44
- debug('Content after removing existing section:', content);
45
44
  // Only add new section if there are files to manage
46
45
  if (managedFiles.length > 0) {
47
46
  const newSection = [
@@ -50,22 +49,15 @@ async function updateGitignore(managedFiles) {
50
49
  GITIGNORE_SECTION_END,
51
50
  '', // Empty line at the end
52
51
  ].join('\n');
53
- debug('New section to add:', newSection);
54
52
  // Append the new section
55
53
  content = `${content.trim()}\n\n${newSection}`;
54
+ debug(`Updated .gitignore with ${managedFiles.length} managed files`);
56
55
  }
57
56
  else {
58
- // Just trim the content when no files to manage
59
57
  content = `${content.trim()}\n`;
58
+ debug('Cleaned up .gitignore managed section');
60
59
  }
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
- }
60
+ await promises_1.default.writeFile(gitignorePath, content);
69
61
  }
70
62
  /** Determines how a file should be managed based on its content */
71
63
  function getFileManagement(content) {
@@ -91,44 +83,84 @@ function getFileManagement(content) {
91
83
  * 4. If the file is managed by graphcommerce: Update if content differs
92
84
  */
93
85
  async function copyFiles() {
86
+ const startTime = performance.now();
94
87
  debug('Starting copyFiles');
95
88
  const cwd = process.cwd();
96
89
  const deps = (0, resolveDependenciesSync_1.resolveDependenciesSync)();
97
90
  const packages = [...deps.values()].filter((p) => p !== '.');
98
- debug('Found packages:', packages);
99
91
  // Track files and their source packages to detect conflicts
100
92
  const fileMap = new Map();
101
- // Track which files are managed by GraphCommerce
102
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
+ }
103
132
  // First pass: collect all files and check for conflicts
133
+ const collectStart = performance.now();
104
134
  await Promise.all(packages.map(async (pkg) => {
105
135
  const copyDir = path_1.default.join(pkg, 'copy');
106
136
  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}'
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}'
114
145
  Found in packages:
115
146
  - ${existing.packagePath} -> ${existing.sourcePath}
116
147
  - ${pkg} -> ${sourcePath}`);
117
- process.exit(1);
148
+ process.exit(1);
149
+ }
150
+ fileMap.set(file, { sourcePath, packagePath: pkg });
118
151
  }
119
- fileMap.set(file, { sourcePath, packagePath: pkg });
120
152
  }
121
153
  }
122
154
  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
- }
155
+ if (err.code === 'ENOENT')
156
+ return;
157
+ console.error(`Error scanning directory ${copyDir}: ${err.message}\nPath: ${copyDir}`);
158
+ process.exit(1);
129
159
  }
130
160
  }));
131
- // Second pass: copy files
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();
132
164
  await Promise.all(Array.from(fileMap.entries()).map(async ([file, { sourcePath }]) => {
133
165
  const targetPath = path_1.default.join(cwd, file);
134
166
  debug(`Processing file: ${file}`);
@@ -136,8 +168,7 @@ Path: ${copyDir}`);
136
168
  await promises_1.default.mkdir(path_1.default.dirname(targetPath), { recursive: true });
137
169
  const sourceContent = await promises_1.default.readFile(sourcePath);
138
170
  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'),
171
+ Buffer.from(`${MANAGED_BY_GC}\n// to modify this file, change it to managed by: local\n\n`),
141
172
  sourceContent,
142
173
  ]);
143
174
  let targetContent;
@@ -161,8 +192,7 @@ Path: ${copyDir}`);
161
192
  Source: ${sourcePath}`);
162
193
  process.exit(1);
163
194
  }
164
- console.log(`Creating new file: ${file}
165
- Source: ${sourcePath}`);
195
+ console.log(`Creating new file: ${file}\nSource: ${sourcePath}`);
166
196
  debug('File does not exist yet');
167
197
  }
168
198
  // Skip if content is identical (including magic comment)
@@ -189,13 +219,74 @@ Source: ${sourcePath}`);
189
219
  process.exit(1);
190
220
  }
191
221
  }));
192
- // Update .gitignore with the list of managed files
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
193
283
  if (managedFiles.size > 0) {
194
284
  debug('Found managed files:', Array.from(managedFiles));
195
285
  await updateGitignore(Array.from(managedFiles));
196
286
  }
197
287
  else {
198
288
  debug('No managed files found, cleaning up .gitignore section');
199
- await updateGitignore([]); // Pass empty array to clean up the section
289
+ await updateGitignore([]);
200
290
  }
291
+ debug(`Total execution time: ${(performance.now() - startTime).toFixed(0)}ms`);
201
292
  }
@@ -155,7 +155,7 @@ function formatAppliedEnv(applyResult) {
155
155
  const lines = applyResult.map(({ from, to, envValue, envVar, dotVar, error, warning }) => {
156
156
  const fromFmt = chalk_1.default.red(JSON.stringify(from));
157
157
  const toFmt = chalk_1.default.green(JSON.stringify(to));
158
- const envVariableFmt = `${envVar}='${envValue}'`;
158
+ const envVariableFmt = `${envVar}`;
159
159
  const dotVariableFmt = chalk_1.default.bold.underline(`${dotVar}`);
160
160
  const baseLog = `${envVariableFmt} => ${dotVariableFmt}`;
161
161
  if (error) {
@@ -169,12 +169,12 @@ function formatAppliedEnv(applyResult) {
169
169
  if (!dotVar)
170
170
  return chalk_1.default.red(`${envVariableFmt} => ignored (no matching config)`);
171
171
  if (from === undefined && to === undefined)
172
- return ` = ${baseLog}: (ignored, no change/wrong format)`;
172
+ return ` = ${baseLog}: (ignored)`;
173
173
  if (from === undefined && to !== undefined)
174
- return ` ${chalk_1.default.green('+')} ${baseLog}: ${toFmt}`;
174
+ return ` ${chalk_1.default.green('+')} ${baseLog}`;
175
175
  if (from !== undefined && to === undefined)
176
- return ` ${chalk_1.default.red('-')} ${baseLog}: ${fromFmt}`;
177
- return ` ${chalk_1.default.yellowBright('~')} ${baseLog}: ${fromFmt} => ${toFmt}`;
176
+ return ` ${chalk_1.default.red('-')} ${baseLog}`;
177
+ return ` ${chalk_1.default.yellowBright('~')} ${baseLog}`;
178
178
  });
179
179
  let header = chalk_1.default.blueBright('info');
180
180
  if (hasWarning)
@@ -4,6 +4,7 @@ exports.WebsitePermissionsSchema = exports.SidebarGalleryPaginationVariantSchema
4
4
  exports.DatalayerConfigSchema = DatalayerConfigSchema;
5
5
  exports.GraphCommerceConfigSchema = GraphCommerceConfigSchema;
6
6
  exports.GraphCommerceDebugConfigSchema = GraphCommerceDebugConfigSchema;
7
+ exports.GraphCommerceGooglePlaystoreConfigSchema = GraphCommerceGooglePlaystoreConfigSchema;
7
8
  exports.GraphCommercePermissionsSchema = GraphCommercePermissionsSchema;
8
9
  exports.GraphCommerceStorefrontConfigSchema = GraphCommerceStorefrontConfigSchema;
9
10
  exports.MagentoConfigurableVariantValuesSchema = MagentoConfigurableVariantValuesSchema;
@@ -46,6 +47,7 @@ function GraphCommerceConfigSchema() {
46
47
  demoMode: zod_1.z.boolean().default(true).nullish(),
47
48
  enableGuestCheckoutLogin: zod_1.z.boolean().nullish(),
48
49
  googleAnalyticsId: zod_1.z.string().nullish(),
50
+ googlePlaystore: GraphCommerceGooglePlaystoreConfigSchema().nullish(),
49
51
  googleRecaptchaKey: zod_1.z.string().nullish(),
50
52
  googleTagmanagerId: zod_1.z.string().nullish(),
51
53
  hygraphEndpoint: zod_1.z.string().min(1),
@@ -77,6 +79,12 @@ function GraphCommerceDebugConfigSchema() {
77
79
  webpackDuplicatesPlugin: zod_1.z.boolean().nullish()
78
80
  });
79
81
  }
82
+ function GraphCommerceGooglePlaystoreConfigSchema() {
83
+ return zod_1.z.object({
84
+ packageName: zod_1.z.string().min(1),
85
+ sha256CertificateFingerprint: zod_1.z.string().min(1)
86
+ });
87
+ }
80
88
  function GraphCommercePermissionsSchema() {
81
89
  return zod_1.z.object({
82
90
  cart: exports.CartPermissionsSchema.nullish(),
package/dist/index.js CHANGED
@@ -21,7 +21,6 @@ __exportStar(require("./utils/sig"), exports);
21
21
  __exportStar(require("./withGraphCommerce"), exports);
22
22
  __exportStar(require("./generated/config"), exports);
23
23
  __exportStar(require("./config"), exports);
24
- __exportStar(require("./runtimeCachingOptimizations"), exports);
25
24
  __exportStar(require("./interceptors/commands/codegenInterceptors"), exports);
26
25
  __exportStar(require("./commands/copyFiles"), exports);
27
26
  __exportStar(require("./commands/codegen"), exports);
@@ -41,8 +41,8 @@ function isReplacePluginConfig(plugin) {
41
41
  function isPluginConfig(plugin) {
42
42
  return isPluginBaseConfig(plugin);
43
43
  }
44
- exports.SOURCE_START = '/** Original source starts here (do not modify!): **/';
45
- exports.SOURCE_END = '/** Original source ends here (do not modify!) **/';
44
+ exports.SOURCE_START = '/** SOURCE_START */';
45
+ exports.SOURCE_END = '/** SOURCE_END */';
46
46
  const originalSuffix = 'Original';
47
47
  const interceptorSuffix = 'Interceptor';
48
48
  const disabledSuffix = 'Disabled';
@@ -68,9 +68,7 @@ const generateIdentifyer = (s) => Math.abs(s.split('').reduce((a, b) => {
68
68
  // eslint-disable-next-line no-bitwise
69
69
  return a & a;
70
70
  }, 0)).toString();
71
- /**
72
- * The is on the first line, with the format: \/* hash:${identifer} *\/
73
- */
71
+ /** The is on the first line, with the format: /* hash:${identifer} */
74
72
  function extractIdentifier(source) {
75
73
  if (!source)
76
74
  return null;
@@ -4,10 +4,72 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isMonorepo = isMonorepo;
7
+ exports.findParentPath = findParentPath;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
7
9
  const node_path_1 = __importDefault(require("node:path"));
10
+ const debug = process.env.DEBUG === '1';
11
+ // eslint-disable-next-line no-console
12
+ const log = (message) => debug && console.log(`isMonorepo: ${message}`);
13
+ function findPackageJson(directory) {
14
+ try {
15
+ const packageJsonPath = node_path_1.default.join(directory, 'package.json');
16
+ const content = node_fs_1.default.readFileSync(packageJsonPath, 'utf8');
17
+ return JSON.parse(content);
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * Determines if we're running in a monorepo context and how to handle postinstall scripts.
25
+ *
26
+ * If there is a parent `@graphcommerce/*` package, we're in a monorepo.
27
+ */
8
28
  function isMonorepo() {
9
- const root = process.cwd();
10
- const meshDir = node_path_1.default.dirname(require.resolve('@graphcommerce/graphql-mesh'));
11
- const relativePath = node_path_1.default.join(node_path_1.default.relative(meshDir, root), '/');
12
- return relativePath.startsWith(`..${node_path_1.default.sep}..${node_path_1.default.sep}examples`);
29
+ let currentDir = process.cwd();
30
+ log(`Starting directory: ${currentDir}`);
31
+ // Start from the parent directory to find a parent @graphcommerce package
32
+ currentDir = node_path_1.default.dirname(currentDir);
33
+ log(`Looking for parent packages starting from: ${currentDir}`);
34
+ // Keep going up until we find a root package or hit the filesystem root
35
+ while (currentDir !== node_path_1.default.parse(currentDir).root) {
36
+ const packageJson = findPackageJson(currentDir);
37
+ if (packageJson) {
38
+ log(`Found package.json in: ${currentDir}`);
39
+ log(`Package name: ${packageJson.name}`);
40
+ if (packageJson.name.startsWith('@graphcommerce/')) {
41
+ log('isMonorepo result: true (found parent @graphcommerce package)');
42
+ return true;
43
+ }
44
+ }
45
+ currentDir = node_path_1.default.dirname(currentDir);
46
+ }
47
+ log('isMonorepo result: false (no parent @graphcommerce package found)');
48
+ return false;
49
+ }
50
+ /**
51
+ * Finds the path of the parent @graphcommerce package if it exists Returns null if no parent
52
+ * package is found
53
+ */
54
+ function findParentPath(directory) {
55
+ let currentDir = directory;
56
+ log(`Starting directory: ${currentDir}`);
57
+ // Start from the parent directory
58
+ currentDir = node_path_1.default.dirname(currentDir);
59
+ log(`Looking for parent packages starting from: ${currentDir}`);
60
+ // Keep going up until we find a root package or hit the filesystem root
61
+ while (currentDir !== node_path_1.default.parse(currentDir).root) {
62
+ const packageJson = findPackageJson(currentDir);
63
+ if (packageJson) {
64
+ log(`Found package.json in: ${currentDir}`);
65
+ log(`Package name: ${packageJson.name}`);
66
+ if (packageJson.name.startsWith('@graphcommerce/')) {
67
+ log(`Found parent @graphcommerce package at: ${currentDir}`);
68
+ return currentDir;
69
+ }
70
+ }
71
+ currentDir = node_path_1.default.dirname(currentDir);
72
+ }
73
+ log('No parent @graphcommerce package found');
74
+ return null;
13
75
  }
@@ -31,7 +31,7 @@ function domains(config) {
31
31
  * module.exports = withGraphCommerce(nextConfig)
32
32
  * ```
33
33
  */
34
- function withGraphCommerce(nextConfig, cwd) {
34
+ function withGraphCommerce(nextConfig, cwd = process.cwd()) {
35
35
  graphcommerceConfig ??= (0, loadConfig_1.loadConfig)(cwd);
36
36
  const importMetaPaths = (0, configToImportMeta_1.configToImportMeta)(graphcommerceConfig);
37
37
  const { storefront } = graphcommerceConfig;
@@ -41,10 +41,10 @@ function withGraphCommerce(nextConfig, cwd) {
41
41
  ];
42
42
  return {
43
43
  ...nextConfig,
44
+ bundlePagesRouterDependencies: true,
44
45
  experimental: {
45
46
  ...nextConfig.experimental,
46
47
  scrollRestoration: true,
47
- bundlePagesExternals: true,
48
48
  swcPlugins: [...(nextConfig.experimental?.swcPlugins ?? []), ['@lingui/swc-plugin', {}]],
49
49
  },
50
50
  i18n: {
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.106",
5
+ "version": "9.0.0-canary.108",
6
6
  "type": "commonjs",
7
7
  "main": "dist/index.js",
8
8
  "types": "src/index.ts",
@@ -17,14 +17,17 @@
17
17
  "@lingui/macro": "4.14.0",
18
18
  "@lingui/react": "4.14.0",
19
19
  "@lingui/swc-plugin": "4.1.0",
20
+ "@serwist/build": "^9.0.10",
21
+ "@serwist/next": "^9.0.10",
20
22
  "@swc/core": "1.9.3",
21
23
  "@swc/wasm-web": "^1.9.3",
22
24
  "@types/circular-dependency-plugin": "^5.0.8",
23
25
  "@types/lodash": "^4.17.13",
24
26
  "babel-plugin-macros": "^3.1.0",
25
27
  "circular-dependency-plugin": "^5.2.2",
28
+ "fast-glob": "^3.3.2",
26
29
  "glob": "^10.4.5",
27
- "graphql": "^16",
30
+ "graphql": "^16.9.0",
28
31
  "inspectpack": "^4.7.1",
29
32
  "js-yaml-loader": "^1.2.2",
30
33
  "lodash": "^4.17.21",