@shopify/cli-hydrogen 6.0.2 → 6.1.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 (30) hide show
  1. package/dist/commands/hydrogen/deploy.js +72 -8
  2. package/dist/commands/hydrogen/deploy.test.js +111 -9
  3. package/dist/commands/hydrogen/dev.js +33 -23
  4. package/dist/commands/hydrogen/preview.js +20 -10
  5. package/dist/commands/hydrogen/shortcut.js +1 -0
  6. package/dist/commands/hydrogen/upgrade.js +705 -0
  7. package/dist/commands/hydrogen/upgrade.test.js +786 -0
  8. package/dist/generator-templates/starter/CHANGELOG.md +70 -0
  9. package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
  10. package/dist/generator-templates/starter/app/components/Layout.tsx +13 -10
  11. package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
  12. package/dist/generator-templates/starter/package.json +10 -9
  13. package/dist/generator-templates/starter/remix.env.d.ts +2 -0
  14. package/dist/lib/check-lockfile.js +1 -0
  15. package/dist/lib/codegen.js +1 -0
  16. package/dist/lib/flags.js +13 -2
  17. package/dist/lib/log.js +1 -0
  18. package/dist/lib/mini-oxygen/assets.js +118 -0
  19. package/dist/lib/mini-oxygen/common.js +2 -1
  20. package/dist/lib/mini-oxygen/index.js +3 -0
  21. package/dist/lib/mini-oxygen/node.js +15 -3
  22. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
  23. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
  24. package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
  25. package/dist/lib/mini-oxygen/workerd.js +54 -47
  26. package/dist/lib/render-errors.js +2 -0
  27. package/dist/lib/setups/i18n/replacers.test.js +2 -0
  28. package/dist/lib/shell.js +1 -1
  29. package/oclif.manifest.json +90 -8
  30. package/package.json +10 -21
@@ -1,22 +1,59 @@
1
1
  import { dirname } from 'node:path';
2
2
  import { readFile } from 'node:fs/promises';
3
+ import { fetch } from '@shopify/cli-kit/node/http';
3
4
  import { SourceMapConsumer } from 'source-map';
4
- import { parse } from 'stack-trace';
5
- import WebSocket from 'ws';
5
+ import { WebSocket } from 'ws';
6
+ import { addInspectorConsoleLogger, formatStack } from './workerd-inspector-logs.js';
7
+ import { AbortError } from '@shopify/cli-kit/node/error';
8
+ import { createInspectorProxy } from './workerd-inspector-proxy.js';
6
9
 
10
+ function createInspectorConnector(options) {
11
+ let inspectorUrl;
12
+ let inspectorConnection;
13
+ let inspectorProxy;
14
+ return async (onBeforeConnect) => {
15
+ inspectorConnection?.close();
16
+ inspectorUrl ??= await findInspectorUrl(options.privateInspectorPort);
17
+ await onBeforeConnect?.();
18
+ inspectorConnection = connectToInspector({
19
+ inspectorUrl,
20
+ sourceMapPath: options.sourceMapPath
21
+ });
22
+ addInspectorConsoleLogger(inspectorConnection);
23
+ if (options.debug) {
24
+ if (inspectorProxy) {
25
+ inspectorProxy.updateInspectorConnection(inspectorConnection);
26
+ } else {
27
+ inspectorProxy = createInspectorProxy(
28
+ options.publicInspectorPort,
29
+ options.absoluteBundlePath,
30
+ inspectorConnection
31
+ );
32
+ }
33
+ }
34
+ };
35
+ }
7
36
  async function findInspectorUrl(inspectorPort) {
8
37
  try {
9
38
  const jsonUrl = `http://127.0.0.1:${inspectorPort}/json`;
10
39
  const body = await (await fetch(jsonUrl)).json();
11
- return body?.find(({ id }) => id === "core:user:hydrogen")?.webSocketDebuggerUrl;
40
+ const url = body?.find(
41
+ ({ id }) => id === "core:user:hydrogen"
42
+ )?.webSocketDebuggerUrl;
43
+ if (!url) {
44
+ throw new Error("Unable to find inspector URL");
45
+ }
46
+ return url;
12
47
  } catch (error) {
13
- console.error("Error attempting to retrieve debugger URL:", error);
48
+ const abortError = new AbortError(
49
+ "Unable to connect to Worker inspector",
50
+ `Please report this issue. ${error.stack}`
51
+ );
52
+ abortError.stack = error.stack;
53
+ throw abortError;
14
54
  }
15
55
  }
16
- function connectToInspector({
17
- inspectorUrl,
18
- sourceMapPath
19
- }) {
56
+ function connectToInspector({ inspectorUrl, sourceMapPath }) {
20
57
  const messageCounterRef = { value: -1 };
21
58
  const getMessageId = () => messageCounterRef.value--;
22
59
  const pendingMessages = /* @__PURE__ */ new Map();
@@ -147,42 +184,6 @@ function connectToInspector({
147
184
  }
148
185
  })();
149
186
  };
150
- ws.addEventListener("message", async (event) => {
151
- if (typeof event.data === "string") {
152
- const evt = JSON.parse(event.data);
153
- cleanupMessageQueue(evt);
154
- if (evt.method === "Runtime.exceptionThrown") {
155
- const params = evt.params;
156
- const errorProperties = {};
157
- const sourceMapConsumer = await getSourceMapConsumer();
158
- if (sourceMapConsumer !== void 0) {
159
- const message = params.exceptionDetails.exception?.description?.split("\n")[0];
160
- const stack = params.exceptionDetails.stackTrace?.callFrames;
161
- const formatted = formatStructuredError(
162
- sourceMapConsumer,
163
- message,
164
- stack
165
- );
166
- errorProperties.message = params.exceptionDetails.text;
167
- errorProperties.stack = formatted;
168
- } else {
169
- errorProperties.message = params.exceptionDetails.text + " " + (params.exceptionDetails.exception?.description ?? "");
170
- }
171
- console.error(
172
- await reconstructError(
173
- errorProperties,
174
- params.exceptionDetails.exception
175
- )
176
- );
177
- }
178
- if (evt.method === "Runtime.consoleAPICalled") {
179
- const params = evt.params;
180
- await logConsoleMessage(params, reconstructError);
181
- }
182
- } else {
183
- console.error("Unrecognised devtools event:", event);
184
- }
185
- });
186
187
  ws.once("open", () => {
187
188
  send("Runtime.enable");
188
189
  keepAliveInterval = setInterval(() => send("Runtime.getIsolateId"), 1e4);
@@ -194,199 +195,25 @@ function connectToInspector({
194
195
  clearInterval(keepAliveInterval);
195
196
  sourceMapAbortController.abort();
196
197
  });
197
- return () => {
198
- clearInterval(keepAliveInterval);
199
- if (!isClosed()) {
200
- try {
201
- ws.close();
202
- } catch (err) {
203
- }
204
- }
205
- sourceMapAbortController.abort();
206
- };
207
- }
208
- const mapConsoleAPIMessageTypeToConsoleMethod = {
209
- log: "log",
210
- debug: "debug",
211
- info: "info",
212
- warning: "warn",
213
- error: "error",
214
- dir: "dir",
215
- dirxml: "dirxml",
216
- table: "table",
217
- trace: "trace",
218
- clear: "clear",
219
- count: "count",
220
- assert: "assert",
221
- profile: "profile",
222
- profileEnd: "profileEnd",
223
- timeEnd: "timeEnd",
224
- startGroup: "group",
225
- startGroupCollapsed: "groupCollapsed",
226
- endGroup: "groupEnd"
227
- };
228
- async function logConsoleMessage(evt, reconstructError) {
229
- const args = [];
230
- for (const ro of evt.args) {
231
- switch (ro.type) {
232
- case "string":
233
- case "number":
234
- case "boolean":
235
- case "undefined":
236
- case "symbol":
237
- case "bigint":
238
- args.push(ro.value);
239
- break;
240
- case "function":
241
- args.push(`[Function: ${ro.description ?? "<no-description>"}]`);
242
- break;
243
- case "object":
244
- if (!ro.preview) {
245
- args.push(
246
- ro.subtype === "null" ? "null" : ro.description ?? "<no-description>"
247
- );
248
- } else {
249
- if (ro.preview.description)
250
- args.push(ro.preview.description);
251
- switch (ro.preview.subtype) {
252
- case "array":
253
- args.push(
254
- "[ " + ro.preview.properties.map(({ value }) => {
255
- return value;
256
- }).join(", ") + (ro.preview.overflow ? "..." : "") + " ]"
257
- );
258
- break;
259
- case "weakmap":
260
- case "map":
261
- ro.preview.entries === void 0 ? args.push("{}") : args.push(
262
- "{\n" + ro.preview.entries.map(({ key, value }) => {
263
- return ` ${key?.description ?? "<unknown>"} => ${value.description}`;
264
- }).join(",\n") + (ro.preview.overflow ? "\n ..." : "") + "\n}"
265
- );
266
- break;
267
- case "weakset":
268
- case "set":
269
- ro.preview.entries === void 0 ? args.push("{}") : args.push(
270
- "{ " + ro.preview.entries.map(({ value }) => {
271
- return `${value.description}`;
272
- }).join(", ") + (ro.preview.overflow ? ", ..." : "") + " }"
273
- );
274
- break;
275
- case "regexp":
276
- break;
277
- case "date":
278
- break;
279
- case "generator":
280
- args.push(ro.preview?.properties[0]?.value || "");
281
- break;
282
- case "promise":
283
- if (ro.preview?.properties[0]?.value === "pending") {
284
- args.push(`{<${ro.preview.properties[0].value}>}`);
285
- } else {
286
- args.push(
287
- `{<${ro.preview?.properties[0]?.value}>: ${ro.preview?.properties[1]?.value}}`
288
- );
289
- }
290
- break;
291
- case "node":
292
- case "iterator":
293
- case "proxy":
294
- case "typedarray":
295
- case "arraybuffer":
296
- case "dataview":
297
- case "webassemblymemory":
298
- case "wasmvalue":
299
- break;
300
- case "error":
301
- const errorProperties = {
302
- message: ro.preview.description?.split("\n").filter((line) => !/^\s+at\s/.test(line)).join("\n") ?? ro.preview.properties.find(({ name }) => name === "message")?.value ?? "",
303
- stack: ro.preview.description ?? ro.description ?? ro.preview.properties.find(({ name }) => name === "stack")?.value,
304
- cause: ro.preview.properties.find(({ name }) => name === "cause")?.value
305
- };
306
- const error = await reconstructError(errorProperties, ro);
307
- args.splice(-1, 1, error);
308
- break;
309
- default:
310
- args.push(
311
- "{\n" + ro.preview.properties.map(({ name, value }) => {
312
- return ` ${name}: ${value}`;
313
- }).join(",\n") + (ro.preview.overflow ? "\n ..." : "") + "\n}"
314
- );
315
- }
316
- }
317
- break;
318
- default:
319
- args.push(ro.description || ro.unserializableValue || "\u{1F98B}");
320
- break;
321
- }
322
- }
323
- const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
324
- if (method in console) {
325
- switch (method) {
326
- case "dir":
327
- console.dir(args);
328
- break;
329
- case "table":
330
- console.table(args);
331
- break;
332
- default:
333
- console[method].apply(console, args);
334
- break;
335
- }
336
- } else {
337
- console.warn(`Unsupported console method: ${method}`);
338
- console.warn("console event:", evt);
339
- }
340
- }
341
- function formatStructuredError(sourceMapConsumer, message, frames) {
342
- const lines = [];
343
- if (message !== void 0)
344
- lines.push(message);
345
- frames?.forEach(({ functionName, lineNumber, columnNumber }, i) => {
346
- try {
347
- if (lineNumber) {
348
- const pos = sourceMapConsumer.originalPositionFor({
349
- line: lineNumber + 1,
350
- column: columnNumber
351
- });
352
- if (i === 0 && pos.source && pos.line) {
353
- const fileSource = sourceMapConsumer.sourceContentFor(pos.source);
354
- const fileSourceLine = fileSource?.split("\n")[pos.line - 1] || "";
355
- lines.push(fileSourceLine.trim());
356
- if (pos.column) {
357
- lines.push(
358
- `${" ".repeat(pos.column - fileSourceLine.search(/\S/))}^`
359
- );
360
- }
361
- }
362
- if (pos && pos.line !== null && pos.column !== null) {
363
- const convertedFnName = pos.name || functionName || "";
364
- let convertedLocation = `${pos.source}:${pos.line}:${pos.column + 1}`;
365
- if (convertedFnName === "") {
366
- lines.push(` at ${convertedLocation}`);
367
- } else {
368
- lines.push(` at ${convertedFnName} (${convertedLocation})`);
369
- }
198
+ return {
199
+ ws,
200
+ send,
201
+ reconstructError,
202
+ getSourceMapConsumer,
203
+ cleanupMessageQueue,
204
+ isClosed,
205
+ close: () => {
206
+ clearInterval(keepAliveInterval);
207
+ if (!isClosed()) {
208
+ try {
209
+ ws.removeAllListeners();
210
+ ws.close();
211
+ } catch (err) {
370
212
  }
371
213
  }
372
- } catch {
214
+ sourceMapAbortController.abort();
373
215
  }
374
- });
375
- return lines.join("\n");
376
- }
377
- function formatStack(sourceMapConsumer, stack) {
378
- const message = stack.split("\n")[0];
379
- const callSites = parse({ stack });
380
- const frames = callSites.map((site) => ({
381
- functionName: site.getFunctionName() ?? "",
382
- // `Protocol.Runtime.CallFrame`s line numbers are 0-indexed, hence `- 1`
383
- lineNumber: (site.getLineNumber() ?? 1) - 1,
384
- columnNumber: site.getColumnNumber() ?? 1,
385
- // Unused by `formattedError`
386
- scriptId: "",
387
- url: ""
388
- }));
389
- return formatStructuredError(sourceMapConsumer, message, frames);
216
+ };
390
217
  }
391
218
 
392
- export { connectToInspector, findInspectorUrl, mapConsoleAPIMessageTypeToConsoleMethod };
219
+ export { createInspectorConnector };
@@ -1,25 +1,27 @@
1
- import { Response, Miniflare, NoOpLog, Request } from 'miniflare';
2
- import { resolvePath } from '@shopify/cli-kit/node/path';
3
- import { glob, readFile, createFileReadStream, fileSize } from '@shopify/cli-kit/node/fs';
1
+ import crypto from 'node:crypto';
2
+ import { Response, Miniflare, fetch, Request, NoOpLog } from 'miniflare';
3
+ import { resolvePath, dirname } from '@shopify/cli-kit/node/path';
4
+ import { readFile } from '@shopify/cli-kit/node/fs';
4
5
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
5
- import { lookupMimeType } from '@shopify/cli-kit/node/mimes';
6
- import { findInspectorUrl, connectToInspector } from './workerd-inspector.js';
7
- import { DEFAULT_PORT } from '../flags.js';
6
+ import { createInspectorConnector } from './workerd-inspector.js';
8
7
  import { findPort } from '../find-port.js';
9
8
  import { OXYGEN_HEADERS_MAP, logRequestLine } from './common.js';
10
9
  import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME, logRequestEvent } from '../request-events.js';
10
+ import { STATIC_ASSET_EXTENSIONS, createAssetsServer, buildAssetsUrl } from './assets.js';
11
11
 
12
- const DEFAULT_INSPECTOR_PORT = 8787;
12
+ const PRIVATE_WORKERD_INSPECTOR_PORT = 9222;
13
13
  async function startWorkerdServer({
14
14
  root,
15
- port = DEFAULT_PORT,
15
+ port: appPort,
16
+ inspectorPort: publicInspectorPort,
17
+ assetsPort,
18
+ debug = false,
16
19
  watch = false,
17
20
  buildPathWorkerFile,
18
21
  buildPathClient,
19
22
  env
20
23
  }) {
21
- const appPort = await findPort(port);
22
- const inspectorPort = await findPort(DEFAULT_INSPECTOR_PORT);
24
+ const privateInspectorPort = await findPort(PRIVATE_WORKERD_INSPECTOR_PORT);
23
25
  const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
24
26
  (acc, item) => {
25
27
  acc[item.name] = item.defaultValue;
@@ -28,11 +30,14 @@ async function startWorkerdServer({
28
30
  {}
29
31
  );
30
32
  setConstructors({ Response });
33
+ const absoluteBundlePath = resolvePath(root, buildPathWorkerFile);
34
+ const handleAssets = createAssetHandler(assetsPort);
35
+ const staticAssetExtensions = STATIC_ASSET_EXTENSIONS.slice();
31
36
  const buildMiniOxygenOptions = async () => ({
32
37
  cf: false,
33
38
  verbose: false,
34
39
  port: appPort,
35
- inspectorPort,
40
+ inspectorPort: privateInspectorPort,
36
41
  log: new NoOpLog(),
37
42
  liveReload: watch,
38
43
  host: "localhost",
@@ -42,23 +47,24 @@ async function startWorkerdServer({
42
47
  modules: true,
43
48
  script: `export default { fetch: ${miniOxygenHandler.toString()} }`,
44
49
  bindings: {
45
- initialAssets: await glob("**/*", { cwd: buildPathClient }),
50
+ staticAssetExtensions,
46
51
  oxygenHeadersMap
47
52
  },
48
53
  serviceBindings: {
49
54
  hydrogen: "hydrogen",
50
- assets: createAssetHandler(buildPathClient),
55
+ assets: handleAssets,
51
56
  debugNetwork: handleDebugNetworkRequest,
52
57
  logRequest
53
58
  }
54
59
  },
55
60
  {
56
61
  name: "hydrogen",
62
+ modulesRoot: dirname(absoluteBundlePath),
57
63
  modules: [
58
64
  {
59
65
  type: "ESModule",
60
- path: resolvePath(root, buildPathWorkerFile),
61
- contents: await readFile(resolvePath(root, buildPathWorkerFile))
66
+ path: absoluteBundlePath,
67
+ contents: await readFile(absoluteBundlePath)
62
68
  }
63
69
  ],
64
70
  compatibilityFlags: ["streams_enable_constructors"],
@@ -74,8 +80,16 @@ async function startWorkerdServer({
74
80
  const miniOxygen = new Miniflare(miniOxygenOptions);
75
81
  const listeningAt = (await miniOxygen.ready).origin;
76
82
  const sourceMapPath = buildPathWorkerFile + ".map";
77
- let inspectorUrl = await findInspectorUrl(inspectorPort);
78
- let cleanupInspector = inspectorUrl ? connectToInspector({ inspectorUrl, sourceMapPath }) : void 0;
83
+ const reconnect = createInspectorConnector({
84
+ debug,
85
+ sourceMapPath,
86
+ absoluteBundlePath,
87
+ privateInspectorPort,
88
+ publicInspectorPort
89
+ });
90
+ await reconnect();
91
+ const assetsServer = createAssetsServer(buildPathClient);
92
+ assetsServer.listen(assetsPort);
79
93
  return {
80
94
  port: appPort,
81
95
  listeningAt,
@@ -89,25 +103,27 @@ async function startWorkerdServer({
89
103
  hydrogen.bindings = { ...nextOptions?.env ?? env };
90
104
  }
91
105
  }
92
- cleanupInspector?.();
93
- await miniOxygen.setOptions(miniOxygenOptions);
94
- inspectorUrl ??= await findInspectorUrl(inspectorPort);
95
- if (inspectorUrl) {
96
- cleanupInspector = connectToInspector({ inspectorUrl, sourceMapPath });
97
- }
106
+ await reconnect(() => miniOxygen.setOptions(miniOxygenOptions));
98
107
  },
99
108
  showBanner(options) {
100
109
  console.log("");
110
+ const debuggerMessage = `
111
+
112
+ Debug mode enabled. Attach a ${process.env.TERM_PROGRAM === "vscode" ? "VSCode " : ""}debugger to port ${publicInspectorPort}
113
+ or open DevTools in http://localhost:${publicInspectorPort}`;
101
114
  renderSuccess({
102
- headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Unstable Worker Runtime) ${options?.mode ?? "development"} server running.`,
115
+ headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Worker Runtime) ${options?.mode ?? "development"} server running.`,
103
116
  body: [
104
117
  `View ${options?.appName ?? "Hydrogen"} app: ${listeningAt}`,
105
- ...options?.extraLines ?? []
118
+ ...options?.extraLines ?? [],
119
+ ...debug ? [{ warn: debuggerMessage }] : []
106
120
  ]
107
121
  });
108
122
  console.log("");
109
123
  },
110
124
  async close() {
125
+ assetsServer.closeAllConnections();
126
+ assetsServer.close();
111
127
  await miniOxygen.dispose();
112
128
  }
113
129
  };
@@ -118,7 +134,11 @@ async function miniOxygenHandler(request, env, context) {
118
134
  return env.debugNetwork.fetch(request);
119
135
  }
120
136
  if (request.method === "GET") {
121
- if (new Set(env.initialAssets).has(pathname.slice(1))) {
137
+ const staticAssetExtensions = new Set(env.staticAssetExtensions);
138
+ const wellKnown = pathname.startsWith("/.well-known");
139
+ const extension = pathname.split(".").at(-1) ?? "";
140
+ const isAsset = wellKnown || !!staticAssetExtensions.has(extension.toUpperCase());
141
+ if (isAsset) {
122
142
  const response2 = await env.assets.fetch(
123
143
  new Request(request.url, {
124
144
  signal: request.signal,
@@ -154,28 +174,15 @@ async function miniOxygenHandler(request, env, context) {
154
174
  );
155
175
  return response;
156
176
  }
157
- function createAssetHandler(buildPathClient) {
177
+ function createAssetHandler(assetsPort) {
178
+ const assetsServerOrigin = buildAssetsUrl(assetsPort);
158
179
  return async (request) => {
159
- const relativeAssetPath = new URL(request.url).pathname.replace("/", "");
160
- if (relativeAssetPath) {
161
- try {
162
- const absoluteAssetPath = resolvePath(
163
- buildPathClient,
164
- relativeAssetPath
165
- );
166
- return new Response(createFileReadStream(absoluteAssetPath), {
167
- headers: {
168
- "Content-Type": lookupMimeType(relativeAssetPath) || "text/plain",
169
- "Content-Length": String(await fileSize(absoluteAssetPath))
170
- }
171
- });
172
- } catch (error) {
173
- if (error.code !== "ENOENT") {
174
- throw error;
175
- }
176
- }
177
- }
178
- return new Response("Not Found", { status: 404 });
180
+ return fetch(
181
+ new Request(
182
+ request.url.replace(new URL(request.url).origin, assetsServerOrigin),
183
+ request
184
+ )
185
+ );
179
186
  };
180
187
  }
181
188
  async function logRequest(request) {
@@ -14,6 +14,7 @@ function renderMissingStorefront({
14
14
  message: outputContent`${outputToken.errorText(
15
15
  "Couldn\u2019t find Hydrogen storefront."
16
16
  )}`.value,
17
+ skipOclifErrorHandling: true,
17
18
  tryMessage: outputContent`Couldn’t find ${storefront.title} (ID: ${parseGid(
18
19
  storefront.id
19
20
  )}) on ${session.storeFqdn}. Check that the storefront exists and run ${outputToken.genericShellCommand(
@@ -29,6 +30,7 @@ function renderMissingLink({ session, cliCommand }) {
29
30
  name: "NoLinkedStorefrontError",
30
31
  type: 0,
31
32
  message: `No linked Hydrogen storefront on ${session.storeFqdn}`,
33
+ skipOclifErrorHandling: true,
32
34
  tryMessage: [
33
35
  "To pull environment variables or to deploy to Oxygen, link this project to a Hydrogen storefront. To select a storefront to link, run",
34
36
  { command: `${cliCommand} link` }
@@ -71,6 +71,8 @@ describe("i18n replacers", () => {
71
71
  PRIVATE_STOREFRONT_API_TOKEN: string;
72
72
  PUBLIC_STORE_DOMAIN: string;
73
73
  PUBLIC_STOREFRONT_ID: string;
74
+ PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string;
75
+ PUBLIC_CUSTOMER_ACCOUNT_API_URL: string;
74
76
  }
75
77
 
76
78
  /**
package/dist/lib/shell.js CHANGED
@@ -160,7 +160,7 @@ async function getCliCommand(directory = process.cwd(), forcePkgManager) {
160
160
  }
161
161
  let cli = "npx";
162
162
  const pkgManager = forcePkgManager ?? await getPackageManager(directory).catch(() => null);
163
- if (pkgManager === "pnpm" || pkgManager === "yarn")
163
+ if (pkgManager === "bun" || pkgManager === "pnpm" || pkgManager === "yarn")
164
164
  cli = pkgManager;
165
165
  return `${cli} shopify hydrogen`;
166
166
  }