@shopify/cli-hydrogen 5.1.0 → 5.1.1

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.
@@ -5,11 +5,11 @@ import { rmdir, fileSize, glob, removeFile, copyFile } from '@shopify/cli-kit/no
5
5
  import { resolvePath, relativePath, joinPath } from '@shopify/cli-kit/node/path';
6
6
  import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
7
7
  import colors from '@shopify/cli-kit/node/colors';
8
- import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
8
+ import { getProjectPaths, getRemixConfig, assertOxygenChecks } from '../../lib/remix-config.js';
9
9
  import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
10
10
  import { checkLockfileStatus } from '../../lib/check-lockfile.js';
11
11
  import { findMissingRoutes } from '../../lib/missing-routes.js';
12
- import { warnOnce } from '../../lib/log.js';
12
+ import { muteRemixLogs, createRemixLogger } from '../../lib/log.js';
13
13
  import { codegen } from '../../lib/codegen.js';
14
14
 
15
15
  const LOG_WORKER_BUILT = "\u{1F4E6} Worker built";
@@ -57,7 +57,7 @@ async function runBuild({
57
57
  process.env.NODE_ENV = "production";
58
58
  }
59
59
  const { root, buildPath, buildPathClient, buildPathWorkerFile, publicPath } = getProjectPaths(appPath);
60
- await checkLockfileStatus(root);
60
+ await Promise.all([checkLockfileStatus(root), muteRemixLogs()]);
61
61
  console.time(LOG_WORKER_BUILT);
62
62
  outputInfo(`
63
63
  \u{1F3D7}\uFE0F Building in ${process.env.NODE_ENV} mode...`);
@@ -68,15 +68,16 @@ async function runBuild({
68
68
  import('@remix-run/dev/dist/compiler/fileWatchCache.js'),
69
69
  rmdir(buildPath, { force: true })
70
70
  ]);
71
+ assertOxygenChecks(remixConfig);
71
72
  await Promise.all([
72
73
  copyPublicFiles(publicPath, buildPathClient),
73
74
  build({
74
75
  config: remixConfig,
75
76
  options: {
76
77
  mode: process.env.NODE_ENV,
77
- onWarning: warnOnce,
78
78
  sourcemap
79
79
  },
80
+ logger: createRemixLogger(),
80
81
  fileWatchCache: createFileWatchCache()
81
82
  }).catch((thrown) => {
82
83
  logThrown(thrown);
@@ -1,7 +1,7 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { resolvePath } from '@shopify/cli-kit/node/path';
3
3
  import { commonFlags } from '../../lib/flags.js';
4
- import { getRemixConfig } from '../../lib/config.js';
4
+ import { getRemixConfig } from '../../lib/remix-config.js';
5
5
  import { logMissingRoutes, findMissingRoutes } from '../../lib/missing-routes.js';
6
6
  import { Args } from '@oclif/core';
7
7
 
@@ -29,7 +29,7 @@ class GenerateRoute extends Command {
29
29
  }
30
30
  }
31
31
  async function runCheckRoutes({ directory }) {
32
- const remixConfig = await getRemixConfig(directory, true);
32
+ const remixConfig = await getRemixConfig(directory);
33
33
  logMissingRoutes(findMissingRoutes(remixConfig));
34
34
  }
35
35
 
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
4
4
  import { Flags } from '@oclif/core';
5
- import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
5
+ import { getProjectPaths, getRemixConfig } from '../../lib/remix-config.js';
6
6
  import { commonFlags, flagsToCamelObject } from '../../lib/flags.js';
7
7
  import { codegen } from '../../lib/codegen.js';
8
8
 
@@ -5,8 +5,8 @@ import { fileExists } from '@shopify/cli-kit/node/fs';
5
5
  import { renderFatalError } from '@shopify/cli-kit/node/ui';
6
6
  import colors from '@shopify/cli-kit/node/colors';
7
7
  import { copyPublicFiles } from './build.js';
8
- import { getProjectPaths, getRemixConfig } from '../../lib/config.js';
9
- import { muteDevLogs, warnOnce, enhanceH2Logs } from '../../lib/log.js';
8
+ import { getProjectPaths, assertOxygenChecks, getRemixConfig } from '../../lib/remix-config.js';
9
+ import { muteDevLogs, muteRemixLogs, createRemixLogger, enhanceH2Logs } from '../../lib/log.js';
10
10
  import { commonFlags, deprecated, flagsToCamelObject } from '../../lib/flags.js';
11
11
  import Command from '@shopify/cli-kit/node/base-command';
12
12
  import { Flags } from '@oclif/core';
@@ -16,6 +16,7 @@ import { addVirtualRoutes } from '../../lib/virtual-routes.js';
16
16
  import { spawnCodegenProcess } from '../../lib/codegen.js';
17
17
  import { getAllEnvironmentVariables } from '../../lib/environment-variables.js';
18
18
  import { getConfig } from '../../lib/shopify-config.js';
19
+ import { checkRemixVersions } from '../../lib/remix-version-check.js';
19
20
 
20
21
  const LOG_REBUILDING = "\u{1F9F1} Rebuilding...";
21
22
  const LOG_REBUILT = "\u{1F680} Rebuilt";
@@ -67,6 +68,7 @@ async function runDev({
67
68
  if (!process.env.NODE_ENV)
68
69
  process.env.NODE_ENV = "development";
69
70
  muteDevLogs();
71
+ await muteRemixLogs();
70
72
  if (debug)
71
73
  (await import('node:inspector')).open();
72
74
  const { root, publicPath, buildPathClient, buildPathWorkerFile } = getProjectPaths(appPath);
@@ -86,7 +88,11 @@ async function runDev({
86
88
  return [fileRelative, path.resolve(root, fileRelative)];
87
89
  };
88
90
  const serverBundleExists = () => fileExists(buildPathWorkerFile);
89
- const { shop, storefront } = await getConfig(root);
91
+ const [remixConfig, { shop, storefront }] = await Promise.all([
92
+ reloadConfig(),
93
+ getConfig(root)
94
+ ]);
95
+ assertOxygenChecks(remixConfig);
90
96
  const fetchRemote = !!shop && !!storefront?.id;
91
97
  const envPromise = getAllEnvironmentVariables({ root, fetchRemote, envBranch });
92
98
  const [{ watch }, { createFileWatchCache }] = await Promise.all([
@@ -120,11 +126,11 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
120
126
  if (useCodegen) {
121
127
  spawnCodegenProcess({ ...remixConfig, configFilePath: codegenConfigPath });
122
128
  }
129
+ checkRemixVersions();
123
130
  const showUpgrade = await checkingHydrogenVersion;
124
131
  if (showUpgrade)
125
132
  showUpgrade();
126
133
  }
127
- const remixConfig = await reloadConfig();
128
134
  const fileWatchCache = createFileWatchCache();
129
135
  let skipRebuildLogs = false;
130
136
  await watch(
@@ -132,10 +138,10 @@ View GraphiQL API browser: ${graphiqlUrl}`)]
132
138
  config: remixConfig,
133
139
  options: {
134
140
  mode: process.env.NODE_ENV,
135
- onWarning: warnOnce,
136
141
  sourcemap
137
142
  },
138
- fileWatchCache
143
+ fileWatchCache,
144
+ logger: createRemixLogger()
139
145
  },
140
146
  {
141
147
  reloadConfig,
@@ -1,6 +1,6 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { muteDevLogs } from '../../lib/log.js';
3
- import { getProjectPaths } from '../../lib/config.js';
3
+ import { getProjectPaths } from '../../lib/remix-config.js';
4
4
  import { commonFlags } from '../../lib/flags.js';
5
5
  import { startMiniOxygen } from '../../lib/mini-oxygen.js';
6
6
 
@@ -4,7 +4,7 @@ import Command from '@shopify/cli-kit/node/base-command';
4
4
  import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
5
5
  import { getPackageManager, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
6
6
  import { Args } from '@oclif/core';
7
- import { getRemixConfig } from '../../../lib/config.js';
7
+ import { getRemixConfig } from '../../../lib/remix-config.js';
8
8
  import { SETUP_CSS_STRATEGIES, renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../../../lib/setups/css/index.js';
9
9
 
10
10
  class SetupCSS extends Command {
@@ -3,7 +3,7 @@ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
3
3
  import Command from '@shopify/cli-kit/node/base-command';
4
4
  import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
5
5
  import { Args } from '@oclif/core';
6
- import { getRemixConfig } from '../../../lib/config.js';
6
+ import { getRemixConfig } from '../../../lib/remix-config.js';
7
7
  import { SETUP_I18N_STRATEGIES, renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../../../lib/setups/i18n/index.js';
8
8
 
9
9
  class SetupMarkets extends Command {
@@ -5,7 +5,7 @@ import { resolvePath, basename } from '@shopify/cli-kit/node/path';
5
5
  import { copyFile } from '@shopify/cli-kit/node/fs';
6
6
  import { commonFlags, overrideFlag, flagsToCamelObject } from '../../lib/flags.js';
7
7
  import { renderI18nPrompt, setupI18nStrategy } from '../../lib/setups/i18n/index.js';
8
- import { getRemixConfig } from '../../lib/config.js';
8
+ import { getRemixConfig } from '../../lib/remix-config.js';
9
9
  import { handleRouteGeneration, generateProjectEntries, handleCliShortcut, renderProjectReady } from '../../lib/onboarding/common.js';
10
10
  import { getCliCommand } from '../../lib/shell.js';
11
11
  import { generateProjectFile } from '../../lib/setups/routes/generate.js';
@@ -1,21 +1,15 @@
1
- import {
2
- isRouteErrorResponse,
3
- useCatch,
4
- useMatches,
5
- useRouteError,
6
- } from '@remix-run/react';
7
- import {
8
- defer,
9
- type LoaderArgs,
10
- type ErrorBoundaryComponent,
11
- } from '@shopify/remix-oxygen';
1
+ import {defer, type LoaderArgs} from '@shopify/remix-oxygen';
12
2
  import {
13
3
  Links,
14
4
  Meta,
15
5
  Outlet,
16
6
  Scripts,
17
- ScrollRestoration,
7
+ useCatch,
8
+ useMatches,
9
+ useRouteError,
18
10
  useLoaderData,
11
+ ScrollRestoration,
12
+ isRouteErrorResponse,
19
13
  } from '@remix-run/react';
20
14
  import type {CustomerAccessToken} from '@shopify/hydrogen-react/storefront-api-types';
21
15
  import type {HydrogenSession} from '../server';
@@ -144,7 +138,7 @@ export function ErrorBoundary() {
144
138
  );
145
139
  }
146
140
 
147
- export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
141
+ export const ErrorBoundaryV1 = ({error}: {error: Error}) => {
148
142
  // eslint-disable-next-line no-console
149
143
  console.error(error);
150
144
 
@@ -79,7 +79,7 @@ aside li {
79
79
  left: 0;
80
80
  opacity: 0;
81
81
  pointer-events: none;
82
- position: absolute;
82
+ position: fixed;
83
83
  right: 0;
84
84
  top: 0;
85
85
  transition: opacity 400ms ease-in-out;
@@ -13,11 +13,11 @@
13
13
  },
14
14
  "prettier": "@shopify/prettier-config",
15
15
  "dependencies": {
16
- "@remix-run/react": "1.17.1",
17
- "@shopify/cli": "3.47.5",
18
- "@shopify/cli-hydrogen": "^5.1.0",
19
- "@shopify/hydrogen": "^2023.7.0",
20
- "@shopify/remix-oxygen": "^1.1.1",
16
+ "@remix-run/react": "1.19.1",
17
+ "@shopify/cli": "3.48.0",
18
+ "@shopify/cli-hydrogen": "^5.1.1",
19
+ "@shopify/hydrogen": "^2023.7.1",
20
+ "@shopify/remix-oxygen": "^1.1.2",
21
21
  "graphql": "^16.6.0",
22
22
  "graphql-tag": "^2.12.6",
23
23
  "isbot": "^3.6.6",
@@ -25,7 +25,7 @@
25
25
  "react-dom": "^18.2.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@remix-run/dev": "1.17.1",
28
+ "@remix-run/dev": "1.19.1",
29
29
  "@shopify/oxygen-workers-types": "^3.17.2",
30
30
  "@shopify/prettier-config": "^1.1.2",
31
31
  "@total-typescript/ts-reset": "^0.4.2",
package/dist/lib/log.js CHANGED
@@ -199,5 +199,36 @@ const warnOnce = (string) => {
199
199
  warnings.add(string);
200
200
  }
201
201
  };
202
+ function createRemixLogger() {
203
+ const noop = () => {
204
+ };
205
+ const buildMessageBody = (message, details) => `In Remix:
206
+
207
+ ` + colors.bold(message) + (details ? "\n\n" + details.join("\n") : "");
208
+ return {
209
+ dev: noop,
210
+ info: noop,
211
+ debug: noop,
212
+ warn: (message, options) => {
213
+ renderWarning({ body: buildMessageBody(message, options?.details) });
214
+ },
215
+ error: (message, options) => {
216
+ renderFatalError({
217
+ name: "error",
218
+ type: 0,
219
+ message: buildMessageBody(message, options?.details),
220
+ tryMessage: ""
221
+ });
222
+ }
223
+ };
224
+ }
225
+ async function muteRemixLogs() {
226
+ try {
227
+ const { logger } = await import('@remix-run/dev/dist/tux/logger.js');
228
+ logger.warn = logger.debug = logger.info = () => {
229
+ };
230
+ } catch {
231
+ }
232
+ }
202
233
 
203
- export { addMessageReplacers, enhanceH2Logs, muteAuthLogs, muteDevLogs, resetAllLogs, warnOnce };
234
+ export { addMessageReplacers, createRemixLogger, enhanceH2Logs, muteAuthLogs, muteDevLogs, muteRemixLogs, resetAllLogs, warnOnce };
@@ -0,0 +1,135 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ import { readdir } from 'node:fs/promises';
5
+ import { AbortError } from '@shopify/cli-kit/node/error';
6
+ import { outputWarn } from '@shopify/cli-kit/node/output';
7
+ import { fileExists } from '@shopify/cli-kit/node/fs';
8
+
9
+ const BUILD_DIR = "dist";
10
+ const CLIENT_SUBDIR = "client";
11
+ const WORKER_SUBDIR = "worker";
12
+ const oxygenServerMainFields = ["browser", "module", "main"];
13
+ function getProjectPaths(appPath, entry) {
14
+ const root = appPath ?? process.cwd();
15
+ const publicPath = path.join(root, "public");
16
+ const buildPath = path.join(root, BUILD_DIR);
17
+ const buildPathClient = path.join(buildPath, CLIENT_SUBDIR);
18
+ const buildPathWorkerFile = path.join(buildPath, WORKER_SUBDIR, "index.js");
19
+ return {
20
+ root,
21
+ buildPath,
22
+ buildPathClient,
23
+ buildPathWorkerFile,
24
+ publicPath
25
+ };
26
+ }
27
+ async function getRemixConfig(root, mode = process.env.NODE_ENV) {
28
+ const { readConfig } = await import('@remix-run/dev/dist/config.js');
29
+ const config = await readConfig(root, mode);
30
+ if (process.env.LOCAL_DEV) {
31
+ const packagesPath = fileURLToPath(new URL("../../..", import.meta.url));
32
+ config.watchPaths ??= [];
33
+ config.watchPaths.push(
34
+ ...(await readdir(packagesPath)).map(
35
+ (pkg) => pkg === "hydrogen-react" ? path.resolve(packagesPath, pkg, "dist", "browser-dev", "index.mjs") : path.resolve(packagesPath, pkg, "dist", "development", "index.js")
36
+ )
37
+ );
38
+ config.watchPaths.push(
39
+ path.join(packagesPath, "cli", "dist", "virtual-routes", "**", "*")
40
+ );
41
+ }
42
+ return config;
43
+ }
44
+ function assertOxygenChecks(config) {
45
+ try {
46
+ createRequire(import.meta.url).resolve("@shopify/remix-oxygen");
47
+ } catch {
48
+ return;
49
+ }
50
+ if (!config.serverEntryPoint) {
51
+ throw new AbortError(
52
+ "Could not find a server entry point.",
53
+ "Please add a server option to your remix.config.js pointing to an Oxygen worker entry file."
54
+ );
55
+ } else {
56
+ assertEntryFileExists(config.rootDirectory, config.serverEntryPoint);
57
+ }
58
+ if (config.serverPlatform !== "neutral") {
59
+ throw new AbortError(
60
+ 'The serverPlatform in remix.config.js must be "neutral".'
61
+ );
62
+ }
63
+ if (config.serverModuleFormat !== "esm") {
64
+ throw new AbortError(
65
+ 'The serverModuleFormat in remix.config.js must be "esm".'
66
+ );
67
+ }
68
+ if (config.serverDependenciesToBundle !== "all") {
69
+ throw new AbortError(
70
+ 'The serverDependenciesToBundle in remix.config.js must be "all".'
71
+ );
72
+ }
73
+ if (!config.serverConditions?.includes("worker")) {
74
+ throw new AbortError(
75
+ 'The serverConditions in remix.config.js must include "worker".'
76
+ );
77
+ }
78
+ if (process.env.NODE_ENV === "development" && !config.serverConditions?.includes("development")) {
79
+ outputWarn(
80
+ "Add `process.env.NODE_ENV` value to serverConditions in remix.config.js to enable debugging features in development."
81
+ );
82
+ }
83
+ if (!config.serverMainFields || !oxygenServerMainFields.every((v, i) => config.serverMainFields?.[i] === v)) {
84
+ throw new AbortError(
85
+ `The serverMainFields in remix.config.js must be ${JSON.stringify(
86
+ oxygenServerMainFields
87
+ )}.`
88
+ );
89
+ }
90
+ const cdnUrl = process.env.HYDROGEN_ASSET_BASE_URL;
91
+ if (cdnUrl && !config.publicPath.startsWith(cdnUrl)) {
92
+ throw new AbortError(
93
+ "The publicPath in remix.config.js must be prepended with the value of `process.env.HYDROGEN_ASSET_BASE_URL`."
94
+ );
95
+ }
96
+ const expectedServerBuildPath = path.join(
97
+ BUILD_DIR,
98
+ WORKER_SUBDIR,
99
+ "index.js"
100
+ );
101
+ if (config.serverBuildPath !== path.resolve(config.rootDirectory, expectedServerBuildPath)) {
102
+ throw new AbortError(
103
+ `The serverBuildPath in remix.config.js must be "${expectedServerBuildPath}".`
104
+ );
105
+ }
106
+ const expectedAssetsBuildDirectory = path.join(BUILD_DIR, CLIENT_SUBDIR);
107
+ if (!config.assetsBuildDirectory.startsWith(
108
+ path.resolve(config.rootDirectory, expectedAssetsBuildDirectory)
109
+ )) {
110
+ throw new AbortError(
111
+ `The assetsBuildDirectory in remix.config.js must be in "${expectedAssetsBuildDirectory}".`
112
+ );
113
+ }
114
+ }
115
+ async function assertEntryFileExists(root, fileRelative) {
116
+ const fileAbsolute = path.resolve(root, fileRelative);
117
+ const exists = await fileExists(fileAbsolute);
118
+ if (!exists) {
119
+ if (!path.extname(fileAbsolute)) {
120
+ const files = await readdir(path.dirname(fileAbsolute));
121
+ const exists2 = files.some((file) => {
122
+ const { name, ext } = path.parse(file);
123
+ return name === path.basename(fileAbsolute) && /^\.[jt]s$/.test(ext);
124
+ });
125
+ if (exists2)
126
+ return;
127
+ }
128
+ throw new AbortError(
129
+ `Entry file "${fileRelative}" not found.`,
130
+ "Please ensure the file exists and that the path is correctly added to the `server` property in remix.config.js."
131
+ );
132
+ }
133
+ }
134
+
135
+ export { assertOxygenChecks, getProjectPaths, getRemixConfig };
@@ -0,0 +1,51 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { renderWarning } from '@shopify/cli-kit/node/ui';
4
+
5
+ function checkRemixVersions() {
6
+ const require2 = createRequire(import.meta.url);
7
+ const hydrogenPkgJson = require2(fileURLToPath(
8
+ new URL("../../package.json", import.meta.url)
9
+ ));
10
+ const requiredVersionInHydrogen = hydrogenPkgJson.dependencies["@remix-run/dev"];
11
+ const pkgs = [
12
+ "dev",
13
+ "react",
14
+ "server-runtime",
15
+ "css-bundle",
16
+ "node",
17
+ "express",
18
+ "eslint-config"
19
+ ].map((name) => getRemixPackageVersion(require2, name));
20
+ const outOfSyncPkgs = pkgs.filter(
21
+ (pkg) => pkg.version && pkg.version !== requiredVersionInHydrogen
22
+ );
23
+ if (outOfSyncPkgs.length === 0)
24
+ return;
25
+ const items = outOfSyncPkgs.reduce((acc, item) => {
26
+ if (item.version) {
27
+ acc.push(`${item.name}@${item.version}`);
28
+ }
29
+ return acc;
30
+ }, []);
31
+ renderWarning({
32
+ headline: `The current version of Hydrogen requires Remix @${requiredVersionInHydrogen}. The following packages are out of sync:`,
33
+ body: [
34
+ { list: { items } },
35
+ "\nPlease ensure your Remix packages have the same version and are in sync with Hydrogen."
36
+ ]
37
+ });
38
+ }
39
+ function getRemixPackageVersion(require2, name) {
40
+ const pkgName = "@remix-run/" + name;
41
+ const result = { name: pkgName, version: "" };
42
+ try {
43
+ const pkgJsonPath = require2.resolve(`${pkgName}/package.json`);
44
+ const pkgJson = require2(pkgJsonPath);
45
+ result.version = pkgJson.version;
46
+ } catch {
47
+ }
48
+ return result;
49
+ }
50
+
51
+ export { checkRemixVersions };
@@ -0,0 +1,38 @@
1
+ import { vi, describe, it, expect } from 'vitest';
2
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
3
+ import { checkRemixVersions } from './remix-version-check.js';
4
+
5
+ const requireMock = vi.fn();
6
+ vi.mock("node:module", async () => {
7
+ const { createRequire } = await vi.importActual(
8
+ "node:module"
9
+ );
10
+ return {
11
+ createRequire: (url) => {
12
+ const actualRequire = createRequire(url);
13
+ requireMock.mockImplementation((mod) => actualRequire(mod));
14
+ const require2 = requireMock;
15
+ require2.resolve = actualRequire.resolve.bind(actualRequire);
16
+ return require2;
17
+ }
18
+ };
19
+ });
20
+ describe("remix-version-check", () => {
21
+ it("does nothing when versions are in sync", () => {
22
+ const outputMock = mockAndCaptureOutput();
23
+ checkRemixVersions();
24
+ expect(outputMock.warn()).toBe("");
25
+ });
26
+ it("warns when versions are out of sync", () => {
27
+ const expectedVersion = "42.0.0-test";
28
+ vi.mocked(requireMock).mockReturnValueOnce({
29
+ dependencies: { "@remix-run/dev": expectedVersion }
30
+ });
31
+ const outputMock = mockAndCaptureOutput();
32
+ checkRemixVersions();
33
+ const output = outputMock.warn();
34
+ expect(output).toMatch(`Hydrogen requires Remix @${expectedVersion}`);
35
+ expect(output).toMatch(`@remix-run/dev@`);
36
+ expect(output).toMatch(`@remix-run/react@`);
37
+ });
38
+ });
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'module';
2
- import { getRemixConfig } from './config.js';
2
+ import { getRemixConfig } from './remix-config.js';
3
3
 
4
4
  function isRemixV2() {
5
5
  try {
@@ -13,7 +13,7 @@ function isRemixV2() {
13
13
  async function getV2Flags(root, remixConfigFuture) {
14
14
  const isV2 = isRemixV2();
15
15
  const futureFlags = {
16
- ...!isV2 && (remixConfigFuture ?? (await getRemixConfig(root, true)).future)
16
+ ...!isV2 && (remixConfigFuture ?? (await getRemixConfig(root)).future)
17
17
  };
18
18
  return {
19
19
  isV2Meta: isV2 || !!futureFlags.v2_meta,
@@ -58,7 +58,7 @@ describe("remix-version-interop", () => {
58
58
  return <div>stuff</div>;
59
59
  }
60
60
 
61
- export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
61
+ export const ErrorBoundaryV1 = ({error}: {error: Error}) => {
62
62
  console.error(error);
63
63
 
64
64
  return <div>There was an error.</div>;
@@ -7,7 +7,7 @@ import { transpileFile } from '../../../lib/transpile-ts.js';
7
7
  import { getCodeFormatOptions, formatCode } from '../../../lib/format-code.js';
8
8
  import { getTemplateAppFile, GENERATOR_ROUTE_DIR, getStarterDir } from '../../../lib/build.js';
9
9
  import { getV2Flags, convertTemplateToRemixVersion, convertRouteToV1 } from '../../../lib/remix-version-interop.js';
10
- import { getRemixConfig } from '../../../lib/config.js';
10
+ import { getRemixConfig } from '../../remix-config.js';
11
11
  import { findFileWithExtension } from '../../file.js';
12
12
 
13
13
  const NO_LOCALE_PATTERNS = [/robots\.txt/];
@@ -5,7 +5,7 @@ import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
5
5
  import { fileExists, readFile, mkdir, writeFile } from '@shopify/cli-kit/node/fs';
6
6
  import { joinPath, dirname } from '@shopify/cli-kit/node/path';
7
7
  import { getTemplateAppFile } from '../../../lib/build.js';
8
- import { getRemixConfig } from '../../../lib/config.js';
8
+ import { getRemixConfig } from '../../remix-config.js';
9
9
 
10
10
  const readProjectFile = (dirs, fileBasename, ext = "tsx") => readFile(joinPath(dirs.appDirectory, `${fileBasename}.${ext}`));
11
11
  describe("generate/route", () => {
@@ -13,7 +13,7 @@ describe("generate/route", () => {
13
13
  vi.resetAllMocks();
14
14
  vi.mock("@shopify/cli-kit/node/output");
15
15
  vi.mock("@shopify/cli-kit/node/ui");
16
- vi.mock("../../config.js", async () => ({ getRemixConfig: vi.fn() }));
16
+ vi.mock("../../remix-config.js", async () => ({ getRemixConfig: vi.fn() }));
17
17
  });
18
18
  describe("generateRoutes", () => {
19
19
  it("generates all routes with correct configuration", async () => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.1.0",
2
+ "version": "5.1.1",
3
3
  "commands": {
4
4
  "hydrogen:build": {
5
5
  "id": "hydrogen:build",
package/package.json CHANGED
@@ -4,14 +4,14 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "5.1.0",
7
+ "version": "5.1.1",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "scripts": {
11
- "build": "tsup --config ./tsup.config.ts && node scripts/build-check.mjs",
12
- "dev": "tsup --watch --config ./tsup.config.ts",
11
+ "build": "rm -rf dist && tsup --config ./tsup.config.ts && node scripts/build-check.mjs",
12
+ "dev": "rm -rf dist && tsup --watch --config ./tsup.config.ts",
13
13
  "typecheck": "tsc --noEmit",
14
- "generate:manifest": "oclif manifest",
14
+ "generate:manifest": "node scripts/generate-manifest.mjs",
15
15
  "test": "cross-env SHOPIFY_UNIT_TEST=1 vitest run --test-timeout=15000",
16
16
  "test:watch": "cross-env SHOPIFY_UNIT_TEST=1 vitest --test-timeout=15000"
17
17
  },
@@ -22,22 +22,21 @@
22
22
  "@types/prettier": "^2.7.2",
23
23
  "@types/recursive-readdir": "^2.2.1",
24
24
  "@types/tar-fs": "^2.0.1",
25
- "oclif": "3.9.1",
26
25
  "tempy": "^3.0.0",
27
26
  "type-fest": "^3.6.0",
28
27
  "vitest": "^0.33.0"
29
28
  },
30
29
  "peerDependencies": {
31
- "@remix-run/react": "^1.17.1",
30
+ "@remix-run/react": "1.19.1",
32
31
  "@shopify/hydrogen-react": "^2023.7.0",
33
- "@shopify/remix-oxygen": "^1.1.1"
32
+ "@shopify/remix-oxygen": "^1.1.2"
34
33
  },
35
34
  "dependencies": {
36
35
  "@ast-grep/napi": "^0.5.3",
37
36
  "@graphql-codegen/cli": "3.3.1",
38
- "@oclif/core": "2.8.5",
39
- "@remix-run/dev": "1.17.1",
40
- "@shopify/cli-kit": "3.47.5",
37
+ "@oclif/core": "2.8.11",
38
+ "@remix-run/dev": "1.19.1",
39
+ "@shopify/cli-kit": "3.48.0",
41
40
  "@shopify/hydrogen-codegen": "^0.0.2",
42
41
  "@shopify/mini-oxygen": "^1.7.0",
43
42
  "ansi-escapes": "^6.2.0",
@@ -1,141 +0,0 @@
1
- import { renderFatalError } from '@shopify/cli-kit/node/ui';
2
- import { outputWarn } from '@shopify/cli-kit/node/output';
3
- import { fileExists } from '@shopify/cli-kit/node/fs';
4
- import { fileURLToPath } from 'url';
5
- import path from 'path';
6
- import fs from 'fs/promises';
7
-
8
- const BUILD_DIR = "dist";
9
- const CLIENT_SUBDIR = "client";
10
- const WORKER_SUBDIR = "worker";
11
- const oxygenServerMainFields = ["browser", "module", "main"];
12
- function getProjectPaths(appPath, entry) {
13
- const root = appPath ?? process.cwd();
14
- const publicPath = path.join(root, "public");
15
- const buildPath = path.join(root, BUILD_DIR);
16
- const buildPathClient = path.join(buildPath, CLIENT_SUBDIR);
17
- const buildPathWorkerFile = path.join(buildPath, WORKER_SUBDIR, "index.js");
18
- return {
19
- root,
20
- buildPath,
21
- buildPathClient,
22
- buildPathWorkerFile,
23
- publicPath
24
- };
25
- }
26
- async function getRemixConfig(root, skipOxygenChecks = false, mode = process.env.NODE_ENV) {
27
- const { readConfig } = await import('@remix-run/dev/dist/config.js');
28
- const config = await readConfig(root, mode);
29
- if (!skipOxygenChecks) {
30
- if (!config.serverEntryPoint) {
31
- throwConfigError(
32
- "Could not find a server entry point.",
33
- "Please add a server option to your remix.config.js pointing to an Oxygen worker entry file."
34
- );
35
- } else {
36
- assertEntryFileExists(config.rootDirectory, config.serverEntryPoint);
37
- }
38
- if (config.serverPlatform !== "neutral") {
39
- throwConfigError(
40
- 'The serverPlatform in remix.config.js must be "neutral".'
41
- );
42
- }
43
- if (config.serverModuleFormat !== "esm") {
44
- throwConfigError(
45
- 'The serverModuleFormat in remix.config.js must be "esm".'
46
- );
47
- }
48
- if (config.serverDependenciesToBundle !== "all") {
49
- throwConfigError(
50
- 'The serverDependenciesToBundle in remix.config.js must be "all".'
51
- );
52
- }
53
- if (!config.serverConditions?.includes("worker")) {
54
- throwConfigError(
55
- 'The serverConditions in remix.config.js must include "worker".'
56
- );
57
- }
58
- if (process.env.NODE_ENV === "development" && !config.serverConditions?.includes("development")) {
59
- outputWarn(
60
- "Add `process.env.NODE_ENV` value to serverConditions in remix.config.js to enable debugging features in development."
61
- );
62
- }
63
- if (!config.serverMainFields || !oxygenServerMainFields.every(
64
- (v, i) => config.serverMainFields?.[i] === v
65
- )) {
66
- throwConfigError(
67
- `The serverMainFields in remix.config.js must be ${JSON.stringify(
68
- oxygenServerMainFields
69
- )}.`
70
- );
71
- }
72
- const cdnUrl = process.env.HYDROGEN_ASSET_BASE_URL;
73
- if (cdnUrl && !config.publicPath.startsWith(cdnUrl)) {
74
- throwConfigError(
75
- "The publicPath in remix.config.js must be prepended with the value of `process.env.HYDROGEN_ASSET_BASE_URL`."
76
- );
77
- }
78
- const expectedServerBuildPath = path.join(
79
- BUILD_DIR,
80
- WORKER_SUBDIR,
81
- "index.js"
82
- );
83
- if (config.serverBuildPath !== path.resolve(root, expectedServerBuildPath)) {
84
- throwConfigError(
85
- `The serverBuildPath in remix.config.js must be "${expectedServerBuildPath}".`
86
- );
87
- }
88
- const expectedAssetsBuildDirectory = path.join(BUILD_DIR, CLIENT_SUBDIR);
89
- if (!config.assetsBuildDirectory.startsWith(
90
- path.resolve(root, expectedAssetsBuildDirectory)
91
- )) {
92
- throwConfigError(
93
- `The assetsBuildDirectory in remix.config.js must be in "${expectedAssetsBuildDirectory}".`
94
- );
95
- }
96
- }
97
- if (process.env.LOCAL_DEV) {
98
- const packagesPath = fileURLToPath(new URL("../../..", import.meta.url));
99
- config.watchPaths ??= [];
100
- config.watchPaths.push(
101
- ...(await fs.readdir(packagesPath)).map(
102
- (pkg) => pkg === "hydrogen-react" ? path.resolve(packagesPath, pkg, "dist", "browser-dev", "index.mjs") : path.resolve(packagesPath, pkg, "dist", "development", "index.js")
103
- )
104
- );
105
- config.watchPaths.push(
106
- path.join(packagesPath, "cli", "dist", "virtual-routes", "**", "*")
107
- );
108
- }
109
- return config;
110
- }
111
- function throwConfigError(message, tryMessage = null) {
112
- renderFatalError({
113
- name: "ConfigError",
114
- type: 0,
115
- message,
116
- tryMessage
117
- });
118
- process.exit(1);
119
- }
120
- async function assertEntryFileExists(root, fileRelative) {
121
- const fileAbsolute = path.resolve(root, fileRelative);
122
- const exists = await fileExists(fileAbsolute);
123
- if (!exists) {
124
- if (!path.extname(fileAbsolute)) {
125
- const { readdir } = await import('fs/promises');
126
- const files = await readdir(path.dirname(fileAbsolute));
127
- const exists2 = files.some((file) => {
128
- const { name, ext } = path.parse(file);
129
- return name === path.basename(fileAbsolute) && /^\.[jt]s$/.test(ext);
130
- });
131
- if (exists2)
132
- return;
133
- }
134
- throwConfigError(
135
- `Entry file "${fileRelative}" not found.`,
136
- "Please ensure the file exists and that the path is correctly added to the `server` property in remix.config.js."
137
- );
138
- }
139
- }
140
-
141
- export { assertEntryFileExists, getProjectPaths, getRemixConfig };