@shopify/cli-hydrogen 13.0.5 → 13.0.6

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.
@@ -1,5 +1,19 @@
1
1
  # skeleton
2
2
 
3
+ ## 2026.4.8
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`27d87085aca32f8b0e8ac0cf9eb4d9e6eb016b99`](https://github.com/Shopify/hydrogen/commit/27d87085aca32f8b0e8ac0cf9eb4d9e6eb016b99)]:
8
+ - @shopify/hydrogen@2026.4.7
9
+
10
+ ## 2026.4.7
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [[`76cdc6c538dc680a3f1ce96a6d704d463afde0ce`](https://github.com/Shopify/hydrogen/commit/76cdc6c538dc680a3f1ce96a6d704d463afde0ce), [`3ef7ec23096ad29e701073b35a892f0938a0acf4`](https://github.com/Shopify/hydrogen/commit/3ef7ec23096ad29e701073b35a892f0938a0acf4)]:
15
+ - @shopify/hydrogen@2026.4.6
16
+
3
17
  ## 2026.4.6
4
18
 
5
19
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2026.4.6",
5
+ "version": "2026.4.8",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "prettier": "@shopify/prettier-config",
16
16
  "dependencies": {
17
- "@shopify/hydrogen": "2026.4.5",
17
+ "@shopify/hydrogen": "2026.4.7",
18
18
  "graphql": "^16.10.0",
19
19
  "graphql-tag": "^2.12.6",
20
20
  "isbot": "^5.1.22",
@@ -20,8 +20,8 @@ function needsQuoting(value) {
20
20
  }
21
21
  function quoteEnvValue(value) {
22
22
  if (!needsQuoting(value)) return value;
23
- const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t");
24
- return `"${escaped}"`;
23
+ if (!/['\r\n]/.test(value)) return `'${value}'`;
24
+ return `"${value.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}"`;
25
25
  }
26
26
  class EnvPull extends Command {
27
27
  static descriptionWithMarkdown = "Pulls environment variables from the linked Hydrogen storefront and writes them to an `.env` file.";
@@ -25,7 +25,7 @@ class Init extends Command {
25
25
  env: "SHOPIFY_HYDROGEN_FLAG_LANGUAGE"
26
26
  }),
27
27
  template: Flags.string({
28
- description: "Scaffolds project based on an existing template or example from the Hydrogen repository.",
28
+ description: "Scaffolds project based on a template: `skeleton` (the default), `demo-store` or a URL to a git repository.",
29
29
  env: "SHOPIFY_HYDROGEN_FLAG_TEMPLATE"
30
30
  }),
31
31
  ...commonFlags.installDeps,
@@ -6,7 +6,7 @@ async function setupTemplate(options) {
6
6
  const controller = new AbortController();
7
7
  try {
8
8
  const template = options.template;
9
- return template ? await setupRemoteTemplate({ ...options, template }, controller) : await setupLocalStarterTemplate(options, controller);
9
+ return template && template !== "skeleton" ? await setupRemoteTemplate({ ...options, template }, controller) : await setupLocalStarterTemplate(options, controller);
10
10
  } catch (error) {
11
11
  controller.abort();
12
12
  throw error;
@@ -1,9 +1,8 @@
1
- import { readdir } from 'node:fs/promises';
2
1
  import { AbortError } from '@shopify/cli-kit/node/error';
3
2
  import { copyFile, fileExists } from '@shopify/cli-kit/node/fs';
4
3
  import { joinPath } from '@shopify/cli-kit/node/path';
5
4
  import { renderTasks, renderInfo } from '@shopify/cli-kit/node/ui';
6
- import { downloadExternalRepo, downloadMonorepoTemplates } from '../template-downloader.js';
5
+ import { downloadExternalRepo } from '../template-downloader.js';
7
6
  import { getCliCommand } from '../shell.js';
8
7
  import { handleProjectLocation, createAbortHandler, handleLanguage, createInitialCommit, handleDependencies, commitAll, renderProjectReady } from './common.js';
9
8
 
@@ -13,7 +12,7 @@ async function setupRemoteTemplate(options, controller) {
13
12
  const project = await handleProjectLocation({ ...options, controller });
14
13
  if (!project) return;
15
14
  const abort = createAbortHandler(controller, project);
16
- const backgroundDownloadPromise = appTemplate.includes("/") ? getExternalTemplate(appTemplate, controller.signal).catch(abort) : getMonorepoTemplate(appTemplate, controller.signal).catch(abort);
15
+ const backgroundDownloadPromise = appTemplate.includes("/") ? getExternalTemplate(appTemplate, controller.signal).catch(abort) : rejectUnknownTemplate(appTemplate).catch(abort);
17
16
  const downloaded = await backgroundDownloadPromise;
18
17
  if (controller.signal.aborted) return;
19
18
  let backgroundWorkPromise = Promise.resolve().then(async () => {
@@ -86,26 +85,18 @@ async function getExternalTemplate(appTemplate, signal) {
86
85
  const { templateDir } = await downloadExternalRepo(appTemplate, signal);
87
86
  return { sourcePath: templateDir };
88
87
  }
89
- async function getMonorepoTemplate(appTemplate, signal) {
90
- const { templatesDir, examplesDir } = await downloadMonorepoTemplates({
91
- signal
92
- });
93
- const skeletonPath = joinPath(templatesDir, "skeleton");
94
- const templatePath = joinPath(templatesDir, appTemplate);
95
- const examplePath = joinPath(examplesDir, appTemplate);
96
- if (await fileExists(templatePath)) {
97
- return { skeletonPath, sourcePath: templatePath };
98
- }
99
- if (await fileExists(examplePath)) {
100
- return { skeletonPath, sourcePath: examplePath };
101
- }
102
- const availableTemplates = (await Promise.all([readdir(examplesDir), readdir(templatesDir)]).catch(
103
- () => []
104
- )).flat().filter((name) => name !== "skeleton" && !name.endsWith(".md")).concat("demo-store").sort();
88
+ async function rejectUnknownTemplate(appTemplate) {
105
89
  throw new AbortError(
106
90
  `Unknown value in \`--template\` flag "${appTemplate}".
107
- Skip the flag or provide the name of a template or example in the Hydrogen repository or a URL to a git repository.`,
108
- availableTemplates.length === 0 ? "" : { list: { title: "Available templates:", items: availableTemplates } }
91
+ Skip the flag or provide the name of a template or a URL to a git repository.`,
92
+ {
93
+ list: {
94
+ title: "Available templates:",
95
+ // Note: skeleton uses the local starter flow, and demo-store is
96
+ // handled as an external template
97
+ items: ["skeleton", "demo-store"]
98
+ }
99
+ }
109
100
  );
110
101
  }
111
102
 
@@ -1,19 +1,11 @@
1
1
  import { rm, symlink } from 'node:fs/promises';
2
2
  import { vi } from 'vitest';
3
3
  import { writeFile } from '@shopify/cli-kit/node/fs';
4
- import { dirname, joinPath } from '@shopify/cli-kit/node/path';
4
+ import { joinPath } from '@shopify/cli-kit/node/path';
5
5
  import { getSkeletonSourceDir, getSkeletonNodeModules } from '../build.js';
6
6
 
7
7
  const { renderTasksHook } = vi.hoisted(() => ({ renderTasksHook: vi.fn() }));
8
8
  vi.mock("../template-downloader.js", async () => ({
9
- downloadMonorepoTemplates: () => Promise.resolve({
10
- version: "",
11
- templatesDir: dirname(getSkeletonSourceDir()),
12
- examplesDir: dirname(getSkeletonSourceDir()).replace(
13
- "templates",
14
- "examples"
15
- )
16
- }),
17
9
  downloadExternalRepo: () => Promise.resolve({
18
10
  templateDir: getSkeletonSourceDir()
19
11
  })
@@ -1,92 +1,10 @@
1
- import path from 'node:path';
2
- import { pipeline } from 'stream/promises';
3
- import gunzipMaybe from 'gunzip-maybe';
4
- import { extract } from 'tar-fs';
5
- import { fetch } from '@shopify/cli-kit/node/http';
6
1
  import { parseGitHubRepositoryURL } from '@shopify/cli-kit/node/github';
7
2
  import { fileExists, mkdir, rmdir } from '@shopify/cli-kit/node/fs';
8
3
  import { AbortError } from '@shopify/cli-kit/node/error';
9
- import { isHydrogenMonorepo, getSkeletonSourceDir, getAssetsDir } from './build.js';
4
+ import { getAssetsDir } from './build.js';
10
5
  import { joinPath } from '@shopify/cli-kit/node/path';
11
6
  import { downloadGitRepository } from '@shopify/cli-kit/node/git';
12
7
 
13
- const REPO_RELEASES_URL = `https://api.github.com/repos/shopify/hydrogen/releases/latest`;
14
- const getTryMessage = (status) => status === 403 ? `If you are using a VPN, WARP, or similar service, consider disabling it momentarily.` : void 0;
15
- async function getLatestReleaseDownloadUrl(signal) {
16
- const response = await fetch(REPO_RELEASES_URL, { signal });
17
- if (!response.ok || response.status >= 400) {
18
- throw new AbortError(
19
- `Failed to fetch the latest release information. Status ${response.status} ${response.statusText.replace(/\.$/, "")}.`,
20
- getTryMessage(response.status)
21
- );
22
- }
23
- const release = await response.json();
24
- return {
25
- // @shopify/package-name@version => package-name@version
26
- version: release.name.split("/").pop() ?? release.name,
27
- url: release.tarball_url
28
- };
29
- }
30
- async function downloadMonorepoTarball(url, storageDir, signal) {
31
- const response = await fetch(url, { signal });
32
- if (!response.ok || response.status >= 400) {
33
- throw new AbortError(
34
- `Failed to download the latest release files. Status ${response.status} ${response.statusText}}`,
35
- getTryMessage(response.status)
36
- );
37
- }
38
- await pipeline(
39
- // Download
40
- response.body,
41
- // Decompress
42
- gunzipMaybe(),
43
- // Unpack
44
- extract(storageDir, {
45
- strip: 1,
46
- filter: (name) => {
47
- name = name.replace(storageDir, "");
48
- return !name.startsWith(path.normalize("/templates/")) && !name.startsWith(path.normalize("/examples/"));
49
- }
50
- })
51
- );
52
- }
53
- async function downloadMonorepoTemplates({
54
- signal
55
- } = {}) {
56
- if (isHydrogenMonorepo && process.env.FORCE_TEMPLATES_SOURCE !== "remote") {
57
- const templatesDir = path.dirname(getSkeletonSourceDir());
58
- return {
59
- version: "local",
60
- templatesDir,
61
- examplesDir: path.resolve(templatesDir, "..", "examples")
62
- };
63
- }
64
- try {
65
- const { version, url } = await getLatestReleaseDownloadUrl(signal);
66
- const templateStoragePath = await getAssetsDir("internal-templates");
67
- if (!await fileExists(templateStoragePath)) {
68
- await mkdir(templateStoragePath);
69
- }
70
- const templateStorageVersionPath = path.join(templateStoragePath, version);
71
- if (!await fileExists(templateStorageVersionPath)) {
72
- await downloadMonorepoTarball(url, templateStorageVersionPath, signal);
73
- }
74
- return {
75
- version,
76
- templatesDir: path.join(templateStorageVersionPath, "templates"),
77
- examplesDir: path.join(templateStorageVersionPath, "examples")
78
- };
79
- } catch (e) {
80
- const error = e;
81
- throw new AbortError(
82
- `Could not download Hydrogen templates from GitHub.
83
- Please check your internet connection and the following error:
84
-
85
- ` + error.message,
86
- error.tryMessage
87
- );
88
- }
89
- }
90
8
  async function downloadExternalRepo(appTemplate, signal) {
91
9
  const parsed = parseGitHubRepositoryURL(appTemplate);
92
10
  if (parsed.isErr()) {
@@ -113,4 +31,4 @@ async function downloadExternalRepo(appTemplate, signal) {
113
31
  return { templateDir };
114
32
  }
115
33
 
116
- export { downloadExternalRepo, downloadMonorepoTemplates };
34
+ export { downloadExternalRepo };
@@ -1045,7 +1045,7 @@
1045
1045
  "type": "option"
1046
1046
  },
1047
1047
  "template": {
1048
- "description": "Scaffolds project based on an existing template or example from the Hydrogen repository.",
1048
+ "description": "Scaffolds project based on a template: `skeleton` (the default), `demo-store` or a URL to a git repository.",
1049
1049
  "env": "SHOPIFY_HYDROGEN_FLAG_TEMPLATE",
1050
1050
  "name": "template",
1051
1051
  "hasDynamicHelp": false,
@@ -1751,5 +1751,5 @@
1751
1751
  ]
1752
1752
  }
1753
1753
  },
1754
- "version": "13.0.5"
1754
+ "version": "13.0.6"
1755
1755
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "13.0.5",
7
+ "version": "13.0.6",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "repository": {