@shopify/cli-hydrogen 5.0.1 → 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 (240) hide show
  1. package/dist/commands/hydrogen/build.js +38 -8
  2. package/dist/commands/hydrogen/codegen-unstable.js +13 -24
  3. package/dist/commands/hydrogen/dev.js +61 -41
  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 +101 -43
  13. package/dist/commands/hydrogen/link.test.js +108 -74
  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/preview.js +2 -1
  19. package/dist/commands/hydrogen/setup/css.js +79 -0
  20. package/dist/commands/hydrogen/setup/markets.js +53 -0
  21. package/dist/commands/hydrogen/setup.js +133 -0
  22. package/dist/commands/hydrogen/shortcut.js +2 -45
  23. package/dist/commands/hydrogen/shortcut.test.js +10 -37
  24. package/dist/generator-templates/assets/css-modules/package.json +6 -0
  25. package/dist/generator-templates/assets/postcss/package.json +10 -0
  26. package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
  27. package/dist/generator-templates/assets/tailwind/package.json +13 -0
  28. package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
  29. package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
  30. package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
  31. package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
  32. package/dist/generator-templates/starter/.eslintignore +5 -0
  33. package/dist/generator-templates/starter/.eslintrc.js +18 -0
  34. package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
  35. package/dist/generator-templates/starter/README.md +40 -0
  36. package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
  37. package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
  38. package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
  39. package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
  40. package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
  41. package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
  42. package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
  43. package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
  44. package/dist/generator-templates/starter/app/root.tsx +270 -0
  45. package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
  46. package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
  47. package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
  48. package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
  49. package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
  50. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
  51. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
  52. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
  53. package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
  54. package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
  55. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
  56. package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
  57. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
  58. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
  59. package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
  60. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
  61. package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
  62. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
  63. package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
  64. package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
  65. package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
  66. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
  67. package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
  68. package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
  69. package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
  70. package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
  71. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
  72. package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
  73. package/dist/generator-templates/starter/app/styles/app.css +473 -0
  74. package/dist/generator-templates/starter/app/styles/reset.css +129 -0
  75. package/dist/generator-templates/starter/app/utils.ts +46 -0
  76. package/dist/generator-templates/starter/package.json +43 -0
  77. package/dist/generator-templates/starter/public/favicon.svg +28 -0
  78. package/dist/generator-templates/starter/remix.config.js +26 -0
  79. package/dist/generator-templates/starter/remix.env.d.ts +39 -0
  80. package/dist/generator-templates/starter/server.ts +253 -0
  81. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
  82. package/dist/generator-templates/starter/tsconfig.json +22 -0
  83. package/dist/lib/auth.js +123 -0
  84. package/dist/lib/auth.test.js +157 -0
  85. package/dist/lib/build.js +51 -0
  86. package/dist/lib/check-version.js +3 -3
  87. package/dist/lib/check-version.test.js +24 -0
  88. package/dist/lib/codegen.js +26 -17
  89. package/dist/lib/environment-variables.js +68 -0
  90. package/dist/lib/environment-variables.test.js +147 -0
  91. package/dist/lib/file.js +41 -0
  92. package/dist/lib/file.test.js +69 -0
  93. package/dist/lib/flags.js +39 -2
  94. package/dist/lib/format-code.js +26 -0
  95. package/dist/lib/gid.js +12 -0
  96. package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
  97. package/dist/lib/graphql/admin/client.js +27 -0
  98. package/dist/lib/graphql/admin/client.test.js +51 -0
  99. package/dist/lib/graphql/admin/create-storefront.js +36 -0
  100. package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
  101. package/dist/lib/graphql/admin/fetch-job.js +39 -0
  102. package/dist/lib/graphql/admin/link-storefront.js +7 -11
  103. package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
  104. package/dist/lib/graphql/admin/list-environments.js +2 -2
  105. package/dist/lib/graphql/admin/list-environments.test.js +44 -0
  106. package/dist/lib/graphql/admin/list-storefronts.js +7 -11
  107. package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
  108. package/dist/lib/graphql/admin/pull-variables.js +3 -3
  109. package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
  110. package/dist/lib/graphql/business-platform/user-account.js +83 -0
  111. package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
  112. package/dist/lib/log.js +185 -9
  113. package/dist/lib/log.test.js +92 -0
  114. package/dist/lib/mini-oxygen.js +32 -14
  115. package/dist/lib/missing-routes.js +0 -2
  116. package/dist/lib/onboarding/common.js +456 -0
  117. package/dist/lib/onboarding/index.js +2 -0
  118. package/dist/lib/onboarding/local.js +229 -0
  119. package/dist/lib/onboarding/remote.js +89 -0
  120. package/dist/lib/remix-version-interop.js +5 -5
  121. package/dist/lib/remix-version-interop.test.js +11 -1
  122. package/dist/lib/render-errors.js +13 -11
  123. package/dist/lib/setups/css/assets.js +89 -0
  124. package/dist/lib/setups/css/css-modules.js +22 -0
  125. package/dist/lib/setups/css/index.js +44 -0
  126. package/dist/lib/setups/css/postcss.js +34 -0
  127. package/dist/lib/setups/css/replacers.js +137 -0
  128. package/dist/lib/setups/css/tailwind.js +54 -0
  129. package/dist/lib/setups/css/vanilla-extract.js +22 -0
  130. package/dist/lib/setups/i18n/domains.test.js +25 -0
  131. package/dist/lib/setups/i18n/index.js +46 -0
  132. package/dist/lib/setups/i18n/replacers.js +227 -0
  133. package/dist/lib/setups/i18n/subdomains.test.js +25 -0
  134. package/dist/lib/setups/i18n/subfolders.test.js +25 -0
  135. package/dist/lib/setups/i18n/templates/domains.js +14 -0
  136. package/dist/lib/setups/i18n/templates/domains.ts +25 -0
  137. package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
  138. package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
  139. package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
  140. package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
  141. package/dist/lib/setups/routes/generate.js +244 -0
  142. package/dist/lib/setups/routes/generate.test.js +313 -0
  143. package/dist/lib/shell.js +52 -5
  144. package/dist/lib/shell.test.js +42 -16
  145. package/dist/lib/shopify-config.js +23 -18
  146. package/dist/lib/shopify-config.test.js +63 -73
  147. package/dist/lib/string.js +7 -0
  148. package/dist/lib/string.test.js +16 -0
  149. package/dist/lib/template-downloader.js +9 -7
  150. package/dist/lib/transpile-ts.js +9 -29
  151. package/dist/virtual-routes/routes/index.jsx +40 -19
  152. package/oclif.manifest.json +710 -1
  153. package/package.json +16 -16
  154. package/dist/commands/hydrogen/build.d.ts +0 -23
  155. package/dist/commands/hydrogen/check.d.ts +0 -15
  156. package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
  157. package/dist/commands/hydrogen/dev.d.ts +0 -21
  158. package/dist/commands/hydrogen/env/list.d.ts +0 -18
  159. package/dist/commands/hydrogen/env/pull.d.ts +0 -22
  160. package/dist/commands/hydrogen/g.d.ts +0 -10
  161. package/dist/commands/hydrogen/generate/route.d.ts +0 -32
  162. package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
  163. package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
  164. package/dist/commands/hydrogen/init.d.ts +0 -24
  165. package/dist/commands/hydrogen/init.test.d.ts +0 -1
  166. package/dist/commands/hydrogen/link.d.ts +0 -23
  167. package/dist/commands/hydrogen/link.test.d.ts +0 -1
  168. package/dist/commands/hydrogen/list.d.ts +0 -21
  169. package/dist/commands/hydrogen/list.test.d.ts +0 -1
  170. package/dist/commands/hydrogen/preview.d.ts +0 -17
  171. package/dist/commands/hydrogen/shortcut.d.ts +0 -9
  172. package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
  173. package/dist/commands/hydrogen/unlink.d.ts +0 -16
  174. package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
  175. package/dist/create-app.d.ts +0 -1
  176. package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
  177. package/dist/generator-templates/routes/account/login.tsx +0 -103
  178. package/dist/generator-templates/routes/account/register.tsx +0 -103
  179. package/dist/generator-templates/routes/cart.tsx +0 -81
  180. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
  181. package/dist/generator-templates/routes/collections/index.tsx +0 -102
  182. package/dist/generator-templates/routes/graphiql.tsx +0 -10
  183. package/dist/generator-templates/routes/index.tsx +0 -40
  184. package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
  185. package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
  186. package/dist/generator-templates/routes/policies/index.tsx +0 -117
  187. package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
  188. package/dist/hooks/init.d.ts +0 -5
  189. package/dist/lib/admin-session.d.ts +0 -6
  190. package/dist/lib/admin-session.js +0 -16
  191. package/dist/lib/admin-session.test.d.ts +0 -1
  192. package/dist/lib/admin-session.test.js +0 -27
  193. package/dist/lib/admin-urls.d.ts +0 -8
  194. package/dist/lib/check-lockfile.d.ts +0 -3
  195. package/dist/lib/check-lockfile.test.d.ts +0 -1
  196. package/dist/lib/check-version.d.ts +0 -16
  197. package/dist/lib/check-version.test.d.ts +0 -1
  198. package/dist/lib/codegen.d.ts +0 -26
  199. package/dist/lib/combined-environment-variables.d.ts +0 -8
  200. package/dist/lib/combined-environment-variables.js +0 -57
  201. package/dist/lib/combined-environment-variables.test.d.ts +0 -1
  202. package/dist/lib/combined-environment-variables.test.js +0 -111
  203. package/dist/lib/config.d.ts +0 -20
  204. package/dist/lib/flags.d.ts +0 -27
  205. package/dist/lib/flags.test.d.ts +0 -1
  206. package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
  207. package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
  208. package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
  209. package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
  210. package/dist/lib/graphql.d.ts +0 -21
  211. package/dist/lib/graphql.js +0 -18
  212. package/dist/lib/graphql.test.d.ts +0 -1
  213. package/dist/lib/log.d.ts +0 -6
  214. package/dist/lib/mini-oxygen.d.ts +0 -14
  215. package/dist/lib/missing-routes.d.ts +0 -8
  216. package/dist/lib/missing-routes.test.d.ts +0 -1
  217. package/dist/lib/missing-storefronts.d.ts +0 -5
  218. package/dist/lib/missing-storefronts.js +0 -18
  219. package/dist/lib/process.d.ts +0 -6
  220. package/dist/lib/pull-environment-variables.d.ts +0 -20
  221. package/dist/lib/pull-environment-variables.js +0 -57
  222. package/dist/lib/pull-environment-variables.test.d.ts +0 -1
  223. package/dist/lib/pull-environment-variables.test.js +0 -174
  224. package/dist/lib/remix-version-interop.d.ts +0 -11
  225. package/dist/lib/remix-version-interop.test.d.ts +0 -1
  226. package/dist/lib/render-errors.d.ts +0 -16
  227. package/dist/lib/shell.d.ts +0 -11
  228. package/dist/lib/shell.test.d.ts +0 -1
  229. package/dist/lib/shop.d.ts +0 -7
  230. package/dist/lib/shop.js +0 -32
  231. package/dist/lib/shop.test.d.ts +0 -1
  232. package/dist/lib/shop.test.js +0 -78
  233. package/dist/lib/shopify-config.d.ts +0 -35
  234. package/dist/lib/shopify-config.test.d.ts +0 -1
  235. package/dist/lib/template-downloader.d.ts +0 -6
  236. package/dist/lib/transpile-ts.d.ts +0 -16
  237. package/dist/lib/virtual-routes.d.ts +0 -7
  238. package/dist/lib/virtual-routes.test.d.ts +0 -1
  239. /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
  240. /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
@@ -1,137 +1,171 @@
1
1
  import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
2
  import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
3
- import { renderSelectPrompt, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
4
- import { adminRequest } from '../../lib/graphql.js';
3
+ import { renderSelectPrompt, renderTextPrompt, renderConfirmationPrompt } from '@shopify/cli-kit/node/ui';
4
+ import { login } from '../../lib/auth.js';
5
5
  import { getStorefronts } from '../../lib/graphql/admin/link-storefront.js';
6
- import { getConfig, setStorefront } from '../../lib/shopify-config.js';
7
- import { linkStorefront } from './link.js';
6
+ import { runLink } from './link.js';
7
+ import { createStorefront } from '../../lib/graphql/admin/create-storefront.js';
8
+ import { waitForJob } from '../../lib/graphql/admin/fetch-job.js';
9
+ import { setStorefront } from '../../lib/shopify-config.js';
8
10
 
9
- const SHOP = "my-shop";
10
- const ADMIN_SESSION = {
11
- token: "abc123",
12
- storeFqdn: SHOP
13
- };
14
11
  vi.mock("@shopify/cli-kit/node/ui", async () => {
15
12
  const original = await vi.importActual("@shopify/cli-kit/node/ui");
16
13
  return {
17
14
  ...original,
18
15
  renderConfirmationPrompt: vi.fn(),
19
- renderSelectPrompt: vi.fn()
16
+ renderSelectPrompt: vi.fn(),
17
+ renderTextPrompt: vi.fn()
20
18
  };
21
19
  });
22
- vi.mock("../../lib/graphql.js");
20
+ vi.mock("../../lib/auth.js");
23
21
  vi.mock("../../lib/shopify-config.js");
24
22
  vi.mock("../../lib/graphql/admin/link-storefront.js");
25
- vi.mock("../../lib/shop.js", () => ({
26
- getHydrogenShop: () => SHOP
27
- }));
28
- vi.mock("../../lib/shell.js", () => ({
29
- getCliCommand: () => "h2"
30
- }));
23
+ vi.mock("../../lib/graphql/admin/create-storefront.js");
24
+ vi.mock("../../lib/graphql/admin/fetch-job.js");
25
+ vi.mock("../../lib/shell.js", () => ({ getCliCommand: () => "h2" }));
31
26
  describe("link", () => {
32
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
+ };
33
45
  beforeEach(async () => {
34
- vi.mocked(getStorefronts).mockResolvedValue({
35
- adminSession: ADMIN_SESSION,
36
- storefronts: [
37
- {
38
- id: "gid://shopify/HydrogenStorefront/1",
39
- parsedId: "1",
40
- title: "Hydrogen",
41
- productionUrl: "https://example.com"
42
- }
43
- ]
46
+ vi.mocked(login).mockResolvedValue({
47
+ session: ADMIN_SESSION,
48
+ config: UNLINKED_SHOPIFY_CONFIG
44
49
  });
45
- vi.mocked(getConfig).mockResolvedValue({});
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);
46
58
  });
47
59
  afterEach(() => {
48
60
  vi.resetAllMocks();
49
61
  outputMock.clear();
50
62
  });
51
- it("makes a GraphQL call to fetch the storefronts", async () => {
52
- await linkStorefront({});
53
- expect(getStorefronts).toHaveBeenCalledWith(SHOP);
63
+ it("fetches the storefronts", async () => {
64
+ await runLink({});
65
+ expect(getStorefronts).toHaveBeenCalledWith(ADMIN_SESSION);
54
66
  });
55
67
  it("renders a list of choices and forwards the selection to setStorefront", async () => {
56
68
  vi.mocked(renderSelectPrompt).mockResolvedValue(
57
- "gid://shopify/HydrogenStorefront/1"
69
+ FULL_SHOPIFY_CONFIG.storefront.id
58
70
  );
59
- await linkStorefront({ path: "my-path" });
71
+ await runLink({ path: "my-path" });
60
72
  expect(setStorefront).toHaveBeenCalledWith(
61
73
  "my-path",
62
- expect.objectContaining({
63
- id: "gid://shopify/HydrogenStorefront/1",
64
- title: "Hydrogen"
65
- })
74
+ expect.objectContaining(FULL_SHOPIFY_CONFIG.storefront)
66
75
  );
67
76
  });
68
- it("renders a success message", async () => {
69
- vi.mocked(renderSelectPrompt).mockResolvedValue(
70
- "gid://shopify/HydrogenStorefront/1"
71
- );
72
- await linkStorefront({ path: "my-path" });
73
- expect(outputMock.info()).toMatch(/Hydrogen is now linked/g);
74
- expect(outputMock.info()).toMatch(
75
- /Run `h2 dev` to start your local development server and start building/g
76
- );
77
+ describe("when you want to link an existing Hydrogen storefront", () => {
78
+ beforeEach(async () => {
79
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
80
+ "gid://shopify/HydrogenStorefront/1"
81
+ );
82
+ });
83
+ it("renders a list of choices and forwards the selection to setStorefront", async () => {
84
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
85
+ FULL_SHOPIFY_CONFIG.storefront.id
86
+ );
87
+ await runLink({ path: "my-path" });
88
+ expect(setStorefront).toHaveBeenCalledWith(
89
+ "my-path",
90
+ expect.objectContaining(FULL_SHOPIFY_CONFIG.storefront)
91
+ );
92
+ });
93
+ it("renders a success message", async () => {
94
+ vi.mocked(renderSelectPrompt).mockResolvedValue(
95
+ FULL_SHOPIFY_CONFIG.storefront.id
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);
100
+ });
77
101
  });
78
- describe("when there are no Hydrogen storefronts", () => {
79
- it("renders a message and returns early", async () => {
80
- vi.mocked(getStorefronts).mockResolvedValue({
81
- adminSession: ADMIN_SESSION,
82
- storefronts: []
102
+ describe("when you want to link a new Hydrogen storefront", () => {
103
+ const expectedStorefrontName = "New Storefront";
104
+ const expectedJobId = "gid://shopify/Job/1";
105
+ beforeEach(async () => {
106
+ vi.mocked(renderSelectPrompt).mockResolvedValue(null);
107
+ vi.mocked(createStorefront).mockResolvedValue({
108
+ storefront: {
109
+ id: "gid://shopify/HydrogenStorefront/1",
110
+ title: expectedStorefrontName,
111
+ productionUrl: "https://example.com"
112
+ },
113
+ jobId: expectedJobId
83
114
  });
84
- await linkStorefront({});
85
- expect(outputMock.info()).toMatch(
86
- /There are no Hydrogen storefronts on your Shop/g
115
+ });
116
+ it("chooses to create a new storefront given the directory path", async () => {
117
+ await runLink({ path: "my-path" });
118
+ expect(renderTextPrompt).toHaveBeenCalledWith({
119
+ message: expect.stringMatching(/name/i),
120
+ defaultValue: "My Path"
121
+ });
122
+ });
123
+ it("handles the successful creation of the storefront on Admin", async () => {
124
+ await runLink({});
125
+ expect(waitForJob).toHaveBeenCalledWith(ADMIN_SESSION, expectedJobId);
126
+ expect(outputMock.info()).toContain(
127
+ `${expectedStorefrontName} is now linked`
87
128
  );
88
- expect(renderSelectPrompt).not.toHaveBeenCalled();
89
- expect(setStorefront).not.toHaveBeenCalled();
90
129
  });
91
- });
92
- describe("when no storefront gets selected", () => {
93
- it("does not call setStorefront", async () => {
94
- vi.mocked(renderSelectPrompt).mockResolvedValue("");
95
- await linkStorefront({});
96
- expect(setStorefront).not.toHaveBeenCalled();
130
+ it("handles the job errors when creating the storefront on Admin", async () => {
131
+ vi.mocked(waitForJob).mockRejectedValue(void 0);
132
+ await expect(runLink({})).rejects.toThrow(Error);
97
133
  });
98
134
  });
99
135
  describe("when a linked storefront already exists", () => {
100
136
  beforeEach(() => {
101
- vi.mocked(getConfig).mockResolvedValue({
102
- storefront: {
103
- id: "gid://shopify/HydrogenStorefront/2",
104
- title: "Existing Link"
105
- }
137
+ vi.mocked(login).mockResolvedValue({
138
+ session: ADMIN_SESSION,
139
+ config: FULL_SHOPIFY_CONFIG
106
140
  });
107
141
  });
108
142
  it("prompts the user to confirm", async () => {
109
143
  vi.mocked(renderConfirmationPrompt).mockResolvedValue(true);
110
- await linkStorefront({});
144
+ await runLink({});
111
145
  expect(renderConfirmationPrompt).toHaveBeenCalledWith({
112
146
  message: expect.stringMatching(
113
- /Do you want to link to a different Hydrogen storefront on Shopify\?/
147
+ /link to a different Hydrogen storefront/i
114
148
  )
115
149
  });
116
150
  });
117
151
  describe("and the user cancels", () => {
118
152
  it("returns early", async () => {
119
153
  vi.mocked(renderConfirmationPrompt).mockResolvedValue(false);
120
- await linkStorefront({});
121
- expect(adminRequest).not.toHaveBeenCalled();
154
+ await runLink({});
155
+ expect(getStorefronts).not.toHaveBeenCalled();
122
156
  expect(setStorefront).not.toHaveBeenCalled();
123
157
  });
124
158
  });
125
159
  describe("and the --force flag is provided", () => {
126
160
  it("does not prompt the user to confirm", async () => {
127
- await linkStorefront({ force: true });
161
+ await runLink({ force: true });
128
162
  expect(renderConfirmationPrompt).not.toHaveBeenCalled();
129
163
  });
130
164
  });
131
165
  });
132
166
  describe("when the --storefront flag is provided", () => {
133
167
  it("does not prompt the user to make a selection", async () => {
134
- await linkStorefront({ path: "my-path", storefront: "Hydrogen" });
168
+ await runLink({ path: "my-path", storefront: "Hydrogen" });
135
169
  expect(renderSelectPrompt).not.toHaveBeenCalled();
136
170
  expect(setStorefront).toHaveBeenCalledWith(
137
171
  "my-path",
@@ -144,7 +178,7 @@ describe("link", () => {
144
178
  describe("and there is no matching storefront", () => {
145
179
  it("renders a warning message and returns early", async () => {
146
180
  const outputMock2 = mockAndCaptureOutput();
147
- await linkStorefront({ storefront: "Does not exist" });
181
+ await runLink({ storefront: "Does not exist" });
148
182
  expect(setStorefront).not.toHaveBeenCalled();
149
183
  expect(outputMock2.warn()).toMatch(/Couldn\'t find Does not exist/g);
150
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 };
@@ -1,83 +1,86 @@
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 { getStorefrontsWithDeployment } from '../../lib/graphql/admin/list-storefronts.js';
4
- import { listStorefronts, formatDeployment } from './list.js';
4
+ import { runList, formatDeployment } from './list.js';
5
+ import { login } from '../../lib/auth.js';
5
6
 
6
- const SHOP_NAME = "my-shop";
7
- vi.mock("../../lib/graphql/admin/list-storefronts.js", async () => {
8
- return { getStorefrontsWithDeployment: vi.fn() };
9
- });
10
- vi.mock("../../lib/shop.js", () => ({
11
- getHydrogenShop: () => SHOP_NAME
12
- }));
7
+ vi.mock("../../lib/auth.js");
8
+ vi.mock("../../lib/graphql/admin/list-storefronts.js");
13
9
  describe("list", () => {
14
10
  const ADMIN_SESSION = {
15
11
  token: "abc123",
16
- storeFqdn: SHOP_NAME
12
+ storeFqdn: "my-shop"
13
+ };
14
+ const SHOPIFY_CONFIG = {
15
+ shop: "my-shop.myshopify.com",
16
+ shopName: "My Shop",
17
+ email: "email",
18
+ storefront: {
19
+ id: "gid://shopify/HydrogenStorefront/1",
20
+ title: "Hydrogen"
21
+ }
17
22
  };
18
23
  beforeEach(async () => {
19
- vi.mocked(getStorefrontsWithDeployment).mockResolvedValue({
20
- adminSession: ADMIN_SESSION,
21
- storefronts: []
24
+ vi.mocked(login).mockResolvedValue({
25
+ session: ADMIN_SESSION,
26
+ config: SHOPIFY_CONFIG
22
27
  });
28
+ vi.mocked(getStorefrontsWithDeployment).mockResolvedValue([]);
23
29
  });
24
30
  afterEach(() => {
25
31
  vi.resetAllMocks();
26
32
  mockAndCaptureOutput().clear();
27
33
  });
28
- it("makes a GraphQL call to fetch the storefronts", async () => {
29
- await listStorefronts({});
30
- expect(getStorefrontsWithDeployment).toHaveBeenCalledWith(SHOP_NAME);
34
+ it("fetches the storefronts", async () => {
35
+ await runList({});
36
+ expect(getStorefrontsWithDeployment).toHaveBeenCalledWith(ADMIN_SESSION);
31
37
  });
32
38
  describe("and there are storefronts", () => {
33
39
  beforeEach(() => {
34
- vi.mocked(getStorefrontsWithDeployment).mockResolvedValue({
35
- adminSession: ADMIN_SESSION,
36
- storefronts: [
37
- {
38
- id: "gid://shopify/HydrogenStorefront/1",
39
- parsedId: "1",
40
- title: "Hydrogen",
41
- productionUrl: "https://example.com",
42
- currentProductionDeployment: null
43
- },
44
- {
45
- id: "gid://shopify/HydrogenStorefront/2",
46
- parsedId: "2",
47
- title: "Demo Store",
48
- productionUrl: "https://demo.example.com",
49
- currentProductionDeployment: {
50
- id: "gid://shopify/HydrogenStorefrontDeployment/1",
51
- createdAt: "2023-03-22T22:28:38Z",
52
- commitMessage: "Update README.md"
53
- }
40
+ vi.mocked(getStorefrontsWithDeployment).mockResolvedValue([
41
+ {
42
+ id: "gid://shopify/HydrogenStorefront/1",
43
+ parsedId: "1",
44
+ title: "Hydrogen",
45
+ productionUrl: "https://example.com",
46
+ currentProductionDeployment: null
47
+ },
48
+ {
49
+ id: "gid://shopify/HydrogenStorefront/2",
50
+ parsedId: "2",
51
+ title: "Demo Store",
52
+ productionUrl: "https://demo.example.com",
53
+ currentProductionDeployment: {
54
+ id: "gid://shopify/HydrogenStorefrontDeployment/1",
55
+ createdAt: "2023-03-22T22:28:38Z",
56
+ commitMessage: "Update README.md"
54
57
  }
55
- ]
56
- });
58
+ }
59
+ ]);
57
60
  });
58
61
  it("renders a list of storefronts", async () => {
59
62
  const outputMock = mockAndCaptureOutput();
60
- await listStorefronts({});
63
+ await runList({});
61
64
  expect(outputMock.info()).toMatch(
62
- /Showing 2 Hydrogen storefronts for the store my-shop/g
65
+ /Showing 2 Hydrogen storefronts for the store my-shop/i
63
66
  );
64
- expect(outputMock.info()).toMatch(/Hydrogen \(id: 1\)/g);
65
- expect(outputMock.info()).toMatch(/https:\/\/example.com/g);
66
- expect(outputMock.info()).toMatch(/Demo Store \(id: 2\)/g);
67
- expect(outputMock.info()).toMatch(/https:\/\/demo.example.com/g);
68
- expect(outputMock.info()).toMatch(/3\/22\/2023, Update README.md/g);
67
+ expect(outputMock.info()).toMatch(/Hydrogen \(id: 1\)/);
68
+ expect(outputMock.info()).toMatch(/https:\/\/example.com/);
69
+ expect(outputMock.info()).toMatch(/Demo Store \(id: 2\)/);
70
+ expect(outputMock.info()).toMatch(/https:\/\/demo.example.com/);
71
+ expect(outputMock.info()).toMatch(/3\/22\/2023, Update README.md/);
69
72
  });
70
73
  });
71
74
  describe("and there are no storefronts", () => {
72
75
  it("prompts the user to create a storefront", async () => {
73
76
  const outputMock = mockAndCaptureOutput();
74
- await listStorefronts({});
77
+ await runList({});
75
78
  expect(outputMock.info()).toMatch(
76
- /There are no Hydrogen storefronts on your Shop\./g
79
+ /There are no Hydrogen storefronts on your Shop\./i
77
80
  );
78
- expect(outputMock.info()).toMatch(/Create a new Hydrogen storefront/g);
81
+ expect(outputMock.info()).toMatch(/Create a new Hydrogen storefront/i);
79
82
  expect(outputMock.info()).toMatch(
80
- /https:\/\/my\-shop\/admin\/custom_storefronts\/new/g
83
+ /https:\/\/my\-shop\/admin\/custom_storefronts\/new/
81
84
  );
82
85
  });
83
86
  });
@@ -0,0 +1,31 @@
1
+ import { Flags } from '@oclif/core';
2
+ import Command from '@shopify/cli-kit/node/base-command';
3
+ import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
4
+ import { commonFlags } from '../../lib/flags.js';
5
+ import { login, renderLoginSuccess } from '../../lib/auth.js';
6
+
7
+ class Login extends Command {
8
+ static description = "Login to your Shopify account.";
9
+ static flags = {
10
+ path: commonFlags.path,
11
+ shop: Flags.string({
12
+ char: "s",
13
+ description: "Shop URL. It can be the shop prefix (janes-apparel) or the full myshopify.com URL (janes-apparel.myshopify.com, https://janes-apparel.myshopify.com).",
14
+ env: "SHOPIFY_SHOP",
15
+ parse: async (input) => normalizeStoreFqdn(input)
16
+ })
17
+ };
18
+ async run() {
19
+ const { flags } = await this.parse(Login);
20
+ await runLogin(flags);
21
+ }
22
+ }
23
+ async function runLogin({
24
+ path: root = process.cwd(),
25
+ shop: shopFlag
26
+ }) {
27
+ const { config } = await login(root, shopFlag ?? true);
28
+ renderLoginSuccess(config);
29
+ }
30
+
31
+ export { Login as default };
@@ -0,0 +1,21 @@
1
+ import Command from '@shopify/cli-kit/node/base-command';
2
+ import { renderSuccess } from '@shopify/cli-kit/node/ui';
3
+ import { commonFlags } from '../../lib/flags.js';
4
+ import { logout } from '../../lib/auth.js';
5
+
6
+ class Logout extends Command {
7
+ static description = "Logout of your local session.";
8
+ static flags = {
9
+ path: commonFlags.path
10
+ };
11
+ async run() {
12
+ const { flags } = await this.parse(Logout);
13
+ await runLogout(flags);
14
+ }
15
+ }
16
+ async function runLogout({ path: root = process.cwd() }) {
17
+ await logout(root);
18
+ renderSuccess({ body: "You are logged out from Shopify." });
19
+ }
20
+
21
+ export { Logout as default };
@@ -23,12 +23,13 @@ async function runPreview({
23
23
  process.env.NODE_ENV = "production";
24
24
  muteDevLogs({ workerReload: false });
25
25
  const { root, buildPathWorkerFile, buildPathClient } = getProjectPaths(appPath);
26
- await startMiniOxygen({
26
+ const miniOxygen = await startMiniOxygen({
27
27
  root,
28
28
  port,
29
29
  buildPathClient,
30
30
  buildPathWorkerFile
31
31
  });
32
+ miniOxygen.showBanner({ mode: "preview" });
32
33
  }
33
34
 
34
35
  export { Preview as default, runPreview };
@@ -0,0 +1,79 @@
1
+ import { resolvePath } from '@shopify/cli-kit/node/path';
2
+ import { commonFlags, overrideFlag, flagsToCamelObject } from '../../../lib/flags.js';
3
+ import Command from '@shopify/cli-kit/node/base-command';
4
+ import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
5
+ import { getPackageManager, installNodeModules } from '@shopify/cli-kit/node/node-package-manager';
6
+ import { Args } from '@oclif/core';
7
+ import { getRemixConfig } from '../../../lib/config.js';
8
+ import { SETUP_CSS_STRATEGIES, renderCssPrompt, setupCssStrategy, CSS_STRATEGY_NAME_MAP } from '../../../lib/setups/css/index.js';
9
+
10
+ class SetupCSS extends Command {
11
+ static description = "Setup CSS strategies for your project.";
12
+ static flags = {
13
+ path: commonFlags.path,
14
+ force: commonFlags.force,
15
+ "install-deps": overrideFlag(commonFlags.installDeps, { default: true })
16
+ };
17
+ static args = {
18
+ strategy: Args.string({
19
+ name: "strategy",
20
+ description: `The CSS strategy to setup. One of ${SETUP_CSS_STRATEGIES.join()}`,
21
+ options: SETUP_CSS_STRATEGIES
22
+ })
23
+ };
24
+ async run() {
25
+ const { flags, args } = await this.parse(SetupCSS);
26
+ const directory = flags.path ? resolvePath(flags.path) : process.cwd();
27
+ await runSetupCSS({
28
+ ...flagsToCamelObject(flags),
29
+ strategy: args.strategy,
30
+ directory
31
+ });
32
+ }
33
+ }
34
+ async function runSetupCSS({
35
+ strategy: flagStrategy,
36
+ directory,
37
+ force = false,
38
+ installDeps = true
39
+ }) {
40
+ const remixConfigPromise = getRemixConfig(directory);
41
+ const strategy = flagStrategy ? flagStrategy : await renderCssPrompt();
42
+ const remixConfig = await remixConfigPromise;
43
+ const setupOutput = await setupCssStrategy(strategy, remixConfig, force);
44
+ if (!setupOutput)
45
+ return;
46
+ const { workPromise, generatedAssets, helpUrl } = setupOutput;
47
+ const tasks = [
48
+ {
49
+ title: "Updating files",
50
+ task: async () => {
51
+ await workPromise;
52
+ }
53
+ }
54
+ ];
55
+ if (installDeps) {
56
+ const gettingPkgManagerPromise = getPackageManager(
57
+ remixConfig.rootDirectory
58
+ );
59
+ tasks.push({
60
+ title: "Installing new dependencies",
61
+ task: async () => {
62
+ const packageManager = await gettingPkgManagerPromise;
63
+ await installNodeModules({
64
+ directory: remixConfig.rootDirectory,
65
+ packageManager,
66
+ args: []
67
+ });
68
+ }
69
+ });
70
+ }
71
+ await renderTasks(tasks);
72
+ renderSuccess({
73
+ headline: `${CSS_STRATEGY_NAME_MAP[strategy]} setup complete.`,
74
+ body: (generatedAssets.length > 0 ? "You can now modify CSS configuration in the following files:\n" + generatedAssets.map((file) => ` - ${file}`).join("\n") + "\n" : "") + `
75
+ For more information, visit ${helpUrl}.`
76
+ });
77
+ }
78
+
79
+ export { SetupCSS as default, runSetupCSS };
@@ -0,0 +1,53 @@
1
+ import { resolvePath } from '@shopify/cli-kit/node/path';
2
+ import { commonFlags, flagsToCamelObject } from '../../../lib/flags.js';
3
+ import Command from '@shopify/cli-kit/node/base-command';
4
+ import { renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
5
+ import { Args } from '@oclif/core';
6
+ import { getRemixConfig } from '../../../lib/config.js';
7
+ import { SETUP_I18N_STRATEGIES, renderI18nPrompt, setupI18nStrategy, I18N_STRATEGY_NAME_MAP } from '../../../lib/setups/i18n/index.js';
8
+
9
+ class SetupMarkets extends Command {
10
+ static description = "Setup support for multiple markets in your project.";
11
+ static flags = {
12
+ path: commonFlags.path
13
+ };
14
+ static args = {
15
+ strategy: Args.string({
16
+ name: "strategy",
17
+ description: `The URL structure strategy to setup multiple markets. One of ${SETUP_I18N_STRATEGIES.join()}`,
18
+ options: SETUP_I18N_STRATEGIES
19
+ })
20
+ };
21
+ async run() {
22
+ const { flags, args } = await this.parse(SetupMarkets);
23
+ const directory = flags.path ? resolvePath(flags.path) : process.cwd();
24
+ await runSetupI18n({
25
+ ...flagsToCamelObject(flags),
26
+ strategy: args.strategy,
27
+ directory
28
+ });
29
+ }
30
+ }
31
+ async function runSetupI18n({
32
+ strategy: flagStrategy,
33
+ directory
34
+ }) {
35
+ const remixConfigPromise = getRemixConfig(directory);
36
+ const strategy = flagStrategy ? flagStrategy : await renderI18nPrompt();
37
+ const remixConfig = await remixConfigPromise;
38
+ await renderTasks([
39
+ {
40
+ title: "Updating files",
41
+ task: async () => {
42
+ await setupI18nStrategy(strategy, remixConfig);
43
+ }
44
+ }
45
+ ]);
46
+ renderSuccess({
47
+ headline: `Makerts support setup complete with strategy ${I18N_STRATEGY_NAME_MAP[strategy].toLocaleLowerCase()}.`,
48
+ body: `You can now modify the supported locales in ${remixConfig.serverEntryPoint ?? "your server entry file."}
49
+ `
50
+ });
51
+ }
52
+
53
+ export { SetupMarkets as default, runSetupI18n };