@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
@@ -0,0 +1,24 @@
1
+ const packageManagers = [
2
+ {
3
+ name: "npm",
4
+ lockfile: "package-lock.json",
5
+ installCommand: "npm ci"
6
+ },
7
+ {
8
+ name: "yarn",
9
+ lockfile: "yarn.lock",
10
+ installCommand: "yarn install --frozen-lockfile"
11
+ },
12
+ {
13
+ name: "pnpm",
14
+ lockfile: "pnpm-lock.yaml",
15
+ installCommand: "pnpm install --frozen-lockfile"
16
+ },
17
+ {
18
+ name: "bun",
19
+ lockfile: "bun.lockb",
20
+ installCommand: "bun install --frozen-lockfile"
21
+ }
22
+ ];
23
+
24
+ export { packageManagers };
@@ -7,7 +7,13 @@ import { outputWarn } from '@shopify/cli-kit/node/output';
7
7
  import { fileExists } from '@shopify/cli-kit/node/fs';
8
8
  import { muteRemixLogs } from './log.js';
9
9
  import { getRequiredRemixVersion } from './remix-version-check.js';
10
+ import { findFileWithExtension } from './file.js';
11
+ import { getViteConfig } from './vite-config.js';
10
12
 
13
+ async function hasRemixConfigFile(root) {
14
+ const result = await findFileWithExtension(root, "remix.config");
15
+ return !!result.filepath;
16
+ }
11
17
  const BUILD_DIR = "dist";
12
18
  const CLIENT_SUBDIR = "client";
13
19
  const WORKER_SUBDIR = "worker";
@@ -33,7 +39,17 @@ function handleRemixImportFail() {
33
39
  `Please make sure you have \`@remix-run/dev@${remixVersion}\` installed and all the other Remix packages have the same version.`
34
40
  );
35
41
  }
42
+ function getRawRemixConfig(root) {
43
+ return findFileWithExtension(root, "remix.config").then(({ filepath }) => {
44
+ if (!filepath)
45
+ throw new AbortError("No remix.config.js file found.");
46
+ return createRequire(import.meta.url)(filepath);
47
+ });
48
+ }
36
49
  async function getRemixConfig(root, mode = process.env.NODE_ENV) {
50
+ if (!await hasRemixConfigFile(root)) {
51
+ return (await getViteConfig(root)).remixConfig;
52
+ }
37
53
  await muteRemixLogs();
38
54
  const { readConfig } = await import('@remix-run/dev/dist/config.js').catch(
39
55
  handleRemixImportFail
@@ -144,4 +160,4 @@ async function assertEntryFileExists(root, fileRelative) {
144
160
  }
145
161
  }
146
162
 
147
- export { assertOxygenChecks, getProjectPaths, getRemixConfig, handleRemixImportFail };
163
+ export { assertOxygenChecks, getProjectPaths, getRawRemixConfig, getRemixConfig, handleRemixImportFail, hasRemixConfigFile };
@@ -9,7 +9,12 @@ let ResponseConstructor;
9
9
  function setConstructors(constructors) {
10
10
  ResponseConstructor = constructors.Response;
11
11
  }
12
- const DEV_ROUTES = /* @__PURE__ */ new Set(["/graphiql", "/subrequest-profiler"]);
12
+ const DEV_ROUTES = /* @__PURE__ */ new Set([
13
+ "/graphiql",
14
+ "/graphiql/customer-account.schema.json",
15
+ "/subrequest-profiler",
16
+ "/debug-network-server"
17
+ ]);
13
18
  const EVENT_MAP = {
14
19
  request: "Request",
15
20
  subrequest: "Sub request"
@@ -1,5 +1,5 @@
1
1
  import { AbortError } from '@shopify/cli-kit/node/error';
2
- import { joinPath } from '@shopify/cli-kit/node/path';
2
+ import { joinPath, resolvePath } from '@shopify/cli-kit/node/path';
3
3
  import { fileExists } from '@shopify/cli-kit/node/fs';
4
4
  import { replaceFileContent, findFileWithExtension } from '../../file.js';
5
5
  import { importLangAstGrep } from '../../ast.js';
@@ -147,9 +147,12 @@ ${localeExtractFn}
147
147
  });
148
148
  }
149
149
  async function replaceRemixEnv({ rootDirectory }, formatConfig, localeExtractImpl) {
150
- const remixEnvPath = joinPath(rootDirectory, "remix.env.d.ts");
151
- if (!await fileExists(remixEnvPath)) {
152
- return;
150
+ let envPath = joinPath(rootDirectory, "env.d.ts");
151
+ if (!await fileExists(envPath)) {
152
+ envPath = "remix.env.d.ts";
153
+ if (!await fileExists(envPath)) {
154
+ return;
155
+ }
153
156
  }
154
157
  const i18nType = localeExtractImpl.match(
155
158
  /^(export )?(type \w+ =\s+\{.*?\};)\n/ms
@@ -158,7 +161,7 @@ async function replaceRemixEnv({ rootDirectory }, formatConfig, localeExtractImp
158
161
  if (!i18nTypeName) {
159
162
  return;
160
163
  }
161
- await replaceFileContent(remixEnvPath, formatConfig, async (content) => {
164
+ await replaceFileContent(envPath, formatConfig, async (content) => {
162
165
  if (content.includes(`Storefront<`))
163
166
  return;
164
167
  const astGrep = await importLangAstGrep("ts");
@@ -237,7 +240,7 @@ async function findEntryFile({
237
240
  serverEntryPoint = "server"
238
241
  }) {
239
242
  const match = serverEntryPoint.match(/\.([jt]sx?)$/)?.[1];
240
- const { filepath, astType } = match ? { filepath: joinPath(rootDirectory, serverEntryPoint), astType: match } : await findFileWithExtension(rootDirectory, serverEntryPoint);
243
+ const { filepath, astType } = match ? { filepath: resolvePath(rootDirectory, serverEntryPoint), astType: match } : await findFileWithExtension(rootDirectory, serverEntryPoint);
241
244
  if (!filepath || !astType) {
242
245
  throw new AbortError(
243
246
  `Could not find a server entry point at ${serverEntryPoint}`
@@ -199,6 +199,7 @@ async function findRouteDependencies(routeFilePath, appDirectory) {
199
199
  for (let [, , match] of importMatches) {
200
200
  if (!match || !/^(\.|~)/.test(match))
201
201
  continue;
202
+ match = match.replace(/\?[a-z.]+$/, "");
202
203
  match = match.replace(
203
204
  "~",
204
205
  // import from '~/components/...'
package/dist/lib/shell.js CHANGED
@@ -113,7 +113,8 @@ async function createPlatformShortcut() {
113
113
  }
114
114
  const BASH_ZSH_COMMAND = `
115
115
  # Shopify Hydrogen alias to local projects
116
- alias ${ALIAS_NAME}='$(npm prefix -s)/node_modules/.bin/shopify hydrogen'`;
116
+ alias ${ALIAS_NAME}='$(npm prefix -s)/node_modules/.bin/shopify hydrogen'
117
+ `;
117
118
  const FISH_FUNCTION = `
118
119
  function ${ALIAS_NAME} --wraps='shopify hydrogen' --description 'Shortcut for the Hydrogen CLI'
119
120
  set npmPrefix (npm prefix -s)
@@ -87,5 +87,23 @@ async function ensureShopifyGitIgnore(root) {
87
87
  return false;
88
88
  }
89
89
  }
90
+ async function setCustomerAccountConfig(root, customerAccountConfig) {
91
+ try {
92
+ const filePath = resolvePath(root, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
93
+ const existingConfig = JSON.parse(await readFile(filePath));
94
+ const config = {
95
+ ...existingConfig,
96
+ storefront: {
97
+ ...existingConfig.storefront,
98
+ customerAccountConfig
99
+ }
100
+ };
101
+ await writeFile(filePath, JSON.stringify(config));
102
+ await ensureShopifyGitIgnore(root);
103
+ return config;
104
+ } catch {
105
+ throw new AbortError("Project configuration could not be found.");
106
+ }
107
+ }
90
108
 
91
- export { SHOPIFY_DIR, SHOPIFY_DIR_PROJECT, ensureShopifyGitIgnore, getConfig, resetConfig, setStorefront, setUserAccount, unsetStorefront };
109
+ export { SHOPIFY_DIR, SHOPIFY_DIR_PROJECT, ensureShopifyGitIgnore, getConfig, resetConfig, setCustomerAccountConfig, setStorefront, setUserAccount, unsetStorefront };
@@ -1,7 +1,7 @@
1
1
  import { rmdirSync } from 'node:fs';
2
2
  import { temporaryDirectory } from 'tempy';
3
- import { createSymlink, copy, remove } from 'fs-extra/esm';
4
- import { copyFile, removeFile } from '@shopify/cli-kit/node/fs';
3
+ import { createSymlink, copy } from 'fs-extra/esm';
4
+ import { copyFile, removeFile, fileExists } from '@shopify/cli-kit/node/fs';
5
5
  import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
6
6
  import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
7
7
  import colors from '@shopify/cli-kit/node/colors';
@@ -66,9 +66,10 @@ ${colors.dim(
66
66
  return targetDirectory;
67
67
  }
68
68
  async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir = getStarterDir()) {
69
- const pkgJson = await readAndParsePackageJson(
69
+ const diffPkgJson = await readAndParsePackageJson(
70
70
  joinPath(diffDirectory, "package.json")
71
71
  );
72
+ const diffOptions = diffPkgJson["h2:diff"] ?? {};
72
73
  const createFilter = (re, skipFiles) => (filepath) => {
73
74
  const filename = relativePath(templateDir, filepath);
74
75
  return !re.test(filename) && !skipFiles?.includes(filename);
@@ -76,7 +77,7 @@ async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir = g
76
77
  await copy(templateDir, targetDirectory, {
77
78
  filter: createFilter(
78
79
  /(^|\/|\\)(dist|node_modules|\.cache|.turbo|CHANGELOG\.md)(\/|\\|$)/i,
79
- pkgJson["h2:diff"]?.["skip-files"] || []
80
+ diffOptions.skipFiles || []
80
81
  )
81
82
  });
82
83
  await copy(diffDirectory, targetDirectory, {
@@ -86,29 +87,49 @@ async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir = g
86
87
  });
87
88
  await mergePackageJson(diffDirectory, targetDirectory, {
88
89
  ignoredKeys: ["h2:diff"],
89
- onResult: (pkgJson2) => {
90
+ onResult: (pkgJson) => {
90
91
  for (const key of ["build", "dev"]) {
91
- const scriptLine = pkgJson2.scripts?.[key];
92
- if (pkgJson2.scripts?.[key] && typeof scriptLine === "string") {
93
- pkgJson2.scripts[key] = scriptLine.replace(/\s+--diff/, "");
92
+ const scriptLine = pkgJson.scripts?.[key];
93
+ if (pkgJson.scripts?.[key] && typeof scriptLine === "string") {
94
+ pkgJson.scripts[key] = scriptLine.replace(/\s+--diff/, "");
94
95
  }
95
96
  }
96
- return pkgJson2;
97
+ if (diffOptions.skipDependencies && pkgJson.dependencies) {
98
+ for (const dep of diffOptions.skipDependencies) {
99
+ delete pkgJson.dependencies[dep];
100
+ }
101
+ }
102
+ if (diffOptions.skipDevDependencies && pkgJson.devDependencies) {
103
+ for (const devDep of diffOptions.skipDevDependencies) {
104
+ delete pkgJson.devDependencies[devDep];
105
+ }
106
+ }
107
+ return pkgJson;
97
108
  }
98
109
  });
99
110
  }
100
- async function copyDiffBuild(targetDirectory, diffDirectory) {
101
- const targetDist = joinPath(diffDirectory, "dist");
102
- await remove(targetDist);
111
+ async function copyDiffBuild(generatedDirectory, diffDirectory) {
112
+ const target = joinPath(diffDirectory, "dist");
113
+ await removeFile(target);
103
114
  await Promise.all([
104
- copy(joinPath(targetDirectory, "dist"), targetDist, {
115
+ copy(joinPath(generatedDirectory, "dist"), target, {
105
116
  overwrite: true
106
117
  }),
107
118
  copyFile(
108
- joinPath(targetDirectory, ".env"),
119
+ joinPath(generatedDirectory, ".env"),
109
120
  joinPath(diffDirectory, ".env")
110
121
  )
111
122
  ]);
112
123
  }
124
+ async function copyShopifyConfig(generatedDirectory, diffDirectory) {
125
+ const source = joinPath(generatedDirectory, ".shopify");
126
+ if (!await fileExists(source))
127
+ return;
128
+ const target = joinPath(diffDirectory, ".shopify");
129
+ await removeFile(target);
130
+ await copy(joinPath(generatedDirectory, ".shopify"), target, {
131
+ overwrite: true
132
+ });
133
+ }
113
134
 
114
- export { applyTemplateDiff, copyDiffBuild, prepareDiffDirectory };
135
+ export { applyTemplateDiff, copyDiffBuild, copyShopifyConfig, prepareDiffDirectory };
@@ -4,9 +4,12 @@ import { pipeline } from 'stream/promises';
4
4
  import gunzipMaybe from 'gunzip-maybe';
5
5
  import { extract } from 'tar-fs';
6
6
  import { fetch } from '@shopify/cli-kit/node/http';
7
- import { fileExists, mkdir } from '@shopify/cli-kit/node/fs';
7
+ import { parseGitHubRepositoryURL } from '@shopify/cli-kit/node/github';
8
+ import { fileExists, mkdir, rmdir } from '@shopify/cli-kit/node/fs';
8
9
  import { AbortError } from '@shopify/cli-kit/node/error';
9
10
  import { getSkeletonSourceDir } from './build.js';
11
+ import { joinPath } from '@shopify/cli-kit/node/path';
12
+ import { downloadGitRepository } from '@shopify/cli-kit/node/git';
10
13
 
11
14
  const REPO_RELEASES_URL = `https://api.github.com/repos/shopify/hydrogen/releases/latest`;
12
15
  const getTryMessage = (status) => status === 403 ? `If you are using a VPN, WARP, or similar service, consider disabling it momentarily.` : void 0;
@@ -25,7 +28,7 @@ async function getLatestReleaseDownloadUrl(signal) {
25
28
  url: release.tarball_url
26
29
  };
27
30
  }
28
- async function downloadTarball(url, storageDir, signal) {
31
+ async function downloadMonorepoTarball(url, storageDir, signal) {
29
32
  const response = await fetch(url, { signal });
30
33
  if (!response.ok || response.status >= 400) {
31
34
  throw new AbortError(
@@ -48,7 +51,7 @@ async function downloadTarball(url, storageDir, signal) {
48
51
  })
49
52
  );
50
53
  }
51
- async function getLatestTemplates({
54
+ async function downloadMonorepoTemplates({
52
55
  signal
53
56
  } = {}) {
54
57
  if (process.env.LOCAL_DEV) {
@@ -69,7 +72,7 @@ async function getLatestTemplates({
69
72
  }
70
73
  const templateStorageVersionPath = path.join(templateStoragePath, version);
71
74
  if (!await fileExists(templateStorageVersionPath)) {
72
- await downloadTarball(url, templateStorageVersionPath, signal);
75
+ await downloadMonorepoTarball(url, templateStorageVersionPath, signal);
73
76
  }
74
77
  return {
75
78
  version,
@@ -87,5 +90,32 @@ Please check your internet connection and the following error:
87
90
  );
88
91
  }
89
92
  }
93
+ async function downloadExternalRepo(appTemplate, signal) {
94
+ const parsed = parseGitHubRepositoryURL(appTemplate);
95
+ if (parsed.isErr()) {
96
+ throw new AbortError(parsed.error.message);
97
+ }
98
+ const externalTemplates = fileURLToPath(
99
+ new URL("../external-templates", import.meta.url)
100
+ );
101
+ if (!await fileExists(externalTemplates)) {
102
+ await mkdir(externalTemplates);
103
+ }
104
+ const result = parsed.value;
105
+ const templateDir = joinPath(
106
+ externalTemplates,
107
+ result.full.replace(/^https?:\/\//, "").replace(/[^\w]+/, "_")
108
+ );
109
+ if (await fileExists(templateDir)) {
110
+ await rmdir(templateDir, { force: true });
111
+ }
112
+ await downloadGitRepository({
113
+ repoUrl: result.full,
114
+ destination: templateDir,
115
+ shallow: true
116
+ });
117
+ await rmdir(joinPath(templateDir, ".git"), { force: true });
118
+ return { templateDir };
119
+ }
90
120
 
91
- export { getLatestTemplates };
121
+ export { downloadExternalRepo, downloadMonorepoTemplates };
@@ -26,13 +26,16 @@ function generateTypeDefs(sourceFile, code) {
26
26
  }
27
27
  }
28
28
  typedefs.push("");
29
+ const knownGenerics = {
30
+ MetaFunction: "T"
31
+ };
29
32
  typedefsFromImports.forEach((typeElements, moduleSpecifier) => {
30
33
  for (const typeElement of typeElements) {
31
34
  if (typeElement === "SerializeFrom")
32
35
  continue;
33
- const hasGeneric = typeElement === "MetaFunction";
36
+ const hasGeneric = !!knownGenerics[typeElement];
34
37
  typedefs.push(
35
- `/** ${hasGeneric ? "@template T " : ""}@typedef {import('${moduleSpecifier}').${typeElement}${hasGeneric ? "<T>" : ""}} ${typeElement} */`
38
+ `/** ${hasGeneric ? `@template ${knownGenerics[typeElement]} ` : ""}@typedef {import('${moduleSpecifier}').${typeElement}${hasGeneric ? `<${knownGenerics[typeElement]}>` : ""}} ${typeElement} */`
36
39
  );
37
40
  }
38
41
  });
@@ -3,6 +3,7 @@ import { outputDebug } from '@shopify/cli-kit/node/output';
3
3
  import { joinPath } from '@shopify/cli-kit/node/path';
4
4
  import { getCodeFormatOptions, formatCode } from '../format-code.js';
5
5
  import { transpileFile } from './file.js';
6
+ import { findFileWithExtension } from '../file.js';
6
7
 
7
8
  const DEFAULT_JS_CONFIG = {
8
9
  checkJs: false,
@@ -120,10 +121,13 @@ async function transpileProject(projectDir, keepTypes = true) {
120
121
  );
121
122
  }
122
123
  try {
123
- const eslintrcPath = joinPath(projectDir, ".eslintrc.js");
124
- let eslintrc = await readFile(eslintrcPath);
125
- eslintrc = eslintrc.replace(/\/\*\*[\s*]+@type.+\s+\*\/\s?/gim, "").replace(/\s*,?\s*['"`]plugin:hydrogen\/typescript['"`]/gim, "").replace(/\s+['"`]@typescript-eslint\/.+,/gim, "");
126
- await writeFile(eslintrcPath, eslintrc);
124
+ const { filepath = joinPath(projectDir, ".eslintrc.cjs") } = await findFileWithExtension(projectDir, ".eslintrc", ["cjs", "js"]);
125
+ let eslintrc = await readFile(filepath);
126
+ if (!keepTypes) {
127
+ eslintrc = eslintrc.replace(/\/\*\*[\s*]+@type.+\s+\*\/\s?/gim, "");
128
+ }
129
+ eslintrc = eslintrc.replace(/\s*,?\s*['"`]plugin:hydrogen\/typescript['"`]/gim, "").replace(/\s+['"`]@typescript-eslint\/.+,/gim, "");
130
+ await writeFile(filepath, await formatCode(eslintrc, formatConfig));
127
131
  } catch (error) {
128
132
  outputDebug(
129
133
  "Could not remove TS rules from .eslintrc:\n" + error.stack
@@ -0,0 +1,44 @@
1
+ import { fanoutHooks } from '@shopify/cli-kit/node/plugins';
2
+ import { outputDebug } from '@shopify/cli-kit/node/output';
3
+ import { BugError, AbortError } from '@shopify/cli-kit/node/error';
4
+
5
+ async function startTunnelPlugin(config, port, provider) {
6
+ const hooks = await fanoutHooks(config, "tunnel_start", { port, provider });
7
+ const results = Object.values(hooks).filter(
8
+ (tunnelResponse) => !tunnelResponse?.isErr() || tunnelResponse.error.type !== "invalid-provider"
9
+ );
10
+ const first = results[0];
11
+ if (!first)
12
+ throw new BugError(`We couldn't find the ${provider} tunnel plugin`);
13
+ if (first.isErr())
14
+ throw new AbortError(
15
+ `${provider} failed to start the tunnel.
16
+ ${first.error.message}`
17
+ );
18
+ return first.value;
19
+ }
20
+ async function pollTunnelURL(tunnelClient) {
21
+ return new Promise((resolve, reject) => {
22
+ let retries = 0;
23
+ const pollTunnelStatus = async () => {
24
+ const result = tunnelClient.getTunnelStatus();
25
+ outputDebug(
26
+ `Polling tunnel status for ${tunnelClient.provider} (attempt ${retries}): ${result.status}`
27
+ );
28
+ if (result.status === "error")
29
+ return reject(new AbortError(result.message, result.tryMessage));
30
+ if (result.status === "connected") {
31
+ resolve(result.url);
32
+ } else {
33
+ retries += 1;
34
+ startPolling();
35
+ }
36
+ };
37
+ const startPolling = () => {
38
+ setTimeout(pollTunnelStatus, 500);
39
+ };
40
+ pollTunnelStatus();
41
+ });
42
+ }
43
+
44
+ export { pollTunnelURL, startTunnelPlugin };
@@ -5,8 +5,8 @@ import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
5
5
  const VIRTUAL_ROUTES_DIR = "virtual-routes/routes";
6
6
  const VIRTUAL_ROOT = "virtual-routes/virtual-root";
7
7
  async function addVirtualRoutes(config) {
8
+ const distPath = process.env.SHOPIFY_UNIT_TEST ? fileURLToPath(new URL("../../../hydrogen/src/vite", import.meta.url)) : fileURLToPath(new URL("..", import.meta.url));
8
9
  const userRouteList = Object.values(config.routes);
9
- const distPath = fileURLToPath(new URL("..", import.meta.url));
10
10
  const virtualRoutesPath = joinPath(distPath, VIRTUAL_ROUTES_DIR);
11
11
  for (const absoluteFilePath of await glob(
12
12
  joinPath(virtualRoutesPath, "**", "*")
@@ -1,6 +1,11 @@
1
- import { joinPath } from '@shopify/cli-kit/node/path';
1
+ import { joinPath, resolvePath, dirname, basename } from '@shopify/cli-kit/node/path';
2
+ import { findFileWithExtension } from './file.js';
2
3
 
3
- async function getViteConfig(root) {
4
+ async function hasViteConfig(root) {
5
+ const result = await findFileWithExtension(root, "vite.config");
6
+ return !!result.filepath;
7
+ }
8
+ async function getViteConfig(root, ssrEntryFlag) {
4
9
  const vite = await import('vite');
5
10
  const command = "build";
6
11
  const mode = process.env.NODE_ENV || "production";
@@ -18,13 +23,19 @@ async function getViteConfig(root) {
18
23
  mode,
19
24
  mode
20
25
  );
26
+ const { appDirectory, serverBuildFile, routes } = getRemixConfigFromVite(resolvedViteConfig);
21
27
  const serverOutDir = resolvedViteConfig.build.outDir;
22
28
  const clientOutDir = serverOutDir.replace(/server$/, "client");
23
29
  const rollupOutput = resolvedViteConfig.build.rollupOptions.output;
24
30
  const { entryFileNames } = (Array.isArray(rollupOutput) ? rollupOutput[0] : rollupOutput) ?? {};
25
31
  const serverOutFile = joinPath(
26
32
  serverOutDir,
27
- typeof entryFileNames === "string" ? entryFileNames : "index.js"
33
+ typeof entryFileNames === "string" ? entryFileNames : serverBuildFile ?? "index.js"
34
+ );
35
+ const ssrEntry = ssrEntryFlag ?? resolvedViteConfig.build.ssr;
36
+ const resolvedSsrEntry = resolvePath(
37
+ resolvedViteConfig.root,
38
+ typeof ssrEntry === "string" ? ssrEntry : "server"
28
39
  );
29
40
  return {
30
41
  clientOutDir,
@@ -32,14 +43,33 @@ async function getViteConfig(root) {
32
43
  serverOutFile,
33
44
  resolvedViteConfig,
34
45
  userViteConfig: maybeConfig.config,
35
- remixConfig: getRemixConfigFromVite(resolvedViteConfig)
46
+ remixConfig: {
47
+ routes: routes ?? {},
48
+ appDirectory: appDirectory ?? joinPath(resolvedViteConfig.root, "app"),
49
+ rootDirectory: resolvedViteConfig.root,
50
+ serverEntryPoint: (await findFileWithExtension(
51
+ dirname(resolvedSsrEntry),
52
+ basename(resolvedSsrEntry)
53
+ )).filepath || resolvedSsrEntry
54
+ }
36
55
  };
37
56
  }
38
57
  function getRemixConfigFromVite(viteConfig) {
39
- const { remixConfig } = viteConfig.__remixPluginContext || {
40
- remixConfig: { appDirectory: joinPath(viteConfig.root, "app") }
41
- };
42
- return remixConfig;
58
+ const { remixConfig } = findHydrogenPlugin(viteConfig)?.api?.getPluginOptions() ?? {};
59
+ return remixConfig ? {
60
+ appDirectory: remixConfig.appDirectory,
61
+ serverBuildFile: remixConfig.serverBuildFile,
62
+ routes: remixConfig.routes
63
+ } : {};
64
+ }
65
+ function findPlugin(config, name) {
66
+ return config.plugins.find((plugin) => plugin.name === name);
67
+ }
68
+ function findHydrogenPlugin(config) {
69
+ return findPlugin(config, "hydrogen:main");
70
+ }
71
+ function findOxygenPlugin(config) {
72
+ return findPlugin(config, "oxygen:main");
43
73
  }
44
74
 
45
- export { getViteConfig };
75
+ export { findHydrogenPlugin, findOxygenPlugin, getViteConfig, hasViteConfig };