@shopify/cli-hydrogen 8.1.0 → 8.2.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 (101) hide show
  1. package/dist/assets/hydrogen/starter/CHANGELOG.md +166 -0
  2. package/dist/assets/hydrogen/starter/app/components/AddToCartButton.tsx +37 -0
  3. package/dist/assets/hydrogen/starter/app/components/CartLineItem.tsx +150 -0
  4. package/dist/assets/hydrogen/starter/app/components/CartMain.tsx +68 -0
  5. package/dist/assets/hydrogen/starter/app/components/CartSummary.tsx +101 -0
  6. package/dist/assets/hydrogen/starter/app/components/Header.tsx +3 -3
  7. package/dist/assets/hydrogen/starter/app/components/PageLayout.tsx +2 -2
  8. package/dist/assets/hydrogen/starter/app/components/ProductForm.tsx +80 -0
  9. package/dist/assets/hydrogen/starter/app/components/ProductImage.tsx +23 -0
  10. package/dist/assets/hydrogen/starter/app/components/ProductPrice.tsx +27 -0
  11. package/dist/assets/hydrogen/starter/app/lib/session.ts +5 -0
  12. package/dist/assets/hydrogen/starter/app/root.tsx +23 -36
  13. package/dist/assets/hydrogen/starter/app/routes/account.$.tsx +1 -5
  14. package/dist/assets/hydrogen/starter/app/routes/account.addresses.tsx +12 -70
  15. package/dist/assets/hydrogen/starter/app/routes/account.orders.$id.tsx +7 -14
  16. package/dist/assets/hydrogen/starter/app/routes/account.orders._index.tsx +1 -8
  17. package/dist/assets/hydrogen/starter/app/routes/account.profile.tsx +5 -22
  18. package/dist/assets/hydrogen/starter/app/routes/account.tsx +0 -1
  19. package/dist/assets/hydrogen/starter/app/routes/cart.tsx +1 -3
  20. package/dist/assets/hydrogen/starter/app/routes/products.$handle.tsx +51 -232
  21. package/dist/assets/hydrogen/starter/package.json +10 -11
  22. package/dist/assets/hydrogen/starter/server.ts +4 -0
  23. package/dist/assets/hydrogen/tailwind/package.json +1 -6
  24. package/dist/assets/hydrogen/tailwind/tailwind.css +6 -3
  25. package/dist/assets/hydrogen/vanilla-extract/package.json +2 -3
  26. package/dist/assets/hydrogen/virtual-routes/components/{Layout.jsx → PageLayout.jsx} +2 -2
  27. package/dist/assets/hydrogen/virtual-routes/components/RequestDetails.jsx +1 -2
  28. package/dist/assets/hydrogen/virtual-routes/components/RequestTable.jsx +1 -2
  29. package/dist/assets/hydrogen/virtual-routes/routes/index.jsx +1 -2
  30. package/dist/assets/hydrogen/virtual-routes/virtual-root.jsx +8 -30
  31. package/dist/commands/hydrogen/build.js +33 -10
  32. package/dist/commands/hydrogen/customer-account/push.js +3 -6
  33. package/dist/commands/hydrogen/debug/cpu.js +3 -3
  34. package/dist/commands/hydrogen/deploy.js +14 -3
  35. package/dist/commands/hydrogen/dev.js +3 -6
  36. package/dist/commands/hydrogen/env/list.js +1 -2
  37. package/dist/commands/hydrogen/env/pull.js +2 -4
  38. package/dist/commands/hydrogen/env/push.js +6 -12
  39. package/dist/commands/hydrogen/init.d.ts +18 -15
  40. package/dist/commands/hydrogen/init.js +12 -24
  41. package/dist/commands/hydrogen/link.js +1 -2
  42. package/dist/commands/hydrogen/preview.js +4 -6
  43. package/dist/commands/hydrogen/setup/css.js +29 -12
  44. package/dist/commands/hydrogen/setup/vite.js +3 -6
  45. package/dist/commands/hydrogen/setup.js +8 -7
  46. package/dist/commands/hydrogen/upgrade.js +16 -32
  47. package/dist/hooks/init.js +50 -6
  48. package/dist/index.d.ts +46 -46
  49. package/dist/lib/auth.js +1 -2
  50. package/dist/lib/build.js +1 -2
  51. package/dist/lib/bundle/analyzer.js +39 -24
  52. package/dist/lib/bundle/vite-plugin.js +161 -0
  53. package/dist/lib/check-cli-version.js +61 -0
  54. package/dist/lib/check-lockfile.js +2 -2
  55. package/dist/lib/classic-compiler/build.js +3 -3
  56. package/dist/lib/classic-compiler/dev.js +5 -10
  57. package/dist/lib/codegen.js +8 -16
  58. package/dist/lib/defer.js +2 -4
  59. package/dist/lib/environment-variables.js +2 -4
  60. package/dist/lib/file.js +15 -7
  61. package/dist/lib/flags.js +10 -0
  62. package/dist/lib/get-oxygen-deployment-data.js +1 -2
  63. package/dist/lib/graphiql-url.js +1 -2
  64. package/dist/lib/import-utils.js +3 -2
  65. package/dist/lib/log.js +11 -22
  66. package/dist/lib/mini-oxygen/common.js +1 -2
  67. package/dist/lib/mini-oxygen/node.js +1 -2
  68. package/dist/lib/missing-routes.js +1 -2
  69. package/dist/lib/onboarding/common.js +60 -15
  70. package/dist/lib/onboarding/local.js +14 -13
  71. package/dist/lib/onboarding/remote.js +16 -9
  72. package/dist/lib/onboarding/setup-template.mocks.js +6 -3
  73. package/dist/lib/remix-config.js +2 -4
  74. package/dist/lib/remix-version-check.js +1 -2
  75. package/dist/lib/request-events.js +3 -6
  76. package/dist/lib/setups/css/assets.js +1 -1
  77. package/dist/lib/setups/css/index.js +17 -10
  78. package/dist/lib/setups/css/replacers.js +74 -76
  79. package/dist/lib/setups/css/tailwind.js +16 -20
  80. package/dist/lib/setups/css/vanilla-extract.js +8 -5
  81. package/dist/lib/setups/i18n/replacers.js +1 -2
  82. package/dist/lib/setups/routes/generate.js +18 -19
  83. package/dist/lib/shell.js +5 -10
  84. package/dist/lib/template-diff.js +83 -104
  85. package/dist/lib/template-downloader.js +2 -2
  86. package/dist/lib/transpile/morph/functions.js +3 -6
  87. package/dist/lib/transpile/morph/index.js +2 -4
  88. package/dist/lib/transpile/morph/typedefs.js +3 -6
  89. package/dist/lib/transpile/morph/utils.js +2 -4
  90. package/dist/lib/transpile/project.js +4 -3
  91. package/oclif.manifest.json +51 -4
  92. package/package.json +8 -12
  93. package/dist/assets/hydrogen/css-modules/package.json +0 -6
  94. package/dist/assets/hydrogen/postcss/package.json +0 -10
  95. package/dist/assets/hydrogen/postcss/postcss.config.js +0 -8
  96. package/dist/assets/hydrogen/starter/app/components/Cart.tsx +0 -364
  97. package/dist/assets/hydrogen/tailwind/postcss.config.js +0 -10
  98. package/dist/assets/hydrogen/tailwind/tailwind.config.js +0 -8
  99. package/dist/lib/check-version.js +0 -75
  100. package/dist/lib/setups/css/css-modules.js +0 -23
  101. package/dist/lib/setups/css/postcss.js +0 -31
@@ -2,73 +2,6 @@ import { AbortError } from '@shopify/cli-kit/node/error';
2
2
  import { findFileWithExtension, replaceFileContent } from '../../file.js';
3
3
  import { importLangAstGrep } from '../../ast.js';
4
4
 
5
- async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
6
- const { filepath, astType } = await findFileWithExtension(
7
- rootDirectory,
8
- "remix.config"
9
- );
10
- if (!filepath || !astType) {
11
- throw new AbortError(
12
- `Could not find remix.config.js file in ${rootDirectory}`
13
- );
14
- }
15
- await replaceFileContent(filepath, formatConfig, async (content) => {
16
- const astGrep = await importLangAstGrep(astType);
17
- const root = astGrep.parse(content).root();
18
- const remixConfigNode = root.find({
19
- rule: {
20
- kind: "object",
21
- inside: {
22
- any: [
23
- {
24
- kind: "export_statement"
25
- // ESM
26
- },
27
- {
28
- kind: "assignment_expression",
29
- // CJS
30
- has: {
31
- kind: "member_expression",
32
- field: "left",
33
- pattern: "module.exports"
34
- }
35
- }
36
- ]
37
- }
38
- }
39
- });
40
- if (!remixConfigNode) {
41
- throw new AbortError(
42
- "Could not find a default export in remix.config.js"
43
- );
44
- }
45
- newProperties = { ...newProperties };
46
- for (const key of Object.keys(newProperties)) {
47
- const propertyNode = remixConfigNode.find({
48
- rule: {
49
- kind: "pair",
50
- has: {
51
- field: "key",
52
- regex: `^${key}$`
53
- }
54
- }
55
- });
56
- if (propertyNode?.text().endsWith(" " + JSON.stringify(newProperties[key]))) {
57
- delete newProperties[key];
58
- }
59
- }
60
- if (Object.keys(newProperties).length === 0) {
61
- return;
62
- }
63
- const childrenNodes = remixConfigNode.children();
64
- const lastNode = childrenNodes.find((node) => node.text().startsWith("future:")) ?? childrenNodes.pop();
65
- if (!lastNode) {
66
- throw new AbortError("Could not add properties to Remix config");
67
- }
68
- const { start } = lastNode.range();
69
- return content.slice(0, start.index) + JSON.stringify(newProperties).slice(1, -1) + "," + content.slice(start.index);
70
- });
71
- }
72
5
  async function replaceRootLinks(appDirectory, formatConfig, importer) {
73
6
  const { filepath, astType } = await findFileWithExtension(appDirectory, "root");
74
7
  if (!filepath || !astType) {
@@ -81,7 +14,8 @@ async function replaceRootLinks(appDirectory, formatConfig, importer) {
81
14
  }
82
15
  const astGrep = await importLangAstGrep(astType);
83
16
  const root = astGrep.parse(content).root();
84
- const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
17
+ const importNodes = root.findAll({ rule: { kind: "import_statement" } });
18
+ const lastImportNode = importNodes.findLast((node) => node.text().includes(".css")) || importNodes.pop();
85
19
  const linksReturnNode = root.find({
86
20
  utils: {
87
21
  "has-links-id": {
@@ -127,14 +61,78 @@ async function replaceRootLinks(appDirectory, formatConfig, importer) {
127
61
  );
128
62
  });
129
63
  }
130
- function injectCssBundlingLink(appDirectory, formatConfig) {
131
- return replaceRootLinks(appDirectory, formatConfig, {
132
- name: "cssBundleHref",
133
- path: "@remix-run/css-bundle",
134
- isDefault: false,
135
- isConditional: true,
136
- isAbsolute: true
64
+ async function injectVitePlugin(rootDirectory, formatConfig, importer, pluginOptions) {
65
+ const { filepath, astType } = await findFileWithExtension(
66
+ rootDirectory,
67
+ "vite.config"
68
+ );
69
+ if (!filepath || !astType) {
70
+ throw new AbortError(`Could not find vite.config file in ${rootDirectory}`);
71
+ }
72
+ await replaceFileContent(filepath, formatConfig, async (content) => {
73
+ const importStatement = `import ${importer.isDefault ? importer.name : `{${importer.name}}`} from '${importer.path}';`;
74
+ if (new RegExp(`['"]${importer.path.replace("/", "\\/")}['"]`).test(content)) {
75
+ return;
76
+ }
77
+ const astGrep = await importLangAstGrep(astType);
78
+ const root = astGrep.parse(content).root();
79
+ const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
80
+ const vitePluginListNode = root.find(vitePluginListRule);
81
+ if (!lastImportNode || !vitePluginListNode) {
82
+ throw new AbortError(
83
+ 'Could not find a "plugins" key in Vite config file. Please add one and try again.'
84
+ );
85
+ }
86
+ const lastImportContent = lastImportNode.text();
87
+ const linksExportReturnContent = vitePluginListNode.text();
88
+ const newVitePluginItem = `${importer.name}(${pluginOptions ? JSON.stringify(pluginOptions) : ""})`;
89
+ return content.replace(lastImportContent, lastImportContent + "\n" + importStatement).replace(
90
+ linksExportReturnContent,
91
+ linksExportReturnContent.replace("[", `[${newVitePluginItem},`)
92
+ );
137
93
  });
138
94
  }
95
+ const vitePluginListRule = {
96
+ rule: {
97
+ // An array
98
+ pattern: "[$$$]",
99
+ inside: {
100
+ // directly in the value part of a `plugins` key
101
+ kind: "pair",
102
+ stopBy: "neighbor",
103
+ has: {
104
+ field: "key",
105
+ regex: "^plugins$",
106
+ stopBy: "neighbor"
107
+ },
108
+ inside: {
109
+ // directly inside an object (the Vite config object)
110
+ kind: "object",
111
+ stopBy: "neighbor",
112
+ // that is exported but is not inside another object
113
+ // e.g. `export default {something:{plugins:[]}}`
114
+ // doesn't match, but `export default {plugins:[]}` does.
115
+ // And `export default defineConfig({plugins:[]})` matches too.
116
+ all: [
117
+ {
118
+ inside: {
119
+ kind: "export_statement",
120
+ regex: "export default",
121
+ stopBy: "end"
122
+ }
123
+ },
124
+ {
125
+ not: {
126
+ inside: {
127
+ kind: "object",
128
+ stopBy: "end"
129
+ }
130
+ }
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ }
136
+ };
139
137
 
140
- export { injectCssBundlingLink, replaceRemixConfig, replaceRootLinks };
138
+ export { injectVitePlugin, replaceRootLinks };
@@ -3,21 +3,15 @@ import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
3
3
  import { mergePackageJson } from '../../file.js';
4
4
  import { canWriteFiles, copyAssets } from './assets.js';
5
5
  import { getCodeFormatOptions } from '../../format-code.js';
6
- import { replaceRootLinks } from './replacers.js';
6
+ import { replaceRootLinks, injectVitePlugin } from './replacers.js';
7
7
  import { getAssetsDir } from '../../build.js';
8
8
 
9
9
  const tailwindCssPath = "styles/tailwind.css";
10
- async function setupTailwind({ rootDirectory, appDirectory, ...futureOptions }, force = false) {
10
+ async function setupTailwind({ rootDirectory, appDirectory }, force = false) {
11
11
  const relativeAppDirectory = relativePath(rootDirectory, appDirectory);
12
12
  const assetMap = {
13
- "tailwind.config.js": "tailwind.config.js",
14
- "postcss.config.js": "postcss.config.js",
15
13
  "tailwind.css": joinPath(relativeAppDirectory, tailwindCssPath)
16
14
  };
17
- if (futureOptions.tailwind && futureOptions.postcss) {
18
- outputInfo(`Tailwind and PostCSS are already setup in ${rootDirectory}.`);
19
- return;
20
- }
21
15
  if (!await canWriteFiles(assetMap, appDirectory, force)) {
22
16
  outputInfo(
23
17
  `Skipping CSS setup as some files already exist. You may use \`--force\` or \`-f\` to override it.`
@@ -26,24 +20,26 @@ async function setupTailwind({ rootDirectory, appDirectory, ...futureOptions },
26
20
  }
27
21
  const workPromise = Promise.all([
28
22
  mergePackageJson(await getAssetsDir("tailwind"), rootDirectory),
29
- copyAssets(
30
- "tailwind",
31
- assetMap,
32
- rootDirectory,
33
- (content, filepath) => filepath === "tailwind.config.js" ? content.replace("{src-dir}", relativeAppDirectory) : content
34
- ),
23
+ copyAssets("tailwind", assetMap, rootDirectory),
35
24
  getCodeFormatOptions(rootDirectory).then(
36
- (formatConfig) => replaceRootLinks(appDirectory, formatConfig, {
37
- name: "tailwindCss",
38
- path: tailwindCssPath,
39
- isDefault: true
40
- })
25
+ (formatConfig) => Promise.all([
26
+ replaceRootLinks(appDirectory, formatConfig, {
27
+ name: "tailwindCss",
28
+ path: `${tailwindCssPath}?url`,
29
+ isDefault: true
30
+ }),
31
+ injectVitePlugin(rootDirectory, formatConfig, {
32
+ name: "tailwindcss",
33
+ path: "@tailwindcss/vite",
34
+ isDefault: true
35
+ })
36
+ ])
41
37
  )
42
38
  ]);
43
39
  return {
44
40
  workPromise,
45
41
  generatedAssets: Object.values(assetMap),
46
- helpUrl: "https://tailwindcss.com/docs/configuration"
42
+ needsInstallDeps: true
47
43
  };
48
44
  }
49
45
 
@@ -1,22 +1,25 @@
1
1
  import { mergePackageJson } from '../../file.js';
2
2
  import { getCodeFormatOptions } from '../../format-code.js';
3
- import { injectCssBundlingLink } from './replacers.js';
3
+ import { injectVitePlugin } from './replacers.js';
4
4
  import { getAssetsDir } from '../../build.js';
5
5
 
6
6
  async function setupVanillaExtract({
7
- rootDirectory,
8
- appDirectory
7
+ rootDirectory
9
8
  }) {
10
9
  const workPromise = Promise.all([
11
10
  mergePackageJson(await getAssetsDir("vanilla-extract"), rootDirectory),
12
11
  getCodeFormatOptions(rootDirectory).then(
13
- (formatConfig) => injectCssBundlingLink(appDirectory, formatConfig)
12
+ (formatConfig) => injectVitePlugin(rootDirectory, formatConfig, {
13
+ path: "@vanilla-extract/vite-plugin",
14
+ name: "vanillaExtractPlugin",
15
+ isDefault: false
16
+ })
14
17
  )
15
18
  ]);
16
19
  return {
17
20
  workPromise,
18
21
  generatedAssets: [],
19
- helpUrl: "https://vanilla-extract.style/documentation/styling/"
22
+ needsInstallDeps: true
20
23
  };
21
24
  }
22
25
 
@@ -162,8 +162,7 @@ async function replaceRemixEnv({ rootDirectory }, formatConfig, localeExtractImp
162
162
  return;
163
163
  }
164
164
  await replaceFileContent(envPath, formatConfig, async (content) => {
165
- if (content.includes(`Storefront<`))
166
- return;
165
+ if (content.includes(`Storefront<`)) return;
167
166
  const astGrep = await importLangAstGrep("ts");
168
167
  const root = astGrep.parse(content).root();
169
168
  const storefrontTypeNode = root.find({
@@ -91,10 +91,8 @@ async function generateRoutes(options, remixConfig) {
91
91
  };
92
92
  }
93
93
  async function getLocalePrefix(routesDirectory, { localePrefix, routeName, v1RouteConvention }) {
94
- if (localePrefix)
95
- return localePrefix;
96
- if (localePrefix !== void 0 || routeName === "all")
97
- return;
94
+ if (localePrefix) return localePrefix;
95
+ if (localePrefix !== void 0 || routeName === "all") return;
98
96
  const existingFiles = await readdir(routesDirectory).catch(() => []);
99
97
  const coreRouteWithLocaleRE = v1RouteConvention ? /^\(\$(\w+)\)$/ : /^\(\$(\w+)\)\.(_index|\$|cart).[jt]sx?$/;
100
98
  const coreRouteWithLocale = existingFiles.find(
@@ -114,19 +112,12 @@ async function generateProjectFile(routeFrom, {
114
112
  formatOptions,
115
113
  localePrefix,
116
114
  v1RouteConvention = false,
117
- signal
115
+ signal,
116
+ overwriteFileDeps = true
118
117
  }) {
119
118
  templatesRoot ??= await getStarterDir();
120
119
  const extension = (routeFrom.match(/(\.[jt]sx?)$/) ?? [])[1] ?? ".tsx";
121
120
  routeFrom = routeFrom.replace(extension, "");
122
- const routeTemplatePath = await getTemplateAppFile(
123
- routeFrom + extension,
124
- templatesRoot
125
- );
126
- const allFilesToGenerate = await findRouteDependencies(
127
- routeTemplatePath,
128
- await getTemplateAppFile("", templatesRoot)
129
- );
130
121
  const routeDestinationPath = joinPath(
131
122
  appDirectory,
132
123
  getDestinationRoute(routeFrom, localePrefix, { v1RouteConvention }) + (typescript ? extension : extension.replace(".ts", ".js"))
@@ -144,16 +135,26 @@ async function generateProjectFile(routeFrom, {
144
135
  cancellationMessage: "No",
145
136
  abortSignal: signal
146
137
  });
147
- if (!shouldOverwrite)
148
- return { ...result, operation: "skipped" };
138
+ if (!shouldOverwrite) return { ...result, operation: "skipped" };
149
139
  result.operation = "replaced";
150
140
  }
141
+ const routeTemplatePath = await getTemplateAppFile(
142
+ routeFrom + extension,
143
+ templatesRoot
144
+ );
145
+ const allFilesToGenerate = await findRouteDependencies(
146
+ routeTemplatePath,
147
+ await getTemplateAppFile("", templatesRoot)
148
+ );
151
149
  for (const filePath of allFilesToGenerate) {
152
150
  const isRoute = filePath.startsWith(ASSETS_STARTER_DIR_ROUTES + "/");
153
151
  const destinationPath = isRoute ? routeDestinationPath : joinPath(
154
152
  appDirectory,
155
153
  filePath.replace(/\.ts(x?)$/, `.${typescript ? "ts$1" : "js$1"}`)
156
154
  );
155
+ if (!overwriteFileDeps && await fileExists(destinationPath)) {
156
+ continue;
157
+ }
157
158
  if (!await fileExists(dirname(destinationPath))) {
158
159
  await mkdir(dirname(destinationPath));
159
160
  }
@@ -200,8 +201,7 @@ async function findRouteDependencies(routeFilePath, appDirectory) {
200
201
  for (const filePath of filesToCheck) {
201
202
  const importMatches = (await readFile(filePath, { encoding: "utf8" })).matchAll(/^(import|export)\s+.*?\s+from\s+['"](.*?)['"];?$/gims);
202
203
  for (let [, , match] of importMatches) {
203
- if (!match || !/^(\.|~)/.test(match))
204
- continue;
204
+ if (!match || !/^(\.|~)/.test(match)) continue;
205
205
  match = match.replace(/\?[a-z.]+$/, "");
206
206
  match = match.replace(
207
207
  "~",
@@ -253,8 +253,7 @@ async function copyRouteTemplate({
253
253
  adapter
254
254
  }) {
255
255
  const routePath = joinPath(routesDirectory, routeName);
256
- if (await fileExists(routePath))
257
- return;
256
+ if (await fileExists(routePath)) return;
258
257
  const templatePath = await getAssetsDir("routes", templateName);
259
258
  if (!await fileExists(templatePath)) {
260
259
  throw new Error("Unknown strategy");
package/dist/lib/shell.js CHANGED
@@ -30,10 +30,8 @@ async function supportsShell(shell) {
30
30
  }
31
31
  }
32
32
  function getShellAliasDefinitionFile(shell) {
33
- if (shell === "bash")
34
- return "~/.bashrc";
35
- if (shell === "zsh")
36
- return "~/.zshrc";
33
+ if (shell === "bash") return "~/.bashrc";
34
+ if (shell === "zsh") return "~/.zshrc";
37
35
  return `~/.config/fish/functions/${ALIAS_NAME}.fish`;
38
36
  }
39
37
  async function hasAliasDefinition(aliasName, shell) {
@@ -71,10 +69,8 @@ async function shellWriteFile(shell, content, append = false) {
71
69
  }
72
70
  }
73
71
  async function shellWriteAlias(shell, aliasName, content) {
74
- if (!await supportsShell(shell))
75
- return false;
76
- if (await hasAliasDefinition(aliasName, shell))
77
- return true;
72
+ if (!await supportsShell(shell)) return false;
73
+ if (await hasAliasDefinition(aliasName, shell)) return true;
78
74
  return await shellWriteFile(shell, content, shell !== "fish");
79
75
  }
80
76
  async function shellRunScript(script, shellBin) {
@@ -98,8 +94,7 @@ async function hasCliAlias() {
98
94
  await execAsync(`Get-Alias -Name ${ALIAS_NAME}`);
99
95
  } else {
100
96
  const shell = os.userInfo().shell?.split("/").pop() ?? "bash";
101
- if (!isKnownUnixShell(shell))
102
- return false;
97
+ if (!isKnownUnixShell(shell)) return false;
103
98
  return await hasAliasDefinition(ALIAS_NAME, shell);
104
99
  }
105
100
  return true;
@@ -1,12 +1,12 @@
1
+ import { symlink, cp } from 'node:fs/promises';
1
2
  import { temporaryDirectory } from 'tempy';
2
- import { createSymlink, copy } from 'fs-extra/esm';
3
3
  import { copyFile, fileExists, removeFile } from '@shopify/cli-kit/node/fs';
4
4
  import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
5
5
  import { readAndParsePackageJson } from '@shopify/cli-kit/node/node-package-manager';
6
6
  import { outputInfo } from '@shopify/cli-kit/node/output';
7
7
  import colors from '@shopify/cli-kit/node/colors';
8
8
  import { getStarterDir, isHydrogenMonorepo, getRepoNodeModules } from './build.js';
9
- import { mergePackageJson } from './file.js';
9
+ import { mergePackageJson, mergeTsConfig } from './file.js';
10
10
 
11
11
  async function prepareDiffDirectory(diffDirectory, watch) {
12
12
  const targetDirectory = temporaryDirectory({ prefix: "tmp-hydrogen-diff-" });
@@ -20,106 +20,81 @@ ${colors.dim(
20
20
  );
21
21
  const templateDirectory = await getStarterDir(isHydrogenMonorepo);
22
22
  await applyTemplateDiff(targetDirectory, diffDirectory, templateDirectory);
23
- await createSymlink(
23
+ await symlink(
24
24
  await getRepoNodeModules(),
25
25
  joinPath(targetDirectory, "node_modules")
26
26
  );
27
- const pw = watch ? await import('@parcel/watcher').catch((error) => {
28
- console.log("Could not watch for file changes.", error);
29
- }) : void 0;
30
- const subscriptions = await Promise.all([
27
+ const { default: chokidar } = await import('chokidar');
28
+ const subscriptions = watch ? [
31
29
  // Copy back the changes in generated d.ts from the
32
30
  // temporary directory to the original diff directory.
33
- pw?.subscribe(
34
- targetDirectory,
35
- (error, events) => {
36
- if (error) {
37
- console.error(error);
38
- return;
39
- }
40
- events.map((event) => {
41
- return copyFile(
42
- event.path,
43
- joinPath(diffDirectory, relativePath(targetDirectory, event.path))
44
- );
45
- });
46
- },
47
- { ignore: ["!*.generated.d.ts"] }
48
- ),
31
+ chokidar.watch(joinPath(targetDirectory, "*.generated.d.ts"), {
32
+ ignoreInitial: true
33
+ }).on("all", async (eventName, eventFilePath) => {
34
+ const targetFile = joinPath(
35
+ diffDirectory,
36
+ relativePath(targetDirectory, eventFilePath)
37
+ );
38
+ await copyFile(eventFilePath, targetFile);
39
+ }),
49
40
  // Copy new changes in the original diff directory to
50
41
  // the temporary directory.
51
- pw?.subscribe(
52
- diffDirectory,
53
- async (error, events) => {
54
- if (error) {
55
- console.error(error);
56
- return;
57
- }
58
- await events.map((event) => {
59
- const targetFile = joinPath(
60
- targetDirectory,
61
- relativePath(diffDirectory, event.path)
62
- );
63
- const fileInTemplate = event.path.replace(
64
- diffDirectory,
65
- templateDirectory
66
- );
67
- return event.type === "delete" ? fileExists(fileInTemplate).then(
68
- (exists) => exists ? (
69
- // Replace it with original file from the starter template.
70
- copyFile(fileInTemplate, targetFile)
71
- ) : (
72
- // Remove the file otherwise.
73
- removeFile(targetFile)
74
- )
75
- ).catch(() => {
76
- }) : copyFile(event.path, targetFile);
42
+ chokidar.watch(diffDirectory, {
43
+ ignoreInitial: true,
44
+ ignored: [
45
+ "**/*.generated.d.ts",
46
+ "**/package.json",
47
+ "**/tsconfig.json",
48
+ "**/.shopify"
49
+ ]
50
+ }).on("all", async (eventName, eventFilePath) => {
51
+ const targetFile = joinPath(
52
+ targetDirectory,
53
+ relativePath(diffDirectory, eventFilePath)
54
+ );
55
+ const fileInTemplate = eventFilePath.replace(
56
+ diffDirectory,
57
+ templateDirectory
58
+ );
59
+ if (eventName === "unlink") {
60
+ return fileExists(fileInTemplate).then(
61
+ (exists) => exists ? (
62
+ // Replace it with original file from the starter template.
63
+ copyFile(fileInTemplate, targetFile)
64
+ ) : (
65
+ // Remove the file otherwise.
66
+ removeFile(targetFile)
67
+ )
68
+ ).catch(() => {
77
69
  });
78
- },
79
- {
80
- ignore: [
81
- "*.generated.d.ts",
82
- "package.json",
83
- "tsconfig.json",
84
- ".shopify"
85
- ]
86
70
  }
87
- ),
71
+ return copyFile(eventFilePath, targetFile);
72
+ }),
88
73
  // Copy new changes in the starter template to the temporary
89
74
  // directory only if they don't overwrite the files in the
90
75
  // original diff directory, which have higher priority.
91
- pw?.subscribe(
92
- templateDirectory,
93
- async (error, events) => {
94
- if (error) {
95
- console.error(error);
96
- return;
97
- }
98
- await events.map(async (event) => {
99
- const fileInDiff = event.path.replace(
100
- templateDirectory,
101
- diffDirectory
102
- );
103
- if (await fileExists(fileInDiff))
104
- return;
105
- const targetFile = joinPath(
106
- targetDirectory,
107
- relativePath(templateDirectory, event.path)
108
- );
109
- return event.type === "delete" ? removeFile(targetFile).catch(() => {
110
- }) : copyFile(event.path, targetFile);
111
- });
112
- },
113
- {
114
- ignore: [
115
- "*.generated.d.ts",
116
- "package.json",
117
- "tsconfig.json",
118
- ".shopify"
119
- ]
120
- }
121
- )
122
- ]);
76
+ chokidar.watch(templateDirectory, {
77
+ ignoreInitial: true,
78
+ ignored: [
79
+ "**/*.generated.d.ts",
80
+ "**/package.json",
81
+ "**/tsconfig.json",
82
+ "**/.shopify"
83
+ ]
84
+ }).on("all", async (eventName, eventFilePath) => {
85
+ const fileInDiff = eventFilePath.replace(
86
+ templateDirectory,
87
+ diffDirectory
88
+ );
89
+ if (await fileExists(fileInDiff)) return;
90
+ const targetFile = joinPath(
91
+ targetDirectory,
92
+ relativePath(templateDirectory, eventFilePath)
93
+ );
94
+ return eventName === "unlink" ? removeFile(targetFile).catch(() => {
95
+ }) : copyFile(eventFilePath, targetFile);
96
+ })
97
+ ] : [];
123
98
  return {
124
99
  /**
125
100
  * The temporary directory with the starter template and diff applied.
@@ -129,7 +104,7 @@ ${colors.dim(
129
104
  * Removes the temporary directory and stops the file watchers.
130
105
  */
131
106
  cleanup: async () => {
132
- await Promise.all(subscriptions.map((sub) => sub?.unsubscribe()));
107
+ await Promise.all(subscriptions.map((sub) => sub.close()));
133
108
  await removeFile(targetDirectory);
134
109
  },
135
110
  /**
@@ -139,11 +114,10 @@ ${colors.dim(
139
114
  */
140
115
  async copyShopifyConfig() {
141
116
  const source = joinPath(targetDirectory, ".shopify");
142
- if (!await fileExists(source))
143
- return;
117
+ if (!await fileExists(source)) return;
144
118
  const target = joinPath(diffDirectory, ".shopify");
145
119
  await removeFile(target);
146
- await copy(source, target, { overwrite: true });
120
+ await cp(source, target, { recursive: true, force: true });
147
121
  },
148
122
  /**
149
123
  * Brings the `dist` directory back to the original project.
@@ -153,8 +127,9 @@ ${colors.dim(
153
127
  const target = joinPath(diffDirectory, "dist");
154
128
  await removeFile(target);
155
129
  await Promise.all([
156
- copy(joinPath(targetDirectory, "dist"), target, {
157
- overwrite: true
130
+ cp(joinPath(targetDirectory, "dist"), target, {
131
+ force: true,
132
+ recursive: true
158
133
  }),
159
134
  copyFile(
160
135
  joinPath(targetDirectory, ".env"),
@@ -166,23 +141,26 @@ ${colors.dim(
166
141
  }
167
142
  async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir) {
168
143
  templateDir ??= await getStarterDir();
169
- const [diffPkgJson, templatePkgJson] = await Promise.all([
170
- readAndParsePackageJson(joinPath(diffDirectory, "package.json")),
171
- readAndParsePackageJson(joinPath(templateDir, "package.json"))
172
- ]);
144
+ const diffPkgJson = await readAndParsePackageJson(
145
+ joinPath(diffDirectory, "package.json")
146
+ );
173
147
  const diffOptions = diffPkgJson["h2:diff"] ?? {};
174
148
  const createFilter = (re, skipFiles) => (filepath) => {
175
149
  const filename = relativePath(templateDir, filepath);
176
150
  return !re.test(filename) && !skipFiles?.includes(filename);
177
151
  };
178
- await copy(templateDir, targetDirectory, {
152
+ await cp(templateDir, targetDirectory, {
153
+ force: true,
154
+ recursive: true,
179
155
  filter: createFilter(
180
156
  // Do not copy .shopify from skeleton to avoid linking in examples inadvertedly
181
157
  /(^|\/|\\)(dist|node_modules|\.cache|\.turbo|\.shopify|CHANGELOG\.md)(\/|\\|$)/i,
182
158
  diffOptions.skipFiles || []
183
159
  )
184
160
  });
185
- await copy(diffDirectory, targetDirectory, {
161
+ await cp(diffDirectory, targetDirectory, {
162
+ force: true,
163
+ recursive: true,
186
164
  filter: createFilter(
187
165
  /(^|\/|\\)(dist|node_modules|\.cache|.turbo|package\.json|tsconfig\.json)(\/|\\|$)/i
188
166
  )
@@ -190,8 +168,8 @@ async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir) {
190
168
  await mergePackageJson(diffDirectory, targetDirectory, {
191
169
  ignoredKeys: ["h2:diff"],
192
170
  onResult: (pkgJson) => {
193
- if (pkgJson.dependencies && templatePkgJson.dependencies) {
194
- pkgJson.dependencies["@shopify/cli-hydrogen"] = templatePkgJson.dependencies["@shopify/cli-hydrogen"] ?? "*";
171
+ if (pkgJson.dependencies) {
172
+ delete pkgJson.dependencies["@shopify/cli-hydrogen"];
195
173
  }
196
174
  for (const key of ["build", "dev", "preview"]) {
197
175
  const scriptLine = pkgJson.scripts?.[key];
@@ -212,6 +190,7 @@ async function applyTemplateDiff(targetDirectory, diffDirectory, templateDir) {
212
190
  return pkgJson;
213
191
  }
214
192
  });
193
+ await mergeTsConfig(diffDirectory, targetDirectory);
215
194
  }
216
195
 
217
196
  export { applyTemplateDiff, prepareDiffDirectory };