@shopify/cli-hydrogen 8.0.2 → 8.0.4

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.
@@ -1,29 +1,24 @@
1
- import fs from 'node:fs/promises';
2
- import { outputInfo, outputDebug } from '@shopify/cli-kit/node/output';
3
- import { fileExists } from '@shopify/cli-kit/node/fs';
4
- import { renderFatalError } from '@shopify/cli-kit/node/ui';
5
- import { resolvePath, relativePath } from '@shopify/cli-kit/node/path';
6
- import { copyPublicFiles } from './build.js';
7
- import { getProjectPaths, assertOxygenChecks, handleRemixImportFail, getRemixConfig } from '../../lib/remix-config.js';
8
- import { setH2OVerbose, isH2Verbose, muteDevLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
9
- import { commonFlags, deprecated, flagsToCamelObject, DEFAULT_APP_PORT } from '../../lib/flags.js';
1
+ import { fileURLToPath } from 'node:url';
2
+ import { setH2OVerbose, isH2Verbose, muteDevLogs, enhanceH2Logs } from '../../lib/log.js';
3
+ import { commonFlags, overrideFlag, deprecated, flagsToCamelObject, DEFAULT_INSPECTOR_PORT, DEFAULT_APP_PORT } from '../../lib/flags.js';
10
4
  import Command from '@shopify/cli-kit/node/base-command';
5
+ import colors from '@shopify/cli-kit/node/colors';
6
+ import { resolvePath } from '@shopify/cli-kit/node/path';
7
+ import { collectLog } from '@shopify/cli-kit/node/output';
8
+ import { renderSuccess } from '@shopify/cli-kit/node/ui';
9
+ import { AbortError } from '@shopify/cli-kit/node/error';
11
10
  import { Flags } from '@oclif/core';
12
- import { buildAssetsUrl, startMiniOxygen } from '../../lib/mini-oxygen/index.js';
13
- import { addVirtualRoutes } from '../../lib/virtual-routes.js';
14
11
  import { spawnCodegenProcess } from '../../lib/codegen.js';
15
12
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
16
- import { setupLiveReload } from '../../lib/live-reload.js';
17
- import { checkRemixVersions } from '../../lib/remix-version-check.js';
18
13
  import { displayDevUpgradeNotice } from './upgrade.js';
19
- import { findPort } from '../../lib/find-port.js';
20
14
  import { prepareDiffDirectory, copyShopifyConfig } from '../../lib/template-diff.js';
21
- import { getDevConfigInBackground, startTunnelAndPushConfig, isMockShop, notifyIssueWithTunnelAndMockShop } from '../../lib/dev-shared.js';
15
+ import { getDevConfigInBackground, TUNNEL_DOMAIN, startTunnelAndPushConfig, getUtilityBannerlines, getDebugBannerLine, isMockShop, notifyIssueWithTunnelAndMockShop } from '../../lib/dev-shared.js';
22
16
  import { getCliCommand } from '../../lib/shell.js';
23
- import { hasViteConfig } from '../../lib/vite-config.js';
17
+ import { findPort } from '../../lib/find-port.js';
18
+ import { logRequestLine } from '../../lib/mini-oxygen/common.js';
19
+ import { hasViteConfig, findHydrogenPlugin, findOxygenPlugin } from '../../lib/vite-config.js';
20
+ import { runClassicCompilerDev } from '../../lib/classic-compiler/dev.js';
24
21
 
25
- const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
26
- const LOG_REBUILT = "\u{1F680} Rebuilt";
27
22
  class Dev extends Command {
28
23
  static descriptionWithMarkdown = `Runs a Hydrogen storefront in a local runtime that emulates an Oxygen worker for development.
29
24
 
@@ -31,11 +26,11 @@ class Dev extends Command {
31
26
  static description = "Runs Hydrogen storefront in an Oxygen worker for development.";
32
27
  static flags = {
33
28
  ...commonFlags.path,
34
- ...commonFlags.port,
35
- worker: deprecated("--worker", { isBoolean: true }),
36
- ...commonFlags.legacyRuntime,
29
+ ...commonFlags.entry,
30
+ ...overrideFlag(commonFlags.port, {
31
+ port: { default: void 0, required: false }
32
+ }),
37
33
  ...commonFlags.codegen,
38
- ...commonFlags.sourcemap,
39
34
  "disable-virtual-routes": Flags.boolean({
40
35
  description: "Disable rendering fallback routes when a route file doesn't exist.",
41
36
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES",
@@ -52,7 +47,24 @@ class Dev extends Command {
52
47
  }),
53
48
  ...commonFlags.diff,
54
49
  ...commonFlags.customerAccountPush,
55
- ...commonFlags.verbose
50
+ ...commonFlags.verbose,
51
+ host: Flags.boolean({
52
+ description: "Expose the server to the local network",
53
+ default: false,
54
+ required: false
55
+ }),
56
+ // For the classic compiler:
57
+ worker: deprecated("--worker", { isBoolean: true }),
58
+ ...overrideFlag(commonFlags.legacyRuntime, {
59
+ "legacy-runtime": {
60
+ description: "[Classic Remix Compiler] " + commonFlags.legacyRuntime["legacy-runtime"].description
61
+ }
62
+ }),
63
+ ...overrideFlag(commonFlags.sourcemap, {
64
+ sourcemap: {
65
+ description: "[Classic Remix Compiler] " + commonFlags.sourcemap.sourcemap.description
66
+ }
67
+ })
56
68
  };
57
69
  async run() {
58
70
  const { flags } = await this.parse(Dev);
@@ -67,9 +79,7 @@ class Dev extends Command {
67
79
  path: directory,
68
80
  cliConfig: this.config
69
81
  };
70
- const { close } = await hasViteConfig(directory ?? process.cwd()) ? await import('./dev-vite.js').then(
71
- ({ runViteDev }) => runViteDev(devParams)
72
- ) : await runDev(devParams);
82
+ const { close } = await hasViteConfig(directory) ? await runDev(devParams) : await runClassicCompilerDev(devParams);
73
83
  let closingPromise;
74
84
  const processExit = process.exit;
75
85
  process.exit = async (code) => {
@@ -83,20 +93,20 @@ class Dev extends Command {
83
93
  }
84
94
  }
85
95
  async function runDev({
96
+ entry: ssrEntry,
86
97
  port: appPort,
87
98
  path: appPath,
99
+ host,
88
100
  codegen: useCodegen = false,
89
- legacyRuntime = false,
90
101
  codegenConfigPath,
91
102
  disableVirtualRoutes,
92
- env: envHandle,
93
103
  envBranch,
104
+ env: envHandle,
94
105
  debug = false,
95
- sourcemap = true,
96
106
  disableVersionCheck = false,
97
107
  inspectorPort,
108
+ isLocalDev = false,
98
109
  customerAccountPush: customerAccountPushFlag = false,
99
- shouldLiveReload = true,
100
110
  cliConfig,
101
111
  verbose
102
112
  }) {
@@ -106,217 +116,138 @@ async function runDev({
106
116
  setH2OVerbose();
107
117
  if (!isH2Verbose())
108
118
  muteDevLogs();
109
- const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
110
- const copyFilesPromise = copyPublicFiles(publicPath, buildPathClient);
119
+ const root = appPath ?? process.cwd();
111
120
  const cliCommandPromise = getCliCommand(root);
112
- const reloadConfig = async () => {
113
- const config = await getRemixConfig(root);
114
- return disableVirtualRoutes ? config : addVirtualRoutes(config).catch((error) => {
115
- outputDebug(
116
- "Could not add virtual routes: " + (error?.stack ?? error?.message ?? error)
117
- );
118
- return config;
119
- });
120
- };
121
- const getFilePaths = (file) => {
122
- const fileRelative = relativePath(root, file);
123
- return [fileRelative, resolvePath(root, fileRelative)];
124
- };
125
- const serverBundleExists = () => fileExists(buildPathWorkerFile);
126
- if (!appPort) {
127
- appPort = await findPort(DEFAULT_APP_PORT);
128
- }
129
- const assetsPort = legacyRuntime ? 0 : await findPort(appPort + 100);
130
- if (assetsPort) {
131
- process.env.HYDROGEN_ASSET_BASE_URL = await buildAssetsUrl(assetsPort);
132
- }
133
121
  const backgroundPromise = getDevConfigInBackground(
134
122
  root,
135
123
  customerAccountPushFlag
136
124
  );
137
- const tunnelPromise = cliConfig && backgroundPromise.then(({ customerAccountPush, storefrontId }) => {
138
- if (customerAccountPush) {
139
- return startTunnelAndPushConfig(
140
- root,
141
- cliConfig,
142
- appPort,
143
- storefrontId
144
- );
145
- }
146
- });
147
- const remixConfig = await reloadConfig();
148
- assertOxygenChecks(remixConfig);
149
125
  const envPromise = backgroundPromise.then(
150
126
  ({ fetchRemote, localVariables }) => getAllEnvironmentVariables({
151
127
  root,
152
- fetchRemote,
153
128
  envBranch,
154
129
  envHandle,
130
+ fetchRemote,
155
131
  localVariables
156
132
  })
157
133
  );
158
- const [{ watch }, { createFileWatchCache }] = await Promise.all([
159
- import('@remix-run/dev/dist/compiler/watch.js'),
160
- import('@remix-run/dev/dist/compiler/fileWatchCache.js')
161
- ]).catch(handleRemixImportFail);
162
- let isInitialBuild = true;
163
- let initialBuildDurationMs = 0;
164
- let initialBuildStartTimeMs = Date.now();
165
- const liveReload = shouldLiveReload ? await setupLiveReload(remixConfig.dev?.port ?? 8002) : void 0;
166
- let miniOxygen;
167
- let codegenProcess;
168
- async function safeStartMiniOxygen() {
169
- if (miniOxygen)
170
- return;
171
- const { allVariables, logInjectedVariables } = await envPromise;
172
- miniOxygen = await startMiniOxygen(
173
- {
174
- root,
175
- debug,
176
- appPort,
177
- assetsPort,
178
- inspectorPort,
179
- watch: !liveReload,
180
- buildPathWorkerFile,
181
- buildPathClient,
182
- env: allVariables
183
- },
184
- legacyRuntime
185
- );
186
- logInjectedVariables();
187
- const host = (await tunnelPromise)?.host ?? miniOxygen.listeningAt;
188
- const cliCommand = await cliCommandPromise;
189
- enhanceH2Logs({ host, cliCommand, ...remixConfig });
190
- const { storefrontTitle } = await backgroundPromise;
191
- miniOxygen.showBanner({
192
- appName: storefrontTitle,
193
- headlinePrefix: initialBuildDurationMs > 0 ? `Initial build: ${initialBuildDurationMs}ms
194
- ` : "",
195
- host
196
- });
197
- if (useCodegen) {
198
- codegenProcess = spawnCodegenProcess({
199
- ...remixConfig,
200
- configFilePath: codegenConfigPath
201
- });
202
- }
203
- checkRemixVersions();
204
- if (!disableVersionCheck) {
205
- displayDevUpgradeNotice({ targetPath: appPath });
206
- }
207
- if (customerAccountPushFlag && isMockShop(allVariables)) {
208
- notifyIssueWithTunnelAndMockShop(cliCommand);
209
- }
134
+ if (debug && !inspectorPort) {
135
+ inspectorPort = await findPort(DEFAULT_INSPECTOR_PORT);
210
136
  }
211
- const fileWatchCache = createFileWatchCache();
212
- let skipRebuildLogs = false;
213
- const closeWatcher = await watch(
214
- {
215
- config: remixConfig,
216
- options: {
217
- mode: process.env.NODE_ENV,
218
- sourcemap
219
- },
220
- fileWatchCache,
221
- logger: createRemixLogger()
222
- },
223
- {
224
- reloadConfig,
225
- onBuildStart(ctx) {
226
- if (!isInitialBuild && !skipRebuildLogs) {
227
- outputInfo(LOG_REBUILDING);
228
- console.time(LOG_REBUILT);
229
- }
230
- liveReload?.onBuildStart(ctx);
231
- },
232
- onBuildManifest: liveReload?.onBuildManifest,
233
- async onBuildFinish(context, duration, succeeded) {
234
- if (isInitialBuild) {
235
- await copyFilesPromise;
236
- initialBuildDurationMs = Date.now() - initialBuildStartTimeMs;
237
- isInitialBuild = false;
238
- } else if (!skipRebuildLogs) {
239
- skipRebuildLogs = false;
240
- console.timeEnd(LOG_REBUILT);
241
- if (!miniOxygen)
242
- console.log("");
243
- }
244
- if (!miniOxygen && !await serverBundleExists()) {
245
- return renderFatalError({
246
- name: "BuildError",
247
- type: 0,
248
- message: "MiniOxygen cannot start because the server bundle has not been generated.",
249
- skipOclifErrorHandling: true,
250
- 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."
251
- });
252
- }
253
- if (succeeded) {
254
- if (!miniOxygen) {
255
- await safeStartMiniOxygen();
256
- } else if (liveReload) {
257
- await miniOxygen.reload();
258
- }
259
- liveReload?.onAppReady(context);
260
- }
261
- },
262
- async onFileCreated(file) {
263
- const [relative, absolute] = getFilePaths(file);
264
- outputInfo(`
265
- \u{1F4C4} File created: ${relative}`);
266
- if (absolute.startsWith(publicPath)) {
267
- await copyPublicFiles(
268
- absolute,
269
- absolute.replace(publicPath, buildPathClient)
270
- );
271
- }
272
- },
273
- async onFileChanged(file) {
274
- fileWatchCache.invalidateFile(file);
275
- const [relative, absolute] = getFilePaths(file);
276
- outputInfo(`
277
- \u{1F4C4} File changed: ${relative}`);
278
- if (relative.endsWith(".env")) {
279
- skipRebuildLogs = true;
280
- const { fetchRemote } = await backgroundPromise;
281
- const { allVariables, logInjectedVariables } = await getAllEnvironmentVariables({
282
- root,
283
- fetchRemote,
284
- envBranch,
285
- envHandle
137
+ const vite = await import('vite');
138
+ const fs = isLocalDev ? { allow: [root, fileURLToPath(new URL("../../../../", import.meta.url))] } : void 0;
139
+ const customLogger = vite.createLogger();
140
+ if (process.env.SHOPIFY_UNIT_TEST) {
141
+ customLogger.info = (msg) => collectLog("info", msg);
142
+ customLogger.warn = (msg) => collectLog("warn", msg);
143
+ customLogger.error = (msg) => collectLog("error", msg);
144
+ }
145
+ const viteServer = await vite.createServer({
146
+ root,
147
+ customLogger,
148
+ clearScreen: false,
149
+ server: { fs, host: host ? true : void 0 },
150
+ plugins: [
151
+ {
152
+ name: "hydrogen:cli",
153
+ configResolved(config) {
154
+ findHydrogenPlugin(config)?.api?.registerPluginOptions({
155
+ disableVirtualRoutes
286
156
  });
287
- logInjectedVariables();
288
- await miniOxygen.reload({
289
- env: allVariables
157
+ findOxygenPlugin(config)?.api?.registerPluginOptions({
158
+ debug,
159
+ entry: ssrEntry,
160
+ envPromise: envPromise.then(({ allVariables: allVariables2 }) => allVariables2),
161
+ inspectorPort,
162
+ logRequestLine
290
163
  });
291
- }
292
- if (absolute.startsWith(publicPath)) {
293
- await copyPublicFiles(
294
- absolute,
295
- absolute.replace(publicPath, buildPathClient)
296
- );
297
- }
298
- },
299
- async onFileDeleted(file) {
300
- fileWatchCache.invalidateFile(file);
301
- const [relative, absolute] = getFilePaths(file);
302
- outputInfo(`
303
- \u{1F4C4} File deleted: ${relative}`);
304
- if (absolute.startsWith(publicPath)) {
305
- await fs.unlink(absolute.replace(publicPath, buildPathClient));
164
+ },
165
+ configureServer: (viteDevServer) => {
166
+ if (customerAccountPushFlag) {
167
+ viteDevServer.middlewares.use((req, res, next) => {
168
+ const host2 = req.headers.host;
169
+ if (host2?.includes(TUNNEL_DOMAIN.ORIGINAL)) {
170
+ req.headers.host = host2.replace(
171
+ TUNNEL_DOMAIN.ORIGINAL,
172
+ TUNNEL_DOMAIN.REBRANDED
173
+ );
174
+ }
175
+ next();
176
+ });
177
+ }
306
178
  }
307
179
  }
308
- }
180
+ ]
181
+ });
182
+ const h2Plugin = findHydrogenPlugin(viteServer.config);
183
+ if (!h2Plugin) {
184
+ await viteServer.close();
185
+ throw new AbortError(
186
+ "Hydrogen plugin not found.",
187
+ "Add `hydrogen()` plugin to your Vite config."
188
+ );
189
+ }
190
+ const h2PluginOptions = h2Plugin.api?.getPluginOptions?.();
191
+ const codegenProcess = useCodegen ? spawnCodegenProcess({
192
+ rootDirectory: root,
193
+ configFilePath: codegenConfigPath,
194
+ appDirectory: h2PluginOptions?.remixConfig?.appDirectory
195
+ }) : void 0;
196
+ process.on("unhandledRejection", (err) => {
197
+ console.log("Unhandled Rejection: ", err);
198
+ });
199
+ const publicPort = appPort ?? viteServer.config.server.port ?? DEFAULT_APP_PORT;
200
+ const [tunnel, cliCommand] = await Promise.all([
201
+ backgroundPromise.then(
202
+ ({ customerAccountPush, storefrontId }) => customerAccountPush ? startTunnelAndPushConfig(root, cliConfig, publicPort, storefrontId) : void 0
203
+ ),
204
+ cliCommandPromise,
205
+ viteServer.listen(publicPort)
206
+ ]);
207
+ const publicUrl = new URL(
208
+ viteServer.resolvedUrls.local[0] ?? viteServer.resolvedUrls.network[0]
309
209
  );
210
+ const finalHost = tunnel?.host || publicUrl.toString() || publicUrl.origin;
211
+ enhanceH2Logs({
212
+ rootDirectory: root,
213
+ host: finalHost,
214
+ cliCommand
215
+ });
216
+ const { logInjectedVariables, allVariables } = await envPromise;
217
+ logInjectedVariables();
218
+ console.log("");
219
+ viteServer.printUrls();
220
+ viteServer.bindCLIShortcuts({ print: true });
221
+ console.log("\n");
222
+ const customSections = [];
223
+ if (!h2PluginOptions?.disableVirtualRoutes) {
224
+ customSections.push({ body: getUtilityBannerlines(finalHost) });
225
+ }
226
+ if (debug && inspectorPort) {
227
+ customSections.push({
228
+ body: { warn: getDebugBannerLine(inspectorPort) }
229
+ });
230
+ }
231
+ const { storefrontTitle } = await backgroundPromise;
232
+ renderSuccess({
233
+ body: [
234
+ `View ${storefrontTitle ? colors.cyan(storefrontTitle) : "Hydrogen"} app:`,
235
+ { link: { url: finalHost } }
236
+ ],
237
+ customSections
238
+ });
239
+ if (!disableVersionCheck) {
240
+ displayDevUpgradeNotice({ targetPath: root });
241
+ }
242
+ if (customerAccountPushFlag && isMockShop(allVariables)) {
243
+ notifyIssueWithTunnelAndMockShop(cliCommand);
244
+ }
310
245
  return {
311
- getUrl: () => miniOxygen.listeningAt,
246
+ getUrl: () => finalHost,
312
247
  async close() {
313
248
  codegenProcess?.removeAllListeners("close");
314
249
  codegenProcess?.kill("SIGINT");
315
- await Promise.allSettled([
316
- closeWatcher(),
317
- miniOxygen?.close(),
318
- Promise.resolve(tunnelPromise).then((tunnel) => tunnel?.cleanup?.())
319
- ]);
250
+ await Promise.allSettled([viteServer.close(), tunnel?.cleanup?.()]);
320
251
  }
321
252
  };
322
253
  }
@@ -3,12 +3,11 @@ import { fileURLToPath } from 'node:url';
3
3
  import { packageManagerFromUserAgent } from '@shopify/cli-kit/node/node-package-manager';
4
4
  import { Flags } from '@oclif/core';
5
5
  import { AbortError } from '@shopify/cli-kit/node/error';
6
- import { AbortController } from '@shopify/cli-kit/node/abort';
7
6
  import { commonFlags, flagsToCamelObject, parseProcessFlags } from '../../lib/flags.js';
8
7
  import { checkHydrogenVersion } from '../../lib/check-version.js';
9
8
  import { I18N_CHOICES } from '../../lib/setups/i18n/index.js';
10
9
  import { supressNodeExperimentalWarnings } from '../../lib/process.js';
11
- import { setupRemoteTemplate, setupLocalStarterTemplate } from '../../lib/onboarding/index.js';
10
+ import { setupTemplate } from '../../lib/onboarding/index.js';
12
11
  import { LANGUAGES } from '../../lib/onboarding/common.js';
13
12
 
14
13
  const FLAG_MAP = { f: "force" };
@@ -103,14 +102,7 @@ async function runInit({
103
102
  packageManager === "unknown" ? "" : `Please use the latest version with \`${packageManager} create @shopify/hydrogen@latest\``
104
103
  );
105
104
  }
106
- const controller = new AbortController();
107
- try {
108
- const template = options.template;
109
- return template ? await setupRemoteTemplate({ ...options, template }, controller) : await setupLocalStarterTemplate(options, controller);
110
- } catch (error) {
111
- controller.abort();
112
- throw error;
113
- }
105
+ return setupTemplate(options);
114
106
  }
115
107
 
116
108
  export { Init as default, runInit };
@@ -6,7 +6,7 @@ import cliTruncate from 'cli-truncate';
6
6
  import { Flags } from '@oclif/core';
7
7
  import { ensureInsideGitDirectory, isClean } from '@shopify/cli-kit/node/git';
8
8
  import Command from '@shopify/cli-kit/node/base-command';
9
- import { renderSuccess, renderInfo, renderConfirmationPrompt, renderTasks, renderSelectPrompt } from '@shopify/cli-kit/node/ui';
9
+ import { renderSuccess, renderInfo, renderConfirmationPrompt, renderTasks, renderSelectPrompt, renderWarning } from '@shopify/cli-kit/node/ui';
10
10
  import { readFile, isDirectory, mkdir, fileExists, touchFile, removeFile, writeFile } from '@shopify/cli-kit/node/fs';
11
11
  import { installNodeModules, getPackageManager, getDependencies } from '@shopify/cli-kit/node/node-package-manager';
12
12
  import { AbortError } from '@shopify/cli-kit/node/error';
@@ -174,7 +174,7 @@ async function getChangelog() {
174
174
  } catch {
175
175
  }
176
176
  throw new AbortError(
177
- "Failed to fetch changelog",
177
+ "Failed to fetch Hydrogen changelog",
178
178
  "Ensure you have internet connection and try again"
179
179
  );
180
180
  }
@@ -182,7 +182,10 @@ function hasOutdatedDependencies({
182
182
  release,
183
183
  currentDependencies
184
184
  }) {
185
- return Object.entries(release.dependencies).some(([name, version]) => {
185
+ return Object.entries({
186
+ ...release.dependencies,
187
+ ...release.devDependencies
188
+ }).some(([name, version]) => {
186
189
  const currentDependencyVersion = currentDependencies?.[name];
187
190
  if (!currentDependencyVersion)
188
191
  return false;
@@ -653,71 +656,81 @@ Do you want to overwrite it?`,
653
656
  async function displayDevUpgradeNotice({
654
657
  targetPath
655
658
  }) {
656
- const appPath = targetPath ? path.resolve(targetPath) : process.cwd();
657
- const { currentVersion } = await getHydrogenVersion({ appPath });
658
- const isPrerelease = semver.prerelease(currentVersion);
659
- if (isPrerelease || /^[a-z]+$/i.test(currentVersion)) {
660
- return;
661
- }
662
- const changelog = await getChangelog();
663
- const { availableUpgrades, uniqueAvailableUpgrades } = getAvailableUpgrades({
664
- releases: changelog.releases,
665
- currentVersion
666
- });
667
- if (availableUpgrades.length === 0 || !availableUpgrades[0]?.version) {
668
- return;
669
- }
670
- const pinnedLatestVersion = getAbsoluteVersion(availableUpgrades[0].version);
671
- const pinnedCurrentVersion = getAbsoluteVersion(currentVersion);
672
- const currentReleaseIndex = changelog.releases.findIndex((release) => {
673
- const pinnedReleaseVersion = getAbsoluteVersion(release.version);
674
- return pinnedReleaseVersion === pinnedCurrentVersion;
675
- });
676
- const uniqueNextReleases = changelog.releases.slice(0, currentReleaseIndex).reverse().reduce((acc, release) => {
677
- if (acc[release.version])
659
+ try {
660
+ const appPath = targetPath ? path.resolve(targetPath) : process.cwd();
661
+ const { currentVersion } = await getHydrogenVersion({ appPath });
662
+ const isPrerelease = semver.prerelease(currentVersion);
663
+ if (isPrerelease || /^[a-z]+$/i.test(currentVersion)) {
664
+ return;
665
+ }
666
+ const changelog = await getChangelog();
667
+ const { availableUpgrades, uniqueAvailableUpgrades } = getAvailableUpgrades({
668
+ releases: changelog.releases,
669
+ currentVersion
670
+ });
671
+ if (availableUpgrades.length === 0 || !availableUpgrades[0]?.version) {
672
+ return;
673
+ }
674
+ const pinnedLatestVersion = getAbsoluteVersion(
675
+ availableUpgrades[0].version
676
+ );
677
+ const pinnedCurrentVersion = getAbsoluteVersion(currentVersion);
678
+ const currentReleaseIndex = changelog.releases.findIndex((release) => {
679
+ const pinnedReleaseVersion = getAbsoluteVersion(release.version);
680
+ return pinnedReleaseVersion === pinnedCurrentVersion;
681
+ });
682
+ const uniqueNextReleases = changelog.releases.slice(0, currentReleaseIndex).reverse().reduce((acc, release) => {
683
+ if (acc[release.version])
684
+ return acc;
685
+ acc[release.version] = release;
678
686
  return acc;
679
- acc[release.version] = release;
680
- return acc;
681
- }, {});
682
- const nextReleases = Object.keys(uniqueNextReleases).length ? Object.entries(uniqueNextReleases).map(([version, release]) => {
683
- return `${version} - ${release.title}`;
684
- }).slice(0, 5) : [];
685
- let headline = Object.keys(uniqueAvailableUpgrades).length > 1 ? `There are ${Object.keys(uniqueAvailableUpgrades).length} new @shopify/hydrogen versions available.` : `There's a new @shopify/hydrogen version available.`;
686
- const cliCommand = await getCliCommand();
687
- renderInfo({
688
- headline,
689
- body: [`Current: ${currentVersion} | Latest: ${pinnedLatestVersion}`],
690
- //@ts-ignore will always be an array
691
- customSections: nextReleases.length ? [
692
- {
693
- title: `The next ${nextReleases.length} version(s) include`,
694
- body: [
695
- {
696
- list: {
697
- items: [
698
- ...nextReleases,
699
- availableUpgrades.length > 5 && `...more`
700
- ].flat().filter(Boolean)
687
+ }, {});
688
+ const nextReleases = Object.keys(uniqueNextReleases).length ? Object.entries(uniqueNextReleases).map(([version, release]) => {
689
+ return `${version} - ${release.title}`;
690
+ }).slice(0, 5) : [];
691
+ let headline = Object.keys(uniqueAvailableUpgrades).length > 1 ? `There are ${Object.keys(uniqueAvailableUpgrades).length} new @shopify/hydrogen versions available.` : `There's a new @shopify/hydrogen version available.`;
692
+ const cliCommand = await getCliCommand();
693
+ renderInfo({
694
+ headline,
695
+ body: [`Current: ${currentVersion} | Latest: ${pinnedLatestVersion}`],
696
+ //@ts-ignore will always be an array
697
+ customSections: nextReleases.length ? [
698
+ {
699
+ title: `The next ${nextReleases.length} version(s) include`,
700
+ body: [
701
+ {
702
+ list: {
703
+ items: [
704
+ ...nextReleases,
705
+ availableUpgrades.length > 5 && `...more`
706
+ ].flat().filter(Boolean)
707
+ }
701
708
  }
702
- }
703
- ].filter(Boolean)
704
- },
705
- {
706
- title: "Next steps",
707
- body: [
708
- {
709
- list: {
710
- items: [
711
- `Run \`${cliCommand} upgrade\` or \`${cliCommand} upgrade --version XXXX.X.XX\``,
712
- ,
713
- `Read release notes at https://hydrogen.shopify.dev/releases`
714
- ]
709
+ ].filter(Boolean)
710
+ },
711
+ {
712
+ title: "Next steps",
713
+ body: [
714
+ {
715
+ list: {
716
+ items: [
717
+ `Run \`${cliCommand} upgrade\` or \`${cliCommand} upgrade --version XXXX.X.XX\``,
718
+ ,
719
+ `Read release notes at https://hydrogen.shopify.dev/releases`
720
+ ]
721
+ }
715
722
  }
716
- }
717
- ]
718
- }
719
- ] : []
720
- });
723
+ ]
724
+ }
725
+ ] : []
726
+ });
727
+ } catch (error) {
728
+ const abortError = error;
729
+ renderWarning({
730
+ headline: abortError.message,
731
+ body: abortError.tryMessage ?? void 0
732
+ });
733
+ }
721
734
  }
722
735
 
723
- export { buildUpgradeCommandArgs, Upgrade as default, displayConfirmation, displayDevUpgradeNotice, getAbsoluteVersion, getAvailableUpgrades, getChangelog, getCummulativeRelease, getHydrogenVersion, getSelectedRelease, hasOutdatedDependencies, isUpgradeableRelease, runUpgrade, upgradeNodeModules };
736
+ export { buildUpgradeCommandArgs, Upgrade as default, displayConfirmation, displayDevUpgradeNotice, getAbsoluteVersion, getAvailableUpgrades, getChangelog, getCummulativeRelease, getHydrogenVersion, getSelectedRelease, isUpgradeableRelease, runUpgrade, upgradeNodeModules };