@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
@@ -0,0 +1,22 @@
1
+ {
2
+ "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
5
+ "isolatedModules": true,
6
+ "esModuleInterop": true,
7
+ "jsx": "react-jsx",
8
+ "moduleResolution": "node",
9
+ "resolveJsonModule": true,
10
+ "target": "ES2022",
11
+ "strict": true,
12
+ "allowJs": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "skipLibCheck": true,
15
+ "baseUrl": ".",
16
+ "types": ["@shopify/oxygen-workers-types"],
17
+ "paths": {
18
+ "~/*": ["app/*"]
19
+ },
20
+ "noEmit": true
21
+ }
22
+ }
@@ -0,0 +1,123 @@
1
+ import { renderSelectPrompt, renderInfo, renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
2
+ import { AbortError } from '@shopify/cli-kit/node/error';
3
+ import { logout as logout$1, ensureAuthenticatedBusinessPlatform, ensureAuthenticatedAdmin } from '@shopify/cli-kit/node/session';
4
+ import { normalizeStoreFqdn } from '@shopify/cli-kit/node/context/fqdn';
5
+ import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
6
+ import colors from '@shopify/cli-kit/node/colors';
7
+ import ansiEscapes from 'ansi-escapes';
8
+ import { resetConfig, getConfig, setUserAccount } from './shopify-config.js';
9
+ import { getUserAccount } from './graphql/business-platform/user-account.js';
10
+ import { muteAuthLogs } from './log.js';
11
+
12
+ async function logout(root) {
13
+ await logout$1();
14
+ await resetConfig(root);
15
+ }
16
+ async function login(root, shop) {
17
+ const forcePrompt = shop === true;
18
+ const existingConfig = root ? await getConfig(root) : {};
19
+ let { email, shopName } = existingConfig;
20
+ if (typeof shop !== "string") {
21
+ shop = existingConfig.shop;
22
+ }
23
+ if (shop)
24
+ shop = await normalizeStoreFqdn(shop);
25
+ const hideLoginInfo = showLoginInfo();
26
+ if (!shop || !shopName || !email || forcePrompt || shop !== existingConfig.shop) {
27
+ const token = await ensureAuthenticatedBusinessPlatform().catch(() => {
28
+ throw new AbortError(
29
+ "Unable to authenticate with Shopify. Please report this issue."
30
+ );
31
+ });
32
+ const userAccount = await getUserAccount(token);
33
+ await hideLoginInfo();
34
+ const preselected = !forcePrompt && shop && userAccount.activeShops.find(({ fqdn }) => shop === fqdn);
35
+ const selected = preselected || await renderSelectPrompt({
36
+ message: "Select a shop to log in to",
37
+ choices: userAccount.activeShops.map(({ name, fqdn }) => ({
38
+ label: `${name} (${fqdn})`,
39
+ value: { name, fqdn }
40
+ }))
41
+ });
42
+ shop = selected.fqdn;
43
+ shopName = selected.name;
44
+ email = userAccount.email;
45
+ }
46
+ const session = await ensureAuthenticatedAdmin(shop).catch(() => {
47
+ throw new AbortError("Unable to authenticate with Shopify", void 0, [
48
+ `Ensure the shop that you specified is correct (you are trying to use: ${shop})`
49
+ ]);
50
+ });
51
+ await hideLoginInfo();
52
+ const config = root ? await setUserAccount(root, { shop, shopName, email }) : { shop, shopName, email };
53
+ return { session, config };
54
+ }
55
+ function showLoginInfo() {
56
+ let deferredResolve;
57
+ const promise = new Promise((resolve) => {
58
+ deferredResolve = resolve;
59
+ });
60
+ console.log("");
61
+ let hasLoggedTimeout = false;
62
+ let hasLoggedPressKey = false;
63
+ const restoreLogs = muteAuthLogs({
64
+ onKeyTimeout: (link) => {
65
+ if (link) {
66
+ hasLoggedTimeout = true;
67
+ process.stdout.write(ansiEscapes.eraseLines(9));
68
+ try {
69
+ const secureLink = link.replace("http://", "https://");
70
+ const url = new URL(secureLink);
71
+ const label = url.origin + "/..." + url.search.slice(-14);
72
+ renderInfo({
73
+ headline: "Log in to Shopify",
74
+ body: outputContent`Timed out. Click to open your browser:\n${outputToken.link(
75
+ colors.white(label),
76
+ secureLink
77
+ )}`.value
78
+ });
79
+ } catch {
80
+ }
81
+ }
82
+ },
83
+ onPressKey: () => {
84
+ hasLoggedPressKey = true;
85
+ renderInfo({
86
+ headline: "Log in to Shopify",
87
+ body: "Press any key to login with your default browser"
88
+ });
89
+ process.stdin.once("data", () => {
90
+ renderTasks([
91
+ {
92
+ title: "Waiting for Shopify authentication",
93
+ task: async () => {
94
+ await promise;
95
+ }
96
+ }
97
+ ]);
98
+ });
99
+ }
100
+ });
101
+ promise.then(() => {
102
+ restoreLogs();
103
+ if (hasLoggedPressKey) {
104
+ process.stdout.write(ansiEscapes.eraseLines(hasLoggedTimeout ? 11 : 10));
105
+ }
106
+ });
107
+ return async () => {
108
+ deferredResolve();
109
+ await new Promise((resolve) => setTimeout(resolve, 0));
110
+ };
111
+ }
112
+ function renderLoginSuccess(config) {
113
+ renderSuccess({
114
+ headline: "Shopify authentication complete",
115
+ body: [
116
+ "You are logged in to",
117
+ { userInput: config.shopName ?? config.shop ?? "your store" },
118
+ ...config.email ? ["as", { userInput: config.email }] : []
119
+ ]
120
+ });
121
+ }
122
+
123
+ export { login, logout, renderLoginSuccess };
@@ -0,0 +1,157 @@
1
+ import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
+ import { ensureAuthenticatedAdmin, ensureAuthenticatedBusinessPlatform } from '@shopify/cli-kit/node/session';
3
+ import { renderSelectPrompt } from '@shopify/cli-kit/node/ui';
4
+ import { AbortError } from '@shopify/cli-kit/node/error';
5
+ import { login } from './auth.js';
6
+ import { getUserAccount } from './graphql/business-platform/user-account.js';
7
+ import { setUserAccount, getConfig } from './shopify-config.js';
8
+
9
+ vi.mock("@shopify/cli-kit/node/session");
10
+ vi.mock("@shopify/cli-kit/node/ui");
11
+ vi.mock("./graphql/business-platform/user-account.js");
12
+ vi.mock("./shopify-config.js");
13
+ describe("auth", () => {
14
+ const EMAIL = "email";
15
+ const SHOP = "my-shop";
16
+ const SHOP_DOMAIN = SHOP + ".myshopify.com";
17
+ const SHOP_NAME = "My Shop";
18
+ const TOKEN = "abc123";
19
+ const ROOT = "path/to/project";
20
+ const SHOPIFY_CONFIG = {
21
+ shop: SHOP_DOMAIN,
22
+ shopName: SHOP_NAME,
23
+ email: EMAIL
24
+ };
25
+ const EXPECTED_LOGIN_RESULT = {
26
+ config: { shop: SHOP_DOMAIN, shopName: SHOP_NAME, email: EMAIL },
27
+ session: {
28
+ token: TOKEN,
29
+ storeFqdn: SHOP_DOMAIN
30
+ }
31
+ };
32
+ beforeEach(() => {
33
+ vi.mocked(setUserAccount).mockImplementation(
34
+ (root, account) => Promise.resolve(account)
35
+ );
36
+ vi.mocked(ensureAuthenticatedAdmin).mockImplementation(
37
+ (shop) => Promise.resolve({ token: TOKEN, storeFqdn: shop })
38
+ );
39
+ vi.mocked(ensureAuthenticatedBusinessPlatform).mockResolvedValue("bp123");
40
+ vi.mocked(getUserAccount).mockResolvedValue({
41
+ email: "email",
42
+ activeShops: [{ name: SHOP_NAME, fqdn: SHOP_DOMAIN }]
43
+ });
44
+ vi.mocked(getConfig).mockResolvedValue({});
45
+ vi.mocked(renderSelectPrompt).mockResolvedValue({
46
+ fqdn: SHOP_DOMAIN,
47
+ name: SHOP_NAME
48
+ });
49
+ });
50
+ afterEach(() => {
51
+ vi.resetAllMocks();
52
+ });
53
+ describe("login", () => {
54
+ it("throws an error when it fails to authenticate", async () => {
55
+ vi.mocked(ensureAuthenticatedBusinessPlatform).mockRejectedValueOnce({});
56
+ await expect(login(ROOT)).rejects.toThrow(AbortError);
57
+ vi.mocked(ensureAuthenticatedAdmin).mockRejectedValueOnce({});
58
+ await expect(login(ROOT, SHOP)).rejects.toThrow(AbortError);
59
+ });
60
+ it("reads shop from local config", async () => {
61
+ vi.mocked(getConfig).mockResolvedValue(SHOPIFY_CONFIG);
62
+ const result = await login(ROOT);
63
+ expect(ensureAuthenticatedBusinessPlatform).not.toHaveBeenCalled();
64
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(SHOP_DOMAIN);
65
+ expect(setUserAccount).toHaveBeenCalledWith(
66
+ ROOT,
67
+ expect.objectContaining({ shop: SHOP_DOMAIN })
68
+ );
69
+ expect(result).toStrictEqual(EXPECTED_LOGIN_RESULT);
70
+ });
71
+ it("uses the shop flag argument when passed", async () => {
72
+ const ANOTHER_SHOP = "another-shop";
73
+ const ANOTHER_SHOP_DOMAIN = "another-shop.myshopify.com";
74
+ const ANOTHER_SHOP_NAME = "ANOTHER SHOP";
75
+ vi.mocked(getConfig).mockResolvedValue(SHOPIFY_CONFIG);
76
+ vi.mocked(renderSelectPrompt).mockResolvedValue({
77
+ fqdn: ANOTHER_SHOP_DOMAIN,
78
+ name: ANOTHER_SHOP_NAME
79
+ });
80
+ const result = await login(ROOT, ANOTHER_SHOP);
81
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(
82
+ ANOTHER_SHOP_DOMAIN
83
+ );
84
+ expect(setUserAccount).toHaveBeenCalledWith(
85
+ ROOT,
86
+ expect.objectContaining({ shop: ANOTHER_SHOP_DOMAIN })
87
+ );
88
+ expect(result).toStrictEqual({
89
+ config: {
90
+ shop: ANOTHER_SHOP_DOMAIN,
91
+ shopName: ANOTHER_SHOP_NAME,
92
+ email: EMAIL
93
+ },
94
+ session: {
95
+ storeFqdn: ANOTHER_SHOP_DOMAIN,
96
+ token: TOKEN
97
+ }
98
+ });
99
+ });
100
+ it("writes shop to local config and returns it with the admin session", async () => {
101
+ const result = await login(ROOT, SHOP);
102
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(SHOP_DOMAIN);
103
+ expect(setUserAccount).toHaveBeenCalledWith(
104
+ ROOT,
105
+ expect.objectContaining({ shop: SHOP_DOMAIN })
106
+ );
107
+ expect(result).toStrictEqual(EXPECTED_LOGIN_RESULT);
108
+ });
109
+ it("prompts for shop is not found in arguments and local config", async () => {
110
+ const result = await login(ROOT);
111
+ expect(ensureAuthenticatedBusinessPlatform).toHaveBeenCalled();
112
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(SHOP_DOMAIN);
113
+ expect(setUserAccount).toHaveBeenCalledWith(
114
+ ROOT,
115
+ expect.objectContaining({ shop: SHOP_DOMAIN })
116
+ );
117
+ expect(renderSelectPrompt).toHaveBeenCalledWith({
118
+ message: expect.any(String),
119
+ choices: [
120
+ {
121
+ label: expect.stringContaining(SHOP_DOMAIN),
122
+ value: { fqdn: SHOP_DOMAIN, name: SHOP_NAME }
123
+ }
124
+ ]
125
+ });
126
+ expect(result).toStrictEqual(EXPECTED_LOGIN_RESULT);
127
+ });
128
+ it("ignores local config and forces prompt when indicated", async () => {
129
+ vi.mocked(getConfig).mockResolvedValue(SHOPIFY_CONFIG);
130
+ const result = await login(ROOT, true);
131
+ expect(ensureAuthenticatedBusinessPlatform).toHaveBeenCalled();
132
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(SHOP_DOMAIN);
133
+ expect(setUserAccount).toHaveBeenCalledWith(
134
+ ROOT,
135
+ expect.objectContaining({ shop: SHOP_DOMAIN })
136
+ );
137
+ expect(renderSelectPrompt).toHaveBeenCalledWith({
138
+ message: expect.any(String),
139
+ choices: [
140
+ {
141
+ label: expect.stringContaining(SHOP_DOMAIN),
142
+ value: { fqdn: SHOP_DOMAIN, name: SHOP_NAME }
143
+ }
144
+ ]
145
+ });
146
+ expect(result).toStrictEqual(EXPECTED_LOGIN_RESULT);
147
+ });
148
+ it("skips config steps when root argument is not passed", async () => {
149
+ const result = await login();
150
+ expect(ensureAuthenticatedBusinessPlatform).toHaveBeenCalled();
151
+ expect(ensureAuthenticatedAdmin).toHaveBeenCalledWith(SHOP_DOMAIN);
152
+ expect(getConfig).not.toHaveBeenCalled();
153
+ expect(setUserAccount).not.toHaveBeenCalled();
154
+ expect(result).toStrictEqual(EXPECTED_LOGIN_RESULT);
155
+ });
156
+ });
157
+ });
@@ -0,0 +1,51 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ const GENERATOR_TEMPLATES_DIR = "generator-templates";
4
+ const GENERATOR_STARTER_DIR = "starter";
5
+ const GENERATOR_APP_DIR = "app";
6
+ const GENERATOR_ROUTE_DIR = "routes";
7
+ const GENERATOR_SETUP_ASSETS_DIR = "assets";
8
+ const GENERATOR_SETUP_ASSETS_SUB_DIRS = [
9
+ "tailwind",
10
+ "css-modules",
11
+ "vanilla-extract",
12
+ "postcss"
13
+ ];
14
+ function getAssetDir(feature) {
15
+ if (process.env.NODE_ENV === "test") {
16
+ return fileURLToPath(
17
+ new URL(`../setup-assets/${feature}`, import.meta.url)
18
+ );
19
+ }
20
+ return fileURLToPath(
21
+ new URL(
22
+ `../${GENERATOR_TEMPLATES_DIR}/${GENERATOR_SETUP_ASSETS_DIR}/${feature}`,
23
+ import.meta.url
24
+ )
25
+ );
26
+ }
27
+ function getTemplateAppFile(filepath, root = getStarterDir()) {
28
+ const url = new URL(
29
+ `${root}/${GENERATOR_APP_DIR}${filepath ? `/${filepath}` : ""}`,
30
+ import.meta.url
31
+ );
32
+ return url.protocol === "file:" ? fileURLToPath(url) : url.toString();
33
+ }
34
+ function getStarterDir() {
35
+ if (process.env.NODE_ENV === "test") {
36
+ return getSkeletonSourceDir();
37
+ }
38
+ return fileURLToPath(
39
+ new URL(
40
+ `../${GENERATOR_TEMPLATES_DIR}/${GENERATOR_STARTER_DIR}`,
41
+ import.meta.url
42
+ )
43
+ );
44
+ }
45
+ function getSkeletonSourceDir() {
46
+ return fileURLToPath(
47
+ new URL(`../../../../templates/skeleton`, import.meta.url)
48
+ );
49
+ }
50
+
51
+ export { GENERATOR_APP_DIR, GENERATOR_ROUTE_DIR, GENERATOR_SETUP_ASSETS_DIR, GENERATOR_SETUP_ASSETS_SUB_DIRS, GENERATOR_STARTER_DIR, GENERATOR_TEMPLATES_DIR, getAssetDir, getSkeletonSourceDir, getStarterDir, getTemplateAppFile };
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import { createRequire } from 'module';
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
3
  import { checkForNewVersion } from '@shopify/cli-kit/node/node-package-manager';
4
4
  import { renderInfo } from '@shopify/cli-kit/node/ui';
5
5
 
@@ -20,7 +20,7 @@ async function checkHydrogenVersion(resolveFrom, pkgKey = "main") {
20
20
  if (!pkgJsonPath)
21
21
  return;
22
22
  const currentVersion = require2(pkgJsonPath).version;
23
- if (!currentVersion)
23
+ if (!currentVersion || currentVersion.includes("next"))
24
24
  return;
25
25
  const newVersionAvailable = await checkForNewVersion(pkgName, currentVersion);
26
26
  if (!newVersionAvailable)
@@ -8,6 +8,21 @@ vi.mock("@shopify/cli-kit/node/node-package-manager", () => {
8
8
  checkForNewVersion: vi.fn()
9
9
  };
10
10
  });
11
+ const requireMock = vi.fn();
12
+ vi.mock("node:module", async () => {
13
+ const { createRequire } = await vi.importActual(
14
+ "node:module"
15
+ );
16
+ return {
17
+ createRequire: (url) => {
18
+ const actualRequire = createRequire(url);
19
+ requireMock.mockImplementation((mod) => actualRequire(mod));
20
+ const require2 = requireMock;
21
+ require2.resolve = actualRequire.resolve.bind(actualRequire);
22
+ return require2;
23
+ }
24
+ };
25
+ });
11
26
  describe("checkHydrogenVersion()", () => {
12
27
  const outputMock = mockAndCaptureOutput();
13
28
  afterEach(() => {
@@ -30,6 +45,15 @@ describe("checkHydrogenVersion()", () => {
30
45
  expect(await checkHydrogenVersion("dir")).toBe(void 0);
31
46
  });
32
47
  });
48
+ describe("and it is using @next", () => {
49
+ it("returns undefined", async () => {
50
+ vi.mocked(checkForNewVersion).mockResolvedValue("2023.1.5");
51
+ vi.mocked(requireMock).mockReturnValueOnce({
52
+ version: "0.0.0-next-a188915-20230713115118"
53
+ });
54
+ expect(await checkHydrogenVersion("dir")).toBe(void 0);
55
+ });
56
+ });
33
57
  describe("and a new version is available", () => {
34
58
  beforeEach(() => {
35
59
  vi.mocked(checkForNewVersion).mockResolvedValue("2023.1.5");
@@ -1,9 +1,9 @@
1
1
  import { loadCodegenConfig, generate } from '@graphql-codegen/cli';
2
2
  import { patchGqlPluck, pluckConfig, preset, schema } from '@shopify/hydrogen-codegen';
3
- export { patchGqlPluck } from '@shopify/hydrogen-codegen';
4
- import { resolveFormatConfig, format } from './transpile-ts.js';
3
+ import { getCodeFormatOptions, formatCode } from './format-code.js';
5
4
  import { renderWarning, renderFatalError } from '@shopify/cli-kit/node/ui';
6
- import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
5
+ import { joinPath } from '@shopify/cli-kit/node/path';
6
+ import { AbortError } from '@shopify/cli-kit/node/error';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { fileURLToPath } from 'node:url';
9
9
 
@@ -11,14 +11,12 @@ const nodePath = process.argv[1];
11
11
  const modulePath = fileURLToPath(import.meta.url);
12
12
  const isStandaloneProcess = nodePath === modulePath;
13
13
  if (isStandaloneProcess) {
14
- patchGqlPluck().then(
15
- () => generateTypes({
16
- rootDirectory: process.argv[2],
17
- appDirectory: process.argv[3],
18
- configFilePath: process.argv[4],
19
- watch: true
20
- })
21
- );
14
+ codegen({
15
+ rootDirectory: process.argv[2],
16
+ appDirectory: process.argv[3],
17
+ configFilePath: process.argv[4],
18
+ watch: true
19
+ });
22
20
  }
23
21
  function normalizeCodegenError(errorMessage, rootDirectory) {
24
22
  const [first = "", ...rest] = errorMessage.replaceAll("[FAILED]", "").replace(/\s{2,}/g, "\n").replace(/\n,\n/, "\n").trim().split("\n");
@@ -66,6 +64,18 @@ function spawnCodegenProcess({
66
64
  });
67
65
  return child;
68
66
  }
67
+ async function codegen(options) {
68
+ await patchGqlPluck();
69
+ try {
70
+ return await generateTypes(options);
71
+ } catch (error) {
72
+ const { message, details } = normalizeCodegenError(
73
+ error.message,
74
+ options.rootDirectory
75
+ );
76
+ throw new AbortError(message, details);
77
+ }
78
+ }
69
79
  async function generateTypes({
70
80
  watch,
71
81
  configFilePath,
@@ -90,7 +100,6 @@ async function generateTypes({
90
100
  }
91
101
  function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapiVersion) {
92
102
  const tsDefaultGlob = "*!(*.d).{ts,tsx}";
93
- const appDirRelative = relativePath(rootDirectory, appDirectory);
94
103
  return {
95
104
  filepath: "virtual:codegen",
96
105
  config: {
@@ -101,8 +110,8 @@ function generateDefaultConfig({ rootDirectory, appDirectory }, forceSfapiVersio
101
110
  preset,
102
111
  schema,
103
112
  documents: [
104
- tsDefaultGlob,
105
- joinPath(appDirRelative, "**", tsDefaultGlob)
113
+ joinPath(rootDirectory, tsDefaultGlob),
114
+ joinPath(appDirectory, "**", tsDefaultGlob)
106
115
  ],
107
116
  ...!!forceSfapiVersion && {
108
117
  presetConfig: { importTypes: false },
@@ -130,12 +139,12 @@ async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
130
139
  ) || [];
131
140
  const hydrogenOptions = Array.isArray(options) ? options[0] : options;
132
141
  if (hydrogenOptions) {
133
- const formatConfig = await resolveFormatConfig(rootDirectory);
142
+ const formatConfig = await getCodeFormatOptions(rootDirectory);
134
143
  hydrogenOptions.hooks = {
135
- beforeOneFileWrite: (file, content) => format(content, formatConfig, file),
144
+ beforeOneFileWrite: (file, content) => formatCode(content, formatConfig, file),
136
145
  ...hydrogenOptions.hooks
137
146
  };
138
147
  }
139
148
  }
140
149
 
141
- export { generateTypes, normalizeCodegenError, spawnCodegenProcess };
150
+ export { codegen, spawnCodegenProcess };
@@ -0,0 +1,68 @@
1
+ import { fileExists } from '@shopify/cli-kit/node/fs';
2
+ import { resolvePath } from '@shopify/cli-kit/node/path';
3
+ import { linesToColumns } from '@shopify/cli-kit/common/string';
4
+ import { outputInfo } from '@shopify/cli-kit/node/output';
5
+ import { readAndParseDotEnv } from '@shopify/cli-kit/node/dot-env';
6
+ import { renderWarning } from '@shopify/cli-kit/node/ui';
7
+ import colors from '@shopify/cli-kit/node/colors';
8
+ import { getStorefrontEnvVariables } from './graphql/admin/pull-variables.js';
9
+ import { login } from './auth.js';
10
+
11
+ async function getAllEnvironmentVariables({
12
+ root,
13
+ envBranch,
14
+ fetchRemote = true
15
+ }) {
16
+ const dotEnvPath = resolvePath(root, ".env");
17
+ const [{ remoteVariables, remoteSecrets }, { variables: localVariables }] = await Promise.all([
18
+ fetchRemote ? getRemoteVariables(root, envBranch) : { remoteVariables: {}, remoteSecrets: {} },
19
+ fileExists(dotEnvPath).then(
20
+ (exists) => exists ? readAndParseDotEnv(dotEnvPath) : { variables: {} }
21
+ )
22
+ ]);
23
+ const remoteSecretKeys = Object.keys(remoteSecrets);
24
+ const remotePublicKeys = Object.keys(remoteVariables);
25
+ const localKeys = Object.keys(localVariables);
26
+ if (localKeys.length > 0 || remotePublicKeys.length + remoteSecretKeys.length > 0) {
27
+ outputInfo("\nEnvironment variables injected into MiniOxygen:\n");
28
+ outputInfo(
29
+ linesToColumns([
30
+ ...remotePublicKeys.filter((key) => !localKeys.includes(key)).map((key) => [key, "from Oxygen"]),
31
+ ...localKeys.map((key) => [key, "from local .env"]),
32
+ ...remoteSecretKeys.filter((key) => !localKeys.includes(key)).map((key) => [
33
+ colors.dim(key),
34
+ colors.dim("from Oxygen (Marked as secret)")
35
+ ])
36
+ ])
37
+ );
38
+ }
39
+ return {
40
+ ...remoteSecrets,
41
+ ...remoteVariables,
42
+ ...localVariables
43
+ };
44
+ }
45
+ async function getRemoteVariables(root, envBranch) {
46
+ const { session, config } = await login(root);
47
+ const envVariables = (await getStorefrontEnvVariables(
48
+ session,
49
+ config.storefront.id,
50
+ envBranch
51
+ ).catch((error) => {
52
+ renderWarning({
53
+ headline: `Failed to load environment variables. The development server will still start, but the following error occurred:`,
54
+ body: error?.stack ?? error?.message ?? error
55
+ });
56
+ }))?.environmentVariables || [];
57
+ const remoteVariables = {};
58
+ const remoteSecrets = {};
59
+ for (const { key, value, isSecret } of envVariables) {
60
+ if (isSecret)
61
+ remoteSecrets[key] = value;
62
+ else
63
+ remoteVariables[key] = value;
64
+ }
65
+ return { remoteVariables, remoteSecrets };
66
+ }
67
+
68
+ export { getAllEnvironmentVariables };