@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,4 +1,4 @@
1
- import { adminRequest } from '../../graphql.js';
1
+ import { adminRequest } from './client.js';
2
2
 
3
3
  const ListEnvironmentsQuery = `#graphql
4
4
  query ListStorefronts($id: ID!) {
@@ -22,7 +22,7 @@ async function getStorefrontEnvironments(adminSession, storefrontId) {
22
22
  adminSession,
23
23
  { id: storefrontId }
24
24
  );
25
- return { storefront: hydrogenStorefront };
25
+ return hydrogenStorefront;
26
26
  }
27
27
 
28
28
  export { getStorefrontEnvironments };
@@ -0,0 +1,44 @@
1
+ import { vi, describe, afterEach, it, expect } from 'vitest';
2
+ import { adminRequest } from './client.js';
3
+ import { getStorefrontEnvironments } from './list-environments.js';
4
+
5
+ vi.mock("./client.js");
6
+ describe("getStorefrontEnvironments", () => {
7
+ const ADMIN_SESSION = {
8
+ token: "abc123",
9
+ storeFqdn: "my-shop.myshopify.com"
10
+ };
11
+ afterEach(() => {
12
+ vi.resetAllMocks();
13
+ });
14
+ it("calls the graphql client and returns Hydrogen storefronts", async () => {
15
+ const mockedResponse = {
16
+ hydrogenStorefront: {
17
+ id: "gid://shopify/HydrogenStorefront/123",
18
+ productionUrl: "https://...",
19
+ environments: [
20
+ {
21
+ createdAt: "2021-01-01T00:00:00Z",
22
+ id: "e123",
23
+ name: "Staging",
24
+ type: "CUSTOM",
25
+ branch: "staging",
26
+ url: "https://..."
27
+ }
28
+ ]
29
+ }
30
+ };
31
+ vi.mocked(adminRequest).mockResolvedValue(
32
+ mockedResponse
33
+ );
34
+ const id = "123";
35
+ await expect(
36
+ getStorefrontEnvironments(ADMIN_SESSION, id)
37
+ ).resolves.toStrictEqual(mockedResponse.hydrogenStorefront);
38
+ expect(adminRequest).toHaveBeenCalledWith(
39
+ expect.stringMatching(/^#graphql.+query.+hydrogenStorefront\(/s),
40
+ ADMIN_SESSION,
41
+ { id }
42
+ );
43
+ });
44
+ });
@@ -1,5 +1,5 @@
1
- import { adminRequest, parseGid } from '../../graphql.js';
2
- import { getAdminSession } from '../../admin-session.js';
1
+ import { adminRequest } from './client.js';
2
+ import { parseGid } from '../../gid.js';
3
3
 
4
4
  const ListStorefrontsQuery = `#graphql
5
5
  query ListStorefronts {
@@ -15,19 +15,15 @@ const ListStorefrontsQuery = `#graphql
15
15
  }
16
16
  }
17
17
  `;
18
- async function getStorefrontsWithDeployment(shop) {
19
- const adminSession = await getAdminSession(shop);
18
+ async function getStorefrontsWithDeployment(adminSession) {
20
19
  const { hydrogenStorefronts } = await adminRequest(
21
20
  ListStorefrontsQuery,
22
21
  adminSession
23
22
  );
24
- return {
25
- adminSession,
26
- storefronts: hydrogenStorefronts.map((storefront) => ({
27
- ...storefront,
28
- parsedId: parseGid(storefront.id)
29
- }))
30
- };
23
+ return hydrogenStorefronts.map((storefront) => ({
24
+ ...storefront,
25
+ parsedId: parseGid(storefront.id)
26
+ }));
31
27
  }
32
28
 
33
29
  export { getStorefrontsWithDeployment };
@@ -0,0 +1,44 @@
1
+ import { vi, describe, afterEach, it, expect } from 'vitest';
2
+ import { adminRequest } from './client.js';
3
+ import { getStorefrontsWithDeployment } from './list-storefronts.js';
4
+
5
+ vi.mock("./client.js");
6
+ describe("getStorefrontsWithDeployment", () => {
7
+ const ADMIN_SESSION = {
8
+ token: "abc123",
9
+ storeFqdn: "my-shop.myshopify.com"
10
+ };
11
+ afterEach(() => {
12
+ vi.resetAllMocks();
13
+ });
14
+ it("calls the graphql client and returns Hydrogen storefronts", async () => {
15
+ const mockedResponse = {
16
+ hydrogenStorefronts: [
17
+ {
18
+ id: "gid://shopify/HydrogenStorefront/123",
19
+ title: "title",
20
+ currentProductionDeployment: {
21
+ id: "d123",
22
+ createdAt: "2021-01-01T00:00:00Z",
23
+ commitMessage: null
24
+ }
25
+ }
26
+ ]
27
+ };
28
+ vi.mocked(adminRequest).mockResolvedValue(
29
+ mockedResponse
30
+ );
31
+ await expect(
32
+ getStorefrontsWithDeployment(ADMIN_SESSION)
33
+ ).resolves.toStrictEqual([
34
+ {
35
+ ...mockedResponse.hydrogenStorefronts[0],
36
+ parsedId: "123"
37
+ }
38
+ ]);
39
+ expect(adminRequest).toHaveBeenCalledWith(
40
+ expect.stringMatching(/^#graphql.+query.+hydrogenStorefronts\s*{/s),
41
+ ADMIN_SESSION
42
+ );
43
+ });
44
+ });
@@ -1,4 +1,4 @@
1
- import { adminRequest } from '../../graphql.js';
1
+ import { adminRequest } from './client.js';
2
2
 
3
3
  const PullVariablesQuery = `#graphql
4
4
  query PullVariables($id: ID!, $branch: String) {
@@ -22,7 +22,7 @@ async function getStorefrontEnvVariables(adminSession, storefrontId, envBranch)
22
22
  branch: envBranch
23
23
  }
24
24
  );
25
- return { storefront: hydrogenStorefront };
25
+ return hydrogenStorefront;
26
26
  }
27
27
 
28
- export { PullVariablesQuery, getStorefrontEnvVariables };
28
+ export { getStorefrontEnvVariables };
@@ -0,0 +1,37 @@
1
+ import { vi, describe, afterEach, it, expect } from 'vitest';
2
+ import { adminRequest } from './client.js';
3
+ import { getStorefrontEnvVariables } from './pull-variables.js';
4
+
5
+ vi.mock("./client.js");
6
+ describe("getStorefrontEnvVariables", () => {
7
+ const ADMIN_SESSION = {
8
+ token: "abc123",
9
+ storeFqdn: "my-shop.myshopify.com"
10
+ };
11
+ afterEach(() => {
12
+ vi.resetAllMocks();
13
+ });
14
+ it("calls the graphql client and returns Hydrogen storefronts", async () => {
15
+ const mockedResponse = {
16
+ hydrogenStorefront: {
17
+ id: "123",
18
+ environmentVariables: [
19
+ { id: "123", isSecret: false, key: "key", value: "value" }
20
+ ]
21
+ }
22
+ };
23
+ vi.mocked(adminRequest).mockResolvedValue(
24
+ mockedResponse
25
+ );
26
+ const id = "123";
27
+ const branch = "staging";
28
+ await expect(
29
+ getStorefrontEnvVariables(ADMIN_SESSION, id, branch)
30
+ ).resolves.toStrictEqual(mockedResponse.hydrogenStorefront);
31
+ expect(adminRequest).toHaveBeenCalledWith(
32
+ expect.stringMatching(/^#graphql.+query.+hydrogenStorefront\(/s),
33
+ ADMIN_SESSION,
34
+ { id, branch }
35
+ );
36
+ });
37
+ });
@@ -0,0 +1,83 @@
1
+ import { businessPlatformRequest } from '@shopify/cli-kit/node/api/business-platform';
2
+ import { AbortError } from '@shopify/cli-kit/node/error';
3
+
4
+ const CurrentUserAccountQuery = `#graphql
5
+ query currentUserAccount {
6
+ currentUserAccount {
7
+ email
8
+ organizations(first: 100) {
9
+ edges {
10
+ node {
11
+ id
12
+ name
13
+ categories(handles: STORES) {
14
+ destinations(first: 100) {
15
+ edges {
16
+ node {
17
+ ...destinationFields
18
+ }
19
+ }
20
+ pageInfo {
21
+ endCursor
22
+ hasNextPage
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ pageInfo {
29
+ endCursor
30
+ hasNextPage
31
+ }
32
+ }
33
+ orphanDestinations {
34
+ categories(handles: STORES) {
35
+ destinations(first: 100) {
36
+ edges {
37
+ node {
38
+ ...destinationFields
39
+ }
40
+ }
41
+ pageInfo {
42
+ endCursor
43
+ hasNextPage
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ fragment destinationFields on Destination {
52
+ name
53
+ status
54
+ webUrl
55
+ }
56
+ `;
57
+ async function getUserAccount(token) {
58
+ const { currentUserAccount } = await businessPlatformRequest(
59
+ CurrentUserAccountQuery,
60
+ token
61
+ );
62
+ if (!currentUserAccount) {
63
+ throw new AbortError("Unable to get current user account");
64
+ }
65
+ return {
66
+ email: currentUserAccount.email,
67
+ activeShops: [
68
+ ...currentUserAccount.organizations?.edges?.flatMap(
69
+ (edge) => edge.node?.categories.flatMap(
70
+ (category) => category?.destinations?.edges?.map((edge2) => edge2.node)
71
+ )
72
+ ) ?? [],
73
+ ...currentUserAccount.orphanDestinations?.categories?.flatMap(
74
+ (category) => category.destinations?.edges?.map((edge) => edge.node)
75
+ ) ?? []
76
+ ].filter(({ status }) => status === "ACTIVE").map(({ name, webUrl }) => ({
77
+ name,
78
+ fqdn: webUrl.replace(/^https?:\/\//, "").replace(/\/admin$/, "")
79
+ }))
80
+ };
81
+ }
82
+
83
+ export { getUserAccount };
@@ -0,0 +1,80 @@
1
+ import { vi, describe, afterEach, it, expect } from 'vitest';
2
+ import { businessPlatformRequest } from '@shopify/cli-kit/node/api/business-platform';
3
+ import { getUserAccount } from './user-account.js';
4
+
5
+ vi.mock("@shopify/cli-kit/node/api/business-platform");
6
+ describe("getUserAccount", () => {
7
+ afterEach(() => {
8
+ vi.resetAllMocks();
9
+ });
10
+ it("fetches the current user account and merges destinations", async () => {
11
+ vi.mocked(businessPlatformRequest).mockResolvedValue({
12
+ currentUserAccount: {
13
+ email: "email",
14
+ organizations: {
15
+ edges: [
16
+ {
17
+ node: {
18
+ id: "gid://shopify/Organization/123",
19
+ name: "name",
20
+ categories: [
21
+ {
22
+ destinations: {
23
+ edges: [
24
+ {
25
+ node: {
26
+ name: "n1",
27
+ webUrl: "w1",
28
+ status: "ACTIVE"
29
+ }
30
+ }
31
+ ],
32
+ pageInfo: {}
33
+ }
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ],
39
+ pageInfo: {}
40
+ },
41
+ orphanDestinations: {
42
+ categories: [
43
+ {
44
+ destinations: {
45
+ edges: [
46
+ {
47
+ node: {
48
+ name: "n2",
49
+ webUrl: "w2",
50
+ status: "NOPE"
51
+ }
52
+ },
53
+ {
54
+ node: {
55
+ name: "n3",
56
+ webUrl: "w3",
57
+ status: "ACTIVE"
58
+ }
59
+ }
60
+ ],
61
+ pageInfo: {}
62
+ }
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ });
68
+ await expect(getUserAccount("123")).resolves.toStrictEqual({
69
+ email: "email",
70
+ activeShops: [
71
+ { name: "n1", fqdn: "w1" },
72
+ { name: "n3", fqdn: "w3" }
73
+ ]
74
+ });
75
+ expect(businessPlatformRequest).toHaveBeenCalledWith(
76
+ expect.stringMatching(/^#graphql.+query.+currentUserAccount\s*{/s),
77
+ "123"
78
+ );
79
+ });
80
+ });
package/dist/lib/log.js CHANGED
@@ -1,21 +1,197 @@
1
- let isFirstWorkerReload = true;
1
+ import { renderFatalError, renderWarning, renderInfo } from '@shopify/cli-kit/node/ui';
2
+ import { BugError } from '@shopify/cli-kit/node/error';
3
+ import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
4
+ import colors from '@shopify/cli-kit/node/colors';
5
+
6
+ const originalConsole = { ...console };
7
+ const methodsReplaced = /* @__PURE__ */ new Set();
8
+ const addedReplacers = /* @__PURE__ */ new Set();
9
+ const messageReplacers = [];
10
+ function resetAllLogs() {
11
+ Object.assign(console, originalConsole);
12
+ methodsReplaced.clear();
13
+ }
14
+ function addMessageReplacers(key, ...items) {
15
+ if (!addedReplacers.has(key)) {
16
+ addedReplacers.add(key);
17
+ messageReplacers.push(...items);
18
+ }
19
+ }
20
+ const printedMessages = /* @__PURE__ */ new Set();
21
+ function debounceMessage(args, debounceFor) {
22
+ const key = args.map((item) => {
23
+ const message = item?.message ?? item;
24
+ return typeof message === "string" ? message : "";
25
+ }).filter(Boolean).join("");
26
+ if (printedMessages.has(key))
27
+ return true;
28
+ printedMessages.add(key);
29
+ if (debounceFor !== true) {
30
+ setTimeout(() => printedMessages.delete(key), debounceFor ?? 1e3);
31
+ }
32
+ return false;
33
+ }
34
+ function injectLogReplacer(method, debouncer) {
35
+ if (!methodsReplaced.has(method)) {
36
+ methodsReplaced.add(method);
37
+ console[method] = (...args) => {
38
+ if (debounceMessage(args, debouncer?.(args)))
39
+ return;
40
+ const replacer = messageReplacers.find(([matcher]) => matcher(args))?.[1];
41
+ if (!replacer)
42
+ return originalConsole[method](...args);
43
+ const result = replacer(args);
44
+ if (result)
45
+ return originalConsole[method](...result);
46
+ };
47
+ }
48
+ }
2
49
  function muteDevLogs({ workerReload } = {}) {
3
- const log = console.log.bind(console);
4
- console.log = (first, ...rest) => {
5
- if (typeof first === "string" && first.includes("[mf:")) {
50
+ injectLogReplacer("log");
51
+ let isFirstWorkerReload = true;
52
+ addMessageReplacers("dev", [
53
+ ([first]) => typeof first === "string" && first.includes("[mf:"),
54
+ (args) => {
55
+ const first = args[0];
6
56
  if (workerReload !== false && first.includes("Worker reloaded")) {
7
- if (isFirstWorkerReload)
57
+ if (isFirstWorkerReload) {
8
58
  isFirstWorkerReload = false;
9
- else
10
- return log(first.replace("[mf:inf] ", "\u{1F504} ") + "\n", ...rest);
59
+ return;
60
+ }
61
+ return [first.replace("[mf:inf] ", "\u{1F504} ") + "\n", ...args.slice(1)];
11
62
  }
12
63
  if (!first.includes("[mf:err]")) {
13
64
  return;
14
65
  }
15
66
  }
16
- return log(first, ...rest);
67
+ ]);
68
+ }
69
+ const originalWrite = process.stdout.write;
70
+ function muteAuthLogs({
71
+ onPressKey,
72
+ onKeyTimeout
73
+ }) {
74
+ if (process.stdout.write === originalWrite) {
75
+ const write = originalWrite.bind(process.stdout);
76
+ process.stdout.write = (item, cb) => {
77
+ if (typeof item !== "string")
78
+ return write(item, cb);
79
+ const replacer = messageReplacers.find(
80
+ ([matcher]) => matcher([item])
81
+ )?.[1];
82
+ if (!replacer)
83
+ return write(item, cb);
84
+ const result = replacer([item]);
85
+ if (result)
86
+ return write(result[0], cb);
87
+ };
88
+ }
89
+ addMessageReplacers(
90
+ "auth",
91
+ [
92
+ ([first]) => typeof first === "string" && first.includes("Auto-open"),
93
+ ([first]) => {
94
+ const content = first.replace(" to Shopify Partners", "");
95
+ const link = content.match(/(https?:\/\/.*)Log in/)?.[1];
96
+ onKeyTimeout(link);
97
+ if (link)
98
+ return;
99
+ return [content];
100
+ }
101
+ ],
102
+ [
103
+ ([first]) => typeof first === "string" && first.includes("\u{1F449}"),
104
+ () => {
105
+ onPressKey();
106
+ return;
107
+ }
108
+ ],
109
+ [
110
+ ([first]) => typeof first === "string" && (first.includes("Shopify Partners") || first.includes("Logged in")),
111
+ () => {
112
+ return;
113
+ }
114
+ ]
115
+ );
116
+ return () => {
117
+ process.stdout.write = originalWrite;
17
118
  };
18
119
  }
120
+ function enhanceH2Logs(options) {
121
+ injectLogReplacer("error");
122
+ injectLogReplacer(
123
+ "warn",
124
+ ([first]) => first?.includes?.("[h2:warn:createStorefrontClient]") ? true : void 0
125
+ );
126
+ addMessageReplacers("h2-warn", [
127
+ ([first]) => {
128
+ const message = first?.message ?? first;
129
+ return typeof message === "string" && message.startsWith("[h2:");
130
+ },
131
+ (args) => {
132
+ const firstArg = args[0];
133
+ const errorObject = typeof firstArg === "object" && !!firstArg.stack ? firstArg : void 0;
134
+ const stringArg = errorObject?.message ?? firstArg;
135
+ const [, type, scope, message] = stringArg.match(/^\[h2:([^:]+):([^\]]+)\]\s+(.*)$/ims) || [];
136
+ if (!type || !scope || !message)
137
+ return args;
138
+ const headline = `In Hydrogen's \`${scope.trim()}\`:
139
+
140
+ `;
141
+ const lines = message.split("\n");
142
+ const lastLine = lines.at(-1) ?? "";
143
+ const hasLinks = /https?:\/\//.test(lastLine);
144
+ if (hasLinks)
145
+ lines.pop();
146
+ if (type === "error" || errorObject) {
147
+ let tryMessage = hasLinks ? lastLine : void 0;
148
+ let stack = errorObject?.stack;
149
+ const cause = errorObject?.cause;
150
+ if (!!cause?.graphql?.query) {
151
+ const { query, variables } = cause.graphql;
152
+ const link = `${options.graphiqlUrl}?query=${encodeURIComponent(
153
+ query
154
+ )}${variables ? `&variables=${encodeURIComponent(variables)}` : ""}`;
155
+ const [, queryType, queryName] = query.match(/(query|mutation)\s+(\w+)/) || [];
156
+ tryMessage = (tryMessage ? `${tryMessage}
157
+
158
+ ` : "") + outputContent`To debug the ${queryType || "query"}${queryName ? ` \`${colors.whiteBright(queryName)}\`` : ""}, try it in ${outputToken.link(colors.bold("GraphiQL"), link)}.`.value;
159
+ }
160
+ const stackLines = stack?.split("\n") ?? [];
161
+ const isAppLine = (line) => line.includes(options.rootDirectory) && !line.includes("node_modules");
162
+ const firstAppLineIndex = stackLines.findIndex(isAppLine);
163
+ const lastAppLineIndex = stackLines.length - [...stackLines].reverse().findIndex(isAppLine);
164
+ if (firstAppLineIndex > 0 && lastAppLineIndex > firstAppLineIndex) {
165
+ stack = [
166
+ stackLines[0],
167
+ ...stackLines.slice(firstAppLineIndex, lastAppLineIndex)
168
+ ].join("\n").trim() || void 0;
169
+ }
170
+ const error = new BugError(
171
+ headline + colors.bold(lines.join("\n").replace(" - Request", "\nRequest")),
172
+ tryMessage
173
+ );
174
+ error.cause = cause;
175
+ error.stack = stack;
176
+ renderFatalError(error);
177
+ return;
178
+ }
179
+ let reference = void 0;
180
+ if (hasLinks) {
181
+ reference = [];
182
+ for (const link of lastLine.matchAll(/https?:\/\/[^\s]+/g)) {
183
+ reference.push(link[0]);
184
+ }
185
+ }
186
+ const render = type === "warn" ? renderWarning : renderInfo;
187
+ render({
188
+ body: headline + colors.bold(lines.join("\n")),
189
+ reference
190
+ });
191
+ return;
192
+ }
193
+ ]);
194
+ }
19
195
  const warnings = /* @__PURE__ */ new Set();
20
196
  const warnOnce = (string) => {
21
197
  if (!warnings.has(string)) {
@@ -24,4 +200,4 @@ const warnOnce = (string) => {
24
200
  }
25
201
  };
26
202
 
27
- export { muteDevLogs, warnOnce };
203
+ export { addMessageReplacers, enhanceH2Logs, muteAuthLogs, muteDevLogs, resetAllLogs, warnOnce };
@@ -0,0 +1,92 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { describe, beforeEach, afterAll, it, expect } from 'vitest';
3
+ import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
4
+ import { resetAllLogs, enhanceH2Logs } from './log.js';
5
+
6
+ describe("log replacer", () => {
7
+ describe("enhanceH2Logs", () => {
8
+ const graphiqlUrl = "http://localhost:3000/graphiql";
9
+ const rootDirectory = fileURLToPath(import.meta.url);
10
+ const outputMock = mockAndCaptureOutput();
11
+ beforeEach(() => {
12
+ resetAllLogs();
13
+ outputMock.clear();
14
+ });
15
+ afterAll(() => {
16
+ resetAllLogs();
17
+ outputMock.clear();
18
+ });
19
+ describe("enhances h2:info pattern", () => {
20
+ it("renders in an info banner", () => {
21
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
22
+ console.warn("[h2:info:storefront.query] Tip");
23
+ const message = outputMock.info();
24
+ expect(message).not.toMatch("h2");
25
+ expect(message).toMatch("info");
26
+ expect(message).toMatch("In Hydrogen's `storefront.query`");
27
+ expect(message).toMatch("Tip");
28
+ });
29
+ });
30
+ describe("enhances h2:warn pattern", () => {
31
+ it("renders in a warning banner", () => {
32
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
33
+ console.warn("[h2:warn:storefront.query] Wrong query 1");
34
+ const warning = outputMock.warn();
35
+ expect(warning).not.toMatch("h2");
36
+ expect(warning).toMatch("warning");
37
+ expect(warning).toMatch("In Hydrogen's `storefront.query`");
38
+ expect(warning).toMatch("Wrong query");
39
+ });
40
+ it("shows links from the last line as a list", () => {
41
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
42
+ console.warn(
43
+ "[h2:warn:storefront.query] Wrong query.\nhttps://docs.com/something"
44
+ );
45
+ const warning = outputMock.warn();
46
+ expect(warning).toMatch(
47
+ /\s+Reference:?\s+.+?\s+https?:\/\/docs\.com\/something\s/is
48
+ );
49
+ });
50
+ });
51
+ describe("enhances h2:error pattern", () => {
52
+ it("renders in an error banner", () => {
53
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
54
+ console.error(new Error("[h2:error:storefront.query] Wrong query 2"));
55
+ const error = outputMock.error();
56
+ expect(error.split("stack trace:")[0]).not.toMatch("h2");
57
+ expect(error).toMatch("error");
58
+ expect(error).toMatch("In Hydrogen's `storefront.query`");
59
+ expect(error).toMatch("Wrong query");
60
+ });
61
+ it("shows a GraphiQL link when the error is related to a GraphQL query", () => {
62
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
63
+ console.error(
64
+ new Error("[h2:error:storefront.query] Wrong query 3", {
65
+ cause: {
66
+ graphql: { query: "query test {}", variables: '{"var1": true}' }
67
+ }
68
+ })
69
+ );
70
+ const error = outputMock.error();
71
+ expect(error).toMatch("GraphiQL");
72
+ expect((error.match(/\s+GraphiQL\s+\(\s+([^\)]+)\s+\)/) ?? [])[1]).toMatchInlineSnapshot(`
73
+ "http://localhost:3000/grap \u2502
74
+ \u2502 hiql?query=query%20test%20%7B%7D&variables=%7B%22var1%22%3A%20true%7D"
75
+ `);
76
+ });
77
+ it("trims stack traces when the error is related to a GraphQL query", () => {
78
+ enhanceH2Logs({ graphiqlUrl, rootDirectory });
79
+ console.error(
80
+ new Error("[h2:error:storefront.query] Wrong query 4", {
81
+ cause: { graphql: { query: "query test {}" } }
82
+ })
83
+ );
84
+ const error = outputMock.error();
85
+ const stack = error.split("stack trace:")[1] ?? "";
86
+ const shortenedAppDir = rootDirectory.split("/cli/").pop();
87
+ expect(stack).toMatch(shortenedAppDir);
88
+ expect(stack).not.toMatch("node_modules");
89
+ });
90
+ });
91
+ });
92
+ });