@shopify/cli-hydrogen 5.0.2 → 5.1.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 (243) hide show
  1. package/dist/commands/hydrogen/build.js +16 -2
  2. package/dist/commands/hydrogen/codegen-unstable.js +13 -24
  3. package/dist/commands/hydrogen/dev.js +45 -39
  4. package/dist/commands/hydrogen/env/list.js +25 -24
  5. package/dist/commands/hydrogen/env/list.test.js +46 -43
  6. package/dist/commands/hydrogen/env/pull.js +53 -25
  7. package/dist/commands/hydrogen/env/pull.test.js +123 -42
  8. package/dist/commands/hydrogen/generate/route.js +31 -132
  9. package/dist/commands/hydrogen/generate/route.test.js +34 -126
  10. package/dist/commands/hydrogen/init.js +46 -127
  11. package/dist/commands/hydrogen/init.test.js +352 -100
  12. package/dist/commands/hydrogen/link.js +70 -69
  13. package/dist/commands/hydrogen/link.test.js +72 -107
  14. package/dist/commands/hydrogen/list.js +22 -12
  15. package/dist/commands/hydrogen/list.test.js +51 -48
  16. package/dist/commands/hydrogen/login.js +31 -0
  17. package/dist/commands/hydrogen/logout.js +21 -0
  18. package/dist/commands/hydrogen/setup/css.js +79 -0
  19. package/dist/commands/hydrogen/setup/markets.js +53 -0
  20. package/dist/commands/hydrogen/setup.js +133 -0
  21. package/dist/commands/hydrogen/shortcut.js +2 -45
  22. package/dist/commands/hydrogen/shortcut.test.js +10 -37
  23. package/dist/generator-templates/assets/css-modules/package.json +6 -0
  24. package/dist/generator-templates/assets/postcss/package.json +10 -0
  25. package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
  26. package/dist/generator-templates/assets/tailwind/package.json +13 -0
  27. package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
  28. package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
  29. package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
  30. package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
  31. package/dist/generator-templates/starter/.eslintignore +5 -0
  32. package/dist/generator-templates/starter/.eslintrc.js +18 -0
  33. package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
  34. package/dist/generator-templates/starter/README.md +40 -0
  35. package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
  36. package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
  37. package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
  38. package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
  39. package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
  40. package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
  41. package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
  42. package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
  43. package/dist/generator-templates/starter/app/root.tsx +270 -0
  44. package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
  45. package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
  46. package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
  47. package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
  48. package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
  49. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
  50. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
  51. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
  52. package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
  53. package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
  54. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
  55. package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
  56. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
  57. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
  58. package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
  59. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
  60. package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
  61. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
  62. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
  63. package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
  64. package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
  65. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
  66. package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
  67. package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
  68. package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
  69. package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
  70. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
  71. package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
  72. package/dist/generator-templates/starter/app/styles/app.css +473 -0
  73. package/dist/generator-templates/starter/app/styles/reset.css +129 -0
  74. package/dist/generator-templates/starter/app/utils.ts +46 -0
  75. package/dist/generator-templates/starter/package.json +43 -0
  76. package/dist/generator-templates/starter/public/favicon.svg +28 -0
  77. package/dist/generator-templates/starter/remix.config.js +26 -0
  78. package/dist/generator-templates/starter/remix.env.d.ts +39 -0
  79. package/dist/generator-templates/starter/server.ts +253 -0
  80. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
  81. package/dist/generator-templates/starter/tsconfig.json +22 -0
  82. package/dist/lib/auth.js +123 -0
  83. package/dist/lib/auth.test.js +157 -0
  84. package/dist/lib/build.js +51 -0
  85. package/dist/lib/check-version.js +3 -3
  86. package/dist/lib/check-version.test.js +24 -0
  87. package/dist/lib/codegen.js +26 -17
  88. package/dist/lib/environment-variables.js +68 -0
  89. package/dist/lib/environment-variables.test.js +147 -0
  90. package/dist/lib/file.js +41 -0
  91. package/dist/lib/file.test.js +69 -0
  92. package/dist/lib/flags.js +39 -2
  93. package/dist/lib/format-code.js +26 -0
  94. package/dist/lib/gid.js +12 -0
  95. package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
  96. package/dist/lib/graphql/admin/client.js +27 -0
  97. package/dist/lib/graphql/admin/client.test.js +51 -0
  98. package/dist/lib/graphql/admin/create-storefront.js +13 -15
  99. package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
  100. package/dist/lib/graphql/admin/fetch-job.js +6 -15
  101. package/dist/lib/graphql/admin/link-storefront.js +7 -11
  102. package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
  103. package/dist/lib/graphql/admin/list-environments.js +2 -2
  104. package/dist/lib/graphql/admin/list-environments.test.js +44 -0
  105. package/dist/lib/graphql/admin/list-storefronts.js +7 -11
  106. package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
  107. package/dist/lib/graphql/admin/pull-variables.js +3 -3
  108. package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
  109. package/dist/lib/graphql/business-platform/user-account.js +83 -0
  110. package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
  111. package/dist/lib/log.js +185 -9
  112. package/dist/lib/log.test.js +92 -0
  113. package/dist/lib/mini-oxygen.js +19 -9
  114. package/dist/lib/missing-routes.js +0 -2
  115. package/dist/lib/onboarding/common.js +456 -0
  116. package/dist/lib/onboarding/index.js +2 -0
  117. package/dist/lib/onboarding/local.js +229 -0
  118. package/dist/lib/onboarding/remote.js +89 -0
  119. package/dist/lib/remix-version-interop.js +5 -5
  120. package/dist/lib/remix-version-interop.test.js +11 -1
  121. package/dist/lib/render-errors.js +13 -11
  122. package/dist/lib/setups/css/assets.js +89 -0
  123. package/dist/lib/setups/css/css-modules.js +22 -0
  124. package/dist/lib/setups/css/index.js +44 -0
  125. package/dist/lib/setups/css/postcss.js +34 -0
  126. package/dist/lib/setups/css/replacers.js +137 -0
  127. package/dist/lib/setups/css/tailwind.js +54 -0
  128. package/dist/lib/setups/css/vanilla-extract.js +22 -0
  129. package/dist/lib/setups/i18n/domains.test.js +25 -0
  130. package/dist/lib/setups/i18n/index.js +46 -0
  131. package/dist/lib/setups/i18n/replacers.js +227 -0
  132. package/dist/lib/setups/i18n/subdomains.test.js +25 -0
  133. package/dist/lib/setups/i18n/subfolders.test.js +25 -0
  134. package/dist/lib/setups/i18n/templates/domains.js +14 -0
  135. package/dist/lib/setups/i18n/templates/domains.ts +25 -0
  136. package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
  137. package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
  138. package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
  139. package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
  140. package/dist/lib/setups/routes/generate.js +244 -0
  141. package/dist/lib/setups/routes/generate.test.js +313 -0
  142. package/dist/lib/shell.js +52 -5
  143. package/dist/lib/shell.test.js +42 -16
  144. package/dist/lib/shopify-config.js +23 -18
  145. package/dist/lib/shopify-config.test.js +63 -73
  146. package/dist/lib/template-downloader.js +9 -7
  147. package/dist/lib/transpile-ts.js +9 -29
  148. package/dist/virtual-routes/routes/index.jsx +40 -19
  149. package/oclif.manifest.json +710 -1
  150. package/package.json +16 -16
  151. package/dist/commands/hydrogen/build.d.ts +0 -23
  152. package/dist/commands/hydrogen/check.d.ts +0 -15
  153. package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
  154. package/dist/commands/hydrogen/dev.d.ts +0 -21
  155. package/dist/commands/hydrogen/env/list.d.ts +0 -18
  156. package/dist/commands/hydrogen/env/pull.d.ts +0 -22
  157. package/dist/commands/hydrogen/g.d.ts +0 -10
  158. package/dist/commands/hydrogen/generate/route.d.ts +0 -32
  159. package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
  160. package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
  161. package/dist/commands/hydrogen/init.d.ts +0 -24
  162. package/dist/commands/hydrogen/init.test.d.ts +0 -1
  163. package/dist/commands/hydrogen/link.d.ts +0 -23
  164. package/dist/commands/hydrogen/link.test.d.ts +0 -1
  165. package/dist/commands/hydrogen/list.d.ts +0 -21
  166. package/dist/commands/hydrogen/list.test.d.ts +0 -1
  167. package/dist/commands/hydrogen/preview.d.ts +0 -17
  168. package/dist/commands/hydrogen/shortcut.d.ts +0 -9
  169. package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
  170. package/dist/commands/hydrogen/unlink.d.ts +0 -16
  171. package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
  172. package/dist/create-app.d.ts +0 -1
  173. package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
  174. package/dist/generator-templates/routes/account/login.tsx +0 -103
  175. package/dist/generator-templates/routes/account/register.tsx +0 -103
  176. package/dist/generator-templates/routes/cart.tsx +0 -81
  177. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
  178. package/dist/generator-templates/routes/collections/index.tsx +0 -102
  179. package/dist/generator-templates/routes/graphiql.tsx +0 -10
  180. package/dist/generator-templates/routes/index.tsx +0 -40
  181. package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
  182. package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
  183. package/dist/generator-templates/routes/policies/index.tsx +0 -117
  184. package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
  185. package/dist/hooks/init.d.ts +0 -5
  186. package/dist/lib/admin-session.d.ts +0 -6
  187. package/dist/lib/admin-session.js +0 -16
  188. package/dist/lib/admin-session.test.d.ts +0 -1
  189. package/dist/lib/admin-session.test.js +0 -27
  190. package/dist/lib/admin-urls.d.ts +0 -8
  191. package/dist/lib/check-lockfile.d.ts +0 -3
  192. package/dist/lib/check-lockfile.test.d.ts +0 -1
  193. package/dist/lib/check-version.d.ts +0 -16
  194. package/dist/lib/check-version.test.d.ts +0 -1
  195. package/dist/lib/codegen.d.ts +0 -26
  196. package/dist/lib/combined-environment-variables.d.ts +0 -8
  197. package/dist/lib/combined-environment-variables.js +0 -57
  198. package/dist/lib/combined-environment-variables.test.d.ts +0 -1
  199. package/dist/lib/combined-environment-variables.test.js +0 -111
  200. package/dist/lib/config.d.ts +0 -20
  201. package/dist/lib/flags.d.ts +0 -27
  202. package/dist/lib/flags.test.d.ts +0 -1
  203. package/dist/lib/graphql/admin/create-storefront.d.ts +0 -17
  204. package/dist/lib/graphql/admin/fetch-job.d.ts +0 -23
  205. package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
  206. package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
  207. package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
  208. package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
  209. package/dist/lib/graphql.d.ts +0 -21
  210. package/dist/lib/graphql.js +0 -18
  211. package/dist/lib/graphql.test.d.ts +0 -1
  212. package/dist/lib/log.d.ts +0 -6
  213. package/dist/lib/mini-oxygen.d.ts +0 -22
  214. package/dist/lib/missing-routes.d.ts +0 -8
  215. package/dist/lib/missing-routes.test.d.ts +0 -1
  216. package/dist/lib/missing-storefronts.d.ts +0 -5
  217. package/dist/lib/missing-storefronts.js +0 -18
  218. package/dist/lib/process.d.ts +0 -6
  219. package/dist/lib/pull-environment-variables.d.ts +0 -20
  220. package/dist/lib/pull-environment-variables.js +0 -57
  221. package/dist/lib/pull-environment-variables.test.d.ts +0 -1
  222. package/dist/lib/pull-environment-variables.test.js +0 -174
  223. package/dist/lib/remix-version-interop.d.ts +0 -11
  224. package/dist/lib/remix-version-interop.test.d.ts +0 -1
  225. package/dist/lib/render-errors.d.ts +0 -16
  226. package/dist/lib/shell.d.ts +0 -11
  227. package/dist/lib/shell.test.d.ts +0 -1
  228. package/dist/lib/shop.d.ts +0 -7
  229. package/dist/lib/shop.js +0 -32
  230. package/dist/lib/shop.test.d.ts +0 -1
  231. package/dist/lib/shop.test.js +0 -78
  232. package/dist/lib/shopify-config.d.ts +0 -35
  233. package/dist/lib/shopify-config.test.d.ts +0 -1
  234. package/dist/lib/string.d.ts +0 -3
  235. package/dist/lib/string.test.d.ts +0 -1
  236. package/dist/lib/template-downloader.d.ts +0 -6
  237. package/dist/lib/transpile-ts.d.ts +0 -16
  238. package/dist/lib/user-errors.d.ts +0 -9
  239. package/dist/lib/user-errors.js +0 -11
  240. package/dist/lib/virtual-routes.d.ts +0 -7
  241. package/dist/lib/virtual-routes.test.d.ts +0 -1
  242. /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
  243. /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
@@ -1,24 +1,22 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import Command from '@shopify/cli-kit/node/base-command';
3
3
  import { basename } from '@shopify/cli-kit/node/path';
4
- import { renderConfirmationPrompt, renderWarning, renderSelectPrompt, renderTextPrompt, renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
4
+ import { renderSuccess, renderConfirmationPrompt, renderWarning, renderSelectPrompt, renderTextPrompt, renderTasks } from '@shopify/cli-kit/node/ui';
5
+ import { AbortError } from '@shopify/cli-kit/node/error';
5
6
  import { commonFlags } from '../../lib/flags.js';
6
- import { getHydrogenShop } from '../../lib/shop.js';
7
7
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
8
+ import { setStorefront } from '../../lib/shopify-config.js';
8
9
  import { createStorefront } from '../../lib/graphql/admin/create-storefront.js';
9
10
  import { waitForJob } from '../../lib/graphql/admin/fetch-job.js';
10
- import { getConfig, setStorefront } from '../../lib/shopify-config.js';
11
- import { logMissingStorefronts } from '../../lib/missing-storefronts.js';
12
11
  import { titleize } from '../../lib/string.js';
13
12
  import { getCliCommand } from '../../lib/shell.js';
14
- import { renderUserErrors, renderError } from '../../lib/user-errors.js';
13
+ import { login } from '../../lib/auth.js';
15
14
 
16
15
  class Link extends Command {
17
16
  static description = "Link a local project to one of your shop's Hydrogen storefronts.";
18
17
  static flags = {
19
18
  force: commonFlags.force,
20
19
  path: commonFlags.path,
21
- shop: commonFlags.shop,
22
20
  storefront: Flags.string({
23
21
  description: `The name of a Hydrogen Storefront (e.g. "Jane's Apparel")`,
24
22
  env: "SHOPIFY_HYDROGEN_STOREFRONT"
@@ -26,35 +24,54 @@ class Link extends Command {
26
24
  };
27
25
  async run() {
28
26
  const { flags } = await this.parse(Link);
29
- await linkStorefront(flags);
27
+ await runLink(flags);
30
28
  }
31
29
  }
32
- const CREATE_NEW_STOREFRONT_ID = "NEW_STOREFRONT";
33
- async function linkStorefront({
30
+ async function runLink({
34
31
  force,
35
- path,
36
- shop: flagShop,
37
- storefront: flagStorefront,
38
- silent = false
32
+ path: root = process.cwd(),
33
+ storefront: flagStorefront
39
34
  }) {
40
- const shop = await getHydrogenShop({ path, shop: flagShop });
41
- const { storefront: configStorefront } = await getConfig(path ?? process.cwd());
42
- if (configStorefront && !force) {
35
+ const [{ session, config }, cliCommand] = await Promise.all([
36
+ login(root),
37
+ getCliCommand()
38
+ ]);
39
+ const linkedStore = await linkStorefront(root, session, config, {
40
+ force,
41
+ flagStorefront,
42
+ cliCommand
43
+ });
44
+ if (!linkedStore)
45
+ return;
46
+ renderSuccess({
47
+ body: [{ userInput: linkedStore.title }, "is now linked"],
48
+ nextSteps: [
49
+ [
50
+ "Run",
51
+ { command: `${cliCommand} dev` },
52
+ "to start your local development server and start building"
53
+ ]
54
+ ]
55
+ });
56
+ }
57
+ async function linkStorefront(root, session, config, {
58
+ force = false,
59
+ flagStorefront,
60
+ cliCommand
61
+ }) {
62
+ if (!config.shop) {
63
+ throw new AbortError("No shop found in local config, login first.");
64
+ }
65
+ if (config.storefront?.id && !force) {
43
66
  const overwriteLink = await renderConfirmationPrompt({
44
- message: `Your project is currently linked to ${configStorefront.title}. Do you want to link to a different Hydrogen storefront on Shopify?`
67
+ message: `Your project is currently linked to ${config.storefront.title}. Do you want to link to a different Hydrogen storefront on Shopify?`
45
68
  });
46
69
  if (!overwriteLink) {
47
70
  return;
48
71
  }
49
72
  }
50
- const { storefronts, adminSession } = await getStorefronts(shop);
51
- if (storefronts.length === 0) {
52
- logMissingStorefronts(adminSession);
53
- return;
54
- }
73
+ const storefronts = await getStorefronts(session);
55
74
  let selectedStorefront;
56
- let selectCreateNewStorefront = false;
57
- const cliCommand = await getCliCommand();
58
75
  if (flagStorefront) {
59
76
  selectedStorefront = storefronts.find(
60
77
  ({ title }) => title === flagStorefront
@@ -66,7 +83,7 @@ async function linkStorefront({
66
83
  "There's no storefront matching",
67
84
  { userInput: flagStorefront },
68
85
  "on your",
69
- { userInput: shop },
86
+ { userInput: config.shop },
70
87
  "shop. To see all available Hydrogen storefronts, run",
71
88
  {
72
89
  command: `${cliCommand} list`
@@ -76,36 +93,36 @@ async function linkStorefront({
76
93
  return;
77
94
  }
78
95
  } else {
79
- const choices = storefronts.map(({ id, title, productionUrl }) => ({
80
- value: id,
81
- label: `${title} (${productionUrl})`
82
- }));
96
+ const choices = [
97
+ {
98
+ label: "Create a new storefront",
99
+ value: null
100
+ },
101
+ ...storefronts.map(({ id, title, productionUrl }) => ({
102
+ label: `${title} (${productionUrl})`,
103
+ value: id
104
+ }))
105
+ ];
83
106
  const storefrontId = await renderSelectPrompt({
84
- message: "Choose a Hydrogen storefront to link",
107
+ message: "Select a Hydrogen storefront to link",
85
108
  choices
86
109
  });
87
- if (storefrontId === CREATE_NEW_STOREFRONT_ID) {
88
- selectCreateNewStorefront = true;
89
- } else {
110
+ if (storefrontId) {
90
111
  selectedStorefront = storefronts.find(({ id }) => id === storefrontId);
112
+ } else {
113
+ selectedStorefront = await createNewStorefront(root, session);
91
114
  }
92
115
  }
93
- if (selectCreateNewStorefront) {
94
- const storefront = await createNewStorefront(path, shop);
95
- if (!storefront) {
96
- return;
97
- }
98
- selectedStorefront = storefront;
99
- }
100
116
  if (selectedStorefront) {
101
- await linkExistingStorefront(path, selectedStorefront, silent, cliCommand);
117
+ await setStorefront(root, selectedStorefront);
102
118
  }
119
+ return selectedStorefront;
103
120
  }
104
- async function createNewStorefront(path, shop) {
105
- const projectDirectory = path && basename(path);
121
+ async function createNewStorefront(root, session) {
122
+ const projectDirectory = basename(root);
106
123
  const projectName = await renderTextPrompt({
107
- message: "What do you want to name the Hydrogen storefront on Shopify?",
108
- defaultValue: titleize(projectDirectory) || "Hydrogen Storefront"
124
+ message: "New storefront name",
125
+ defaultValue: titleize(projectDirectory)
109
126
  });
110
127
  let storefront;
111
128
  let jobId;
@@ -113,45 +130,29 @@ async function createNewStorefront(path, shop) {
113
130
  {
114
131
  title: "Creating storefront",
115
132
  task: async () => {
116
- const result = await createStorefront(shop, projectName);
133
+ const result = await createStorefront(session, projectName);
117
134
  storefront = result.storefront;
118
135
  jobId = result.jobId;
119
- if (result.userErrors.length > 0) {
120
- renderUserErrors(result.userErrors);
121
- }
122
136
  }
123
137
  },
124
138
  {
125
139
  title: "Creating API tokens",
126
140
  task: async () => {
127
141
  try {
128
- await waitForJob(shop, jobId);
142
+ await waitForJob(session, jobId);
129
143
  } catch (_err) {
130
144
  storefront = void 0;
131
- renderError(
132
- "Please try again or contact support if the error persists."
133
- );
134
145
  }
135
146
  },
136
147
  skip: () => !jobId
137
148
  }
138
149
  ]);
139
- return storefront;
140
- }
141
- async function linkExistingStorefront(path, selectedStorefront, silent, cliCommand) {
142
- await setStorefront(path ?? process.cwd(), selectedStorefront);
143
- if (!silent) {
144
- renderSuccess({
145
- body: [{ userInput: selectedStorefront.title }, "is now linked"],
146
- nextSteps: [
147
- [
148
- "Run",
149
- { command: `${cliCommand} dev` },
150
- "to start your local development server and start building"
151
- ]
152
- ]
153
- });
150
+ if (!storefront) {
151
+ throw new AbortError(
152
+ "Unknown error ocurred. Please try again or contact support if the error persists."
153
+ );
154
154
  }
155
+ return storefront;
155
156
  }
156
157
 
157
- export { Link as default, linkStorefront };
158
+ export { Link as default, linkStorefront, runLink };
@@ -1,19 +1,13 @@
1
1
  import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
2
  import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
3
3
  import { renderSelectPrompt, renderTextPrompt, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
4
- import { adminRequest } from '../../lib/graphql.js';
4
+ import { login } from '../../lib/auth.js';
5
5
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
6
+ import { runLink } from './link.js';
6
7
  import { createStorefront } from '../../lib/graphql/admin/create-storefront.js';
7
8
  import { waitForJob } from '../../lib/graphql/admin/fetch-job.js';
8
- import { getConfig, setStorefront } from '../../lib/shopify-config.js';
9
- import { renderUserErrors, renderError } from '../../lib/user-errors.js';
10
- import { linkStorefront } from './link.js';
9
+ import { setStorefront } from '../../lib/shopify-config.js';
11
10
 
12
- const SHOP = "my-shop";
13
- const ADMIN_SESSION = {
14
- token: "abc123",
15
- storeFqdn: SHOP
16
- };
17
11
  vi.mock("@shopify/cli-kit/node/ui", async () => {
18
12
  const original = await vi.importActual("@shopify/cli-kit/node/ui");
19
13
  return {
@@ -23,41 +17,62 @@ vi.mock("@shopify/cli-kit/node/ui", async () => {
23
17
  renderTextPrompt: vi.fn()
24
18
  };
25
19
  });
26
- vi.mock("../../lib/graphql.js");
20
+ vi.mock("../../lib/auth.js");
27
21
  vi.mock("../../lib/shopify-config.js");
28
22
  vi.mock("../../lib/graphql/admin/link-storefront.js");
29
23
  vi.mock("../../lib/graphql/admin/create-storefront.js");
30
24
  vi.mock("../../lib/graphql/admin/fetch-job.js");
31
- vi.mock("../../lib/user-errors.js");
32
- vi.mock("../../lib/shop.js", () => ({
33
- getHydrogenShop: () => SHOP
34
- }));
35
- vi.mock("../../lib/shell.js", () => ({
36
- getCliCommand: () => "h2"
37
- }));
25
+ vi.mock("../../lib/shell.js", () => ({ getCliCommand: () => "h2" }));
38
26
  describe("link", () => {
39
27
  const outputMock = mockAndCaptureOutput();
28
+ const ADMIN_SESSION = {
29
+ token: "abc123",
30
+ storeFqdn: "my-shop.myshopify.com"
31
+ };
32
+ const FULL_SHOPIFY_CONFIG = {
33
+ shop: "my-shop.myshopify.com",
34
+ shopName: "My Shop",
35
+ email: "email",
36
+ storefront: {
37
+ id: "gid://shopify/HydrogenStorefront/1",
38
+ title: "Hydrogen"
39
+ }
40
+ };
41
+ const UNLINKED_SHOPIFY_CONFIG = {
42
+ ...FULL_SHOPIFY_CONFIG,
43
+ storefront: void 0
44
+ };
40
45
  beforeEach(async () => {
41
- vi.mocked(getStorefronts).mockResolvedValue({
42
- adminSession: ADMIN_SESSION,
43
- storefronts: [
44
- {
45
- id: "gid://shopify/HydrogenStorefront/1",
46
- parsedId: "1",
47
- title: "Hydrogen",
48
- productionUrl: "https://example.com"
49
- }
50
- ]
51
- });
52
- vi.mocked(getConfig).mockResolvedValue({});
46
+ vi.mocked(login).mockResolvedValue({
47
+ session: ADMIN_SESSION,
48
+ config: UNLINKED_SHOPIFY_CONFIG
49
+ });
50
+ vi.mocked(getStorefronts).mockResolvedValue([
51
+ {
52
+ ...FULL_SHOPIFY_CONFIG.storefront,
53
+ parsedId: "1",
54
+ productionUrl: "https://example.com"
55
+ }
56
+ ]);
57
+ vi.mocked(renderSelectPrompt).mockResolvedValue(FULL_SHOPIFY_CONFIG.shop);
53
58
  });
54
59
  afterEach(() => {
55
60
  vi.resetAllMocks();
56
61
  outputMock.clear();
57
62
  });
58
- it("makes a GraphQL call to fetch the storefronts", async () => {
59
- await linkStorefront({});
60
- expect(getStorefronts).toHaveBeenCalledWith(SHOP);
63
+ it("fetches the storefronts", async () => {
64
+ await runLink({});
65
+ expect(getStorefronts).toHaveBeenCalledWith(ADMIN_SESSION);
66
+ });
67
+ it("renders a list of choices and forwards the selection to setStorefront", async () => {
68
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
69
+ FULL_SHOPIFY_CONFIG.storefront.id
70
+ );
71
+ await runLink({ path: "my-path" });
72
+ expect(setStorefront).toHaveBeenCalledWith(
73
+ "my-path",
74
+ expect.objectContaining(FULL_SHOPIFY_CONFIG.storefront)
75
+ );
61
76
  });
62
77
  describe("when you want to link an existing Hydrogen storefront", () => {
63
78
  beforeEach(async () => {
@@ -66,141 +81,91 @@ describe("link", () => {
66
81
  );
67
82
  });
68
83
  it("renders a list of choices and forwards the selection to setStorefront", async () => {
69
- await linkStorefront({ path: "my-path" });
84
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
85
+ FULL_SHOPIFY_CONFIG.storefront.id
86
+ );
87
+ await runLink({ path: "my-path" });
70
88
  expect(setStorefront).toHaveBeenCalledWith(
71
89
  "my-path",
72
- expect.objectContaining({
73
- id: "gid://shopify/HydrogenStorefront/1",
74
- title: "Hydrogen"
75
- })
90
+ expect.objectContaining(FULL_SHOPIFY_CONFIG.storefront)
76
91
  );
77
92
  });
78
93
  it("renders a success message", async () => {
79
- await linkStorefront({ path: "my-path" });
80
- expect(outputMock.info()).toMatch(/Hydrogen is now linked/g);
81
- expect(outputMock.info()).toMatch(
82
- /Run `h2 dev` to start your local development server and start building/g
94
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
95
+ FULL_SHOPIFY_CONFIG.storefront.id
83
96
  );
97
+ await runLink({ path: "my-path" });
98
+ expect(outputMock.info()).toMatch(/is now linked/i);
99
+ expect(outputMock.info()).toMatch(/Run `h2 dev`/i);
84
100
  });
85
101
  });
86
102
  describe("when you want to link a new Hydrogen storefront", () => {
87
103
  const expectedStorefrontName = "New Storefront";
88
104
  const expectedJobId = "gid://shopify/Job/1";
89
105
  beforeEach(async () => {
90
- vi.mocked(renderSelectPrompt).mockResolvedValue("NEW_STOREFRONT");
106
+ vi.mocked(renderSelectPrompt).mockResolvedValue(null);
91
107
  vi.mocked(createStorefront).mockResolvedValue({
92
- adminSession: ADMIN_SESSION,
93
108
  storefront: {
94
109
  id: "gid://shopify/HydrogenStorefront/1",
95
110
  title: expectedStorefrontName,
96
111
  productionUrl: "https://example.com"
97
112
  },
98
- userErrors: [],
99
113
  jobId: expectedJobId
100
114
  });
101
115
  });
102
116
  it("chooses to create a new storefront given the directory path", async () => {
103
- await linkStorefront({ path: "my-path" });
117
+ await runLink({ path: "my-path" });
104
118
  expect(renderTextPrompt).toHaveBeenCalledWith({
105
119
  message: expect.stringMatching(/name/i),
106
120
  defaultValue: "My Path"
107
121
  });
108
122
  });
109
- it("chooses to create a new storefront without directory path", async () => {
110
- await linkStorefront({});
111
- expect(renderTextPrompt).toHaveBeenCalledWith({
112
- message: expect.stringMatching(/name/i),
113
- defaultValue: "Hydrogen Storefront"
114
- });
115
- });
116
123
  it("handles the successful creation of the storefront on Admin", async () => {
117
- await linkStorefront({});
118
- expect(waitForJob).toHaveBeenCalledWith(SHOP, expectedJobId);
124
+ await runLink({});
125
+ expect(waitForJob).toHaveBeenCalledWith(ADMIN_SESSION, expectedJobId);
119
126
  expect(outputMock.info()).toContain(
120
127
  `${expectedStorefrontName} is now linked`
121
128
  );
122
129
  });
123
- it("handles the user-errors when creating the storefront on Admin", async () => {
124
- const expectedUserErrors = [
125
- {
126
- code: "INVALID",
127
- field: [],
128
- message: "Bad thing happend."
129
- }
130
- ];
131
- vi.mocked(createStorefront).mockResolvedValue({
132
- adminSession: ADMIN_SESSION,
133
- storefront: void 0,
134
- userErrors: expectedUserErrors,
135
- jobId: void 0
136
- });
137
- await linkStorefront({});
138
- expect(waitForJob).not.toHaveBeenCalled();
139
- expect(renderUserErrors).toHaveBeenCalledWith(expectedUserErrors);
140
- });
141
130
  it("handles the job errors when creating the storefront on Admin", async () => {
142
131
  vi.mocked(waitForJob).mockRejectedValue(void 0);
143
- await linkStorefront({});
144
- expect(renderError).toHaveBeenCalled();
145
- });
146
- });
147
- describe("when there are no Hydrogen storefronts", () => {
148
- it("renders a message and returns early", async () => {
149
- vi.mocked(getStorefronts).mockResolvedValue({
150
- adminSession: ADMIN_SESSION,
151
- storefronts: []
152
- });
153
- await linkStorefront({});
154
- expect(outputMock.info()).toMatch(
155
- /There are no Hydrogen storefronts on your Shop/g
156
- );
157
- expect(renderSelectPrompt).not.toHaveBeenCalled();
158
- expect(setStorefront).not.toHaveBeenCalled();
159
- });
160
- });
161
- describe("when no storefront gets selected", () => {
162
- it("does not call setStorefront", async () => {
163
- vi.mocked(renderSelectPrompt).mockResolvedValue("");
164
- await linkStorefront({});
165
- expect(setStorefront).not.toHaveBeenCalled();
132
+ await expect(runLink({})).rejects.toThrow(Error);
166
133
  });
167
134
  });
168
135
  describe("when a linked storefront already exists", () => {
169
136
  beforeEach(() => {
170
- vi.mocked(getConfig).mockResolvedValue({
171
- storefront: {
172
- id: "gid://shopify/HydrogenStorefront/2",
173
- title: "Existing Link"
174
- }
137
+ vi.mocked(login).mockResolvedValue({
138
+ session: ADMIN_SESSION,
139
+ config: FULL_SHOPIFY_CONFIG
175
140
  });
176
141
  });
177
142
  it("prompts the user to confirm", async () => {
178
143
  vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
179
- await linkStorefront({});
144
+ await runLink({});
180
145
  expect(renderConfirmationPrompt).toHaveBeenCalledWith({
181
146
  message: expect.stringMatching(
182
- /Do you want to link to a different Hydrogen storefront on Shopify\?/
147
+ /link to a different Hydrogen storefront/i
183
148
  )
184
149
  });
185
150
  });
186
151
  describe("and the user cancels", () => {
187
152
  it("returns early", async () => {
188
153
  vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);
189
- await linkStorefront({});
190
- expect(adminRequest).not.toHaveBeenCalled();
154
+ await runLink({});
155
+ expect(getStorefronts).not.toHaveBeenCalled();
191
156
  expect(setStorefront).not.toHaveBeenCalled();
192
157
  });
193
158
  });
194
159
  describe("and the --force flag is provided", () => {
195
160
  it("does not prompt the user to confirm", async () => {
196
- await linkStorefront({ force: true });
161
+ await runLink({ force: true });
197
162
  expect(renderConfirmationPrompt).not.toHaveBeenCalled();
198
163
  });
199
164
  });
200
165
  });
201
166
  describe("when the --storefront flag is provided", () => {
202
167
  it("does not prompt the user to make a selection", async () => {
203
- await linkStorefront({ path: "my-path", storefront: "Hydrogen" });
168
+ await runLink({ path: "my-path", storefront: "Hydrogen" });
204
169
  expect(renderSelectPrompt).not.toHaveBeenCalled();
205
170
  expect(setStorefront).toHaveBeenCalledWith(
206
171
  "my-path",
@@ -213,7 +178,7 @@ describe("link", () => {
213
178
  describe("and there is no matching storefront", () => {
214
179
  it("renders a warning message and returns early", async () => {
215
180
  const outputMock2 = mockAndCaptureOutput();
216
- await linkStorefront({ storefront: "Does not exist" });
181
+ await runLink({ storefront: "Does not exist" });
217
182
  expect(setStorefront).not.toHaveBeenCalled();
218
183
  expect(outputMock2.warn()).toMatch(/Couldn\'t find Does not exist/g);
219
184
  });
@@ -2,32 +2,33 @@ import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { pluralize } from '@shopify/cli-kit/common/string';
3
3
  import colors from '@shopify/cli-kit/node/colors';
4
4
  import { outputNewline, outputInfo, outputContent } from '@shopify/cli-kit/node/output';
5
+ import { renderInfo } from '@shopify/cli-kit/node/ui';
5
6
  import { commonFlags } from '../../lib/flags.js';
6
- import { getHydrogenShop } from '../../lib/shop.js';
7
- import { parseGid } from '../../lib/graphql.js';
7
+ import { parseGid } from '../../lib/gid.js';
8
8
  import { getStorefrontsWithDeployment } from '../../lib/graphql/admin/list-storefronts.js';
9
- import { logMissingStorefronts } from '../../lib/missing-storefronts.js';
9
+ import { newHydrogenStorefrontUrl } from '../../lib/admin-urls.js';
10
+ import { login } from '../../lib/auth.js';
11
+ import { getCliCommand } from '../../lib/shell.js';
10
12
 
11
13
  class List extends Command {
12
14
  static description = "Returns a list of Hydrogen storefronts available on a given shop.";
13
15
  static flags = {
14
- path: commonFlags.path,
15
- shop: commonFlags.shop
16
+ path: commonFlags.path
16
17
  };
17
18
  async run() {
18
19
  const { flags } = await this.parse(List);
19
- await listStorefronts(flags);
20
+ await runList(flags);
20
21
  }
21
22
  }
22
- async function listStorefronts({ path, shop: flagShop }) {
23
- const shop = await getHydrogenShop({ path, shop: flagShop });
24
- const { storefronts, adminSession } = await getStorefrontsWithDeployment(shop);
23
+ async function runList({ path: root = process.cwd() }) {
24
+ const { session } = await login(root);
25
+ const storefronts = await getStorefrontsWithDeployment(session);
25
26
  if (storefronts.length > 0) {
26
27
  outputNewline();
27
28
  outputInfo(
28
29
  pluralizedStorefronts({
29
30
  storefronts,
30
- shop
31
+ shop: session.storeFqdn
31
32
  }).toString()
32
33
  );
33
34
  storefronts.forEach(
@@ -53,7 +54,16 @@ async function listStorefronts({ path, shop: flagShop }) {
53
54
  }
54
55
  );
55
56
  } else {
56
- logMissingStorefronts(adminSession);
57
+ renderInfo({
58
+ headline: "Hydrogen storefronts",
59
+ body: "There are no Hydrogen storefronts on your Shop.",
60
+ nextSteps: [
61
+ `Ensure you are logged in to the correct shop (currently: ${session.storeFqdn})`,
62
+ `Create a new Hydrogen storefront: Run \`${await getCliCommand(
63
+ root
64
+ )} link\` or visit ${newHydrogenStorefrontUrl(session)}`
65
+ ]
66
+ });
57
67
  }
58
68
  }
59
69
  const dateFormat = new Intl.DateTimeFormat("default", {
@@ -84,4 +94,4 @@ const pluralizedStorefronts = ({
84
94
  );
85
95
  };
86
96
 
87
- export { List as default, formatDeployment, listStorefronts };
97
+ export { List as default, formatDeployment, runList };