@shopify/cli-hydrogen 6.0.1 → 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
@@ -3,9 +3,10 @@ import Command from '@shopify/cli-kit/node/base-command';
3
3
  import colors from '@shopify/cli-kit/node/colors';
4
4
  import { outputWarn, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
5
5
  import { AbortError } from '@shopify/cli-kit/node/error';
6
- import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
6
+ import { writeFile } from '@shopify/cli-kit/node/fs';
7
+ import { ensureIsClean, getLatestGitCommit, GitDirectoryNotCleanError } from '@shopify/cli-kit/node/git';
7
8
  import { resolvePath } from '@shopify/cli-kit/node/path';
8
- import { renderFatalError, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
9
+ import { renderFatalError, renderWarning, renderSelectPrompt, renderSuccess, renderTasks } from '@shopify/cli-kit/node/ui';
9
10
  import { ciPlatform } from '@shopify/cli-kit/node/context/local';
10
11
  import { parseToken, createDeploy } from '@shopify/oxygen-cli/deploy';
11
12
  import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
@@ -24,6 +25,13 @@ class Deploy extends Command {
24
25
  description: "Environment branch (tag) for environment to deploy to",
25
26
  required: false
26
27
  }),
28
+ force: Flags.boolean({
29
+ char: "f",
30
+ description: "Forces a deployment to proceed if there are uncommited changes in its Git repository.",
31
+ default: false,
32
+ env: "SHOPIFY_HYDROGEN_FLAG_FORCE",
33
+ required: false
34
+ }),
27
35
  path: commonFlags.path,
28
36
  shop: commonFlags.shop,
29
37
  "public-deployment": Flags.boolean({
@@ -32,12 +40,22 @@ class Deploy extends Command {
32
40
  required: false,
33
41
  default: false
34
42
  }),
43
+ "no-json-output": Flags.boolean({
44
+ description: "Prevents the command from creating a JSON file containing the deployment URL (in CI environments).",
45
+ required: false,
46
+ default: false
47
+ }),
35
48
  token: Flags.string({
36
49
  char: "t",
37
50
  description: "Oxygen deployment token",
38
51
  env: "SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN",
39
52
  required: false
40
53
  }),
54
+ "metadata-description": Flags.string({
55
+ description: "Description of the changes in the deployment. Defaults to the commit message of the latest commit if there are no uncommited changes.",
56
+ required: false,
57
+ env: "SHOPIFY_HYDROGEN_FLAG_METADATA_DESCRIPTION"
58
+ }),
41
59
  "metadata-url": Flags.string({
42
60
  description: "URL that links to the deployment. Will be saved and displayed in the Shopify admin",
43
61
  required: false,
@@ -77,6 +95,8 @@ class Deploy extends Command {
77
95
  async function oxygenDeploy(options) {
78
96
  const {
79
97
  environmentTag,
98
+ force: forceOnUncommitedChanges,
99
+ noJsonOutput,
80
100
  path,
81
101
  shop,
82
102
  publicDeployment,
@@ -84,20 +104,53 @@ async function oxygenDeploy(options) {
84
104
  metadataUser,
85
105
  metadataVersion
86
106
  } = options;
87
- const ci = ciPlatform();
107
+ let { metadataDescription } = options;
108
+ let isCleanGit = true;
109
+ try {
110
+ await ensureIsClean(path);
111
+ } catch (error) {
112
+ if (error instanceof GitDirectoryNotCleanError) {
113
+ isCleanGit = false;
114
+ }
115
+ if (!forceOnUncommitedChanges && !isCleanGit) {
116
+ throw new AbortError("Uncommitted changes detected.", null, [
117
+ [
118
+ "Commit your changes before deploying or use the ",
119
+ { command: "--force" },
120
+ " flag to deploy with uncommitted changes."
121
+ ]
122
+ ]);
123
+ }
124
+ }
125
+ const isCI = ciPlatform().isCI;
88
126
  let token = options.token;
89
127
  let branch;
128
+ let commitHash;
90
129
  let deploymentData;
91
130
  let deploymentEnvironmentTag = void 0;
92
131
  let gitCommit;
93
132
  try {
94
133
  gitCommit = await getLatestGitCommit(path);
95
134
  branch = (/HEAD -> ([^,]*)/.exec(gitCommit.refs) || [])[1];
135
+ commitHash = gitCommit.hash;
96
136
  } catch (error) {
97
137
  outputWarn("Could not retrieve Git history.");
98
138
  branch = void 0;
99
139
  }
100
- if (!ci.isCI) {
140
+ if (!metadataDescription && !isCleanGit) {
141
+ renderWarning({
142
+ headline: "No deployment description provided",
143
+ body: [
144
+ "Deploying uncommited changes, but no description has been provided. Use the ",
145
+ { command: "--metadata-description" },
146
+ "flag to provide a description. If no description is provided, the description defaults to ",
147
+ { userInput: "<sha> with additional changes" },
148
+ " using the SHA of the last commit."
149
+ ]
150
+ });
151
+ metadataDescription = `${commitHash} with additional changes`;
152
+ }
153
+ if (!isCI) {
101
154
  deploymentData = await getOxygenDeploymentData({
102
155
  root: path,
103
156
  flagShop: shop
@@ -108,14 +161,14 @@ async function oxygenDeploy(options) {
108
161
  token = token || deploymentData.oxygenDeploymentToken;
109
162
  }
110
163
  if (!token) {
111
- const errMessage = ci.isCI ? [
164
+ const errMessage = isCI ? [
112
165
  "No deployment token provided. Use the ",
113
166
  { command: "--token" },
114
167
  " flag to provide a token."
115
168
  ] : `Could not obtain an Oxygen deployment token, please try again or contact Shopify support.`;
116
169
  throw new AbortError(errMessage);
117
170
  }
118
- if (!ci.isCI && !environmentTag && deploymentData?.environments) {
171
+ if (!isCI && !environmentTag && deploymentData?.environments) {
119
172
  if (deploymentData.environments.length > 1) {
120
173
  const choices = [
121
174
  ...deploymentData.environments.map(({ name, branch: branch2 }) => ({
@@ -134,14 +187,22 @@ async function oxygenDeploy(options) {
134
187
  );
135
188
  }
136
189
  }
190
+ let deploymentUrl = "https://oxygen.shopifyapps.com";
191
+ if (process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL) {
192
+ deploymentUrl = process.env.UNSAFE_SHOPIFY_HYDROGEN_DEPLOYMENT_URL;
193
+ outputWarn(
194
+ "Using a custom deployment service. Don't do this in production!"
195
+ );
196
+ }
137
197
  const config = {
138
198
  assetsDir: "dist/client",
139
199
  bugsnag: true,
140
- deploymentUrl: "https://oxygen.shopifyapps.com",
200
+ deploymentUrl,
141
201
  deploymentToken: parseToken(token),
142
202
  environmentTag: environmentTag || deploymentEnvironmentTag || branch,
143
203
  verificationMaxDuration: 180,
144
204
  metadata: {
205
+ ...metadataDescription ? { description: metadataDescription } : {},
145
206
  ...metadataUrl ? { url: metadataUrl } : {},
146
207
  ...metadataUser ? { user: metadataUser } : {},
147
208
  ...metadataVersion ? { version: metadataVersion } : {}
@@ -211,7 +272,7 @@ async function oxygenDeploy(options) {
211
272
  }
212
273
  ]);
213
274
  };
214
- await createDeploy({ config, hooks, logger: deploymentLogger }).then((url) => {
275
+ await createDeploy({ config, hooks, logger: deploymentLogger }).then(async (url) => {
215
276
  const deploymentType = config.publicDeployment ? "public" : "private";
216
277
  renderSuccess({
217
278
  body: ["Successfully deployed to Oxygen"],
@@ -221,6 +282,9 @@ async function oxygenDeploy(options) {
221
282
  ]
222
283
  ]
223
284
  });
285
+ if (isCI && !noJsonOutput) {
286
+ await writeFile("h2_deploy_log.json", JSON.stringify({ url }));
287
+ }
224
288
  resolveDeploy();
225
289
  }).catch((error) => {
226
290
  rejectDeploy(deployError || error);
@@ -2,14 +2,18 @@ import { vi, describe, expect, beforeEach, afterEach, it } from 'vitest';
2
2
  import { login } from '../../lib/auth.js';
3
3
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
4
4
  import { AbortError } from '@shopify/cli-kit/node/error';
5
- import { renderSelectPrompt, renderSuccess, renderFatalError } from '@shopify/cli-kit/node/ui';
6
- import { getLatestGitCommit } from '@shopify/cli-kit/node/git';
5
+ import { writeFile } from '@shopify/cli-kit/node/fs';
6
+ import { renderSelectPrompt, renderSuccess, renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
7
+ import { ensureIsClean, GitDirectoryNotCleanError, getLatestGitCommit } from '@shopify/cli-kit/node/git';
7
8
  import { oxygenDeploy, deploymentLogger } from './deploy.js';
8
9
  import { getOxygenDeploymentData } from '../../lib/get-oxygen-deployment-data.js';
9
10
  import { createDeploy, parseToken } from '@shopify/oxygen-cli/deploy';
11
+ import { ciPlatform } from '@shopify/cli-kit/node/context/local';
10
12
 
11
13
  vi.mock("../../lib/get-oxygen-deployment-data.js");
12
14
  vi.mock("@shopify/oxygen-cli/deploy");
15
+ vi.mock("@shopify/cli-kit/node/fs");
16
+ vi.mock("@shopify/cli-kit/node/context/local");
13
17
  vi.mock("../../lib/auth.js");
14
18
  vi.mock("../../lib/shopify-config.js");
15
19
  vi.mock("../../lib/graphql/admin/link-storefront.js");
@@ -30,17 +34,16 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
30
34
  renderFatalError: vi.fn(),
31
35
  renderSelectPrompt: vi.fn(),
32
36
  renderSuccess: vi.fn(),
33
- renderTasks: vi.fn()
37
+ renderTasks: vi.fn(),
38
+ renderWarning: vi.fn()
34
39
  };
35
40
  });
36
41
  vi.mock("@shopify/cli-kit/node/git", async () => {
42
+ const actual = await vi.importActual("@shopify/cli-kit/node/git");
37
43
  return {
38
- getLatestGitCommit: vi.fn()
39
- };
40
- });
41
- vi.mock("@shopify/cli-kit/node/context/local", async () => {
42
- return {
43
- ciPlatform: () => ({ isCI: false })
44
+ ...actual,
45
+ getLatestGitCommit: vi.fn(),
46
+ ensureIsClean: vi.fn()
44
47
  };
45
48
  });
46
49
  describe("deploy", () => {
@@ -63,6 +66,8 @@ describe("deploy", () => {
63
66
  };
64
67
  const originalExit = process.exit;
65
68
  const deployParams = {
69
+ force: false,
70
+ noJsonOutput: false,
66
71
  path: "./",
67
72
  shop: "snowdevil.myshopify.com",
68
73
  publicDeployment: false,
@@ -111,6 +116,7 @@ describe("deploy", () => {
111
116
  session: ADMIN_SESSION,
112
117
  config: UNLINKED_SHOPIFY_CONFIG
113
118
  });
119
+ vi.mocked(ciPlatform).mockReturnValue({ isCI: false });
114
120
  vi.mocked(getStorefronts).mockResolvedValue([
115
121
  {
116
122
  ...FULL_SHOPIFY_CONFIG.storefront,
@@ -149,6 +155,81 @@ describe("deploy", () => {
149
155
  });
150
156
  expect(vi.mocked(renderSuccess)).toHaveBeenCalled;
151
157
  });
158
+ it("errors when there are uncommited changes", async () => {
159
+ vi.mocked(ensureIsClean).mockRejectedValue(
160
+ new GitDirectoryNotCleanError("Uncommitted changes")
161
+ );
162
+ await expect(oxygenDeploy(deployParams)).rejects.toThrowError(
163
+ "Uncommitted changes detected"
164
+ );
165
+ expect(vi.mocked(createDeploy)).not.toHaveBeenCalled;
166
+ });
167
+ it("proceeds with warning and modified description when there are uncommited changes and the force flag is used", async () => {
168
+ vi.mocked(ensureIsClean).mockRejectedValue(
169
+ new GitDirectoryNotCleanError("Uncommitted changes")
170
+ );
171
+ vi.mocked(getLatestGitCommit).mockResolvedValue({
172
+ hash: "123",
173
+ message: "test commit",
174
+ date: "2021-01-01",
175
+ author_name: "test author",
176
+ author_email: "test@author.com",
177
+ body: "test body",
178
+ refs: "HEAD -> main"
179
+ });
180
+ await oxygenDeploy({
181
+ ...deployParams,
182
+ force: true
183
+ });
184
+ expect(vi.mocked(renderWarning)).toHaveBeenCalledWith({
185
+ headline: "No deployment description provided",
186
+ body: expect.anything()
187
+ });
188
+ expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
189
+ config: {
190
+ ...expectedConfig,
191
+ environmentTag: "main",
192
+ metadata: {
193
+ ...expectedConfig.metadata,
194
+ description: "123 with additional changes"
195
+ }
196
+ },
197
+ hooks: expectedHooks,
198
+ logger: deploymentLogger
199
+ });
200
+ });
201
+ it("proceeds with provided description without warning when there are uncommited changes and the force flag is used", async () => {
202
+ vi.mocked(ensureIsClean).mockRejectedValue(
203
+ new GitDirectoryNotCleanError("Uncommitted changes")
204
+ );
205
+ vi.mocked(getLatestGitCommit).mockResolvedValue({
206
+ hash: "123",
207
+ message: "test commit",
208
+ date: "2021-01-01",
209
+ author_name: "test author",
210
+ author_email: "test@author.com",
211
+ body: "test body",
212
+ refs: "HEAD -> main"
213
+ });
214
+ await oxygenDeploy({
215
+ ...deployParams,
216
+ force: true,
217
+ metadataDescription: "cool new stuff"
218
+ });
219
+ expect(vi.mocked(renderWarning)).not.toHaveBeenCalled;
220
+ expect(vi.mocked(createDeploy)).toHaveBeenCalledWith({
221
+ config: {
222
+ ...expectedConfig,
223
+ environmentTag: "main",
224
+ metadata: {
225
+ ...expectedConfig.metadata,
226
+ description: "cool new stuff"
227
+ }
228
+ },
229
+ hooks: expectedHooks,
230
+ logger: deploymentLogger
231
+ });
232
+ });
152
233
  it("calls createDeploy with the checked out branch name", async () => {
153
234
  vi.mocked(getLatestGitCommit).mockResolvedValue({
154
235
  hash: "123",
@@ -184,6 +265,27 @@ describe("deploy", () => {
184
265
  ]
185
266
  });
186
267
  });
268
+ it("writes a file with JSON content in CI environments", async () => {
269
+ vi.mocked(ciPlatform).mockReturnValue({
270
+ isCI: true,
271
+ name: "github",
272
+ metadata: {}
273
+ });
274
+ const ciDeployParams = {
275
+ ...deployParams,
276
+ token: "some-token",
277
+ metadataDescription: "cool new stuff"
278
+ };
279
+ await oxygenDeploy(ciDeployParams);
280
+ expect(vi.mocked(writeFile)).toHaveBeenCalledWith(
281
+ "h2_deploy_log.json",
282
+ JSON.stringify({ url: "https://a-lovely-deployment.com" })
283
+ );
284
+ vi.mocked(writeFile).mockClear();
285
+ ciDeployParams.noJsonOutput = true;
286
+ await oxygenDeploy(ciDeployParams);
287
+ expect(vi.mocked(writeFile)).not.toHaveBeenCalled();
288
+ });
187
289
  it("handles error during uploadFiles", async () => {
188
290
  const mockRenderFatalError = vi.fn();
189
291
  vi.mocked(renderFatalError).mockImplementation(mockRenderFatalError);
@@ -7,11 +7,10 @@ import colors from '@shopify/cli-kit/node/colors';
7
7
  import { copyPublicFiles } from './build.js';
8
8
  import { getProjectPaths, assertOxygenChecks, handleRemixImportFail, getRemixConfig } from '../../lib/remix-config.js';
9
9
  import { muteDevLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
- import { commonFlags, overrideFlag, deprecated, flagsToCamelObject, DEFAULT_PORT } from '../../lib/flags.js';
10
+ import { commonFlags, overrideFlag, deprecated, flagsToCamelObject } from '../../lib/flags.js';
11
11
  import Command from '@shopify/cli-kit/node/base-command';
12
12
  import { Flags } from '@oclif/core';
13
- import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
14
- import { checkHydrogenVersion } from '../../lib/check-version.js';
13
+ import { buildAssetsUrl, startMiniOxygen } from '../../lib/mini-oxygen/index.js';
15
14
  import { addVirtualRoutes } from '../../lib/virtual-routes.js';
16
15
  import { spawnCodegenProcess } from '../../lib/codegen.js';
17
16
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
@@ -19,6 +18,8 @@ import { getConfig } from '../../lib/shopify-config.js';
19
18
  import { setupLiveReload } from '../../lib/live-reload.js';
20
19
  import { checkRemixVersions } from '../../lib/remix-version-check.js';
21
20
  import { getGraphiQLUrl } from '../../lib/graphiql-url.js';
21
+ import { displayDevUpgradeNotice } from './upgrade.js';
22
+ import { findPort } from '../../lib/find-port.js';
22
23
 
23
24
  const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
24
25
  const LOG_REBUILT = "\u{1F680} Rebuilt";
@@ -27,7 +28,7 @@ class Dev extends Command {
27
28
  static flags = {
28
29
  path: commonFlags.path,
29
30
  port: commonFlags.port,
30
- ["worker-unstable"]: commonFlags.workerRuntime,
31
+ worker: commonFlags.workerRuntime,
31
32
  codegen: overrideFlag(commonFlags.codegen, {
32
33
  description: commonFlags.codegen.description + " It updates the types on file save."
33
34
  }),
@@ -38,43 +39,42 @@ class Dev extends Command {
38
39
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
39
40
  default: false
40
41
  }),
41
- debug: Flags.boolean({
42
- description: "Attaches a Node inspector",
43
- env: "SHOPIFY_HYDROGEN_FLAG_DEBUG",
44
- default: false
45
- }),
42
+ debug: commonFlags.debug,
43
+ "inspector-port": commonFlags.inspectorPort,
46
44
  host: deprecated("--host")(),
47
- ["env-branch"]: commonFlags.envBranch
45
+ ["env-branch"]: commonFlags.envBranch,
46
+ ["disable-version-check"]: Flags.boolean({
47
+ description: "Skip the version check when running `hydrogen dev`",
48
+ default: false,
49
+ required: false
50
+ })
48
51
  };
49
52
  async run() {
50
53
  const { flags } = await this.parse(Dev);
51
54
  const directory = flags.path ? path.resolve(flags.path) : process.cwd();
52
55
  await runDev({
53
56
  ...flagsToCamelObject(flags),
54
- useCodegen: flags.codegen,
55
- workerRuntime: flags["worker-unstable"],
56
57
  path: directory
57
58
  });
58
59
  }
59
60
  }
60
61
  async function runDev({
61
- port: portFlag = DEFAULT_PORT,
62
+ port: appPort,
62
63
  path: appPath,
63
- useCodegen = false,
64
- workerRuntime = false,
64
+ codegen: useCodegen = false,
65
+ worker: workerRuntime = false,
65
66
  codegenConfigPath,
66
67
  disableVirtualRoutes,
67
68
  envBranch,
68
69
  debug = false,
69
- sourcemap = true
70
+ sourcemap = true,
71
+ disableVersionCheck = false,
72
+ inspectorPort
70
73
  }) {
71
74
  if (!process.env.NODE_ENV)
72
75
  process.env.NODE_ENV = "development";
73
76
  muteDevLogs();
74
- if (debug)
75
- (await import('node:inspector')).open();
76
77
  const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
77
- const checkingHydrogenVersion = checkHydrogenVersion(root);
78
78
  const copyingFiles = copyPublicFiles(publicPath, buildPathClient);
79
79
  const reloadConfig = async () => {
80
80
  const config = await getRemixConfig(root);
@@ -90,6 +90,12 @@ async function runDev({
90
90
  return [fileRelative, path.resolve(root, fileRelative)];
91
91
  };
92
92
  const serverBundleExists = () => fileExists(buildPathWorkerFile);
93
+ inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort;
94
+ appPort = workerRuntime ? await findPort(appPort) : appPort;
95
+ const assetsPort = workerRuntime ? await findPort(appPort + 100) : 0;
96
+ if (assetsPort) {
97
+ process.env.HYDROGEN_ASSET_BASE_URL = buildAssetsUrl(assetsPort);
98
+ }
93
99
  const [remixConfig, { shop, storefront }] = await Promise.all([
94
100
  reloadConfig(),
95
101
  getConfig(root)
@@ -112,7 +118,10 @@ async function runDev({
112
118
  miniOxygen = await startMiniOxygen(
113
119
  {
114
120
  root,
115
- port: portFlag,
121
+ debug,
122
+ assetsPort,
123
+ inspectorPort,
124
+ port: appPort,
116
125
  watch: !liveReload,
117
126
  buildPathWorkerFile,
118
127
  buildPathClient,
@@ -142,9 +151,9 @@ View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
142
151
  spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
143
152
  }
144
153
  checkRemixVersions();
145
- const showUpgrade = await checkingHydrogenVersion;
146
- if (showUpgrade)
147
- showUpgrade();
154
+ if (!disableVersionCheck) {
155
+ displayDevUpgradeNotice({ targetPath: appPath });
156
+ }
148
157
  }
149
158
  const fileWatchCache = createFileWatchCache();
150
159
  let skipRebuildLogs = false;
@@ -184,6 +193,7 @@ View server-side network requests: ${miniOxygen.listeningAt}/debug-network`
184
193
  name: "BuildError",
185
194
  type: 0,
186
195
  message: "MiniOxygen cannot start because the server bundle has not been generated.",
196
+ skipOclifErrorHandling: true,
187
197
  tryMessage: "This is likely due to an error in your app and Remix is unable to compile. Try fixing the app and MiniOxygen will start."
188
198
  });
189
199
  }
@@ -1,32 +1,36 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { muteDevLogs } from '../../lib/log.js';
3
3
  import { getProjectPaths } from '../../lib/remix-config.js';
4
- import { commonFlags, flagsToCamelObject, DEFAULT_PORT } from '../../lib/flags.js';
4
+ import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
5
5
  import { startMiniOxygen } from '../../lib/mini-oxygen/index.js';
6
6
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
7
7
  import { getConfig } from '../../lib/shopify-config.js';
8
+ import { findPort } from '../../lib/find-port.js';
8
9
 
9
10
  class Preview extends Command {
10
11
  static description = "Runs a Hydrogen storefront in an Oxygen worker for production.";
11
12
  static flags = {
12
13
  path: commonFlags.path,
13
14
  port: commonFlags.port,
14
- ["worker-unstable"]: commonFlags.workerRuntime,
15
- ["env-branch"]: commonFlags.envBranch
15
+ worker: commonFlags.workerRuntime,
16
+ "env-branch": commonFlags.envBranch,
17
+ "inspector-port": commonFlags.inspectorPort,
18
+ debug: commonFlags.debug
16
19
  };
17
20
  async run() {
18
21
  const { flags } = await this.parse(Preview);
19
22
  await runPreview({
20
- ...flagsToCamelObject(flags),
21
- workerRuntime: flags["worker-unstable"]
23
+ ...flagsToCamelObject(flags)
22
24
  });
23
25
  }
24
26
  }
25
27
  async function runPreview({
26
- port = DEFAULT_PORT,
28
+ port: appPort,
27
29
  path: appPath,
28
- workerRuntime = false,
29
- envBranch
30
+ worker: workerRuntime = false,
31
+ envBranch,
32
+ inspectorPort,
33
+ debug
30
34
  }) {
31
35
  if (!process.env.NODE_ENV)
32
36
  process.env.NODE_ENV = "production";
@@ -35,13 +39,19 @@ async function runPreview({
35
39
  const { shop, storefront } = await getConfig(root);
36
40
  const fetchRemote = !!shop && !!storefront?.id;
37
41
  const env = await getAllEnvironmentVariables({ root, fetchRemote, envBranch });
42
+ appPort = workerRuntime ? await findPort(appPort) : appPort;
43
+ inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort;
44
+ const assetsPort = workerRuntime ? await findPort(appPort + 100) : 0;
38
45
  const miniOxygen = await startMiniOxygen(
39
46
  {
40
47
  root,
41
- port,
48
+ port: appPort,
49
+ assetsPort,
50
+ env,
42
51
  buildPathClient,
43
52
  buildPathWorkerFile,
44
- env
53
+ inspectorPort,
54
+ debug
45
55
  },
46
56
  workerRuntime
47
57
  );
@@ -22,6 +22,7 @@ Restart your terminal session and run \`${ALIAS_NAME}\` from your local project.
22
22
  name: "error",
23
23
  type: 0,
24
24
  message: "No supported shell found.",
25
+ skipOclifErrorHandling: true,
25
26
  tryMessage: "Please create a shortcut manually."
26
27
  });
27
28
  }