@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
@@ -1,23 +1,16 @@
1
- import { Response, Miniflare, Request, fetch, NoOpLog } from 'miniflare';
1
+ import { createRequire } from 'node:module';
2
2
  import { resolvePath, dirname } from '@shopify/cli-kit/node/path';
3
- import { readFile } from '@shopify/cli-kit/node/fs';
3
+ import { readFile, createFileReadStream } from '@shopify/cli-kit/node/fs';
4
4
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
5
- import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
5
+ import { outputNewline } from '@shopify/cli-kit/node/output';
6
6
  import colors from '@shopify/cli-kit/node/colors';
7
- import { createInspectorConnector } from './workerd-inspector.js';
8
- import { findPort } from '../find-port.js';
9
- import { OXYGEN_HEADERS_MAP, SUBREQUEST_PROFILER_ENDPOINT, logRequestLine } from './common.js';
7
+ import { handleMiniOxygenImportFail, logRequestLine, SUBREQUEST_PROFILER_ENDPOINT } from './common.js';
8
+ import { TUNNEL_DOMAIN, getUtilityBannerlines, getDebugBannerLine } from '../dev-shared.js';
10
9
  import { setConstructors, handleDebugNetworkRequest, H2O_BINDING_NAME, createLogRequestEvent } from '../request-events.js';
11
- import { STATIC_ASSET_EXTENSIONS, createAssetsServer, buildAssetsUrl } from './assets.js';
12
10
 
13
- const PRIVATE_WORKERD_INSPECTOR_PORT = 9222;
14
- const OXYGEN_WORKERD_COMPAT_PARAMS = {
15
- compatibilityFlags: ["streams_enable_constructors"],
16
- compatibilityDate: "2022-10-31"
17
- };
18
11
  async function startWorkerdServer({
19
12
  root,
20
- port: appPort,
13
+ appPort,
21
14
  inspectorPort: publicInspectorPort,
22
15
  assetsPort,
23
16
  debug = false,
@@ -26,58 +19,56 @@ async function startWorkerdServer({
26
19
  buildPathClient,
27
20
  env
28
21
  }) {
29
- const privateInspectorPort = await findPort(PRIVATE_WORKERD_INSPECTOR_PORT);
30
- const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
31
- (acc, item) => {
32
- acc[item.name] = item.defaultValue;
33
- return acc;
34
- },
35
- {}
36
- );
22
+ const { createMiniOxygen, Response } = await import('@shopify/mini-oxygen').catch(handleMiniOxygenImportFail);
37
23
  setConstructors({ Response });
38
- const absoluteBundlePath = resolvePath(root, buildPathWorkerFile);
39
- const handleAssets = createAssetHandler(assetsPort);
40
- const staticAssetExtensions = STATIC_ASSET_EXTENSIONS.slice();
41
- let stringifiedOxygenHandler = miniOxygenHandler.toString().replace(
42
- "SUBREQUEST_PROFILER_ENDPOINT",
43
- JSON.stringify(SUBREQUEST_PROFILER_ENDPOINT)
44
- );
45
- if (process.env.NODE_ENV === "test") {
46
- stringifiedOxygenHandler = stringifiedOxygenHandler.replace(
47
- /\w*vite_ssr_import[\w\d]*\./g,
48
- ""
24
+ async function handleCustomerAccountSchema() {
25
+ const require2 = createRequire(import.meta.url);
26
+ const filePath = require2.resolve(
27
+ "@shopify/hydrogen/customer-account.schema.json"
49
28
  );
29
+ return new Response(createFileReadStream(filePath), {
30
+ headers: { "Content-Type": "application/json" }
31
+ });
50
32
  }
51
- const buildMiniOxygenOptions = async () => ({
52
- cf: false,
53
- verbose: false,
33
+ const absoluteBundlePath = resolvePath(root, buildPathWorkerFile);
34
+ const mainWorkerName = "hydrogen";
35
+ const miniOxygen = createMiniOxygen({
36
+ debug,
54
37
  port: appPort,
55
- inspectorPort: privateInspectorPort,
56
- log: new NoOpLog(),
57
- liveReload: watch,
58
38
  host: "localhost",
59
- handleRuntimeStdio(stdout, stderr) {
60
- stdout.destroy();
61
- stderr.destroy();
62
- },
39
+ liveReload: watch,
40
+ requestHook: logRequestLine,
41
+ inspectorPort: publicInspectorPort,
42
+ inspectWorkerName: mainWorkerName,
43
+ assets: { port: assetsPort, directory: buildPathClient },
63
44
  workers: [
64
45
  {
65
- name: "mini-oxygen",
46
+ name: "hydrogen:middleware",
66
47
  modules: true,
67
- script: `export default { fetch: ${stringifiedOxygenHandler} }`,
68
- bindings: {
69
- staticAssetExtensions,
70
- oxygenHeadersMap
71
- },
48
+ script: `export default { fetch: (request, env) => {
49
+ const url = new URL(request.url);
50
+ if (url.hostname.endsWith('${TUNNEL_DOMAIN.ORIGINAL}')) {
51
+ url.hostname = url.hostname.replace(
52
+ '${TUNNEL_DOMAIN.ORIGINAL}',
53
+ '${TUNNEL_DOMAIN.REBRANDED}',
54
+ );
55
+ }
56
+
57
+ return url.pathname === '${SUBREQUEST_PROFILER_ENDPOINT}'
58
+ ? env.profiler.fetch(url, request)
59
+ : url.pathname === '/graphiql/customer-account.schema.json'
60
+ ? env.assets.fetch(url, request)
61
+ : env.next.fetch(url, request)
62
+ }
63
+ }`,
72
64
  serviceBindings: {
73
- hydrogen: "hydrogen",
74
- assets: handleAssets,
75
- debugNetwork: handleDebugNetworkRequest,
76
- logRequest
65
+ profiler: handleDebugNetworkRequest,
66
+ assets: handleCustomerAccountSchema,
67
+ next: mainWorkerName
77
68
  }
78
69
  },
79
70
  {
80
- name: "hydrogen",
71
+ name: mainWorkerName,
81
72
  modulesRoot: dirname(absoluteBundlePath),
82
73
  modules: [
83
74
  {
@@ -86,7 +77,6 @@ async function startWorkerdServer({
86
77
  contents: await readFile(absoluteBundlePath)
87
78
  }
88
79
  ],
89
- ...OXYGEN_WORKERD_COMPAT_PARAMS,
90
80
  bindings: { ...env },
91
81
  serviceBindings: {
92
82
  [H2O_BINDING_NAME]: createLogRequestEvent({
@@ -96,130 +86,47 @@ async function startWorkerdServer({
96
86
  }
97
87
  ]
98
88
  });
99
- let miniOxygenOptions = await buildMiniOxygenOptions();
100
- const miniOxygen = new Miniflare(miniOxygenOptions);
101
- const listeningAt = (await miniOxygen.ready).origin;
102
- const sourceMapPath = buildPathWorkerFile + ".map";
103
- const reconnect = createInspectorConnector({
104
- debug,
105
- sourceMapPath,
106
- absoluteBundlePath,
107
- privateInspectorPort,
108
- publicInspectorPort
109
- });
110
- await reconnect();
111
- const assetsServer = createAssetsServer(buildPathClient);
112
- assetsServer.listen(assetsPort);
89
+ const { workerUrl, inspectorUrl } = await miniOxygen.ready;
113
90
  return {
114
- port: appPort,
115
- listeningAt,
116
- async reload(nextOptions) {
117
- miniOxygenOptions = await buildMiniOxygenOptions();
118
- if (nextOptions) {
119
- const hydrogen = miniOxygenOptions.workers.find(
120
- (worker) => worker.name === "hydrogen"
121
- );
122
- if (hydrogen) {
123
- hydrogen.bindings = { ...nextOptions?.env ?? env };
91
+ port: Number(workerUrl.port),
92
+ listeningAt: workerUrl.origin,
93
+ reload(nextOptions) {
94
+ return miniOxygen.reload(async ({ workers }) => {
95
+ const mainWorker = workers.find(({ name }) => name === mainWorkerName);
96
+ if (Array.isArray(mainWorker.modules) && mainWorker.modules[0]) {
97
+ mainWorker.modules[0].contents = await readFile(absoluteBundlePath);
124
98
  }
125
- }
126
- await reconnect(() => miniOxygen.setOptions(miniOxygenOptions));
99
+ if (nextOptions) {
100
+ mainWorker.bindings = { ...nextOptions?.env ?? env };
101
+ }
102
+ return { workers };
103
+ });
127
104
  },
128
105
  showBanner(options) {
129
- console.log("");
106
+ outputNewline();
107
+ const customSections = [];
108
+ if (options?.host) {
109
+ customSections.push({ body: getUtilityBannerlines(options.host) });
110
+ }
111
+ if (inspectorUrl) {
112
+ customSections.push({
113
+ body: { warn: getDebugBannerLine(Number(inspectorUrl.port)) }
114
+ });
115
+ }
130
116
  renderSuccess({
131
117
  headline: `${options?.headlinePrefix ?? ""}MiniOxygen (Worker Runtime) ${options?.mode ?? "development"} server running.`,
132
118
  body: [
133
- `View ${options?.appName ?? "Hydrogen"} app: ${listeningAt}`,
134
- ...options?.extraLines ?? [],
135
- ...debug ? [{ warn: getDebugBannerLine(publicInspectorPort) }] : []
136
- ]
119
+ `View ${options?.appName ? colors.cyan(options?.appName) : "Hydrogen"} app:`,
120
+ { link: { url: options?.host || workerUrl.origin } }
121
+ ],
122
+ customSections
137
123
  });
138
124
  console.log("");
139
125
  },
140
126
  async close() {
141
- assetsServer.closeAllConnections();
142
- assetsServer.close();
143
127
  await miniOxygen.dispose();
144
128
  }
145
129
  };
146
130
  }
147
- async function miniOxygenHandler(request, env, context) {
148
- const { pathname } = new URL(request.url);
149
- if (pathname === SUBREQUEST_PROFILER_ENDPOINT) {
150
- return env.debugNetwork.fetch(request);
151
- }
152
- if (request.method === "GET") {
153
- const staticAssetExtensions = new Set(env.staticAssetExtensions);
154
- const wellKnown = pathname.startsWith("/.well-known");
155
- const extension = pathname.split(".").at(-1) ?? "";
156
- const isAsset = wellKnown || !!staticAssetExtensions.has(extension.toUpperCase());
157
- if (isAsset) {
158
- const response2 = await env.assets.fetch(
159
- new Request(request.url, {
160
- signal: request.signal,
161
- headers: request.headers
162
- })
163
- );
164
- if (response2.status !== 404)
165
- return response2;
166
- }
167
- }
168
- const requestInit = {
169
- headers: {
170
- "request-id": crypto.randomUUID(),
171
- ...env.oxygenHeadersMap,
172
- ...Object.fromEntries(request.headers.entries())
173
- }
174
- };
175
- const startTimeMs = Date.now();
176
- const response = await env.hydrogen.fetch(request, requestInit);
177
- const durationMs = Date.now() - startTimeMs;
178
- context.waitUntil(
179
- env.logRequest.fetch(
180
- new Request(request.url, {
181
- method: request.method,
182
- signal: request.signal,
183
- headers: {
184
- ...requestInit.headers,
185
- "h2-duration-ms": String(durationMs),
186
- "h2-response-status": String(response.status)
187
- }
188
- })
189
- )
190
- );
191
- return response;
192
- }
193
- function createAssetHandler(assetsPort) {
194
- const assetsServerOrigin = buildAssetsUrl(assetsPort);
195
- return async (request) => {
196
- return fetch(
197
- new Request(
198
- request.url.replace(
199
- new URL(request.url).origin + "/",
200
- assetsServerOrigin
201
- ),
202
- request
203
- )
204
- );
205
- };
206
- }
207
- async function logRequest(request) {
208
- logRequestLine(request, {
209
- responseStatus: Number(request.headers.get("h2-response-status") || 200),
210
- durationMs: Number(request.headers.get("h2-duration-ms") || 0)
211
- });
212
- return new Response("ok");
213
- }
214
- function getDebugBannerLine(publicInspectorPort) {
215
- const isVSCode = process.env.TERM_PROGRAM === "vscode";
216
- const debuggingDocsLink = "https://h2o.fyi/debugging/server-code" + (isVSCode ? "#visual-studio-code" : "#step-2-attach-a-debugger");
217
- return outputContent`\n\nDebugging enabled on port ${String(
218
- publicInspectorPort
219
- )}.\nAttach a ${outputToken.link(
220
- colors.yellow(isVSCode ? "VSCode debugger" : "debugger"),
221
- debuggingDocsLink
222
- )} or open DevTools in http://localhost:${String(publicInspectorPort)}.`.value;
223
- }
224
131
 
225
- export { OXYGEN_WORKERD_COMPAT_PARAMS, PRIVATE_WORKERD_INSPECTOR_PORT, getDebugBannerLine, startWorkerdServer };
132
+ export { startWorkerdServer };
@@ -58,7 +58,7 @@ function findMissingRoutes(config, requiredRoutes = REQUIRED_ROUTES) {
58
58
  }
59
59
  const optionalSegment = ":?[^\\/\\?]+\\?";
60
60
  const reString = `^(${optionalSegment}\\/)?` + // Starts with an optional segment
61
- requiredRoute.replaceAll(".", "\\.").replace(/\//g, `\\/(${optionalSegment}\\/)?`).replace(/:[^/)?]+/g, ":[^\\/]+") + // Replace params with regex
61
+ requiredRoute.replaceAll(".", "\\.").replace(/\//g, `\\/(${optionalSegment}\\/)?`).replace(/:[^/)?]+/g, "(:[^\\/]+|\\*)") + // Replace params with regex
62
62
  `(\\/${optionalSegment})?$`;
63
63
  if (new RegExp(reString).test(currentRoute.path)) {
64
64
  missingRoutes.delete(requiredRoute);
@@ -6,14 +6,14 @@ import { joinPath, resolvePath, basename } from '@shopify/cli-kit/node/path';
6
6
  import { initializeGitRepository, addAllToGitFromDirectory, createGitCommit } from '@shopify/cli-kit/node/git';
7
7
  import { AbortError } from '@shopify/cli-kit/node/error';
8
8
  import { rmdir, writeFile, fileExists, isDirectory } from '@shopify/cli-kit/node/fs';
9
- import { outputDebug, outputContent, outputToken, formatPackageManagerCommand } from '@shopify/cli-kit/node/output';
9
+ import { outputDebug, formatPackageManagerCommand } from '@shopify/cli-kit/node/output';
10
10
  import colors from '@shopify/cli-kit/node/colors';
11
11
  import { login, renderLoginSuccess } from '../auth.js';
12
12
  import { renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../setups/i18n/index.js';
13
13
  import { titleize } from '../string.js';
14
14
  import { ALIAS_NAME, createPlatformShortcut } from '../shell.js';
15
15
  import { transpileProject } from '../transpile/index.js';
16
- import { renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../setups/css/index.js';
16
+ import { CSS_STRATEGY_NAME_MAP } from '../setups/css/index.js';
17
17
  import { renderRoutePrompt, generateRoutes, generateProjectFile } from '../setups/routes/generate.js';
18
18
  import { execAsync } from '../process.js';
19
19
  import { getStorefronts } from '../graphql/admin/link-storefront.js';
@@ -141,6 +141,9 @@ async function handleStorefrontSelection(storefronts) {
141
141
  value: id
142
142
  }))
143
143
  ];
144
+ if (choices.length === 1) {
145
+ return;
146
+ }
144
147
  const storefrontId = await renderSelectPrompt({
145
148
  message: "Select a Hydrogen storefront to link",
146
149
  choices
@@ -189,7 +192,7 @@ async function handleProjectLocation({
189
192
  return;
190
193
  }
191
194
  }
192
- await rmdir(directory);
195
+ await rmdir(directory, { force: true });
193
196
  }
194
197
  return {
195
198
  name: basename(location),
@@ -220,33 +223,10 @@ async function handleLanguage(projectDir, controller, flagLanguage) {
220
223
  };
221
224
  }
222
225
  async function handleCssStrategy(projectDir, controller, flagStyling) {
223
- const selection = flagStyling ?? await renderCssPrompt({
224
- abortSignal: controller.signal,
225
- extraChoices: { none: "Skip and set up later" }
226
- });
227
- const cssStrategy = selection === "none" ? void 0 : selection;
228
- return {
229
- cssStrategy,
230
- async setupCss() {
231
- if (cssStrategy) {
232
- const result = await setupCssStrategy(
233
- cssStrategy,
234
- {
235
- rootDirectory: projectDir,
236
- appDirectory: joinPath(projectDir, "app")
237
- // Default value in new projects
238
- },
239
- true
240
- );
241
- if (result) {
242
- await result.workPromise;
243
- }
244
- }
245
- }
246
- };
226
+ return {};
247
227
  }
248
- async function handleDependencies(projectDir, controller, shouldInstallDeps) {
249
- const detectedPackageManager = packageManagerFromUserAgent();
228
+ async function handleDependencies(projectDir, controller, packageManagerFromFlag, shouldInstallDeps) {
229
+ const detectedPackageManager = packageManagerFromFlag ?? packageManagerFromUserAgent();
250
230
  let actualPackageManager = "npm";
251
231
  if (shouldInstallDeps !== false) {
252
232
  if (detectedPackageManager === "unknown") {
@@ -403,39 +383,6 @@ async function renderProjectReady(project, {
403
383
  }
404
384
  ]
405
385
  },
406
- {
407
- title: "Help\n",
408
- body: {
409
- list: {
410
- items: [
411
- {
412
- link: {
413
- label: "Guides",
414
- url: "https://h2o.fyi/building"
415
- }
416
- },
417
- {
418
- link: {
419
- label: "API reference",
420
- url: "https://shopify.dev/docs/api/storefront"
421
- }
422
- },
423
- {
424
- link: {
425
- label: "Demo Store code",
426
- url: "https://github.com/Shopify/hydrogen/tree/HEAD/templates/demo-store"
427
- }
428
- },
429
- [
430
- "Run",
431
- {
432
- command: `${cliCommand} --help`
433
- }
434
- ]
435
- ]
436
- }
437
- }
438
- },
439
386
  {
440
387
  title: "Next steps\n",
441
388
  body: [
@@ -445,13 +392,11 @@ async function renderProjectReady(project, {
445
392
  [
446
393
  "Run",
447
394
  {
448
- command: outputContent`${outputToken.genericShellCommand(
449
- [
450
- project.directory === process.cwd() ? void 0 : `cd ${project.location.replace(/^\.\//, "")}`,
451
- depsInstalled ? void 0 : `${packageManager} install`,
452
- formatPackageManagerCommand(packageManager, "dev")
453
- ].filter(Boolean).join(" && ")
454
- )}`.value
395
+ command: [
396
+ project.directory === process.cwd() ? void 0 : `cd ${project.location.replace(/^\.\//, "")}`,
397
+ depsInstalled ? void 0 : `${packageManager} install`,
398
+ formatPackageManagerCommand(packageManager, "dev")
399
+ ].filter(Boolean).join(" && ")
455
400
  }
456
401
  ]
457
402
  ].filter((step) => Boolean(step))
@@ -485,5 +430,75 @@ async function projectExists(projectDir) {
485
430
  function normalizeRoutePath(routePath) {
486
431
  return routePath.replace(/(^|\.)_index$/, "").replace(/((^|\.)[^\.]+)_\./g, "$1.").replace(/\.(?!\w+\])/g, "/").replace(/\$$/g, ":catchAll").replace(/\$/g, ":").replace(/[\[\]]/g, "").replace(/:\w*Handle/i, ":handle");
487
432
  }
433
+ function generateRandomName() {
434
+ function getRandomElement(arr) {
435
+ return arr[Math.floor(Math.random() * arr.length)];
436
+ }
437
+ const geographicalFeature = getRandomElement([
438
+ "Bay",
439
+ "Bend",
440
+ "Cape",
441
+ "Cliff",
442
+ "Cove",
443
+ "Creek",
444
+ "Dale",
445
+ "Dune",
446
+ "Fjord",
447
+ "Glade",
448
+ "Gulf",
449
+ "Hill",
450
+ "Isle",
451
+ "Knoll",
452
+ "Lake",
453
+ "Loch",
454
+ "Mesa",
455
+ "Peak",
456
+ "Pond",
457
+ "Quay",
458
+ "Reef",
459
+ "Ridge",
460
+ "Rise",
461
+ "River",
462
+ "Road",
463
+ "Shore",
464
+ "Strait",
465
+ "Stream",
466
+ "Vale",
467
+ "Valley",
468
+ "View",
469
+ "Vista"
470
+ ]);
471
+ const colorNames = getRandomElement([
472
+ "Crimson",
473
+ "Azure",
474
+ "Coral",
475
+ "Fuchsia",
476
+ "Indigo",
477
+ "Ivory",
478
+ "Lavender",
479
+ "Lime",
480
+ "Magenta",
481
+ "Maroon",
482
+ "Orchid",
483
+ "Peach",
484
+ "Plum",
485
+ "Quartz",
486
+ "Salmon",
487
+ "Teal",
488
+ "Turquoise",
489
+ "Violet",
490
+ "Yellow",
491
+ "Ebony",
492
+ "Jade",
493
+ "Lilac",
494
+ "Mint",
495
+ "Onyx",
496
+ "Pearl",
497
+ "Ruby",
498
+ "Sapphire",
499
+ "Topaz"
500
+ ]);
501
+ return `${colorNames} ${geographicalFeature}`;
502
+ }
488
503
 
489
- export { LANGUAGES, commitAll, createAbortHandler, createInitialCommit, generateProjectEntries, handleCliShortcut, handleCssStrategy, handleDependencies, handleI18n, handleLanguage, handleProjectLocation, handleRouteGeneration, handleStorefrontLink, handleStorefrontSelection, renderProjectReady };
504
+ export { LANGUAGES, commitAll, createAbortHandler, createInitialCommit, generateProjectEntries, generateRandomName, handleCliShortcut, handleCssStrategy, handleDependencies, handleI18n, handleLanguage, handleProjectLocation, handleRouteGeneration, handleStorefrontLink, handleStorefrontSelection, renderProjectReady };
@@ -63,15 +63,20 @@ async function setupLocalStarterTemplate(options, controller) {
63
63
  })
64
64
  )
65
65
  ).catch(abort);
66
+ const initMsg = {
67
+ create: "Creating storefront",
68
+ setup: `Setting up ${options.quickstart ? "Quickstart " : ""}project`,
69
+ install: "Installing dependencies. This could take a few minutes"
70
+ };
66
71
  const tasks = [
67
72
  {
68
- title: "Creating storefront",
73
+ title: initMsg.create,
69
74
  task: async () => {
70
75
  await createStorefrontPromise;
71
76
  }
72
77
  },
73
78
  {
74
- title: "Setting up project",
79
+ title: initMsg.setup,
75
80
  task: async () => {
76
81
  await backgroundWorkPromise;
77
82
  }
@@ -152,6 +157,7 @@ async function setupLocalStarterTemplate(options, controller) {
152
157
  const { packageManager, shouldInstallDeps, installDeps } = await handleDependencies(
153
158
  project.directory,
154
159
  controller,
160
+ options.packageManager,
155
161
  options.installDeps
156
162
  );
157
163
  const setupSummary = {
@@ -171,7 +177,7 @@ async function setupLocalStarterTemplate(options, controller) {
171
177
  }
172
178
  });
173
179
  tasks.push({
174
- title: "Installing dependencies. This could take a few minutes",
180
+ title: initMsg.install,
175
181
  task: async () => {
176
182
  await installingDepsPromise;
177
183
  }
@@ -190,12 +196,16 @@ async function setupLocalStarterTemplate(options, controller) {
190
196
  });
191
197
  showShortcutBanner();
192
198
  }
193
- renderSuccess({
194
- headline: [
195
- { userInput: storefrontInfo?.title ?? project.name },
196
- "is ready to build."
197
- ]
198
- });
199
+ if (options.quickstart) {
200
+ console.log("\n");
201
+ } else {
202
+ renderSuccess({
203
+ headline: [
204
+ { userInput: storefrontInfo?.title ?? project.name },
205
+ "is ready to build."
206
+ ]
207
+ });
208
+ }
199
209
  const continueWithSetup = (options.i18n ?? options.routes) !== void 0 || await renderConfirmationPrompt({
200
210
  message: "Do you want to scaffold routes and core functionality?",
201
211
  confirmationMessage: "Yes, set up now",
@@ -1,37 +1,19 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { AbortError } from '@shopify/cli-kit/node/error';
3
- import { fileExists, copyFile } from '@shopify/cli-kit/node/fs';
3
+ import { copyFile, fileExists } from '@shopify/cli-kit/node/fs';
4
4
  import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
5
5
  import { joinPath } from '@shopify/cli-kit/node/path';
6
6
  import { renderTasks, renderInfo } from '@shopify/cli-kit/node/ui';
7
- import { getLatestTemplates } from '../template-downloader.js';
7
+ import { downloadExternalRepo, downloadMonorepoTemplates } from '../template-downloader.js';
8
8
  import { applyTemplateDiff } from '../template-diff.js';
9
9
  import { getCliCommand } from '../shell.js';
10
10
  import { createAbortHandler, handleProjectLocation, handleLanguage, createInitialCommit, handleDependencies, commitAll, renderProjectReady } from './common.js';
11
11
 
12
+ const DEMO_STORE_REPO = "shopify/hydrogen-demo-store";
12
13
  async function setupRemoteTemplate(options, controller) {
13
- const appTemplate = options.template;
14
+ const appTemplate = options.template === "demo-store" ? DEMO_STORE_REPO : options.template;
14
15
  let abort = createAbortHandler(controller);
15
- const backgroundDownloadPromise = getLatestTemplates({
16
- signal: controller.signal
17
- }).then(async ({ templatesDir, examplesDir }) => {
18
- const templatePath = joinPath(templatesDir, appTemplate);
19
- const examplePath = joinPath(examplesDir, appTemplate);
20
- if (await fileExists(templatePath)) {
21
- return { templatesDir, sourcePath: templatePath };
22
- }
23
- if (await fileExists(examplePath)) {
24
- return { templatesDir, sourcePath: examplePath };
25
- }
26
- const availableTemplates = (await Promise.all([readdir(examplesDir), readdir(templatesDir)]).catch(
27
- () => []
28
- )).flat().filter((name) => name !== "skeleton" && !name.endsWith(".md")).sort();
29
- throw new AbortError(
30
- `Unknown value in \`--template\` flag "${appTemplate}".
31
- Skip the flag or provide the name of a template or example in the Hydrogen repository.`,
32
- availableTemplates.length === 0 ? "" : { list: { title: "Available templates:", items: availableTemplates } }
33
- );
34
- }).catch(abort);
16
+ const backgroundDownloadPromise = appTemplate.includes("/") ? getExternalTemplate(appTemplate, controller.signal).catch(abort) : getMonorepoTemplate(appTemplate, controller.signal).catch(abort);
35
17
  const project = await handleProjectLocation({ ...options, controller });
36
18
  if (!project)
37
19
  return;
@@ -42,16 +24,12 @@ Skip the flag or provide the name of a template or example in the Hydrogen repos
42
24
  let backgroundWorkPromise = Promise.resolve().then(async () => {
43
25
  if (controller.signal.aborted)
44
26
  return;
45
- const { sourcePath, templatesDir } = downloaded;
27
+ const { sourcePath, skeletonPath } = downloaded;
46
28
  const pkgJson = await readAndParsePackageJson(
47
29
  joinPath(sourcePath, "package.json")
48
30
  );
49
31
  if (pkgJson.scripts?.dev?.includes("--diff")) {
50
- return applyTemplateDiff(
51
- project.directory,
52
- sourcePath,
53
- joinPath(templatesDir, "skeleton")
54
- );
32
+ return applyTemplateDiff(project.directory, sourcePath, skeletonPath);
55
33
  }
56
34
  return copyFile(sourcePath, project.directory);
57
35
  }).catch(abort);
@@ -65,6 +43,7 @@ Skip the flag or provide the name of a template or example in the Hydrogen repos
65
43
  const { packageManager, shouldInstallDeps, installDeps } = await handleDependencies(
66
44
  project.directory,
67
45
  controller,
46
+ options.packageManager,
68
47
  options.installDeps
69
48
  );
70
49
  const setupSummary = {
@@ -108,7 +87,7 @@ Skip the flag or provide the name of a template or example in the Hydrogen repos
108
87
  }
109
88
  await renderProjectReady(project, setupSummary);
110
89
  renderInfo({
111
- headline: `Your project will display inventory from ${options.template === "demo-store" ? "the Hydrogen Demo Store" : "Mock.shop"}.`,
90
+ headline: `Your project will display inventory from ${options.template.endsWith(DEMO_STORE_REPO) ? "the Hydrogen Demo Store" : "Mock.shop"}.`,
112
91
  body: `To connect this project to your Shopify store\u2019s inventory, update \`${project.name}/.env\` with your store ID and Storefront API key.`
113
92
  });
114
93
  return {
@@ -116,5 +95,31 @@ Skip the flag or provide the name of a template or example in the Hydrogen repos
116
95
  ...setupSummary
117
96
  };
118
97
  }
98
+ async function getExternalTemplate(appTemplate, signal) {
99
+ const { templateDir } = await downloadExternalRepo(appTemplate, signal);
100
+ return { sourcePath: templateDir };
101
+ }
102
+ async function getMonorepoTemplate(appTemplate, signal) {
103
+ const { templatesDir, examplesDir } = await downloadMonorepoTemplates({
104
+ signal
105
+ });
106
+ const skeletonPath = joinPath(templatesDir, "skeleton");
107
+ const templatePath = joinPath(templatesDir, appTemplate);
108
+ const examplePath = joinPath(examplesDir, appTemplate);
109
+ if (await fileExists(templatePath)) {
110
+ return { skeletonPath, sourcePath: templatePath };
111
+ }
112
+ if (await fileExists(examplePath)) {
113
+ return { skeletonPath, sourcePath: examplePath };
114
+ }
115
+ const availableTemplates = (await Promise.all([readdir(examplesDir), readdir(templatesDir)]).catch(
116
+ () => []
117
+ )).flat().filter((name) => name !== "skeleton" && !name.endsWith(".md")).concat("demo-store").sort();
118
+ throw new AbortError(
119
+ `Unknown value in \`--template\` flag "${appTemplate}".
120
+ Skip the flag or provide the name of a template or example in the Hydrogen repository or a URL to a git repository.`,
121
+ availableTemplates.length === 0 ? "" : { list: { title: "Available templates:", items: availableTemplates } }
122
+ );
123
+ }
119
124
 
120
125
  export { setupRemoteTemplate };