@shopify/cli-hydrogen 7.1.2 → 8.0.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.
Files changed (145) hide show
  1. package/dist/commands/hydrogen/build-vite.js +19 -10
  2. package/dist/commands/hydrogen/build.js +10 -2
  3. package/dist/commands/hydrogen/check.js +1 -0
  4. package/dist/commands/hydrogen/codegen.js +1 -0
  5. package/dist/commands/hydrogen/customer-account/push.js +170 -0
  6. package/dist/commands/hydrogen/debug/cpu.js +10 -1
  7. package/dist/commands/hydrogen/deploy.js +110 -36
  8. package/dist/commands/hydrogen/dev-vite.js +128 -59
  9. package/dist/commands/hydrogen/dev.js +108 -51
  10. package/dist/commands/hydrogen/env/list.js +20 -28
  11. package/dist/commands/hydrogen/env/pull.js +29 -19
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +34 -68
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +39 -21
  15. package/dist/commands/hydrogen/link.js +25 -6
  16. package/dist/commands/hydrogen/list.js +1 -0
  17. package/dist/commands/hydrogen/login.js +1 -0
  18. package/dist/commands/hydrogen/logout.js +1 -0
  19. package/dist/commands/hydrogen/preview.js +31 -16
  20. package/dist/commands/hydrogen/setup/css.js +8 -1
  21. package/dist/commands/hydrogen/setup/markets.js +1 -0
  22. package/dist/commands/hydrogen/setup/vite.js +244 -138
  23. package/dist/commands/hydrogen/setup.js +21 -23
  24. package/dist/commands/hydrogen/shortcut.js +10 -0
  25. package/dist/commands/hydrogen/unlink.js +1 -0
  26. package/dist/commands/hydrogen/upgrade.js +2 -1
  27. package/dist/generator-templates/assets/vite/package.json +3 -4
  28. package/dist/generator-templates/assets/vite/vite.config.js +15 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +129 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/components/Footer.tsx +1 -1
  32. package/dist/generator-templates/starter/app/components/Header.tsx +1 -1
  33. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  34. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  35. package/dist/generator-templates/starter/app/lib/root-data.ts +11 -0
  36. package/dist/generator-templates/starter/app/root.tsx +4 -20
  37. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  38. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  39. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +3 -3
  40. package/dist/generator-templates/starter/app/routes/cart.tsx +1 -1
  41. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  42. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  43. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  44. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  45. package/dist/generator-templates/starter/package.json +14 -9
  46. package/dist/generator-templates/starter/server.ts +2 -1
  47. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  48. package/dist/generator-templates/starter/vite.config.ts +26 -0
  49. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -4
  50. package/dist/lib/check-lockfile.js +12 -18
  51. package/dist/lib/codegen.js +37 -13
  52. package/dist/lib/common.js +50 -0
  53. package/dist/lib/cpu-profiler.js +4 -1
  54. package/dist/lib/dev-shared.js +99 -0
  55. package/dist/lib/environment-variables.js +51 -30
  56. package/dist/lib/file.js +8 -1
  57. package/dist/lib/flags.js +37 -26
  58. package/dist/lib/get-oxygen-deployment-data.js +10 -17
  59. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  60. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  61. package/dist/lib/graphql/admin/list-environments.js +1 -0
  62. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  63. package/dist/lib/graphql/admin/test-helper.js +37 -0
  64. package/dist/lib/log.js +86 -13
  65. package/dist/lib/mini-oxygen/common.js +19 -33
  66. package/dist/lib/mini-oxygen/index.js +6 -2
  67. package/dist/lib/mini-oxygen/node.js +43 -31
  68. package/dist/lib/mini-oxygen/workerd.js +72 -165
  69. package/dist/lib/missing-routes.js +1 -1
  70. package/dist/lib/onboarding/common.js +85 -70
  71. package/dist/lib/onboarding/local.js +19 -9
  72. package/dist/lib/onboarding/remote.js +35 -30
  73. package/dist/lib/package-managers.js +24 -0
  74. package/dist/lib/remix-config.js +17 -1
  75. package/dist/lib/render-errors.js +17 -10
  76. package/dist/lib/request-events.js +6 -1
  77. package/dist/lib/setups/i18n/replacers.js +9 -6
  78. package/dist/lib/setups/routes/generate.js +1 -0
  79. package/dist/lib/shell.js +2 -1
  80. package/dist/lib/shopify-config.js +19 -1
  81. package/dist/lib/template-diff.js +36 -15
  82. package/dist/lib/template-downloader.js +35 -5
  83. package/dist/lib/transpile/morph/functions.js +26 -8
  84. package/dist/lib/transpile/morph/typedefs.js +6 -4
  85. package/dist/lib/transpile/project.js +8 -4
  86. package/dist/lib/tunneling.js +44 -0
  87. package/dist/lib/verify-linked-storefront.js +24 -0
  88. package/dist/lib/virtual-routes.js +1 -1
  89. package/dist/lib/vite-config.js +39 -9
  90. package/oclif.manifest.json +704 -508
  91. package/package.json +32 -24
  92. package/dist/commands/hydrogen/deploy.test.js +0 -553
  93. package/dist/commands/hydrogen/env/list.test.js +0 -148
  94. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  95. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  96. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  97. package/dist/commands/hydrogen/init.test.js +0 -641
  98. package/dist/commands/hydrogen/link.test.js +0 -187
  99. package/dist/commands/hydrogen/list.test.js +0 -111
  100. package/dist/commands/hydrogen/setup.test.js +0 -61
  101. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  102. package/dist/commands/hydrogen/unlink.test.js +0 -36
  103. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  104. package/dist/generator-templates/starter/remix.config.js +0 -24
  105. package/dist/lib/auth.test.js +0 -157
  106. package/dist/lib/check-lockfile.test.js +0 -81
  107. package/dist/lib/check-version.test.js +0 -86
  108. package/dist/lib/environment-variables.test.js +0 -149
  109. package/dist/lib/file.test.js +0 -68
  110. package/dist/lib/flags.test.js +0 -43
  111. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  112. package/dist/lib/gid.test.js +0 -15
  113. package/dist/lib/graphql/admin/client.test.js +0 -76
  114. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  115. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  116. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  117. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  118. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  119. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  120. package/dist/lib/log.test.js +0 -92
  121. package/dist/lib/mini-oxygen/assets.js +0 -134
  122. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  123. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  124. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  125. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  126. package/dist/lib/missing-routes.test.js +0 -45
  127. package/dist/lib/remix-version-check.test.js +0 -39
  128. package/dist/lib/remix-version-interop.test.js +0 -13
  129. package/dist/lib/setups/i18n/domains.test.js +0 -39
  130. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  131. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  132. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  133. package/dist/lib/setups/routes/generate.test.js +0 -296
  134. package/dist/lib/shell.test.js +0 -111
  135. package/dist/lib/shopify-config.test.js +0 -199
  136. package/dist/lib/string.test.js +0 -16
  137. package/dist/lib/virtual-routes.test.js +0 -49
  138. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  139. package/dist/lib/vite/mini-oxygen.js +0 -152
  140. package/dist/lib/vite/plugins.d.ts +0 -27
  141. package/dist/lib/vite/plugins.js +0 -139
  142. package/dist/lib/vite/shared.js +0 -10
  143. package/dist/lib/vite/utils.js +0 -55
  144. package/dist/lib/vite/worker-entry.js +0 -1518
  145. /package/dist/generator-templates/starter/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/dist/lib/flags.js CHANGED
@@ -3,11 +3,10 @@ import { camelize } from '@shopify/cli-kit/common/string';
3
3
  import { renderInfo } from '@shopify/cli-kit/node/ui';
4
4
  import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
5
5
  import colors from '@shopify/cli-kit/node/colors';
6
- import { STYLING_CHOICES } from './setups/css/index.js';
7
6
  import { I18N_CHOICES } from './setups/i18n/index.js';
8
- import { DEFAULT_INSPECTOR_PORT } from './mini-oxygen/common.js';
9
7
 
10
- const DEFAULT_PORT = 3e3;
8
+ const DEFAULT_APP_PORT = 3e3;
9
+ const DEFAULT_INSPECTOR_PORT = 9229;
11
10
  const commonFlags = {
12
11
  path: {
13
12
  path: Flags.string({
@@ -17,9 +16,8 @@ const commonFlags = {
17
16
  },
18
17
  port: {
19
18
  port: Flags.integer({
20
- description: `The port to run the server on. Defaults to ${DEFAULT_PORT}.`,
21
- env: "SHOPIFY_HYDROGEN_FLAG_PORT",
22
- default: DEFAULT_PORT
19
+ description: `The port to run the server on. Defaults to ${DEFAULT_APP_PORT}.`,
20
+ env: "SHOPIFY_HYDROGEN_FLAG_PORT"
23
21
  })
24
22
  },
25
23
  legacyRuntime: {
@@ -50,17 +48,23 @@ const commonFlags = {
50
48
  allowNo: true
51
49
  })
52
50
  },
51
+ env: {
52
+ env: Flags.string({
53
+ description: "Specifies the environment to perform the operation using its handle. Fetch the handle using the `env list` command.",
54
+ exclusive: ["env-branch"]
55
+ })
56
+ },
57
+ /**
58
+ * @deprecated use `env` instead.
59
+ */
53
60
  envBranch: {
54
61
  "env-branch": Flags.string({
55
- description: "Specifies the environment to pull variables from using its Git branch name.",
62
+ description: "Specifies the environment to perform the operation using its Git branch name.",
56
63
  env: "SHOPIFY_HYDROGEN_ENVIRONMENT_BRANCH",
57
- char: "e"
58
- })
59
- },
60
- env: {
61
- env: Flags.string({
62
- description: "Specifies an environment's name when using remote environment variables.",
63
- env: "SHOPIFY_HYDROGEN_ENVIRONMENT_NAME"
64
+ deprecated: {
65
+ to: "env",
66
+ message: "--env-branch is deprecated. Use --env instead."
67
+ }
64
68
  })
65
69
  },
66
70
  sourcemap: {
@@ -83,15 +87,6 @@ const commonFlags = {
83
87
  dependsOn: ["codegen"]
84
88
  })
85
89
  },
86
- styling: {
87
- styling: Flags.string({
88
- description: `Sets the styling strategy to use. One of ${STYLING_CHOICES.map(
89
- (item) => `\`${item}\``
90
- ).join(", ")}.`,
91
- choices: STYLING_CHOICES,
92
- env: "SHOPIFY_HYDROGEN_FLAG_STYLING"
93
- })
94
- },
95
90
  markets: {
96
91
  markets: Flags.string({
97
92
  description: `Sets the URL structure to support multiple markets. Must be one of: ${I18N_CHOICES.map(
@@ -118,8 +113,7 @@ const commonFlags = {
118
113
  inspectorPort: {
119
114
  "inspector-port": Flags.integer({
120
115
  description: `The port where the inspector is available. Defaults to ${DEFAULT_INSPECTOR_PORT}.`,
121
- env: "SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT",
122
- default: DEFAULT_INSPECTOR_PORT
116
+ env: "SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT"
123
117
  })
124
118
  },
125
119
  diff: {
@@ -149,6 +143,23 @@ const commonFlags = {
149
143
  description: "Disables any warnings about missing standard routes.",
150
144
  env: "SHOPIFY_HYDROGEN_FLAG_DISABLE_ROUTE_WARNING"
151
145
  })
146
+ },
147
+ customerAccountPush: {
148
+ "customer-account-push__unstable": Flags.boolean({
149
+ hidden: true,
150
+ description: "Use tunneling for local development and push the tunneling domain to admin. Required to use Customer Account API's Oauth flow",
151
+ required: false,
152
+ default: false,
153
+ env: "SHOPIFY_HYDROGEN_FLAG_CUSTOMER_ACCOUNT_PUSH"
154
+ })
155
+ },
156
+ verbose: {
157
+ verbose: Flags.boolean({
158
+ description: "Outputs more information about the command's execution.",
159
+ required: false,
160
+ default: false,
161
+ env: "SHOPIFY_HYDROGEN_FLAG_VERBOSE"
162
+ })
152
163
  }
153
164
  };
154
165
  function flagsToCamelObject(obj) {
@@ -202,4 +213,4 @@ function overrideFlag(flags, extra) {
202
213
  );
203
214
  }
204
215
 
205
- export { DEFAULT_PORT, commonFlags, deprecated, flagsToCamelObject, overrideFlag, parseProcessFlags };
216
+ export { DEFAULT_APP_PORT, DEFAULT_INSPECTOR_PORT, commonFlags, deprecated, flagsToCamelObject, overrideFlag, parseProcessFlags };
@@ -1,10 +1,9 @@
1
- import { renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
2
1
  import { outputWarn } from '@shopify/cli-kit/node/output';
3
- import { linkStorefront } from '../commands/hydrogen/link.js';
4
2
  import { login } from './auth.js';
5
3
  import { getCliCommand } from './shell.js';
6
- import { renderMissingLink, renderMissingStorefront } from './render-errors.js';
4
+ import { renderMissingStorefront } from './render-errors.js';
7
5
  import { getOxygenData } from './graphql/admin/get-oxygen-data.js';
6
+ import { verifyLinkedStorefront } from './verify-linked-storefront.js';
8
7
 
9
8
  async function getOxygenDeploymentData({
10
9
  root
@@ -13,21 +12,15 @@ async function getOxygenDeploymentData({
13
12
  login(root),
14
13
  getCliCommand()
15
14
  ]);
16
- if (!config.storefront?.id) {
17
- renderMissingLink({ session, cliCommand });
18
- const runLink = await renderConfirmationPrompt({
19
- message: ["Run", { command: `${cliCommand} link` }]
20
- });
21
- if (!runLink) {
22
- return;
23
- }
24
- config.storefront = await linkStorefront(root, session, config, {
25
- cliCommand
26
- });
27
- }
28
- if (!config.storefront) {
15
+ const linkedStorefront = await verifyLinkedStorefront({
16
+ root,
17
+ session,
18
+ config,
19
+ cliCommand
20
+ });
21
+ if (!linkedStorefront)
29
22
  return;
30
- }
23
+ config.storefront = linkedStorefront;
31
24
  const { storefront } = await getOxygenData(session, config.storefront.id);
32
25
  if (!storefront) {
33
26
  renderMissingStorefront({
@@ -0,0 +1,29 @@
1
+ import { adminRequest } from './client.js';
2
+
3
+ const CUSTOMER_APPLICATION_URLS_REPLACE = `#graphql
4
+ mutation CustomerApplicationUrlsReplace($storefrontId: ID!, $urlsReplaceInput: HydrogenStorefrontCustomerApplicationUrlsReplaceInput!) {
5
+ hydrogenStorefrontCustomerApplicationUrlsReplace(
6
+ storefrontId: $storefrontId,
7
+ urlsReplaceInput: $urlsReplaceInput,
8
+ ) {
9
+ success
10
+ userErrors {
11
+ code
12
+ field
13
+ message
14
+ }
15
+ }
16
+ }
17
+ `;
18
+ async function replaceCustomerApplicationUrls(adminSession, storefrontId, urlsReplaceInput) {
19
+ const {
20
+ hydrogenStorefrontCustomerApplicationUrlsReplace: { success, userErrors }
21
+ } = await adminRequest(
22
+ CUSTOMER_APPLICATION_URLS_REPLACE,
23
+ adminSession,
24
+ { storefrontId, urlsReplaceInput }
25
+ );
26
+ return { success, userErrors };
27
+ }
28
+
29
+ export { CUSTOMER_APPLICATION_URLS_REPLACE, replaceCustomerApplicationUrls };
@@ -6,6 +6,7 @@ const GetDeploymentDataQuery = `#graphql
6
6
  oxygenDeploymentToken
7
7
  environments {
8
8
  name
9
+ handle
9
10
  branch
10
11
  type
11
12
  }
@@ -10,6 +10,7 @@ const ListEnvironmentsQuery = `#graphql
10
10
  createdAt
11
11
  id
12
12
  name
13
+ handle
13
14
  type
14
15
  url
15
16
  }
@@ -1,10 +1,10 @@
1
1
  import { adminRequest } from './client.js';
2
2
 
3
3
  const PullVariablesQuery = `#graphql
4
- query PullVariables($id: ID!, $branch: String) {
4
+ query PullVariables($id: ID!, $handle: String) {
5
5
  hydrogenStorefront(id: $id) {
6
6
  id
7
- environmentVariables(branchName: $branch) {
7
+ environmentVariables(handle: $handle) {
8
8
  id
9
9
  isSecret
10
10
  readOnly
@@ -14,13 +14,13 @@ const PullVariablesQuery = `#graphql
14
14
  }
15
15
  }
16
16
  `;
17
- async function getStorefrontEnvVariables(adminSession, storefrontId, envBranch) {
17
+ async function getStorefrontEnvVariables(adminSession, storefrontId, envHandle) {
18
18
  const { hydrogenStorefront } = await adminRequest(
19
19
  PullVariablesQuery,
20
20
  adminSession,
21
21
  {
22
22
  id: storefrontId,
23
- branch: envBranch
23
+ handle: envHandle
24
24
  }
25
25
  );
26
26
  return hydrogenStorefront;
@@ -0,0 +1,37 @@
1
+ function dummyListEnvironments(storefrontId) {
2
+ return {
3
+ id: storefrontId,
4
+ productionUrl: "https://my-shop.myshopify.com",
5
+ environments: [
6
+ {
7
+ id: "gid://shopify/HydrogenStorefrontEnvironment/1",
8
+ branch: "main",
9
+ type: "PRODUCTION",
10
+ name: "Production",
11
+ handle: "production",
12
+ createdAt: "2023-02-16T22:35:42Z",
13
+ url: "https://oxygen-123.example.com"
14
+ },
15
+ {
16
+ id: "gid://shopify/HydrogenStorefrontEnvironment/2",
17
+ branch: null,
18
+ type: "PREVIEW",
19
+ name: "Preview",
20
+ handle: "preview",
21
+ createdAt: "2023-02-16T22:35:42Z",
22
+ url: null
23
+ },
24
+ {
25
+ id: "gid://shopify/HydrogenStorefrontEnvironment/3",
26
+ branch: "staging",
27
+ type: "CUSTOM",
28
+ name: "Staging",
29
+ handle: "staging",
30
+ createdAt: "2023-05-08T20:52:29Z",
31
+ url: "https://oxygen-456.example.com"
32
+ }
33
+ ]
34
+ };
35
+ }
36
+
37
+ export { dummyListEnvironments };
package/dist/lib/log.js CHANGED
@@ -34,16 +34,19 @@ function debounceMessage(args, debounceFor) {
34
34
  }
35
35
  function warningDebouncer([first]) {
36
36
  return typeof first === "string" && // Show these warnings only once.
37
- (first.includes("[h2:warn:createStorefrontClient]") || first.includes("[h2:warn:createCustomerAccountClient]")) ? true : void 0;
37
+ /\[h2:(warn|info):(createStorefrontClient|createCustomerAccountClient)\]/.test(
38
+ first
39
+ ) ? true : void 0;
38
40
  }
39
41
  function injectLogReplacer(method, debouncer) {
40
42
  if (!methodsReplaced.has(method)) {
41
43
  methodsReplaced.add(method);
42
44
  console[method] = (...args) => {
43
- if (debounceMessage(args, debouncer?.(args)))
45
+ if (debouncer !== false && debounceMessage(args, debouncer?.(args))) {
44
46
  return;
47
+ }
45
48
  const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
46
- if (matcher(args))
49
+ if (matcher(args, acc.length))
47
50
  acc.push(replacer);
48
51
  return acc;
49
52
  }, []);
@@ -62,6 +65,7 @@ function muteDevLogs({ workerReload } = {}) {
62
65
  injectLogReplacer("log");
63
66
  injectLogReplacer("error");
64
67
  injectLogReplacer("warn", warningDebouncer);
68
+ injectLogReplacer("debug", false);
65
69
  let isFirstWorkerReload = true;
66
70
  addMessageReplacers("dev-node", [
67
71
  ([first]) => typeof first === "string" && first.includes("[mf:"),
@@ -114,6 +118,60 @@ function muteDevLogs({ workerReload } = {}) {
114
118
  }
115
119
  ]
116
120
  );
121
+ let isLastLineBlank = false;
122
+ let isLastLineRequestLog = false;
123
+ addMessageReplacers(
124
+ "dev-vite",
125
+ // Vite logs
126
+ [
127
+ // This log must come from Rollup and does not go through Vite's customLogger
128
+ ([first]) => typeof first === "string" && /^Sourcemap for .*@remix-run/i.test(first),
129
+ () => {
130
+ }
131
+ ],
132
+ [
133
+ // Log generated from Workerd HMR connection to Vite
134
+ ([first]) => typeof first === "string" && /^\[vite\] (connected|program reload)/i.test(first),
135
+ () => {
136
+ }
137
+ ],
138
+ [
139
+ // Log that gets entangled with our initial dev logs
140
+ ([first]) => typeof first === "string" && /^Re-optimizing dependencies because/i.test(first),
141
+ () => {
142
+ }
143
+ ],
144
+ [
145
+ // This error is fixed in new Remix versions:
146
+ // https://github.com/remix-run/remix/pull/9194
147
+ ([first]) => {
148
+ const message = first?.message ?? first;
149
+ return /virtual-routes/.test(message) && /(Failed to load url|Could not resolve module for file)/i.test(
150
+ message
151
+ );
152
+ },
153
+ () => {
154
+ }
155
+ ],
156
+ [
157
+ // Log new lines between Request logs and other logs
158
+ ([first], existingMatches) => {
159
+ if (existingMatches === 0 && typeof first === "string") {
160
+ const isRequestLog = /^\s+[A-Z]+\s+\d{3}\s+[a-z]+\s+\//.test(
161
+ // Clear ANSI colors before matching
162
+ first.replace(/\u001b\[.*?m/g, "")
163
+ );
164
+ if (!isLastLineBlank && (isRequestLog && !isLastLineRequestLog || !isRequestLog && isLastLineRequestLog)) {
165
+ process.stdout.write("\n");
166
+ }
167
+ isLastLineRequestLog = isRequestLog;
168
+ isLastLineBlank = /\n$/.test(first);
169
+ }
170
+ return false;
171
+ },
172
+ (params) => params
173
+ ]
174
+ );
117
175
  }
118
176
  const originalWrite = process.stdout.write;
119
177
  function muteAuthLogs({
@@ -126,7 +184,7 @@ function muteAuthLogs({
126
184
  if (typeof item !== "string")
127
185
  return write(item, cb);
128
186
  const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
129
- if (matcher([item]))
187
+ if (matcher([item], acc.length))
130
188
  acc.push(replacer);
131
189
  return acc;
132
190
  }, []);
@@ -175,6 +233,7 @@ function enhanceH2Logs(options) {
175
233
  injectLogReplacer("error");
176
234
  injectLogReplacer("warn", warningDebouncer);
177
235
  injectLogReplacer("log", warningDebouncer);
236
+ injectLogReplacer("info", warningDebouncer);
178
237
  addMessageReplacers("h2-warn", [
179
238
  ([first]) => {
180
239
  const message = first?.message ?? first;
@@ -183,7 +242,10 @@ function enhanceH2Logs(options) {
183
242
  (args) => {
184
243
  const firstArg = args[0];
185
244
  const errorObject = typeof firstArg === "object" && !!firstArg.stack ? firstArg : void 0;
186
- const stringArg = errorObject?.message ?? firstArg;
245
+ let stringArg = errorObject?.message ?? firstArg;
246
+ if (stringArg.startsWith("[h2:info:createStorefrontClient]") && stringArg.includes("defaulting to mock.shop")) {
247
+ stringArg += "\nRun `h2 link` to link your store.";
248
+ }
187
249
  const [, type, scope, message] = stringArg.match(/\[h2:([^:]+):([^\]]+)\]\s+(.*)$/ims) || [];
188
250
  if (!type || !scope || !message)
189
251
  return args;
@@ -191,9 +253,15 @@ function enhanceH2Logs(options) {
191
253
 
192
254
  `;
193
255
  const lines = message.split("\n");
194
- const lastLine = lines.at(-1) ?? "";
256
+ let lastLine = lines.at(-1) ?? "";
195
257
  const hasLinks = /https?:\/\//.test(lastLine);
196
258
  const hasCommands = /`h2 [^`]+`/.test(lastLine);
259
+ if (hasCommands && lastLine) {
260
+ lastLine = lastLine.replace(
261
+ /`(h2) ([^`]+)`/g,
262
+ colors.magentaBright(`\`${options.cliCommand ?? "$1"} $2\``)
263
+ );
264
+ }
197
265
  if (hasLinks || hasCommands)
198
266
  lines.pop();
199
267
  if (type === "error" || errorObject) {
@@ -248,12 +316,7 @@ function enhanceH2Logs(options) {
248
316
  render({
249
317
  body: headline + colors.bold(lines.join("\n")),
250
318
  reference,
251
- nextSteps: hasCommands ? [
252
- lastLine.replace(
253
- /`h2 [^`]+`/g,
254
- (cmd) => colors.bold(colors.yellow(cmd))
255
- )
256
- ] : void 0
319
+ nextSteps: hasCommands ? [lastLine] : void 0
257
320
  });
258
321
  return;
259
322
  }
@@ -298,5 +361,15 @@ async function muteRemixLogs() {
298
361
  } catch {
299
362
  }
300
363
  }
364
+ function setH2OVerbose() {
365
+ if (!process.env.DEBUG || process.env.DEBUG === "*") {
366
+ process.env.DEBUG = "h2:*,o2:*";
367
+ } else {
368
+ process.env.DEBUG += ",h2:*,o2:*";
369
+ }
370
+ }
371
+ function isH2Verbose() {
372
+ return !!(process.env.DEBUG === "*" || process.env.DEBUG?.includes("h2:*"));
373
+ }
301
374
 
302
- export { addMessageReplacers, createRemixLogger, enhanceH2Logs, muteAuthLogs, muteDevLogs, muteRemixLogs, resetAllLogs, warnOnce };
375
+ export { addMessageReplacers, createRemixLogger, enhanceH2Logs, isH2Verbose, muteAuthLogs, muteDevLogs, muteRemixLogs, resetAllLogs, setH2OVerbose, warnOnce };
@@ -1,16 +1,24 @@
1
1
  import { outputToken, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
2
2
  import colors from '@shopify/cli-kit/node/colors';
3
3
  import { DEV_ROUTES } from '../request-events.js';
4
+ import { AbortError } from '@shopify/cli-kit/node/error';
4
5
 
5
6
  const DEFAULT_INSPECTOR_PORT = 9229;
6
7
  const SUBREQUEST_PROFILER_ENDPOINT = "/debug-network-server";
7
- function logRequestLine(request, {
8
- responseStatus = 200,
9
- durationMs = 0
10
- } = {}) {
8
+ function handleMiniOxygenImportFail() {
9
+ throw new AbortError(
10
+ "Could not load MiniOxygen.",
11
+ "Please make sure you have `@shopify/mini-oxygen` installed."
12
+ );
13
+ }
14
+ function logRequestLine({
15
+ request,
16
+ response,
17
+ meta
18
+ }) {
11
19
  try {
12
20
  const url = new URL(request.url);
13
- if (DEV_ROUTES.has(url.pathname))
21
+ if (DEV_ROUTES.has(url.pathname) || url.pathname === "/favicon.ico")
14
22
  return;
15
23
  const isDataRequest = url.searchParams.has("_data");
16
24
  let route = request.url.replace(url.origin, "");
@@ -22,39 +30,17 @@ function logRequestLine(request, {
22
30
  route = url.pathname;
23
31
  info = `[${dataParam}]`;
24
32
  }
25
- const colorizeStatus = responseStatus < 300 ? outputToken.green : responseStatus < 400 ? outputToken.cyan : outputToken.errorText;
33
+ const colorizeStatus = response.status < 300 ? outputToken.green : response.status < 400 ? outputToken.cyan : outputToken.errorText;
26
34
  outputInfo(
27
35
  outputContent`${request.method.padStart(6)} ${colorizeStatus(
28
- String(responseStatus)
29
- )} ${outputToken.italic(type.padEnd(7, " "))} ${route} ${durationMs > 0 ? colors.dim(` ${durationMs}ms`) : ""}${info ? " " + colors.dim(info) : ""}${request.headers.get("purpose") === "prefetch" ? outputToken.italic(colors.dim(" prefetch")) : ""}`
36
+ String(response.status)
37
+ )} ${outputToken.italic(type.padEnd(7, " "))} ${route} ${meta.durationMs > 0 ? colors.dim(` ${meta.durationMs}ms`) : ""}${info ? " " + colors.dim(info) : ""}${request.headers["purpose"] === "prefetch" ? outputToken.italic(colors.dim(" prefetch")) : ""}`
30
38
  );
31
39
  } catch {
32
- if (request && responseStatus) {
33
- outputInfo(`${request.method} ${responseStatus} ${request.url}`);
40
+ if (request && response?.status) {
41
+ outputInfo(`${request.method} ${response.status} ${request.url}`);
34
42
  }
35
43
  }
36
44
  }
37
- const OXYGEN_HEADERS_MAP = {
38
- ip: { name: "oxygen-buyer-ip", defaultValue: "127.0.0.1" },
39
- longitude: { name: "oxygen-buyer-longitude", defaultValue: "-122.40140" },
40
- latitude: { name: "oxygen-buyer-latitude", defaultValue: "37.78855" },
41
- continent: { name: "oxygen-buyer-continent", defaultValue: "NA" },
42
- country: { name: "oxygen-buyer-country", defaultValue: "US" },
43
- region: { name: "oxygen-buyer-region", defaultValue: "California" },
44
- regionCode: { name: "oxygen-buyer-region-code", defaultValue: "CA" },
45
- city: { name: "oxygen-buyer-city", defaultValue: "San Francisco" },
46
- isEuCountry: { name: "oxygen-buyer-is-eu-country", defaultValue: "" },
47
- timezone: {
48
- name: "oxygen-buyer-timezone",
49
- defaultValue: "America/Los_Angeles"
50
- },
51
- // Not documented but available in Oxygen:
52
- deploymentId: { name: "oxygen-buyer-deployment-id", defaultValue: "local" },
53
- shopId: { name: "oxygen-buyer-shop-id", defaultValue: "development" },
54
- storefrontId: {
55
- name: "oxygen-buyer-storefront-id",
56
- defaultValue: "development"
57
- }
58
- };
59
45
 
60
- export { DEFAULT_INSPECTOR_PORT, OXYGEN_HEADERS_MAP, SUBREQUEST_PROFILER_ENDPOINT, logRequestLine };
46
+ export { DEFAULT_INSPECTOR_PORT, SUBREQUEST_PROFILER_ENDPOINT, handleMiniOxygenImportFail, logRequestLine };
@@ -1,6 +1,10 @@
1
+ import { handleMiniOxygenImportFail } from './common.js';
1
2
  export { DEFAULT_INSPECTOR_PORT } from './common.js';
2
- export { buildAssetsUrl } from './assets.js';
3
3
 
4
+ async function buildAssetsUrl(port) {
5
+ const { buildAssetsUrl: _buildAssetsUrl } = await import('@shopify/mini-oxygen').catch(handleMiniOxygenImportFail);
6
+ return _buildAssetsUrl(port);
7
+ }
4
8
  async function startMiniOxygen(options, useNodeRuntime = false) {
5
9
  if (useNodeRuntime) {
6
10
  process.env.MINIFLARE_SUBREQUEST_LIMIT = 100;
@@ -11,4 +15,4 @@ async function startMiniOxygen(options, useNodeRuntime = false) {
11
15
  return startWorkerdServer(options);
12
16
  }
13
17
 
14
- export { startMiniOxygen };
18
+ export { buildAssetsUrl, startMiniOxygen };
@@ -1,14 +1,16 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { AsyncLocalStorage } from 'node:async_hooks';
3
2
  import { readFile } from '@shopify/cli-kit/node/fs';
4
3
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
5
- import { Response, startServer, Request } from '@shopify/mini-oxygen';
6
- import { DEFAULT_PORT } from '../flags.js';
7
- import { OXYGEN_HEADERS_MAP, SUBREQUEST_PROFILER_ENDPOINT, logRequestLine } from './common.js';
4
+ import colors from '@shopify/cli-kit/node/colors';
5
+ import { DEFAULT_INSPECTOR_PORT } from '../flags.js';
6
+ import { handleMiniOxygenImportFail, SUBREQUEST_PROFILER_ENDPOINT, logRequestLine } from './common.js';
8
7
  import { setConstructors, createLogRequestEvent, handleDebugNetworkRequest, H2O_BINDING_NAME } from '../request-events.js';
8
+ import { findPort } from '../find-port.js';
9
+ import { getUtilityBannerlines } from '../dev-shared.js';
10
+ import { outputNewline } from '@shopify/cli-kit/node/output';
9
11
 
10
12
  async function startNodeServer({
11
- port = DEFAULT_PORT,
13
+ appPort,
12
14
  watch = false,
13
15
  buildPathWorkerFile,
14
16
  buildPathClient,
@@ -16,11 +18,7 @@ async function startNodeServer({
16
18
  debug = false,
17
19
  inspectorPort
18
20
  }) {
19
- const oxygenHeaders = Object.fromEntries(
20
- Object.entries(OXYGEN_HEADERS_MAP).map(([key, value]) => {
21
- return [key, value.defaultValue];
22
- })
23
- );
21
+ const { startServer, Request, Response } = await import('@shopify/mini-oxygen/node').catch(handleMiniOxygenImportFail);
24
22
  setConstructors({ Response });
25
23
  const logRequestEvent = createLogRequestEvent();
26
24
  const asyncLocalStorage = new AsyncLocalStorage();
@@ -38,6 +36,8 @@ async function startNodeServer({
38
36
  }
39
37
  };
40
38
  if (debug) {
39
+ if (!inspectorPort)
40
+ inspectorPort = await findPort(DEFAULT_INSPECTOR_PORT);
41
41
  (await import('node:inspector')).open(inspectorPort);
42
42
  }
43
43
  const miniOxygen = await startServer({
@@ -45,7 +45,7 @@ async function startNodeServer({
45
45
  workerFile: buildPathWorkerFile,
46
46
  assetsDir: buildPathClient,
47
47
  publicPath: "",
48
- port,
48
+ port: appPort,
49
49
  watch,
50
50
  autoReload: watch,
51
51
  modules: true,
@@ -56,25 +56,34 @@ async function startNodeServer({
56
56
  },
57
57
  log: () => {
58
58
  },
59
- oxygenHeaders,
60
59
  async onRequest(request, defaultDispatcher) {
61
60
  const url = new URL(request.url);
62
61
  if (url.pathname === SUBREQUEST_PROFILER_ENDPOINT) {
63
62
  return handleDebugNetworkRequest(request);
64
63
  }
65
- let requestId = request.headers.get("request-id");
66
- if (!requestId) {
67
- requestId = randomUUID();
68
- request.headers.set("request-id", requestId);
69
- }
64
+ const requestId = request.headers.get("request-id");
70
65
  const startTimeMs = Date.now();
71
66
  const response = await asyncLocalStorage.run(
72
67
  { requestId, purpose: request.headers.get("purpose") },
73
68
  () => defaultDispatcher(request)
74
69
  );
75
- logRequestLine(request, {
76
- responseStatus: response.status,
77
- durationMs: startTimeMs > 0 ? Date.now() - startTimeMs : 0
70
+ const endTimeMs = Date.now();
71
+ logRequestLine({
72
+ request: {
73
+ url: request.url,
74
+ method: request.method,
75
+ headers: Object.fromEntries(request.headers.entries())
76
+ },
77
+ response: {
78
+ status: response.status,
79
+ statusText: response.statusText,
80
+ headers: Object.fromEntries(response.headers.entries())
81
+ },
82
+ meta: {
83
+ startTimeMs,
84
+ endTimeMs,
85
+ durationMs: startTimeMs > 0 ? endTimeMs - startTimeMs : 0
86
+ }
78
87
  });
79
88
  return response;
80
89
  }
@@ -95,20 +104,23 @@ async function startNodeServer({
95
104
  await miniOxygen.reload(nextOptions);
96
105
  },
97
106
  showBanner(options) {
98
- console.log("");
107
+ outputNewline();
108
+ const customSections = [];
109
+ if (options?.host) {
110
+ customSections.push({ body: getUtilityBannerlines(options.host) });
111
+ }
112
+ if (debug && inspectorPort) {
113
+ customSections.push({
114
+ body: { warn: `Debugger listening on ws://localhost:${inspectorPort}` }
115
+ });
116
+ }
99
117
  renderSuccess({
100
118
  headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Node Sandbox) ${options?.mode ?? "development"} server running.`,
101
119
  body: [
102
- `View ${options?.appName ?? "Hydrogen"} app: ${listeningAt}`,
103
- ...options?.extraLines ?? [],
104
- ...debug ? [
105
- {
106
- warn: `
107
-
108
- Debugger listening on ws://localhost:${inspectorPort}`
109
- }
110
- ] : []
111
- ]
120
+ `View ${options?.appName ? colors.cyan(options?.appName) : "Hydrogen"} app:`,
121
+ { link: { url: options?.host || listeningAt } }
122
+ ],
123
+ customSections
112
124
  });
113
125
  console.log("");
114
126
  },