@shopify/cli-hydrogen 8.2.0 → 8.4.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 (75) hide show
  1. package/dist/assets/hydrogen/i18n/domains.ts +4 -11
  2. package/dist/assets/hydrogen/i18n/mock-i18n-types.ts +4 -2
  3. package/dist/assets/hydrogen/i18n/subdomains.ts +4 -11
  4. package/dist/assets/hydrogen/i18n/subfolders.ts +4 -11
  5. package/dist/assets/hydrogen/starter/.graphqlrc.ts +27 -0
  6. package/dist/assets/hydrogen/starter/CHANGELOG.md +174 -0
  7. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +5 -2
  8. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +2 -2
  9. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +65 -19
  10. package/dist/assets/hydrogen/starter/app/components/PaginatedResourceSection.tsx +42 -0
  11. package/dist/assets/hydrogen/starter/app/components/SearchForm.tsx +68 -0
  12. package/dist/assets/hydrogen/starter/app/components/SearchFormPredictive.tsx +76 -0
  13. package/dist/assets/hydrogen/starter/app/components/SearchResults.tsx +164 -0
  14. package/dist/assets/hydrogen/starter/app/components/SearchResultsPredictive.tsx +322 -0
  15. package/dist/assets/hydrogen/starter/app/entry.client.tsx +10 -8
  16. package/dist/assets/hydrogen/starter/app/entry.server.tsx +1 -1
  17. package/dist/assets/hydrogen/starter/app/lib/context.ts +43 -0
  18. package/dist/assets/hydrogen/starter/app/lib/fragments.ts +53 -0
  19. package/dist/assets/hydrogen/starter/app/lib/search.ts +74 -24
  20. package/dist/assets/hydrogen/starter/app/root.tsx +4 -7
  21. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +2 -3
  22. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +5 -19
  23. package/dist/assets/hydrogen/starter/app/routes/blogs.$blogHandle._index.tsx +11 -24
  24. package/dist/assets/hydrogen/starter/app/routes/blogs._index.tsx +14 -27
  25. package/dist/assets/hydrogen/starter/app/routes/collections.$handle.tsx +12 -30
  26. package/dist/assets/hydrogen/starter/app/routes/collections._index.tsx +13 -27
  27. package/dist/assets/hydrogen/starter/app/routes/collections.all.tsx +9 -31
  28. package/dist/assets/hydrogen/starter/app/routes/search.tsx +312 -73
  29. package/dist/assets/hydrogen/starter/app/styles/reset.css +12 -2
  30. package/dist/assets/hydrogen/starter/env.d.ts +11 -30
  31. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.jpg +0 -0
  32. package/dist/assets/hydrogen/starter/guides/predictiveSearch/predictiveSearch.md +391 -0
  33. package/dist/assets/hydrogen/starter/guides/search/search.jpg +0 -0
  34. package/dist/assets/hydrogen/starter/guides/search/search.md +333 -0
  35. package/dist/assets/hydrogen/starter/package.json +4 -4
  36. package/dist/assets/hydrogen/starter/server.ts +18 -74
  37. package/dist/assets/hydrogen/starter/storefrontapi.generated.d.ts +242 -172
  38. package/dist/assets/hydrogen/virtual-routes/components/{PageLayout.jsx → Layout.jsx} +2 -2
  39. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +7 -6
  40. package/dist/commands/hydrogen/build.js +4 -2
  41. package/dist/commands/hydrogen/codegen.js +11 -3
  42. package/dist/commands/hydrogen/debug/cpu.js +2 -3
  43. package/dist/commands/hydrogen/deploy.js +8 -6
  44. package/dist/commands/hydrogen/dev.js +13 -11
  45. package/dist/commands/hydrogen/env/pull.js +5 -3
  46. package/dist/commands/hydrogen/env/push.js +2 -5
  47. package/dist/commands/hydrogen/login.js +4 -0
  48. package/dist/commands/hydrogen/logout.js +2 -0
  49. package/dist/commands/hydrogen/preview.js +11 -7
  50. package/dist/commands/hydrogen/setup.js +2 -4
  51. package/dist/hooks/init.js +6 -3
  52. package/dist/index.d.ts +5 -1
  53. package/dist/lib/auth.js +3 -63
  54. package/dist/lib/classic-compiler/dev.js +8 -4
  55. package/dist/lib/codegen.js +68 -33
  56. package/dist/lib/dev-shared.js +17 -15
  57. package/dist/lib/environment-variables.js +5 -4
  58. package/dist/lib/flags.js +7 -0
  59. package/dist/lib/import-utils.js +8 -2
  60. package/dist/lib/log.js +7 -38
  61. package/dist/lib/onboarding/common.js +2 -0
  62. package/dist/lib/onboarding/local.js +22 -7
  63. package/dist/lib/remix-config.js +8 -3
  64. package/dist/lib/setups/css/replacers.js +2 -2
  65. package/dist/lib/setups/i18n/index.js +3 -6
  66. package/dist/lib/setups/i18n/replacers.js +62 -134
  67. package/dist/lib/template-diff.js +15 -0
  68. package/dist/lib/transpile/morph/functions.js +3 -2
  69. package/dist/lib/transpile/morph/typedefs.js +4 -1
  70. package/dist/lib/vite-config.js +13 -1
  71. package/oclif.manifest.json +39 -2
  72. package/package.json +3 -3
  73. package/dist/assets/hydrogen/starter/.graphqlrc.yml +0 -12
  74. package/dist/assets/hydrogen/starter/app/components/Search.tsx +0 -514
  75. package/dist/assets/hydrogen/starter/app/routes/api.predictive-search.tsx +0 -318
@@ -70,6 +70,7 @@ function spawnCodegenProcess({
70
70
  if (/`punycode`/.test(message)) return;
71
71
  if (/\.body\[\d\]/.test(message)) return;
72
72
  if (/console\.time(End)?\(\)/.test(message)) return;
73
+ if (/─ (warning|info|success) ───/.test(message)) return;
73
74
  console.log("");
74
75
  renderWarning({ headline: message, body: details });
75
76
  });
@@ -168,53 +169,83 @@ async function generateDefaultConfig({
168
169
  }).catch(() => void 0);
169
170
  const sfapiSchema = getSchema("storefront");
170
171
  const sfapiProject = findGqlProject(sfapiSchema, gqlConfig);
171
- const defaultGlob = "*!(*.d).{ts,tsx,js,jsx}";
172
- const appDirRelative = relativePath(rootDirectory, appDirectory);
173
172
  const caapiSchema = getSchema("customer-account", { throwIfMissing: false });
174
173
  const caapiProject = caapiSchema ? findGqlProject(caapiSchema, gqlConfig) : void 0;
175
- const customerAccountAPIConfig = caapiProject?.documents ? {
176
- ["customer-accountapi.generated.d.ts"]: {
177
- preset,
178
- schema: caapiSchema,
179
- documents: caapiProject?.documents
180
- }
181
- } : void 0;
174
+ const defaultGlob = "*!(*.d).{ts,tsx,js,jsx}";
175
+ const appDirRelative = relativePath(rootDirectory, appDirectory);
176
+ const isKnownSchema = (schema) => {
177
+ const baseSfapiSchema = basename(sfapiSchema);
178
+ const baseCaapiSchema = caapiSchema && basename(caapiSchema);
179
+ return Boolean(
180
+ schema.endsWith(baseSfapiSchema) || baseCaapiSchema && schema.endsWith(baseCaapiSchema)
181
+ );
182
+ };
183
+ const otherCodegenProjects = Object.values(gqlConfig?.projects ?? {}).filter(
184
+ (project) => project.hasExtension("codegen") && (typeof project.schema !== "string" || !isKnownSchema(project.schema))
185
+ );
182
186
  return {
183
187
  filepath: "virtual:codegen",
184
188
  config: {
185
189
  overwrite: true,
186
190
  pluckConfig,
187
191
  generates: {
188
- ["storefrontapi.generated.d.ts"]: {
189
- preset,
190
- schema: sfapiSchema,
191
- documents: sfapiProject?.documents ?? [
192
- defaultGlob,
193
- // E.g. ./server.(t|j)s
194
- joinPath(appDirRelative, "**", defaultGlob)
195
- // E.g. app/routes/_index.(t|j)sx
196
- ],
197
- ...!!forceSfapiVersion && {
198
- presetConfig: { importTypes: false },
199
- schema: {
200
- [`https://hydrogen-preview.myshopify.com/api/${forceSfapiVersion.split(":")[0]}/graphql.json`]: {
201
- headers: {
202
- "content-type": "application/json",
203
- "X-Shopify-Storefront-Access-Token": forceSfapiVersion.split(":")[1] ?? "3b580e70970c4528da70c98e097c2fa0"
192
+ // If the SFAPI project in GraphQL config has a codegen extension, use it.
193
+ // Otherwise, always fallback to our default config for SFAPI.
194
+ ...getCodegenFromGraphQLConfig(sfapiProject) ?? {
195
+ ["storefrontapi.generated.d.ts"]: {
196
+ preset,
197
+ schema: sfapiSchema,
198
+ documents: sfapiProject?.documents ?? [
199
+ defaultGlob,
200
+ // E.g. ./server.(t|j)s
201
+ joinPath(appDirRelative, "**", defaultGlob)
202
+ // E.g. app/routes/_index.(t|j)sx
203
+ ],
204
+ ...!!forceSfapiVersion && {
205
+ presetConfig: { importTypes: false },
206
+ schema: {
207
+ [`https://hydrogen-preview.myshopify.com/api/${forceSfapiVersion.split(":")[0]}/graphql.json`]: {
208
+ headers: {
209
+ "content-type": "application/json",
210
+ "X-Shopify-Storefront-Access-Token": forceSfapiVersion.split(":")[1] ?? "3b580e70970c4528da70c98e097c2fa0"
211
+ }
204
212
  }
213
+ },
214
+ config: {
215
+ defaultScalarType: "string",
216
+ scalars: { JSON: "unknown" }
205
217
  }
206
- },
207
- config: {
208
- defaultScalarType: "string",
209
- scalars: { JSON: "unknown" }
210
218
  }
211
219
  }
212
220
  },
213
- ...customerAccountAPIConfig
221
+ // If the CAAPI project in GraphQL config has a codegen extension, use it.
222
+ // Otherwise, check if the user provided a list of documents to scan for queries
223
+ // before falling back to our default config for CAAPI.
224
+ ...getCodegenFromGraphQLConfig(caapiProject) ?? (caapiProject?.documents ? {
225
+ ["customer-accountapi.generated.d.ts"]: {
226
+ preset,
227
+ schema: caapiSchema,
228
+ documents: caapiProject.documents
229
+ }
230
+ } : {}),
231
+ // Use other unknown codegen projects from the GraphQL config as they are.
232
+ ...otherCodegenProjects.reduce(
233
+ (acc, project) => ({ ...acc, ...getCodegenFromGraphQLConfig(project) }),
234
+ {}
235
+ )
214
236
  }
215
237
  }
216
238
  };
217
239
  }
240
+ function getCodegenFromGraphQLConfig(project) {
241
+ if (!project?.extensions?.codegen?.generates) return;
242
+ return Object.entries(
243
+ project.extensions.codegen.generates
244
+ ).reduce((acc, [key, value]) => {
245
+ acc[key] = { ...project, ...Array.isArray(value) ? value[0] : value };
246
+ return acc;
247
+ }, {});
248
+ }
218
249
  function findGqlProject(schemaFilepath, gqlConfig) {
219
250
  if (!gqlConfig) return;
220
251
  const schemaFilename = basename(schemaFilepath);
@@ -223,13 +254,17 @@ function findGqlProject(schemaFilepath, gqlConfig) {
223
254
  );
224
255
  }
225
256
  async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
257
+ const name = Symbol.for("name");
226
258
  const hydrogenProjectsOptions = Object.values(codegenConfig.generates).filter(
227
259
  (value) => {
228
260
  const foundPreset = (Array.isArray(value) ? value[0] : value)?.preset;
229
261
  if (typeof foundPreset === "object") {
230
- const name = Symbol.for("name");
231
262
  if (name in foundPreset) {
232
- return foundPreset[name] === "hydrogen";
263
+ return (
264
+ // Preset from @shopify/hydrogen-codegen (e.g. SFAPI, CAAPI)
265
+ foundPreset[name] === "hydrogen" || // Preset from @shopify/graphql-codegen (e.g. Admin API)
266
+ foundPreset[name] === "@shopify/graphql-codegen"
267
+ );
233
268
  }
234
269
  }
235
270
  }
@@ -246,4 +281,4 @@ async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
246
281
  }
247
282
  }
248
283
 
249
- export { codegen, spawnCodegenProcess };
284
+ export { codegen, generateDefaultConfig, spawnCodegenProcess };
@@ -27,22 +27,24 @@ function notifyIssueWithTunnelAndMockShop(cliCommand) {
27
27
  ]
28
28
  });
29
29
  }
30
- function getDevConfigInBackground(root, customerAccountPushFlag) {
31
- return getLocalVariables(root).then(async ({ variables: localVariables }) => {
32
- const customerAccountPush = customerAccountPushFlag && !isMockShop(localVariables);
33
- if (customerAccountPush) {
34
- await getStorefrontId(root);
30
+ function getDevConfigInBackground(root, customerAccountPushFlag, envFile) {
31
+ return getLocalVariables(root, envFile).then(
32
+ async ({ variables: localVariables }) => {
33
+ const customerAccountPush = customerAccountPushFlag && !isMockShop(localVariables);
34
+ if (customerAccountPush) {
35
+ await getStorefrontId(root);
36
+ }
37
+ const { shop, storefront } = await getConfig(root);
38
+ const storefrontId = storefront?.id;
39
+ return {
40
+ storefrontId,
41
+ customerAccountPush,
42
+ fetchRemote: !!shop && !!storefrontId,
43
+ localVariables,
44
+ storefrontTitle: storefront?.title
45
+ };
35
46
  }
36
- const { shop, storefront } = await getConfig(root);
37
- const storefrontId = storefront?.id;
38
- return {
39
- storefrontId,
40
- customerAccountPush,
41
- fetchRemote: !!shop && !!storefrontId,
42
- localVariables,
43
- storefrontTitle: storefront?.title
44
- };
45
- });
47
+ );
46
48
  }
47
49
  const TUNNEL_DOMAIN = Object.freeze({
48
50
  ORIGINAL: ".trycloudflare.com",
@@ -18,6 +18,7 @@ async function getAllEnvironmentVariables({
18
18
  root,
19
19
  envHandle,
20
20
  envBranch,
21
+ envFile,
21
22
  fetchRemote = true,
22
23
  localVariables: inlineLocalVariables
23
24
  }) {
@@ -33,7 +34,7 @@ async function getAllEnvironmentVariables({
33
34
  }
34
35
  ) : createEmptyRemoteVars(),
35
36
  // Get local vars
36
- inlineLocalVariables ? { variables: inlineLocalVariables } : getLocalVariables(root)
37
+ inlineLocalVariables ? { variables: inlineLocalVariables } : getLocalVariables(root, envFile)
37
38
  ]);
38
39
  const remoteSecretKeys = Object.keys(remoteSecrets);
39
40
  const remotePublicKeys = Object.keys(remoteVariables);
@@ -44,7 +45,7 @@ async function getAllEnvironmentVariables({
44
45
  outputInfo(
45
46
  linesToColumns([
46
47
  ...remotePublicKeys.filter((key) => !localKeys.includes(key)).map((key) => [key, "from Oxygen"]),
47
- ...localKeys.map((key) => [key, "from local .env"]),
48
+ ...localKeys.map((key) => [key, `from local ${envFile}`]),
48
49
  // Ensure secret variables always get added to the bottom of the list
49
50
  ...remoteSecretKeys.filter((key) => !localKeys.includes(key)).map((key) => [
50
51
  colors.dim(key),
@@ -81,8 +82,8 @@ async function getRemoteVariables(root, envHandle, envBranch) {
81
82
  }
82
83
  return { remoteVariables, remoteSecrets };
83
84
  }
84
- async function getLocalVariables(root) {
85
- const dotEnvPath = resolvePath(root, ".env");
85
+ async function getLocalVariables(root, envFile) {
86
+ const dotEnvPath = resolvePath(root, envFile);
86
87
  return await fileExists(dotEnvPath).then(
87
88
  (exists) => exists ? readAndParseDotEnv(dotEnvPath) : { variables: {} }
88
89
  );
package/dist/lib/flags.js CHANGED
@@ -68,6 +68,13 @@ const commonFlags = {
68
68
  }
69
69
  })
70
70
  },
71
+ envFile: {
72
+ "env-file": Flags.string({
73
+ description: "Path to an environment file to override existing environment variables. Defaults to the '.env' located in your project path `--path`.",
74
+ required: false,
75
+ default: ".env"
76
+ })
77
+ },
71
78
  sourcemap: {
72
79
  sourcemap: Flags.boolean({
73
80
  description: "Controls whether sourcemaps are generated. Default to `true`. Deactivate `--no-sourcemaps`.",
@@ -5,7 +5,10 @@ import { joinPath, dirname } from '@shopify/cli-kit/node/path';
5
5
 
6
6
  const require2 = createRequire(import.meta.url);
7
7
  async function importVite(root) {
8
- const vitePath = require2.resolve("vite", { paths: [root] });
8
+ const vitePath = require2.resolve(
9
+ "vite",
10
+ process.env.SHOPIFY_UNIT_TEST ? void 0 : { paths: [root] }
11
+ );
9
12
  const vitePackageJson = await findUpAndReadPackageJson(vitePath);
10
13
  const viteNodeIndexFile = vitePackageJson.content.exports?.["."].import.default;
11
14
  const viteNodePath = joinPath(
@@ -15,7 +18,10 @@ async function importVite(root) {
15
18
  return import(pathToFileURL(viteNodePath).href);
16
19
  }
17
20
  function importLocal(packageName, path) {
18
- const realPath = require2.resolve(packageName, { paths: [path] });
21
+ const realPath = require2.resolve(
22
+ packageName,
23
+ process.env.SHOPIFY_UNIT_TEST ? void 0 : { paths: [path] }
24
+ );
19
25
  return import(pathToFileURL(realPath).href);
20
26
  }
21
27
 
package/dist/lib/log.js CHANGED
@@ -2,6 +2,7 @@ import { renderFatalError, renderWarning, renderInfo } from '@shopify/cli-kit/no
2
2
  import { BugError } from '@shopify/cli-kit/node/error';
3
3
  import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
4
4
  import colors from '@shopify/cli-kit/node/colors';
5
+ import ansiEscapes from 'ansi-escapes';
5
6
  import { getGraphiQLUrl } from './graphiql-url.js';
6
7
  import { importLocal } from './import-utils.js';
7
8
 
@@ -188,56 +189,24 @@ function muteDevLogs({ workerReload } = {}) {
188
189
  return processStderrWrite.apply(process.stderr, args);
189
190
  };
190
191
  }
191
- const originalWrite = process.stdout.write;
192
- function muteAuthLogs({
193
- onPressKey,
194
- onKeyTimeout
195
- }) {
196
- if (process.stdout.write === originalWrite) {
197
- const write = originalWrite.bind(process.stdout);
198
- process.stdout.write = (item, cb) => {
199
- if (typeof item !== "string") return write(item, cb);
200
- const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
201
- if (matcher([item], acc.length)) acc.push(replacer);
202
- return acc;
203
- }, []);
204
- if (replacers.length === 0) return write(item, cb);
205
- const result = replacers.reduce(
206
- (resultArgs, replacer) => resultArgs && replacer(resultArgs),
207
- [item]
208
- );
209
- if (result) return write(result[0], cb);
210
- };
211
- }
192
+ function enhanceAuthLogs(hideInitialLog = false) {
193
+ injectLogReplacer("log", warningDebouncer);
212
194
  addMessageReplacers(
213
195
  "auth",
214
196
  [
215
- ([first]) => typeof first === "string" && first.includes("Auto-open"),
197
+ ([first]) => hideInitialLog && typeof first === "string" && first.includes("To run this command,"),
216
198
  ([first]) => {
217
- const content = first.replace(" to Shopify Partners", "");
218
- const link = content.match(/(https?:\/\/.*)Log in/)?.[1];
219
- onKeyTimeout(link);
220
- if (link) return;
221
- return [content];
222
- }
223
- ],
224
- [
225
- ([first]) => typeof first === "string" && first.includes("\u{1F449}"),
226
- () => {
227
- onPressKey();
228
199
  return;
229
200
  }
230
201
  ],
231
202
  [
232
- ([first]) => typeof first === "string" && (first.includes("Shopify Partners") || first.includes("Logged in")),
203
+ ([first]) => typeof first === "string" && first.includes("Logged in."),
233
204
  () => {
205
+ process.stdout.write(ansiEscapes.eraseLines(hideInitialLog ? 5 : 6));
234
206
  return;
235
207
  }
236
208
  ]
237
209
  );
238
- return () => {
239
- process.stdout.write = originalWrite;
240
- };
241
210
  }
242
211
  function enhanceH2Logs(options) {
243
212
  injectLogReplacer("error");
@@ -383,4 +352,4 @@ function isH2Verbose() {
383
352
  return !!(process.env.DEBUG === "*" || process.env.DEBUG?.includes("h2:*"));
384
353
  }
385
354
 
386
- export { addMessageReplacers, createRemixLogger, enhanceH2Logs, isH2Verbose, muteAuthLogs, muteDevLogs, muteRemixLogs, resetAllLogs, setH2OVerbose, warnOnce };
355
+ export { addMessageReplacers, createRemixLogger, enhanceAuthLogs, enhanceH2Logs, isH2Verbose, muteDevLogs, muteRemixLogs, resetAllLogs, setH2OVerbose, warnOnce };
@@ -19,6 +19,7 @@ import { renderRoutePrompt, generateRoutes, generateProjectFile } from '../setup
19
19
  import { execAsync } from '../process.js';
20
20
  import { getStorefronts } from '../graphql/admin/link-storefront.js';
21
21
  import { isHydrogenMonorepo, getSkeletonSourceDir, getRepoNodeModules } from '../build.js';
22
+ import { enhanceAuthLogs } from '../log.js';
22
23
 
23
24
  const LANGUAGES = {
24
25
  js: "JavaScript",
@@ -110,6 +111,7 @@ async function handleCliShortcut(controller, cliCommand, flagShortcut) {
110
111
  };
111
112
  }
112
113
  async function handleStorefrontLink(controller) {
114
+ enhanceAuthLogs(true);
113
115
  const { session, config } = await login();
114
116
  renderLoginSuccess(config);
115
117
  const storefronts = await getStorefronts(session);
@@ -47,7 +47,7 @@ async function setupLocalStarterTemplate(options, controller) {
47
47
  {
48
48
  force: true,
49
49
  recursive: true,
50
- filter: (filepath) => !/^(app\/|dist\/|node_modules\/|server\.ts)/i.test(
50
+ filter: (filepath) => !/^(app\/|dist\/|node_modules\/|server\.ts|\.shopify\/)/i.test(
51
51
  relativePath(templateDir, filepath)
52
52
  )
53
53
  }
@@ -87,14 +87,16 @@ async function setupLocalStarterTemplate(options, controller) {
87
87
  # or \`${cliCommand} env pull\` to populate this file.`;
88
88
  backgroundWorkPromise = backgroundWorkPromise.then(() => {
89
89
  const promises = [
90
- // Add project name to package.json
91
90
  replaceFileContent(
92
91
  joinPath(project.directory, "package.json"),
93
92
  false,
94
- (content) => content.replace(
95
- /"name": "[^"]+"/,
96
- `"name": "${hyphenate(storefrontInfo?.title ?? project.name)}"`
97
- )
93
+ (content) => {
94
+ content = maybeInjectCliHydrogen(content);
95
+ return content.replace(
96
+ /"name": "[^"]+"/,
97
+ `"name": "${hyphenate(storefrontInfo?.title ?? project.name)}"`
98
+ );
99
+ }
98
100
  )
99
101
  ];
100
102
  let storefrontToLink;
@@ -223,7 +225,7 @@ async function setupLocalStarterTemplate(options, controller) {
223
225
  backgroundWorkPromise = backgroundWorkPromise.then(async () => {
224
226
  await setupI18n({
225
227
  rootDirectory: project.directory,
226
- serverEntryPoint: language === "ts" ? "server.ts" : "server.js"
228
+ contextCreate: language === "ts" ? "app/lib/context.ts" : "app/lib/context.js"
227
229
  }).then(
228
230
  () => options.git ? commitAll(
229
231
  project.directory,
@@ -260,5 +262,18 @@ async function setupLocalStarterTemplate(options, controller) {
260
262
  ...setupSummary
261
263
  };
262
264
  }
265
+ function maybeInjectCliHydrogen(pkgJsonContent) {
266
+ const hydrogenDep = pkgJsonContent.match(
267
+ /^\s+"@shopify\/hydrogen":\s+"[^"]+",\n/m
268
+ )?.[0];
269
+ if (hydrogenDep && /"0\.0\.0-\w+-/.test(hydrogenDep)) {
270
+ const cliHydrogenDep = hydrogenDep.replace("hydrogen", "cli-hydrogen");
271
+ pkgJsonContent = pkgJsonContent.replace(
272
+ hydrogenDep,
273
+ cliHydrogenDep + hydrogenDep
274
+ );
275
+ }
276
+ return pkgJsonContent;
277
+ }
263
278
 
264
279
  export { setupLocalStarterTemplate };
@@ -7,7 +7,7 @@ import { fileExists } from '@shopify/cli-kit/node/fs';
7
7
  import { muteRemixLogs } from './log.js';
8
8
  import { REQUIRED_REMIX_VERSION } from './remix-version-check.js';
9
9
  import { findFileWithExtension } from './file.js';
10
- import { getViteConfig } from './vite-config.js';
10
+ import { isViteProject, getViteConfig } from './vite-config.js';
11
11
  import { importLocal } from './import-utils.js';
12
12
  import { isHydrogenMonorepo, hydrogenPackagesPath } from './build.js';
13
13
 
@@ -15,6 +15,11 @@ async function hasRemixConfigFile(root) {
15
15
  const result = await findFileWithExtension(root, "remix.config");
16
16
  return !!result.filepath;
17
17
  }
18
+ async function isClassicProject(root) {
19
+ const isVite = await isViteProject(root);
20
+ if (isVite) return false;
21
+ return hasRemixConfigFile(root);
22
+ }
18
23
  const BUILD_DIR = "dist";
19
24
  const CLIENT_SUBDIR = "client";
20
25
  const WORKER_SUBDIR = "worker";
@@ -47,7 +52,7 @@ function getRawRemixConfig(root) {
47
52
  });
48
53
  }
49
54
  async function getRemixConfig(root, mode = process.env.NODE_ENV) {
50
- if (!await hasRemixConfigFile(root)) {
55
+ if (await isViteProject(root)) {
51
56
  return (await getViteConfig(root)).remixConfig;
52
57
  }
53
58
  await muteRemixLogs(root);
@@ -160,4 +165,4 @@ async function assertEntryFileExists(root, fileRelative) {
160
165
  }
161
166
  }
162
167
 
163
- export { assertOxygenChecks, getProjectPaths, getRawRemixConfig, getRemixConfig, handleRemixImportFail, hasRemixConfigFile };
168
+ export { assertOxygenChecks, getProjectPaths, getRawRemixConfig, getRemixConfig, handleRemixImportFail, hasRemixConfigFile, isClassicProject };
@@ -14,8 +14,8 @@ async function replaceRootLinks(appDirectory, formatConfig, importer) {
14
14
  }
15
15
  const astGrep = await importLangAstGrep(astType);
16
16
  const root = astGrep.parse(content).root();
17
- const importNodes = root.findAll({ rule: { kind: "import_statement" } });
18
- const lastImportNode = importNodes.findLast((node) => node.text().includes(".css")) || importNodes.pop();
17
+ const importNodes = root.findAll({ rule: { kind: "import_statement" } }).reverse();
18
+ const lastImportNode = importNodes.find((node) => node.text().includes(".css")) || importNodes.shift();
19
19
  const linksReturnNode = root.find({
20
20
  utils: {
21
21
  "has-links-id": {
@@ -1,7 +1,7 @@
1
1
  import { renderSelectPrompt } from '@shopify/cli-kit/node/ui';
2
- import { fileExists, readFile } from '@shopify/cli-kit/node/fs';
2
+ import { fileExists } from '@shopify/cli-kit/node/fs';
3
3
  import { getCodeFormatOptions } from '../../format-code.js';
4
- import { replaceServerI18n, replaceRemixEnv } from './replacers.js';
4
+ import { replaceContextI18n } from './replacers.js';
5
5
  import { getAssetsDir } from '../../build.js';
6
6
 
7
7
  const SETUP_I18N_STRATEGIES = [
@@ -20,11 +20,8 @@ async function setupI18nStrategy(strategy, options) {
20
20
  if (!await fileExists(templatePath)) {
21
21
  throw new Error("Unknown strategy");
22
22
  }
23
- const template = await readFile(templatePath);
24
23
  const formatConfig = await getCodeFormatOptions(options.rootDirectory);
25
- const isJs = options.serverEntryPoint?.endsWith(".js") ?? false;
26
- await replaceServerI18n(options, formatConfig, template, isJs);
27
- await replaceRemixEnv(options, formatConfig, template);
24
+ await replaceContextI18n(options, formatConfig, templatePath);
28
25
  }
29
26
  async function renderI18nPrompt(options) {
30
27
  const i18nStrategies = Object.entries({