@shopify/cli-hydrogen 7.1.2 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) 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 +3 -0
  7. package/dist/commands/hydrogen/deploy.js +121 -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 +7 -8
  11. package/dist/commands/hydrogen/env/pull.js +17 -1
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +23 -50
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +45 -17
  15. package/dist/commands/hydrogen/link.js +20 -4
  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 -22
  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 +10 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +89 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  32. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  33. package/dist/generator-templates/starter/app/root.tsx +2 -5
  34. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  35. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  36. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  37. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  38. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  39. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  40. package/dist/generator-templates/starter/package.json +14 -9
  41. package/dist/generator-templates/starter/server.ts +2 -1
  42. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  43. package/dist/generator-templates/starter/vite.config.ts +21 -0
  44. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -3
  45. package/dist/lib/check-lockfile.js +12 -18
  46. package/dist/lib/codegen.js +37 -13
  47. package/dist/lib/common.js +50 -0
  48. package/dist/lib/cpu-profiler.js +4 -1
  49. package/dist/lib/dev-shared.js +97 -0
  50. package/dist/lib/environment-variables.js +51 -30
  51. package/dist/lib/file.js +8 -1
  52. package/dist/lib/flags.js +37 -16
  53. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  54. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  55. package/dist/lib/graphql/admin/list-environments.js +1 -0
  56. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  57. package/dist/lib/graphql/admin/test-helper.js +37 -0
  58. package/dist/lib/log.js +86 -13
  59. package/dist/lib/mini-oxygen/common.js +19 -33
  60. package/dist/lib/mini-oxygen/index.js +6 -2
  61. package/dist/lib/mini-oxygen/node.js +43 -31
  62. package/dist/lib/mini-oxygen/workerd.js +72 -165
  63. package/dist/lib/missing-routes.js +1 -1
  64. package/dist/lib/onboarding/common.js +82 -70
  65. package/dist/lib/onboarding/local.js +19 -9
  66. package/dist/lib/onboarding/remote.js +35 -30
  67. package/dist/lib/package-managers.js +24 -0
  68. package/dist/lib/remix-config.js +17 -1
  69. package/dist/lib/request-events.js +6 -1
  70. package/dist/lib/setups/i18n/replacers.js +9 -6
  71. package/dist/lib/setups/routes/generate.js +1 -0
  72. package/dist/lib/shell.js +2 -1
  73. package/dist/lib/shopify-config.js +19 -1
  74. package/dist/lib/template-diff.js +36 -15
  75. package/dist/lib/template-downloader.js +35 -5
  76. package/dist/lib/transpile/morph/typedefs.js +5 -2
  77. package/dist/lib/transpile/project.js +8 -4
  78. package/dist/lib/tunneling.js +44 -0
  79. package/dist/lib/virtual-routes.js +1 -1
  80. package/dist/lib/vite-config.js +39 -9
  81. package/oclif.manifest.json +711 -498
  82. package/package.json +32 -24
  83. package/dist/commands/hydrogen/deploy.test.js +0 -553
  84. package/dist/commands/hydrogen/env/list.test.js +0 -148
  85. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  86. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  87. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  88. package/dist/commands/hydrogen/init.test.js +0 -641
  89. package/dist/commands/hydrogen/link.test.js +0 -187
  90. package/dist/commands/hydrogen/list.test.js +0 -111
  91. package/dist/commands/hydrogen/setup.test.js +0 -61
  92. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  93. package/dist/commands/hydrogen/unlink.test.js +0 -36
  94. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  95. package/dist/generator-templates/starter/remix.config.js +0 -24
  96. package/dist/lib/auth.test.js +0 -157
  97. package/dist/lib/check-lockfile.test.js +0 -81
  98. package/dist/lib/check-version.test.js +0 -86
  99. package/dist/lib/environment-variables.test.js +0 -149
  100. package/dist/lib/file.test.js +0 -68
  101. package/dist/lib/flags.test.js +0 -43
  102. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  103. package/dist/lib/gid.test.js +0 -15
  104. package/dist/lib/graphql/admin/client.test.js +0 -76
  105. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  106. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  107. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  108. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  109. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  110. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  111. package/dist/lib/log.test.js +0 -92
  112. package/dist/lib/mini-oxygen/assets.js +0 -134
  113. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  114. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  115. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  116. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  117. package/dist/lib/missing-routes.test.js +0 -45
  118. package/dist/lib/remix-version-check.test.js +0 -39
  119. package/dist/lib/remix-version-interop.test.js +0 -13
  120. package/dist/lib/setups/i18n/domains.test.js +0 -39
  121. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  122. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  123. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  124. package/dist/lib/setups/routes/generate.test.js +0 -296
  125. package/dist/lib/shell.test.js +0 -111
  126. package/dist/lib/shopify-config.test.js +0 -199
  127. package/dist/lib/string.test.js +0 -16
  128. package/dist/lib/virtual-routes.test.js +0 -49
  129. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  130. package/dist/lib/vite/mini-oxygen.js +0 -152
  131. package/dist/lib/vite/plugins.d.ts +0 -27
  132. package/dist/lib/vite/plugins.js +0 -139
  133. package/dist/lib/vite/shared.js +0 -10
  134. package/dist/lib/vite/utils.js +0 -55
  135. package/dist/lib/vite/worker-entry.js +0 -1518
  136. /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';
@@ -189,7 +189,7 @@ async function handleProjectLocation({
189
189
  return;
190
190
  }
191
191
  }
192
- await rmdir(directory);
192
+ await rmdir(directory, { force: true });
193
193
  }
194
194
  return {
195
195
  name: basename(location),
@@ -220,33 +220,10 @@ async function handleLanguage(projectDir, controller, flagLanguage) {
220
220
  };
221
221
  }
222
222
  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
- };
223
+ return {};
247
224
  }
248
- async function handleDependencies(projectDir, controller, shouldInstallDeps) {
249
- const detectedPackageManager = packageManagerFromUserAgent();
225
+ async function handleDependencies(projectDir, controller, packageManagerFromFlag, shouldInstallDeps) {
226
+ const detectedPackageManager = packageManagerFromFlag ?? packageManagerFromUserAgent();
250
227
  let actualPackageManager = "npm";
251
228
  if (shouldInstallDeps !== false) {
252
229
  if (detectedPackageManager === "unknown") {
@@ -403,39 +380,6 @@ async function renderProjectReady(project, {
403
380
  }
404
381
  ]
405
382
  },
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
383
  {
440
384
  title: "Next steps\n",
441
385
  body: [
@@ -445,13 +389,11 @@ async function renderProjectReady(project, {
445
389
  [
446
390
  "Run",
447
391
  {
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
392
+ command: [
393
+ project.directory === process.cwd() ? void 0 : `cd ${project.location.replace(/^\.\//, "")}`,
394
+ depsInstalled ? void 0 : `${packageManager} install`,
395
+ formatPackageManagerCommand(packageManager, "dev")
396
+ ].filter(Boolean).join(" && ")
455
397
  }
456
398
  ]
457
399
  ].filter((step) => Boolean(step))
@@ -485,5 +427,75 @@ async function projectExists(projectDir) {
485
427
  function normalizeRoutePath(routePath) {
486
428
  return routePath.replace(/(^|\.)_index$/, "").replace(/((^|\.)[^\.]+)_\./g, "$1.").replace(/\.(?!\w+\])/g, "/").replace(/\$$/g, ":catchAll").replace(/\$/g, ":").replace(/[\[\]]/g, "").replace(/:\w*Handle/i, ":handle");
487
429
  }
430
+ function generateRandomName() {
431
+ function getRandomElement(arr) {
432
+ return arr[Math.floor(Math.random() * arr.length)];
433
+ }
434
+ const geographicalFeature = getRandomElement([
435
+ "Bay",
436
+ "Bend",
437
+ "Cape",
438
+ "Cliff",
439
+ "Cove",
440
+ "Creek",
441
+ "Dale",
442
+ "Dune",
443
+ "Fjord",
444
+ "Glade",
445
+ "Gulf",
446
+ "Hill",
447
+ "Isle",
448
+ "Knoll",
449
+ "Lake",
450
+ "Loch",
451
+ "Mesa",
452
+ "Peak",
453
+ "Pond",
454
+ "Quay",
455
+ "Reef",
456
+ "Ridge",
457
+ "Rise",
458
+ "River",
459
+ "Road",
460
+ "Shore",
461
+ "Strait",
462
+ "Stream",
463
+ "Vale",
464
+ "Valley",
465
+ "View",
466
+ "Vista"
467
+ ]);
468
+ const colorNames = getRandomElement([
469
+ "Crimson",
470
+ "Azure",
471
+ "Coral",
472
+ "Fuchsia",
473
+ "Indigo",
474
+ "Ivory",
475
+ "Lavender",
476
+ "Lime",
477
+ "Magenta",
478
+ "Maroon",
479
+ "Orchid",
480
+ "Peach",
481
+ "Plum",
482
+ "Quartz",
483
+ "Salmon",
484
+ "Teal",
485
+ "Turquoise",
486
+ "Violet",
487
+ "Yellow",
488
+ "Ebony",
489
+ "Jade",
490
+ "Lilac",
491
+ "Mint",
492
+ "Onyx",
493
+ "Pearl",
494
+ "Ruby",
495
+ "Sapphire",
496
+ "Topaz"
497
+ ]);
498
+ return `${colorNames} ${geographicalFeature}`;
499
+ }
488
500
 
489
- export { LANGUAGES, commitAll, createAbortHandler, createInitialCommit, generateProjectEntries, handleCliShortcut, handleCssStrategy, handleDependencies, handleI18n, handleLanguage, handleProjectLocation, handleRouteGeneration, handleStorefrontLink, handleStorefrontSelection, renderProjectReady };
501
+ 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 };