@shopify/cli-hydrogen 8.2.0 → 8.3.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.
@@ -0,0 +1,27 @@
1
+ import type {IGraphQLConfig} from 'graphql-config';
2
+ import {getSchema} from '@shopify/hydrogen-codegen';
3
+
4
+ /**
5
+ * GraphQL Config
6
+ * @see https://the-guild.dev/graphql/config/docs/user/usage
7
+ * @type {IGraphQLConfig}
8
+ */
9
+ export default {
10
+ projects: {
11
+ default: {
12
+ schema: getSchema('storefront'),
13
+ documents: [
14
+ './*.{ts,tsx,js,jsx}',
15
+ './app/**/*.{ts,tsx,js,jsx}',
16
+ '!./app/graphql/**/*.{ts,tsx,js,jsx}',
17
+ ],
18
+ },
19
+
20
+ customer: {
21
+ schema: getSchema('customer-account'),
22
+ documents: ['./app/graphql/customer-account/*.{ts,tsx,js,jsx}'],
23
+ },
24
+
25
+ // Add your own GraphQL projects here for CMS, Shopify Admin API, etc.
26
+ },
27
+ } as IGraphQLConfig;
@@ -1,5 +1,14 @@
1
1
  # skeleton
2
2
 
3
+ ## 2024.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Changed the GraphQL config file format to be TS/JS instead of YAML. ([#2311](https://github.com/Shopify/hydrogen/pull/2311)) by [@frandiox](https://github.com/frandiox)
8
+
9
+ - Updated dependencies [[`18ea233c`](https://github.com/Shopify/hydrogen/commit/18ea233cd327bf3001ec9b107ad66b05c9c78584), [`8b2322d7`](https://github.com/Shopify/hydrogen/commit/8b2322d783078298cd5d20ec5f3b1faf99b7895b)]:
10
+ - @shopify/cli-hydrogen@8.3.0
11
+
3
12
  ## 2024.7.1
4
13
 
5
14
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
  "name": "skeleton",
3
3
  "private": true,
4
4
  "sideEffects": false,
5
- "version": "2024.7.1",
5
+ "version": "2024.7.2",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "shopify hydrogen build --codegen",
@@ -28,7 +28,7 @@
28
28
  "@graphql-codegen/cli": "5.0.2",
29
29
  "@remix-run/dev": "^2.10.1",
30
30
  "@remix-run/eslint-config": "^2.10.1",
31
- "@shopify/cli": "^3.63.2",
31
+ "@shopify/cli": "^3.64.0",
32
32
  "@shopify/hydrogen-codegen": "^0.3.1",
33
33
  "@shopify/mini-oxygen": "^3.0.4",
34
34
  "@shopify/oxygen-workers-types": "^4.1.2",
@@ -1,6 +1,8 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
+ import { outputNewline } from '@shopify/cli-kit/node/output';
2
3
  import { commonFlags } from '../../lib/flags.js';
3
4
  import { login, renderLoginSuccess } from '../../lib/auth.js';
5
+ import { enhanceAuthLogs } from '../../lib/log.js';
4
6
 
5
7
  class Login extends Command {
6
8
  static descriptionWithMarkdown = "Logs in to the specified shop and saves the shop domain to the project.";
@@ -18,6 +20,8 @@ async function runLogin({
18
20
  path: root = process.cwd(),
19
21
  shop: shopFlag
20
22
  }) {
23
+ outputNewline();
24
+ enhanceAuthLogs(true);
21
25
  const { config } = await login(root, shopFlag ?? true);
22
26
  renderLoginSuccess(config);
23
27
  }
@@ -1,5 +1,6 @@
1
1
  import Command from '@shopify/cli-kit/node/base-command';
2
2
  import { renderSuccess } from '@shopify/cli-kit/node/ui';
3
+ import { outputNewline } from '@shopify/cli-kit/node/output';
3
4
  import { commonFlags } from '../../lib/flags.js';
4
5
  import { logout } from '../../lib/auth.js';
5
6
 
@@ -15,6 +16,7 @@ class Logout extends Command {
15
16
  }
16
17
  }
17
18
  async function runLogout({ path: root = process.cwd() }) {
19
+ outputNewline();
18
20
  await logout(root);
19
21
  renderSuccess({ body: "You are logged out from Shopify." });
20
22
  }
@@ -53,9 +53,12 @@ function commandNeedsVM(id = "", argv = []) {
53
53
  function isHydrogenProject(projectPath) {
54
54
  try {
55
55
  const require2 = createRequire(import.meta.url);
56
- const { dependencies } = require2(joinPath(projectPath, "package.json"));
57
- return !!dependencies["@shopify/hydrogen"] || // Diff examples only have this package as a dependency
58
- !!dependencies["@shopify/cli-hydrogen"];
56
+ const { dependencies, scripts } = require2(joinPath(
57
+ projectPath,
58
+ "package.json"
59
+ ));
60
+ return !!dependencies?.["@shopify/hydrogen"] || // Diff examples don't have dependencies:
61
+ !!scripts?.dev?.includes("--diff");
59
62
  } catch {
60
63
  return false;
61
64
  }
package/dist/lib/auth.js CHANGED
@@ -1,20 +1,17 @@
1
- import { renderSelectPrompt, renderInfo, renderTasks, renderSuccess } from '@shopify/cli-kit/node/ui';
1
+ import { renderSelectPrompt, renderSuccess } from '@shopify/cli-kit/node/ui';
2
2
  import { AbortError } from '@shopify/cli-kit/node/error';
3
3
  import { logout as logout$1, ensureAuthenticatedBusinessPlatform, ensureAuthenticatedAdmin } from '@shopify/cli-kit/node/session';
4
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
5
  import { resetConfig, getConfig, setUserAccount } from './shopify-config.js';
9
6
  import { getUserAccount } from './graphql/business-platform/user-account.js';
10
- import { muteAuthLogs } from './log.js';
11
- import { deferPromise } from './defer.js';
7
+ import { enhanceAuthLogs } from './log.js';
12
8
 
13
9
  async function logout(root) {
14
10
  await logout$1();
15
11
  await resetConfig(root);
16
12
  }
17
13
  async function login(root, shop) {
14
+ enhanceAuthLogs(false);
18
15
  const forcePrompt = shop === true;
19
16
  const existingConfig = root ? await getConfig(root) : {};
20
17
  let { email, shopName } = existingConfig;
@@ -22,7 +19,6 @@ async function login(root, shop) {
22
19
  shop = existingConfig.shop;
23
20
  }
24
21
  if (shop) shop = await normalizeStoreFqdn(shop);
25
- const hideLoginInfo = showLoginInfo();
26
22
  if (!shop || !shopName || !email || forcePrompt || shop !== existingConfig.shop) {
27
23
  const token = await ensureAuthenticatedBusinessPlatform().catch(() => {
28
24
  throw new AbortError(
@@ -30,7 +26,6 @@ async function login(root, shop) {
30
26
  );
31
27
  });
32
28
  const userAccount = await getUserAccount(token);
33
- await hideLoginInfo();
34
29
  const preselected = !forcePrompt && shop && userAccount.activeShops.find(({ fqdn }) => shop === fqdn);
35
30
  const selected = preselected || await renderSelectPrompt({
36
31
  message: "Select a shop to log in to",
@@ -48,64 +43,9 @@ async function login(root, shop) {
48
43
  `Ensure the shop that you specified is correct (you are trying to use: ${shop})`
49
44
  ]);
50
45
  });
51
- await hideLoginInfo();
52
46
  const config = root ? await setUserAccount(root, { shop, shopName, email }) : { shop, shopName, email };
53
47
  return { session, config };
54
48
  }
55
- function showLoginInfo() {
56
- const deferred = deferPromise();
57
- console.log("");
58
- let hasLoggedTimeout = false;
59
- let hasLoggedPressKey = false;
60
- const restoreLogs = muteAuthLogs({
61
- onKeyTimeout: (link) => {
62
- if (link) {
63
- hasLoggedTimeout = true;
64
- process.stdout.write(ansiEscapes.eraseLines(9));
65
- try {
66
- const secureLink = link.replace("http://", "https://");
67
- const url = new URL(secureLink);
68
- const label = url.origin + "/..." + url.search.slice(-14);
69
- renderInfo({
70
- headline: "Log in to Shopify",
71
- body: outputContent`Timed out. Click to open your browser:\n${outputToken.link(
72
- colors.white(label),
73
- secureLink
74
- )}`.value
75
- });
76
- } catch {
77
- }
78
- }
79
- },
80
- onPressKey: () => {
81
- hasLoggedPressKey = true;
82
- renderInfo({
83
- headline: "Log in to Shopify",
84
- body: "Press any key to login with your default browser"
85
- });
86
- process.stdin.once("data", () => {
87
- renderTasks([
88
- {
89
- title: "Waiting for Shopify authentication",
90
- task: async () => {
91
- await deferred.promise;
92
- }
93
- }
94
- ]);
95
- });
96
- }
97
- });
98
- deferred.promise.then(() => {
99
- restoreLogs();
100
- if (hasLoggedPressKey) {
101
- process.stdout.write(ansiEscapes.eraseLines(hasLoggedTimeout ? 11 : 10));
102
- }
103
- });
104
- return async () => {
105
- deferred.resolve();
106
- await new Promise((resolve) => setTimeout(resolve, 0));
107
- };
108
- }
109
49
  function renderLoginSuccess(config) {
110
50
  renderSuccess({
111
51
  headline: "Shopify authentication complete",
@@ -168,53 +168,83 @@ async function generateDefaultConfig({
168
168
  }).catch(() => void 0);
169
169
  const sfapiSchema = getSchema("storefront");
170
170
  const sfapiProject = findGqlProject(sfapiSchema, gqlConfig);
171
- const defaultGlob = "*!(*.d).{ts,tsx,js,jsx}";
172
- const appDirRelative = relativePath(rootDirectory, appDirectory);
173
171
  const caapiSchema = getSchema("customer-account", { throwIfMissing: false });
174
172
  const caapiProject = caapiSchema ? findGqlProject(caapiSchema, gqlConfig) : void 0;
175
- const customerAccountAPIConfig = caapiProject?.documents ? {
176
- ["customer-accountapi.generated.d.ts"]: {
177
- preset,
178
- schema: caapiSchema,
179
- documents: caapiProject?.documents
180
- }
181
- } : void 0;
173
+ const defaultGlob = "*!(*.d).{ts,tsx,js,jsx}";
174
+ const appDirRelative = relativePath(rootDirectory, appDirectory);
175
+ const isKnownSchema = (schema) => {
176
+ const baseSfapiSchema = basename(sfapiSchema);
177
+ const baseCaapiSchema = caapiSchema && basename(caapiSchema);
178
+ return Boolean(
179
+ schema.endsWith(baseSfapiSchema) || baseCaapiSchema && schema.endsWith(baseCaapiSchema)
180
+ );
181
+ };
182
+ const otherCodegenProjects = Object.values(gqlConfig?.projects ?? {}).filter(
183
+ (project) => project.hasExtension("codegen") && (typeof project.schema !== "string" || !isKnownSchema(project.schema))
184
+ );
182
185
  return {
183
186
  filepath: "virtual:codegen",
184
187
  config: {
185
188
  overwrite: true,
186
189
  pluckConfig,
187
190
  generates: {
188
- ["storefrontapi.generated.d.ts"]: {
189
- preset,
190
- schema: sfapiSchema,
191
- documents: sfapiProject?.documents ?? [
192
- defaultGlob,
193
- // E.g. ./server.(t|j)s
194
- joinPath(appDirRelative, "**", defaultGlob)
195
- // E.g. app/routes/_index.(t|j)sx
196
- ],
197
- ...!!forceSfapiVersion && {
198
- presetConfig: { importTypes: false },
199
- schema: {
200
- [`https://hydrogen-preview.myshopify.com/api/${forceSfapiVersion.split(":")[0]}/graphql.json`]: {
201
- headers: {
202
- "content-type": "application/json",
203
- "X-Shopify-Storefront-Access-Token": forceSfapiVersion.split(":")[1] ?? "3b580e70970c4528da70c98e097c2fa0"
191
+ // If the SFAPI project in GraphQL config has a codegen extension, use it.
192
+ // Otherwise, always fallback to our default config for SFAPI.
193
+ ...getCodegenFromGraphQLConfig(sfapiProject) ?? {
194
+ ["storefrontapi.generated.d.ts"]: {
195
+ preset,
196
+ schema: sfapiSchema,
197
+ documents: sfapiProject?.documents ?? [
198
+ defaultGlob,
199
+ // E.g. ./server.(t|j)s
200
+ joinPath(appDirRelative, "**", defaultGlob)
201
+ // E.g. app/routes/_index.(t|j)sx
202
+ ],
203
+ ...!!forceSfapiVersion && {
204
+ presetConfig: { importTypes: false },
205
+ schema: {
206
+ [`https://hydrogen-preview.myshopify.com/api/${forceSfapiVersion.split(":")[0]}/graphql.json`]: {
207
+ headers: {
208
+ "content-type": "application/json",
209
+ "X-Shopify-Storefront-Access-Token": forceSfapiVersion.split(":")[1] ?? "3b580e70970c4528da70c98e097c2fa0"
210
+ }
204
211
  }
212
+ },
213
+ config: {
214
+ defaultScalarType: "string",
215
+ scalars: { JSON: "unknown" }
205
216
  }
206
- },
207
- config: {
208
- defaultScalarType: "string",
209
- scalars: { JSON: "unknown" }
210
217
  }
211
218
  }
212
219
  },
213
- ...customerAccountAPIConfig
220
+ // If the CAAPI project in GraphQL config has a codegen extension, use it.
221
+ // Otherwise, check if the user provided a list of documents to scan for queries
222
+ // before falling back to our default config for CAAPI.
223
+ ...getCodegenFromGraphQLConfig(caapiProject) ?? (caapiProject?.documents ? {
224
+ ["customer-accountapi.generated.d.ts"]: {
225
+ preset,
226
+ schema: caapiSchema,
227
+ documents: caapiProject.documents
228
+ }
229
+ } : {}),
230
+ // Use other unknown codegen projects from the GraphQL config as they are.
231
+ ...otherCodegenProjects.reduce(
232
+ (acc, project) => ({ ...acc, ...getCodegenFromGraphQLConfig(project) }),
233
+ {}
234
+ )
214
235
  }
215
236
  }
216
237
  };
217
238
  }
239
+ function getCodegenFromGraphQLConfig(project) {
240
+ if (!project?.extensions?.codegen?.generates) return;
241
+ return Object.entries(
242
+ project.extensions.codegen.generates
243
+ ).reduce((acc, [key, value]) => {
244
+ acc[key] = { ...project, ...Array.isArray(value) ? value[0] : value };
245
+ return acc;
246
+ }, {});
247
+ }
218
248
  function findGqlProject(schemaFilepath, gqlConfig) {
219
249
  if (!gqlConfig) return;
220
250
  const schemaFilename = basename(schemaFilepath);
@@ -223,13 +253,17 @@ function findGqlProject(schemaFilepath, gqlConfig) {
223
253
  );
224
254
  }
225
255
  async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
256
+ const name = Symbol.for("name");
226
257
  const hydrogenProjectsOptions = Object.values(codegenConfig.generates).filter(
227
258
  (value) => {
228
259
  const foundPreset = (Array.isArray(value) ? value[0] : value)?.preset;
229
260
  if (typeof foundPreset === "object") {
230
- const name = Symbol.for("name");
231
261
  if (name in foundPreset) {
232
- return foundPreset[name] === "hydrogen";
262
+ return (
263
+ // Preset from @shopify/hydrogen-codegen (e.g. SFAPI, CAAPI)
264
+ foundPreset[name] === "hydrogen" || // Preset from @shopify/graphql-codegen (e.g. Admin API)
265
+ foundPreset[name] === "@shopify/graphql-codegen"
266
+ );
233
267
  }
234
268
  }
235
269
  }
@@ -246,4 +280,4 @@ async function addHooksToHydrogenOptions(codegenConfig, { rootDirectory }) {
246
280
  }
247
281
  }
248
282
 
249
- export { codegen, spawnCodegenProcess };
283
+ export { codegen, generateDefaultConfig, spawnCodegenProcess };
@@ -5,7 +5,10 @@ import { joinPath, dirname } from '@shopify/cli-kit/node/path';
5
5
 
6
6
  const require2 = createRequire(import.meta.url);
7
7
  async function importVite(root) {
8
- const vitePath = require2.resolve("vite", { paths: [root] });
8
+ const vitePath = require2.resolve(
9
+ "vite",
10
+ process.env.SHOPIFY_UNIT_TEST ? void 0 : { paths: [root] }
11
+ );
9
12
  const vitePackageJson = await findUpAndReadPackageJson(vitePath);
10
13
  const viteNodeIndexFile = vitePackageJson.content.exports?.["."].import.default;
11
14
  const viteNodePath = joinPath(
@@ -15,7 +18,10 @@ async function importVite(root) {
15
18
  return import(pathToFileURL(viteNodePath).href);
16
19
  }
17
20
  function importLocal(packageName, path) {
18
- const realPath = require2.resolve(packageName, { paths: [path] });
21
+ const realPath = require2.resolve(
22
+ packageName,
23
+ process.env.SHOPIFY_UNIT_TEST ? void 0 : { paths: [path] }
24
+ );
19
25
  return import(pathToFileURL(realPath).href);
20
26
  }
21
27
 
package/dist/lib/log.js CHANGED
@@ -2,6 +2,7 @@ import { renderFatalError, renderWarning, renderInfo } from '@shopify/cli-kit/no
2
2
  import { BugError } from '@shopify/cli-kit/node/error';
3
3
  import { outputContent, outputToken } from '@shopify/cli-kit/node/output';
4
4
  import colors from '@shopify/cli-kit/node/colors';
5
+ import ansiEscapes from 'ansi-escapes';
5
6
  import { getGraphiQLUrl } from './graphiql-url.js';
6
7
  import { importLocal } from './import-utils.js';
7
8
 
@@ -188,56 +189,30 @@ function muteDevLogs({ workerReload } = {}) {
188
189
  return processStderrWrite.apply(process.stderr, args);
189
190
  };
190
191
  }
191
- const originalWrite = process.stdout.write;
192
- function muteAuthLogs({
193
- onPressKey,
194
- onKeyTimeout
195
- }) {
196
- if (process.stdout.write === originalWrite) {
197
- const write = originalWrite.bind(process.stdout);
198
- process.stdout.write = (item, cb) => {
199
- if (typeof item !== "string") return write(item, cb);
200
- const replacers = messageReplacers.reduce((acc, [matcher, replacer]) => {
201
- if (matcher([item], acc.length)) acc.push(replacer);
202
- return acc;
203
- }, []);
204
- if (replacers.length === 0) return write(item, cb);
205
- const result = replacers.reduce(
206
- (resultArgs, replacer) => resultArgs && replacer(resultArgs),
207
- [item]
208
- );
209
- if (result) return write(result[0], cb);
210
- };
211
- }
192
+ function enhanceAuthLogs(hideInitialLog = false) {
193
+ injectLogReplacer("log", warningDebouncer);
212
194
  addMessageReplacers(
213
195
  "auth",
214
196
  [
215
- ([first]) => typeof first === "string" && first.includes("Auto-open"),
197
+ ([first]) => hideInitialLog && typeof first === "string" && first.includes("To run this command,"),
216
198
  ([first]) => {
217
- const content = first.replace(" to Shopify Partners", "");
218
- const link = content.match(/(https?:\/\/.*)Log in/)?.[1];
219
- onKeyTimeout(link);
220
- if (link) return;
221
- return [content];
199
+ return;
222
200
  }
223
201
  ],
224
202
  [
225
- ([first]) => typeof first === "string" && first.includes("\u{1F449}"),
226
- () => {
227
- onPressKey();
228
- return;
203
+ ([first]) => typeof first === "string" && first.includes("Open this link to start the auth process"),
204
+ ([first]) => {
205
+ return [first.replace("\u{1F449} ", "").replace(": ", ":\n")];
229
206
  }
230
207
  ],
231
208
  [
232
- ([first]) => typeof first === "string" && (first.includes("Shopify Partners") || first.includes("Logged in")),
209
+ ([first]) => typeof first === "string" && first.includes("Logged in."),
233
210
  () => {
211
+ process.stdout.write(ansiEscapes.eraseLines(hideInitialLog ? 4 : 5));
234
212
  return;
235
213
  }
236
214
  ]
237
215
  );
238
- return () => {
239
- process.stdout.write = originalWrite;
240
- };
241
216
  }
242
217
  function enhanceH2Logs(options) {
243
218
  injectLogReplacer("error");
@@ -383,4 +358,4 @@ function isH2Verbose() {
383
358
  return !!(process.env.DEBUG === "*" || process.env.DEBUG?.includes("h2:*"));
384
359
  }
385
360
 
386
- export { addMessageReplacers, createRemixLogger, enhanceH2Logs, isH2Verbose, muteAuthLogs, muteDevLogs, muteRemixLogs, resetAllLogs, setH2OVerbose, warnOnce };
361
+ export { addMessageReplacers, createRemixLogger, enhanceAuthLogs, enhanceH2Logs, isH2Verbose, muteDevLogs, muteRemixLogs, resetAllLogs, setH2OVerbose, warnOnce };
@@ -19,6 +19,7 @@ import { renderRoutePrompt, generateRoutes, generateProjectFile } from '../setup
19
19
  import { execAsync } from '../process.js';
20
20
  import { getStorefronts } from '../graphql/admin/link-storefront.js';
21
21
  import { isHydrogenMonorepo, getSkeletonSourceDir, getRepoNodeModules } from '../build.js';
22
+ import { enhanceAuthLogs } from '../log.js';
22
23
 
23
24
  const LANGUAGES = {
24
25
  js: "JavaScript",
@@ -110,6 +111,7 @@ async function handleCliShortcut(controller, cliCommand, flagShortcut) {
110
111
  };
111
112
  }
112
113
  async function handleStorefrontLink(controller) {
114
+ enhanceAuthLogs(true);
113
115
  const { session, config } = await login();
114
116
  renderLoginSuccess(config);
115
117
  const storefronts = await getStorefronts(session);
@@ -1714,5 +1714,5 @@
1714
1714
  ]
1715
1715
  }
1716
1716
  },
1717
- "version": "8.2.0"
1717
+ "version": "8.3.0"
1718
1718
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "8.2.0",
7
+ "version": "8.3.0",
8
8
  "license": "MIT",
9
9
  "type": "module",
10
10
  "scripts": {
@@ -34,9 +34,9 @@
34
34
  "dependencies": {
35
35
  "@ast-grep/napi": "0.11.0",
36
36
  "@oclif/core": "3.26.5",
37
- "@shopify/cli-kit": "3.63.2",
37
+ "@shopify/cli-kit": "3.64.0",
38
38
  "@shopify/oxygen-cli": "4.4.9",
39
- "@shopify/plugin-cloudflare": "3.63.2",
39
+ "@shopify/plugin-cloudflare": "3.64.0",
40
40
  "ansi-escapes": "^6.2.0",
41
41
  "chokidar": "3.5.3",
42
42
  "cli-truncate": "^4.0.0",
@@ -1,12 +0,0 @@
1
- projects:
2
- default:
3
- schema: 'node_modules/@shopify/hydrogen/storefront.schema.json'
4
- documents:
5
- - '!*.d.ts'
6
- - '*.{ts,tsx,js,jsx}'
7
- - 'app/**/*.{ts,tsx,js,jsx}'
8
- - '!app/graphql/**/*.{ts,tsx,js,jsx}'
9
- customer-account:
10
- schema: 'node_modules/@shopify/hydrogen/customer-account.schema.json'
11
- documents:
12
- - 'app/graphql/customer-account/**/*.{ts,tsx,js,jsx}'