@shopify/cli-hydrogen 6.0.2 → 7.0.0

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 (108) hide show
  1. package/dist/commands/hydrogen/build.js +40 -78
  2. package/dist/commands/hydrogen/codegen.js +8 -3
  3. package/dist/commands/hydrogen/deploy.js +173 -37
  4. package/dist/commands/hydrogen/deploy.test.js +192 -20
  5. package/dist/commands/hydrogen/dev.js +56 -31
  6. package/dist/commands/hydrogen/init.js +1 -1
  7. package/dist/commands/hydrogen/init.test.js +155 -53
  8. package/dist/commands/hydrogen/link.js +5 -21
  9. package/dist/commands/hydrogen/link.test.js +10 -10
  10. package/dist/commands/hydrogen/preview.js +22 -11
  11. package/dist/commands/hydrogen/setup.js +0 -4
  12. package/dist/commands/hydrogen/setup.test.js +0 -1
  13. package/dist/commands/hydrogen/shortcut.js +1 -0
  14. package/dist/commands/hydrogen/upgrade.js +720 -0
  15. package/dist/commands/hydrogen/upgrade.test.js +786 -0
  16. package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
  17. package/dist/generator-templates/starter/CHANGELOG.md +126 -0
  18. package/dist/generator-templates/starter/README.md +23 -0
  19. package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
  20. package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
  21. package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
  22. package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
  23. package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
  24. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  25. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
  26. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  27. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  28. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  29. package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
  30. package/dist/generator-templates/starter/app/lib/session.ts +67 -0
  31. package/dist/generator-templates/starter/app/root.tsx +11 -45
  32. package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
  33. package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
  34. package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
  35. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
  36. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
  37. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
  38. package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
  39. package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
  40. package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
  41. package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
  42. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
  43. package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
  44. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
  45. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
  46. package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
  47. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
  48. package/dist/generator-templates/starter/package.json +11 -10
  49. package/dist/generator-templates/starter/remix.config.js +4 -0
  50. package/dist/generator-templates/starter/remix.env.d.ts +6 -11
  51. package/dist/generator-templates/starter/server.ts +24 -167
  52. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
  53. package/dist/hooks/init.js +4 -4
  54. package/dist/lib/auth.js +5 -10
  55. package/dist/lib/build.js +6 -1
  56. package/dist/lib/bundle/analyzer.js +36 -26
  57. package/dist/lib/check-lockfile.js +1 -0
  58. package/dist/lib/codegen.js +59 -18
  59. package/dist/lib/defer.js +12 -0
  60. package/dist/lib/file.js +52 -3
  61. package/dist/lib/flags.js +27 -9
  62. package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
  63. package/dist/lib/graphql/admin/client.test.js +2 -2
  64. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  65. package/dist/lib/log.js +32 -14
  66. package/dist/lib/mini-oxygen/assets.js +118 -0
  67. package/dist/lib/mini-oxygen/common.js +2 -1
  68. package/dist/lib/mini-oxygen/index.js +7 -5
  69. package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
  70. package/dist/lib/mini-oxygen/node.js +19 -5
  71. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
  72. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
  73. package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
  74. package/dist/lib/mini-oxygen/workerd.js +74 -50
  75. package/dist/lib/missing-routes.js +6 -3
  76. package/dist/lib/onboarding/common.js +40 -9
  77. package/dist/lib/onboarding/local.js +19 -11
  78. package/dist/lib/onboarding/remote.js +48 -28
  79. package/dist/lib/render-errors.js +2 -0
  80. package/dist/lib/request-events.js +65 -31
  81. package/dist/lib/setups/css/assets.js +1 -46
  82. package/dist/lib/setups/css/css-modules.js +3 -2
  83. package/dist/lib/setups/css/postcss.js +4 -2
  84. package/dist/lib/setups/css/tailwind.js +4 -2
  85. package/dist/lib/setups/css/vanilla-extract.js +3 -2
  86. package/dist/lib/setups/i18n/replacers.test.js +56 -38
  87. package/dist/lib/shell.js +1 -1
  88. package/dist/lib/template-diff.js +89 -0
  89. package/dist/lib/template-downloader.js +3 -2
  90. package/dist/lib/transpile/project.js +1 -1
  91. package/dist/virtual-routes/assets/debug-network.css +592 -0
  92. package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
  93. package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
  94. package/dist/virtual-routes/components/IconClose.jsx +38 -0
  95. package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
  96. package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
  97. package/dist/virtual-routes/components/RequestTable.jsx +92 -0
  98. package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
  99. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
  100. package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  101. package/oclif.manifest.json +134 -59
  102. package/package.json +18 -26
  103. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
  104. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
  105. package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
  106. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
  107. package/dist/virtual-routes/routes/debug-network.jsx +0 -289
  108. /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
@@ -2,11 +2,11 @@ import { spawnSync } from 'child_process';
2
2
  import { outputDebug } from '@shopify/cli-kit/node/output';
3
3
 
4
4
  const EXPERIMENTAL_VM_MODULES_FLAG = "--experimental-vm-modules";
5
+ function commandNeedsVM(id = "", argv = []) {
6
+ return "hydrogen:debug:cpu";
7
+ }
5
8
  const hook = async function(options) {
6
- if (options.id && // All the commands that rely on MiniOxygen:
7
- ["hydrogen:dev", "hydrogen:preview", "hydrogen:debug:cpu"].includes(
8
- options.id
9
- ) && !process.execArgv.includes(EXPERIMENTAL_VM_MODULES_FLAG) && !(process.env.NODE_OPTIONS ?? "").includes(EXPERIMENTAL_VM_MODULES_FLAG)) {
9
+ if (commandNeedsVM(options.id, options.argv) && !process.execArgv.includes(EXPERIMENTAL_VM_MODULES_FLAG) && !(process.env.NODE_OPTIONS ?? "").includes(EXPERIMENTAL_VM_MODULES_FLAG)) {
10
10
  outputDebug(
11
11
  `Restarting CLI process with ${EXPERIMENTAL_VM_MODULES_FLAG} flag.`
12
12
  );
package/dist/lib/auth.js CHANGED
@@ -8,6 +8,7 @@ import ansiEscapes from 'ansi-escapes';
8
8
  import { resetConfig, getConfig, setUserAccount } from './shopify-config.js';
9
9
  import { getUserAccount } from './graphql/business-platform/user-account.js';
10
10
  import { muteAuthLogs } from './log.js';
11
+ import { deferPromise } from './defer.js';
11
12
 
12
13
  async function logout(root) {
13
14
  await logout$1();
@@ -24,14 +25,11 @@ async function login(root, shop) {
24
25
  shop = await normalizeStoreFqdn(shop);
25
26
  const hideLoginInfo = showLoginInfo();
26
27
  if (!shop || !shopName || !email || forcePrompt || shop !== existingConfig.shop) {
27
- const dummyTimeout = setTimeout(() => {
28
- }, 6e5);
29
28
  const token = await ensureAuthenticatedBusinessPlatform().catch(() => {
30
29
  throw new AbortError(
31
30
  "Unable to authenticate with Shopify. Please report this issue."
32
31
  );
33
32
  });
34
- clearTimeout(dummyTimeout);
35
33
  const userAccount = await getUserAccount(token);
36
34
  await hideLoginInfo();
37
35
  const preselected = !forcePrompt && shop && userAccount.activeShops.find(({ fqdn }) => shop === fqdn);
@@ -56,10 +54,7 @@ async function login(root, shop) {
56
54
  return { session, config };
57
55
  }
58
56
  function showLoginInfo() {
59
- let deferredResolve;
60
- const promise = new Promise((resolve) => {
61
- deferredResolve = resolve;
62
- });
57
+ const deferred = deferPromise();
63
58
  console.log("");
64
59
  let hasLoggedTimeout = false;
65
60
  let hasLoggedPressKey = false;
@@ -94,21 +89,21 @@ function showLoginInfo() {
94
89
  {
95
90
  title: "Waiting for Shopify authentication",
96
91
  task: async () => {
97
- await promise;
92
+ await deferred.promise;
98
93
  }
99
94
  }
100
95
  ]);
101
96
  });
102
97
  }
103
98
  });
104
- promise.then(() => {
99
+ deferred.promise.then(() => {
105
100
  restoreLogs();
106
101
  if (hasLoggedPressKey) {
107
102
  process.stdout.write(ansiEscapes.eraseLines(hasLoggedTimeout ? 11 : 10));
108
103
  }
109
104
  });
110
105
  return async () => {
111
- deferredResolve();
106
+ deferred.resolve();
112
107
  await new Promise((resolve) => setTimeout(resolve, 0));
113
108
  };
114
109
  }
package/dist/lib/build.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { fileURLToPath } from 'node:url';
2
+ import { execAsync } from './process.js';
2
3
 
3
4
  const GENERATOR_TEMPLATES_DIR = "generator-templates";
4
5
  const GENERATOR_STARTER_DIR = "starter";
@@ -47,5 +48,9 @@ function getSkeletonSourceDir() {
47
48
  new URL(`../../../../templates/skeleton`, import.meta.url)
48
49
  );
49
50
  }
51
+ async function getRepoNodeModules() {
52
+ const { stdout } = await execAsync("npm root");
53
+ return stdout.trim() || fileURLToPath(new URL(`../../../../node_modules`, import.meta.url));
54
+ }
50
55
 
51
- export { GENERATOR_APP_DIR, GENERATOR_ROUTE_DIR, GENERATOR_SETUP_ASSETS_DIR, GENERATOR_SETUP_ASSETS_SUB_DIRS, GENERATOR_STARTER_DIR, GENERATOR_TEMPLATES_DIR, getAssetDir, getSkeletonSourceDir, getStarterDir, getTemplateAppFile };
56
+ export { GENERATOR_APP_DIR, GENERATOR_ROUTE_DIR, GENERATOR_SETUP_ASSETS_DIR, GENERATOR_SETUP_ASSETS_SUB_DIRS, GENERATOR_STARTER_DIR, GENERATOR_TEMPLATES_DIR, getAssetDir, getRepoNodeModules, getSkeletonSourceDir, getStarterDir, getTemplateAppFile };
@@ -2,30 +2,43 @@ import { joinPath, dirname } from '@shopify/cli-kit/node/path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { fileExists, readFile, writeFile } from '@shopify/cli-kit/node/fs';
4
4
  import colors from '@shopify/cli-kit/node/colors';
5
+ import { renderWarning } from '@shopify/cli-kit/node/ui';
5
6
 
6
- async function hasMetafile(buildPath) {
7
- return (await Promise.all([
8
- fileExists(joinPath(buildPath, "worker", "metafile.server.json")),
9
- fileExists(joinPath(buildPath, "worker", "metafile.js.json"))
10
- ])).every(Boolean);
11
- }
12
7
  async function buildBundleAnalysis(buildPath) {
13
- await Promise.all([
14
- writeBundleAnalyzerFile(
15
- buildPath,
16
- "metafile.server.json",
17
- "worker-bundle-analyzer.html"
18
- ),
19
- writeBundleAnalyzerFile(
20
- buildPath,
21
- "metafile.js.json",
22
- "client-bundle-analyzer.html"
23
- )
24
- ]);
25
- return "file://" + joinPath(buildPath, "worker", "worker-bundle-analyzer.html");
8
+ const workerBuildPath = joinPath(buildPath, "worker");
9
+ const serverMetafile = "metafile.server.json";
10
+ const clientMetafile = "metafile.js.json";
11
+ const hasMetafile = (await Promise.all([
12
+ fileExists(joinPath(workerBuildPath, serverMetafile)),
13
+ fileExists(joinPath(workerBuildPath, clientMetafile))
14
+ ])).every(Boolean);
15
+ if (!hasMetafile)
16
+ return null;
17
+ try {
18
+ await Promise.all([
19
+ writeBundleAnalyzerFile(
20
+ workerBuildPath,
21
+ serverMetafile,
22
+ "worker-bundle-analyzer.html"
23
+ ),
24
+ writeBundleAnalyzerFile(
25
+ workerBuildPath,
26
+ clientMetafile,
27
+ "client-bundle-analyzer.html"
28
+ )
29
+ ]);
30
+ return "file://" + joinPath(workerBuildPath, "worker-bundle-analyzer.html");
31
+ } catch (thrown) {
32
+ const error = thrown;
33
+ renderWarning({
34
+ headline: "Could not generate bundle analysis",
35
+ body: error?.stack ?? error?.message ?? error
36
+ });
37
+ return null;
38
+ }
26
39
  }
27
- async function writeBundleAnalyzerFile(buildPath, metafileName, outputFile) {
28
- const metafile = await readFile(joinPath(buildPath, "worker", metafileName), {
40
+ async function writeBundleAnalyzerFile(workerBuildPath, metafileName, outputFile) {
41
+ const metafile = await readFile(joinPath(workerBuildPath, metafileName), {
29
42
  encoding: "utf8"
30
43
  });
31
44
  const metafile64 = Buffer.from(metafile, "utf-8").toString("base64");
@@ -38,10 +51,7 @@ async function writeBundleAnalyzerFile(buildPath, metafileName, outputFile) {
38
51
  `globalThis.METAFILE = '';`,
39
52
  `globalThis.METAFILE = '${metafile64}';`
40
53
  );
41
- await writeFile(
42
- joinPath(buildPath, "worker", outputFile),
43
- templateWithMetafile
44
- );
54
+ await writeFile(joinPath(workerBuildPath, outputFile), templateWithMetafile);
45
55
  }
46
56
  async function getBundleAnalysisSummary(bundlePath) {
47
57
  const esbuild = await import('esbuild').catch(() => {
@@ -59,4 +69,4 @@ async function getBundleAnalysisSummary(bundlePath) {
59
69
  }
60
70
  }
61
71
 
62
- export { buildBundleAnalysis, getBundleAnalysisSummary, hasMetafile };
72
+ export { buildBundleAnalysis, getBundleAnalysisSummary };
@@ -25,6 +25,7 @@ function missingLockfileWarning(shouldExit) {
25
25
  }
26
26
  function multipleLockfilesWarning(lockfiles2, shouldExit) {
27
27
  const packageManagers = {
28
+ "bun.lockb": "bun",
28
29
  "yarn.lock": "yarn",
29
30
  "package-lock.json": "npm",
30
31
  "pnpm-lock.yaml": "pnpm"
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { getCodeFormatOptions, formatCode } from './format-code.js';
4
4
  import { renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
5
- import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
5
+ import { relativePath, joinPath, basename } from '@shopify/cli-kit/node/path';
6
6
  import { AbortError } from '@shopify/cli-kit/node/error';
7
7
 
8
8
  const nodePath = process.argv[1];
@@ -55,6 +55,7 @@ function spawnCodegenProcess({
55
55
  type: 0,
56
56
  name: "CodegenError",
57
57
  message: `Codegen process exited with code ${code}`,
58
+ skipOclifErrorHandling: true,
58
59
  tryMessage: "Try restarting the dev server."
59
60
  });
60
61
  process.exit(code);
@@ -109,12 +110,35 @@ async function generateTypes({
109
110
  });
110
111
  codegenContext.cwd = dirs.rootDirectory;
111
112
  await generate(codegenContext, true);
112
- return Object.keys(codegenConfig.generates);
113
+ return Object.entries(codegenConfig.generates).reduce((acc, [key, value]) => {
114
+ if ("documents" in value) {
115
+ acc[key] = (Array.isArray(value.documents) ? value.documents : [value.documents]).filter((document) => typeof document === "string");
116
+ }
117
+ return acc;
118
+ }, {});
113
119
  }
114
120
  async function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapiVersion) {
115
- const { schema, preset, pluckConfig } = await import('@shopify/hydrogen-codegen');
121
+ const { getSchema, preset, pluckConfig } = await import('@shopify/hydrogen-codegen');
122
+ const { loadConfig } = await import('graphql-config');
123
+ const gqlConfig = await loadConfig({
124
+ rootDir: rootDirectory,
125
+ throwOnEmpty: false,
126
+ throwOnMissing: false,
127
+ legacy: false
128
+ }).catch(() => void 0);
129
+ const sfapiSchema = getSchema("storefront");
130
+ const sfapiProject = findGqlProject(sfapiSchema, gqlConfig);
116
131
  const defaultGlob = "*!(*.d).{ts,tsx,js,jsx}";
117
132
  const appDirRelative = relativePath(rootDirectory, appDirectory);
133
+ const caapiSchema = getSchema("customer-account");
134
+ const caapiProject = findGqlProject(caapiSchema, gqlConfig);
135
+ const customerAccountAPIConfig = caapiProject?.documents ? {
136
+ ["customer-accountapi.generated.d.ts"]: {
137
+ preset,
138
+ schema: caapiSchema,
139
+ documents: caapiProject?.documents
140
+ }
141
+ } : void 0;
118
142
  return {
119
143
  filepath: "virtual:codegen",
120
144
  config: {
@@ -123,8 +147,8 @@ async function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapi
123
147
  generates: {
124
148
  ["storefrontapi.generated.d.ts"]: {
125
149
  preset,
126
- schema,
127
- documents: [
150
+ schema: sfapiSchema,
151
+ documents: sfapiProject?.documents ?? [
128
152
  defaultGlob,
129
153
  // E.g. ./server.(t|j)s
130
154
  joinPath(appDirRelative, "**", defaultGlob)
@@ -145,24 +169,41 @@ async function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapi
145
169
  scalars: { JSON: "unknown" }
146
170
  }
147
171
  }
148
- }
172
+ },
173
+ ...customerAccountAPIConfig
149
174
  }
150
175
  }
151
176
  };
152
177
  }
178
+ function findGqlProject(schemaFilepath, gqlConfig) {
179
+ if (!gqlConfig)
180
+ return;
181
+ const schemaFilename = basename(schemaFilepath);
182
+ return Object.values(gqlConfig.projects || {}).find(
183
+ (project) => typeof project.schema === "string" && project.schema.endsWith(schemaFilename)
184
+ );
185
+ }
153
186
  async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
154
- const { schema } = await import('@shopify/hydrogen-codegen');
155
- const [, options] = Object.entries(codegenConfig.generates).find(
156
- ([, value]) => (Array.isArray(value) ? value[0] : value)?.schema === schema
157
- ) || [];
158
- const hydrogenOptions = Array.isArray(options) ? options[0] : options;
159
- if (hydrogenOptions) {
160
- const formatConfig = await getCodeFormatOptions(rootDirectory);
161
- hydrogenOptions.hooks = {
162
- beforeOneFileWrite: (file, content) => formatCode(content, formatConfig, file),
163
- // Run Prettier before writing files
164
- ...hydrogenOptions.hooks
165
- };
187
+ const hydrogenProjectsOptions = Object.values(codegenConfig.generates).filter(
188
+ (value) => {
189
+ const foundPreset = (Array.isArray(value) ? value[0] : value)?.preset;
190
+ if (typeof foundPreset === "object") {
191
+ const name = Symbol.for("name");
192
+ if (name in foundPreset) {
193
+ return foundPreset[name] === "hydrogen";
194
+ }
195
+ }
196
+ }
197
+ );
198
+ for (const options of hydrogenProjectsOptions) {
199
+ const hydrogenOptions = Array.isArray(options) ? options[0] : options;
200
+ if (hydrogenOptions) {
201
+ const formatConfig = await getCodeFormatOptions(rootDirectory);
202
+ hydrogenOptions.hooks = {
203
+ beforeOneFileWrite: (file, content) => formatCode(content, formatConfig, file),
204
+ ...hydrogenOptions.hooks
205
+ };
206
+ }
166
207
  }
167
208
  }
168
209
 
@@ -0,0 +1,12 @@
1
+ function deferPromise() {
2
+ let resolve = (value) => {
3
+ };
4
+ let reject = resolve;
5
+ const promise = new Promise((_resolve, _reject) => {
6
+ resolve = _resolve;
7
+ reject = _reject;
8
+ });
9
+ return { promise, resolve, reject };
10
+ }
11
+
12
+ export { deferPromise };
package/dist/lib/file.js CHANGED
@@ -1,6 +1,7 @@
1
- import { resolvePath } from '@shopify/cli-kit/node/path';
1
+ import { readdir } from 'node:fs/promises';
2
+ import { resolvePath, joinPath } from '@shopify/cli-kit/node/path';
2
3
  import { readFile, writeFile, isDirectory, fileExists } from '@shopify/cli-kit/node/fs';
3
- import { readdir } from 'fs/promises';
4
+ import { readAndParsePackageJson, writePackageJSON } from '@shopify/cli-kit/node/node-package-manager';
4
5
  import { formatCode } from './format-code.js';
5
6
 
6
7
  async function replaceFileContent(filepath, formatConfig, replacer) {
@@ -37,5 +38,53 @@ async function findFileWithExtension(directory, fileBase, extensions = DEFAULT_E
37
38
  }
38
39
  return {};
39
40
  }
41
+ const MANAGED_PACKAGE_JSON_KEYS = Object.freeze([
42
+ "dependencies",
43
+ "devDependencies",
44
+ "peerDependencies"
45
+ ]);
46
+ async function mergePackageJson(sourceDir, targetDir, options) {
47
+ const targetPkgJson = await readAndParsePackageJson(
48
+ joinPath(targetDir, "package.json")
49
+ );
50
+ const sourcePkgJson = await readAndParsePackageJson(
51
+ joinPath(sourceDir, "package.json")
52
+ );
53
+ const ignoredKeys = /* @__PURE__ */ new Set(["comment", ...options?.ignoredKeys ?? []]);
54
+ const unmanagedKeys = Object.keys(sourcePkgJson).filter(
55
+ (key) => !MANAGED_PACKAGE_JSON_KEYS.includes(key)
56
+ );
57
+ for (const key of unmanagedKeys) {
58
+ if (ignoredKeys.has(key))
59
+ continue;
60
+ const sourceValue = sourcePkgJson[key];
61
+ const targetValue = targetPkgJson[key];
62
+ const newValue = Array.isArray(sourceValue) && Array.isArray(targetValue) ? [...targetValue, ...sourceValue] : typeof sourceValue === "object" && typeof targetValue === "object" ? { ...targetValue, ...sourceValue } : sourceValue;
63
+ targetPkgJson[key] = newValue;
64
+ }
65
+ const remixVersion = Object.entries(targetPkgJson.dependencies || {}).find(
66
+ ([dep]) => dep.startsWith("@remix-run/")
67
+ )?.[1];
68
+ for (const key of MANAGED_PACKAGE_JSON_KEYS) {
69
+ if (ignoredKeys.has(key))
70
+ continue;
71
+ if (sourcePkgJson[key]) {
72
+ targetPkgJson[key] = [
73
+ .../* @__PURE__ */ new Set([
74
+ ...Object.keys(targetPkgJson[key] ?? {}),
75
+ ...Object.keys(sourcePkgJson[key] ?? {})
76
+ ])
77
+ ].sort().reduce((acc, dep) => {
78
+ let version = sourcePkgJson[key]?.[dep] ?? targetPkgJson[key]?.[dep];
79
+ if (dep.startsWith("@remix-run/") && remixVersion) {
80
+ version = remixVersion;
81
+ }
82
+ acc[dep] = version;
83
+ return acc;
84
+ }, {});
85
+ }
86
+ }
87
+ await writePackageJSON(targetDir, targetPkgJson);
88
+ }
40
89
 
41
- export { findFileWithExtension, replaceFileContent };
90
+ export { findFileWithExtension, mergePackageJson, replaceFileContent };
package/dist/lib/flags.js CHANGED
@@ -5,6 +5,7 @@ import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
5
5
  import colors from '@shopify/cli-kit/node/colors';
6
6
  import { STYLING_CHOICES } from './setups/css/index.js';
7
7
  import { I18N_CHOICES } from './setups/i18n/index.js';
8
+ import { DEFAULT_INSPECTOR_PORT } from './mini-oxygen/common.js';
8
9
 
9
10
  const DEFAULT_PORT = 3e3;
10
11
  const commonFlags = {
@@ -17,9 +18,9 @@ const commonFlags = {
17
18
  env: "SHOPIFY_HYDROGEN_FLAG_PORT",
18
19
  default: DEFAULT_PORT
19
20
  }),
20
- workerRuntime: Flags.boolean({
21
- description: "Run the app in a worker environment closer to Oxygen production instead of a Node.js sandbox. This flag is unstable and may change without notice.",
22
- env: "SHOPIFY_HYDROGEN_FLAG_WORKER_UNSTABLE"
21
+ legacyRuntime: Flags.boolean({
22
+ description: "Run the app in a Node.js sandbox instead of an Oxygen worker.",
23
+ env: "SHOPIFY_HYDROGEN_FLAG_WORKER"
23
24
  }),
24
25
  force: Flags.boolean({
25
26
  description: "Overwrite the destination directory and files if they already exist.",
@@ -51,9 +52,7 @@ const commonFlags = {
51
52
  codegen: Flags.boolean({
52
53
  description: "Generate types for the Storefront API queries found in your project.",
53
54
  required: false,
54
- default: false,
55
- deprecateAliases: true,
56
- aliases: ["codegen-unstable"]
55
+ default: false
57
56
  }),
58
57
  codegenConfigPath: Flags.string({
59
58
  description: "Specify a path to a codegen configuration file. Defaults to `<root>/codegen.ts` if it exists.",
@@ -78,6 +77,21 @@ const commonFlags = {
78
77
  description: "Create a shortcut to the Shopify Hydrogen CLI.",
79
78
  env: "SHOPIFY_HYDROGEN_FLAG_SHORTCUT",
80
79
  allowNo: true
80
+ }),
81
+ debug: Flags.boolean({
82
+ description: "Enables inspector connections with a debugger.",
83
+ env: "SHOPIFY_HYDROGEN_FLAG_DEBUG",
84
+ default: false
85
+ }),
86
+ inspectorPort: Flags.integer({
87
+ description: "Port where the inspector will be available.",
88
+ env: "SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT",
89
+ default: DEFAULT_INSPECTOR_PORT
90
+ }),
91
+ diff: Flags.boolean({
92
+ description: "Applies the current files on top of Hydrogen's starter template in a temporary directory.",
93
+ default: false,
94
+ required: false
81
95
  })
82
96
  };
83
97
  function flagsToCamelObject(obj) {
@@ -104,18 +118,22 @@ function parseProcessFlags(processArgv, flagMap = {}) {
104
118
  }
105
119
  return flagsToCamelObject(options);
106
120
  }
107
- function deprecated(name) {
108
- return Flags.custom({
121
+ function deprecated(name, { isBoolean = false } = {}) {
122
+ const customFlag = Flags.custom({
109
123
  parse: () => {
110
124
  renderInfo({
111
125
  headline: `The ${colors.bold(
112
126
  name
113
- )} flag is deprecated and will be removed in a future version of Shopify CLI.`
127
+ )} flag is deprecated and will be removed in a future version of Shopify Hydrogen CLI.`
114
128
  });
115
129
  return Promise.resolve(" ");
116
130
  },
117
131
  hidden: true
118
132
  });
133
+ return {
134
+ ...customFlag(),
135
+ type: isBoolean ? "boolean" : "option"
136
+ };
119
137
  }
120
138
  function overrideFlag(flag, extra) {
121
139
  return {
@@ -25,11 +25,13 @@ describe("getOxygenDeploymentData", () => {
25
25
  const environments = [
26
26
  {
27
27
  name: "production",
28
- branch: "main"
28
+ branch: "main",
29
+ type: "PRODUCTION"
29
30
  },
30
31
  {
31
32
  name: "preview",
32
- branch: "staging"
33
+ branch: null,
34
+ type: "PREVIEW"
33
35
  }
34
36
  ];
35
37
  beforeEach(() => {
@@ -14,7 +14,7 @@ describe("adminRequest", () => {
14
14
  token: "",
15
15
  storeFqdn: ""
16
16
  });
17
- expect(response).toContain(fakeResponse);
17
+ expect(response).toMatchObject(fakeResponse);
18
18
  });
19
19
  describe("when there is an unknown error response", () => {
20
20
  it("passes along the error message", async () => {
@@ -30,7 +30,7 @@ describe("adminRequest", () => {
30
30
  token: "",
31
31
  storeFqdn: ""
32
32
  });
33
- await expect(response).rejects.toContain(fakeGraphqlError);
33
+ await expect(response).rejects.toMatchObject(fakeGraphqlError);
34
34
  });
35
35
  });
36
36
  describe("when the app isn't installed", () => {
@@ -7,6 +7,7 @@ const GetDeploymentDataQuery = `#graphql
7
7
  environments {
8
8
  name
9
9
  branch
10
+ type
10
11
  }
11
12
  }
12
13
  }
package/dist/lib/log.js CHANGED
@@ -32,6 +32,10 @@ function debounceMessage(args, debounceFor) {
32
32
  }
33
33
  return false;
34
34
  }
35
+ function warningDebouncer([first]) {
36
+ return typeof first === "string" && // Show these warnings only once.
37
+ (first.includes("[h2:warn:createStorefrontClient]") || first.includes("[h2:warn:createCustomerAccountClient]")) ? true : void 0;
38
+ }
35
39
  function injectLogReplacer(method, debouncer) {
36
40
  if (!methodsReplaced.has(method)) {
37
41
  methodsReplaced.add(method);
@@ -57,7 +61,7 @@ function injectLogReplacer(method, debouncer) {
57
61
  function muteDevLogs({ workerReload } = {}) {
58
62
  injectLogReplacer("log");
59
63
  injectLogReplacer("error");
60
- injectLogReplacer("warn");
64
+ injectLogReplacer("warn", warningDebouncer);
61
65
  let isFirstWorkerReload = true;
62
66
  addMessageReplacers("dev-node", [
63
67
  ([first]) => typeof first === "string" && first.includes("[mf:"),
@@ -77,18 +81,30 @@ function muteDevLogs({ workerReload } = {}) {
77
81
  ]);
78
82
  addMessageReplacers(
79
83
  "dev-workerd",
84
+ // Workerd logs
85
+ [
86
+ ([first]) => typeof first === "string" && /^\x1B\[31m(workerd\/|stack:( (0|[a-f\d]{4,})){3,})/.test(first),
87
+ () => {
88
+ }
89
+ ],
80
90
  [
81
- // Workerd logs
82
- ([first]) => typeof first === "string" && /^\x1B\[31m(workerd\/|stack:)/.test(first),
91
+ ([first]) => typeof first === "string" && /\$LLVM_SYMBOLIZER|recursive isolate lock/.test(first),
83
92
  () => {
84
93
  }
85
94
  ],
86
- // Non-actionable warnings/errors:
95
+ // Non-actionable warnings:
87
96
  [
88
97
  ([first]) => typeof first === "string" && /^A promise rejection/i.test(first),
89
98
  () => {
90
99
  }
91
100
  ],
101
+ [
102
+ ([first]) => typeof first === "string" && /resolved to multiple addresses/i.test(first),
103
+ // For win32
104
+ () => {
105
+ }
106
+ ],
107
+ // Non-actionable errors:
92
108
  [
93
109
  ([first]) => {
94
110
  const message = first?.message ?? first;
@@ -157,13 +173,7 @@ function muteAuthLogs({
157
173
  }
158
174
  function enhanceH2Logs(options) {
159
175
  injectLogReplacer("error");
160
- injectLogReplacer(
161
- "warn",
162
- ([first]) => (
163
- // Show createStorefrontClient warnings only once.
164
- first?.includes?.("[h2:warn:createStorefrontClient]") ? true : void 0
165
- )
166
- );
176
+ injectLogReplacer("warn", warningDebouncer);
167
177
  addMessageReplacers("h2-warn", [
168
178
  ([first]) => {
169
179
  const message = first?.message ?? first;
@@ -182,10 +192,11 @@ function enhanceH2Logs(options) {
182
192
  const lines = message.split("\n");
183
193
  const lastLine = lines.at(-1) ?? "";
184
194
  const hasLinks = /https?:\/\//.test(lastLine);
185
- if (hasLinks)
195
+ const hasCommands = /`h2 [^`]+`/.test(lastLine);
196
+ if (hasLinks || hasCommands)
186
197
  lines.pop();
187
198
  if (type === "error" || errorObject) {
188
- let tryMessage = hasLinks ? lastLine : void 0;
199
+ let tryMessage = hasLinks || hasCommands ? lastLine : void 0;
189
200
  let stack = errorObject?.stack;
190
201
  let cause = errorObject?.cause;
191
202
  if (typeof cause === "string") {
@@ -235,7 +246,13 @@ function enhanceH2Logs(options) {
235
246
  const render = type === "warn" ? renderWarning : renderInfo;
236
247
  render({
237
248
  body: headline + colors.bold(lines.join("\n")),
238
- reference
249
+ reference,
250
+ nextSteps: hasCommands ? [
251
+ lastLine.replace(
252
+ /`h2 [^`]+`/g,
253
+ (cmd) => colors.bold(colors.yellow(cmd))
254
+ )
255
+ ] : void 0
239
256
  });
240
257
  return;
241
258
  }
@@ -266,6 +283,7 @@ function createRemixLogger() {
266
283
  name: "error",
267
284
  type: 0,
268
285
  message: buildMessageBody(message, options?.details),
286
+ skipOclifErrorHandling: true,
269
287
  tryMessage: ""
270
288
  });
271
289
  }