@shopify/cli-hydrogen 4.0.4 → 4.0.6

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.
@@ -4,10 +4,11 @@ import Command from '@shopify/cli-kit/node/base-command';
4
4
  declare class Init extends Command {
5
5
  static description: string;
6
6
  static flags: {
7
- path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
8
7
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
9
- language: _oclif_core_lib_interfaces_parser_js.OptionFlag<string>;
8
+ path: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
9
+ language: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
10
10
  template: _oclif_core_lib_interfaces_parser_js.OptionFlag<string | undefined>;
11
+ 'install-deps': _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
11
12
  };
12
13
  run(): Promise<void>;
13
14
  }
@@ -17,6 +18,7 @@ declare function runInit(options?: {
17
18
  language?: string;
18
19
  token?: string;
19
20
  force?: boolean;
21
+ installDeps?: boolean;
20
22
  }): Promise<void>;
21
23
 
22
24
  export { Init as default, runInit };
@@ -3,25 +3,35 @@ import { packageManagerUsedForCreating, installNodeModules } from '@shopify/cli-
3
3
  import { renderFatalError } from '@shopify/cli-kit/node/ui';
4
4
  import Flags from '@oclif/core/lib/flags.js';
5
5
  import { output, path } from '@shopify/cli-kit';
6
- import { commonFlags } from '../../utils/flags.js';
6
+ import { commonFlags, parseProcessFlags } from '../../utils/flags.js';
7
7
  import { transpileProject } from '../../utils/transpile-ts.js';
8
8
  import { getLatestTemplates } from '../../utils/template-downloader.js';
9
9
  import { readdir } from 'fs/promises';
10
10
 
11
+ const STARTER_TEMPLATES = ["hello-world", "demo-store"];
12
+ const FLAG_MAP = { f: "force" };
11
13
  class Init extends Command {
12
14
  static description = "Creates a new Hydrogen storefront.";
13
15
  static flags = {
14
- path: commonFlags.path,
15
16
  force: commonFlags.force,
17
+ path: Flags.string({
18
+ description: "The path to the directory of the new Hydrogen storefront.",
19
+ env: "SHOPIFY_HYDROGEN_FLAG_PATH"
20
+ }),
16
21
  language: Flags.string({
17
22
  description: "Sets the template language to use. One of `js` or `ts`.",
18
23
  choices: ["js", "ts"],
19
- default: "js",
20
24
  env: "SHOPIFY_HYDROGEN_FLAG_LANGUAGE"
21
25
  }),
22
26
  template: Flags.string({
23
27
  description: "Sets the template to use. One of `demo-store` or `hello-world`.",
28
+ choices: STARTER_TEMPLATES,
24
29
  env: "SHOPIFY_HYDROGEN_FLAG_TEMPLATE"
30
+ }),
31
+ "install-deps": Flags.boolean({
32
+ description: "Auto install dependencies using the active package manager",
33
+ env: "SHOPIFY_HYDROGEN_INSTALL_DEPS",
34
+ allowNo: true
25
35
  })
26
36
  };
27
37
  async run() {
@@ -29,7 +39,7 @@ class Init extends Command {
29
39
  await runInit({ ...flags });
30
40
  }
31
41
  }
32
- async function runInit(options = getProcessFlags()) {
42
+ async function runInit(options = parseProcessFlags(process.argv, FLAG_MAP)) {
33
43
  supressNodeExperimentalWarnings();
34
44
  const templatesPromise = getLatestTemplates().catch((error) => {
35
45
  output.info("\n\n\n");
@@ -44,7 +54,7 @@ async function runInit(options = getProcessFlags()) {
44
54
  type: "select",
45
55
  name: "template",
46
56
  message: "Choose a template",
47
- choices: ["hello-world", "demo-store"].map((value) => ({
57
+ choices: STARTER_TEMPLATES.map((value) => ({
48
58
  name: value.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
49
59
  value
50
60
  }))
@@ -119,7 +129,7 @@ async function runInit(options = getProcessFlags()) {
119
129
  let depsInstalled = false;
120
130
  let packageManager = await packageManagerUsedForCreating();
121
131
  if (packageManager !== "unknown") {
122
- const { installDeps } = await ui.prompt([
132
+ const installDeps = options.installDeps ?? (await ui.prompt([
123
133
  {
124
134
  type: "select",
125
135
  name: "installDeps",
@@ -130,8 +140,8 @@ async function runInit(options = getProcessFlags()) {
130
140
  ],
131
141
  default: "true"
132
142
  }
133
- ]);
134
- if (installDeps === "true") {
143
+ ])).installDeps === "true";
144
+ if (installDeps) {
135
145
  await installNodeModules({
136
146
  directory: projectDir,
137
147
  packageManager,
@@ -162,15 +172,6 @@ async function projectExists(projectDir) {
162
172
  const { file } = await import('@shopify/cli-kit');
163
173
  return await file.exists(projectDir) && await file.isDirectory(projectDir) && (await readdir(projectDir)).length > 0;
164
174
  }
165
- function getProcessFlags() {
166
- const flagMap = { f: "force" };
167
- const [, , ...flags] = process.argv;
168
- return flags.reduce((acc, flag) => {
169
- const [key, value = true] = flag.replace(/^\-+/, "").split("=");
170
- const mappedKey = flagMap[key] || key;
171
- return { ...acc, [mappedKey]: value };
172
- }, {});
173
- }
174
175
  function supressNodeExperimentalWarnings() {
175
176
  const warningListener = process.listeners("warning")[0];
176
177
  if (warningListener) {
@@ -1,10 +1,10 @@
1
- import {flattenConnection} from '@shopify/hydrogen-react';
1
+ import {flattenConnection} from '@shopify/hydrogen';
2
2
  import type {LoaderArgs} from '@shopify/remix-oxygen';
3
3
  import {
4
4
  CollectionConnection,
5
5
  PageConnection,
6
6
  ProductConnection,
7
- } from '@shopify/hydrogen-react/storefront-api-types';
7
+ } from '@shopify/hydrogen/storefront-api-types';
8
8
 
9
9
  const MAX_URLS = 250; // the google limit is 50K, however, SF API only allow querying for 250 resources each time
10
10
 
@@ -1,7 +1,7 @@
1
1
  import {Await, useMatches} from '@remix-run/react';
2
2
  import {Suspense} from 'react';
3
- import {flattenConnection} from '@shopify/hydrogen-react';
4
- import type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';
3
+ import {flattenConnection} from '@shopify/hydrogen';
4
+ import type {Cart as CartType} from '@shopify/hydrogen/storefront-api-types';
5
5
 
6
6
  export async function action() {
7
7
  // @TODO implement cart action
@@ -11,7 +11,7 @@ export default function CartRoute() {
11
11
  const [root] = useMatches();
12
12
  return (
13
13
  <Suspense fallback="loading">
14
- <Await resolve={root.data?.cart}>
14
+ <Await resolve={root.data?.cart as CartType}>
15
15
  {(cart) => {
16
16
  const linesCount = Boolean(cart?.lines?.edges?.length || 0);
17
17
  if (!linesCount) {
@@ -20,9 +20,7 @@ export default function CartRoute() {
20
20
  );
21
21
  }
22
22
 
23
- const cartLines = cart?.lines
24
- ? flattenConnection<CartLine>(cart?.lines)
25
- : [];
23
+ const cartLines = cart?.lines ? flattenConnection(cart?.lines) : [];
26
24
 
27
25
  return (
28
26
  <>
@@ -1,6 +1,6 @@
1
1
  import {json, type LoaderArgs} from '@shopify/remix-oxygen';
2
2
  import {useLoaderData} from '@remix-run/react';
3
- import type {Collection as CollectionType} from '@shopify/hydrogen-react/storefront-api-types';
3
+ import type {Collection as CollectionType} from '@shopify/hydrogen/storefront-api-types';
4
4
  import {Link} from '@remix-run/react';
5
5
 
6
6
  export async function loader({params, request, context}: LoaderArgs) {
@@ -6,7 +6,7 @@ import {
6
6
  } from '@shopify/remix-oxygen';
7
7
  import {useLoaderData} from '@remix-run/react';
8
8
  import invariant from 'tiny-invariant';
9
- import type {Page as PageType} from '@shopify/hydrogen-react/storefront-api-types';
9
+ import type {Page as PageType} from '@shopify/hydrogen/storefront-api-types';
10
10
  import type {SeoHandleFunction} from '@shopify/hydrogen';
11
11
 
12
12
  export async function loader({params, context}: LoaderArgs) {
@@ -1,7 +1,7 @@
1
1
  import {json, type MetaFunction, type LoaderArgs} from '@shopify/remix-oxygen';
2
2
  import {useLoaderData} from '@remix-run/react';
3
3
 
4
- import {ShopPolicy} from '@shopify/hydrogen-react/storefront-api-types';
4
+ import {ShopPolicy} from '@shopify/hydrogen/storefront-api-types';
5
5
 
6
6
  export async function loader({request, params, context}: LoaderArgs) {
7
7
  const handle = params.policyHandle;
@@ -1,6 +1,6 @@
1
1
  import {json, type LoaderArgs} from '@shopify/remix-oxygen';
2
2
  import {useLoaderData, Link} from '@remix-run/react';
3
- import type {ShopPolicy} from '@shopify/hydrogen-react/storefront-api-types';
3
+ import type {ShopPolicy} from '@shopify/hydrogen/storefront-api-types';
4
4
 
5
5
  export async function loader({context: {storefront}}: LoaderArgs) {
6
6
  const data = await storefront.query<{
@@ -26,7 +26,9 @@ export default function Policies() {
26
26
  {policies.map((policy) => {
27
27
  return (
28
28
  policy && (
29
- <Link to={`/policies/${policy.handle}`}>{policy.title}</Link>
29
+ <Link key={policy.id} to={`/policies/${policy.handle}`}>
30
+ {policy.title}
31
+ </Link>
30
32
  )
31
33
  );
32
34
  })}
@@ -3,7 +3,7 @@ import {useLoaderData} from '@remix-run/react';
3
3
  import type {
4
4
  ProductVariant,
5
5
  Product as ProductType,
6
- } from '@shopify/hydrogen-react/storefront-api-types';
6
+ } from '@shopify/hydrogen/storefront-api-types';
7
7
 
8
8
  export async function loader({params, context}: LoaderArgs) {
9
9
  const {productHandle} = params;
@@ -0,0 +1,74 @@
1
+ import { checkLockfileStatus } from './check-lockfile.js';
2
+ import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
3
+ import { git, outputMocker, file, path } from '@shopify/cli-kit';
4
+
5
+ vi.mock("@shopify/cli-kit", async () => {
6
+ const cliKit = await vi.importActual("@shopify/cli-kit");
7
+ return {
8
+ ...cliKit,
9
+ git: {
10
+ factory: vi.fn()
11
+ }
12
+ };
13
+ });
14
+ describe("checkLockfileStatus()", () => {
15
+ const checkIgnoreMock = vi.fn();
16
+ const gitFactoryMock = {
17
+ checkIgnore: checkIgnoreMock
18
+ };
19
+ beforeEach(() => {
20
+ vi.mocked(git.factory).mockReturnValue(gitFactoryMock);
21
+ vi.mocked(checkIgnoreMock).mockResolvedValue([]);
22
+ });
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ outputMocker.mockAndCaptureOutput().clear();
26
+ });
27
+ describe("when a lockfile is present", () => {
28
+ it("does not call displayLockfileWarning", async () => {
29
+ await file.inTemporaryDirectory(async (tmpDir) => {
30
+ await file.write(path.join(tmpDir, "package-lock.json"), "");
31
+ const outputMock = outputMocker.mockAndCaptureOutput();
32
+ await checkLockfileStatus(tmpDir);
33
+ expect(outputMock.warn()).toBe("");
34
+ });
35
+ });
36
+ describe("and it is being ignored by Git", () => {
37
+ beforeEach(() => {
38
+ vi.mocked(checkIgnoreMock).mockResolvedValue(["package-lock.json"]);
39
+ });
40
+ it("renders a warning", async () => {
41
+ await file.inTemporaryDirectory(async (tmpDir) => {
42
+ await file.write(path.join(tmpDir, "package-lock.json"), "");
43
+ const outputMock = outputMocker.mockAndCaptureOutput();
44
+ await checkLockfileStatus(tmpDir);
45
+ expect(outputMock.warn()).toMatch(
46
+ / warning .+ Lockfile ignored by Git .+/is
47
+ );
48
+ });
49
+ });
50
+ });
51
+ });
52
+ describe("when there are multiple lockfiles", () => {
53
+ it("renders a warning", async () => {
54
+ await file.inTemporaryDirectory(async (tmpDir) => {
55
+ await file.write(path.join(tmpDir, "package-lock.json"), "");
56
+ await file.write(path.join(tmpDir, "pnpm-lock.yaml"), "");
57
+ const outputMock = outputMocker.mockAndCaptureOutput();
58
+ await checkLockfileStatus(tmpDir);
59
+ expect(outputMock.warn()).toMatch(
60
+ / warning .+ Multiple lockfiles found .+/is
61
+ );
62
+ });
63
+ });
64
+ });
65
+ describe("when a lockfile is missing", () => {
66
+ it("renders a warning", async () => {
67
+ await file.inTemporaryDirectory(async (tmpDir) => {
68
+ const outputMock = outputMocker.mockAndCaptureOutput();
69
+ await checkLockfileStatus(tmpDir);
70
+ expect(outputMock.warn()).toMatch(/ warning .+ No lockfile found .+/is);
71
+ });
72
+ });
73
+ });
74
+ });
@@ -6,5 +6,13 @@ declare const commonFlags: {
6
6
  force: _oclif_core_lib_interfaces_parser_js.BooleanFlag<boolean>;
7
7
  };
8
8
  declare function flagsToCamelObject(obj: Record<string, any>): any;
9
+ /**
10
+ * Parse process arguments into an object for use in the cli as flags.
11
+ * This is used when starting the init command from create-hydrogen without Oclif.
12
+ * @example
13
+ * input: `node ./bin --force --no-install-deps --language js`
14
+ * output: { force: true, installDeps: false, language: 'js' }
15
+ */
16
+ declare function parseProcessFlags(processArgv: string[], flagMap?: Record<string, string>): any;
9
17
 
10
- export { commonFlags, flagsToCamelObject };
18
+ export { commonFlags, flagsToCamelObject, parseProcessFlags };
@@ -23,5 +23,23 @@ function flagsToCamelObject(obj) {
23
23
  return acc;
24
24
  }, {});
25
25
  }
26
+ function parseProcessFlags(processArgv, flagMap = {}) {
27
+ const [, , ...args] = processArgv;
28
+ const options = {};
29
+ for (let i = 0; i < args.length; i++) {
30
+ const arg = args[i];
31
+ const nextArg = args[i + 1];
32
+ if (arg?.startsWith("-")) {
33
+ let key = arg.replace(/^\-{1,2}/, "");
34
+ let value = !nextArg || nextArg.startsWith("-") ? true : nextArg;
35
+ if (value === true && key.startsWith("no-")) {
36
+ value = false;
37
+ key = key.replace("no-", "");
38
+ }
39
+ options[flagMap[key] || key] = value;
40
+ }
41
+ }
42
+ return flagsToCamelObject(options);
43
+ }
26
44
 
27
- export { commonFlags, flagsToCamelObject };
45
+ export { commonFlags, flagsToCamelObject, parseProcessFlags };
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { flagsToCamelObject } from './flags.js';
2
+ import { flagsToCamelObject, parseProcessFlags } from './flags.js';
3
3
 
4
4
  describe("CLI flag utils", () => {
5
5
  it("turns kebab-case flags to camelCase", async () => {
@@ -15,4 +15,29 @@ describe("CLI flag utils", () => {
15
15
  flag: "value"
16
16
  });
17
17
  });
18
+ it("parses flags from process.argv", async () => {
19
+ expect(
20
+ parseProcessFlags(
21
+ "node ./bin --force --install-deps --template hello-world --path test --language ts".split(
22
+ " "
23
+ )
24
+ )
25
+ ).toMatchObject({
26
+ force: true,
27
+ installDeps: true,
28
+ template: "hello-world",
29
+ path: "test",
30
+ language: "ts"
31
+ });
32
+ expect(
33
+ parseProcessFlags(
34
+ "node ./bin -f --no-install-deps --language js".split(" "),
35
+ { f: "force" }
36
+ )
37
+ ).toMatchObject({
38
+ force: true,
39
+ installDeps: false,
40
+ language: "js"
41
+ });
42
+ });
18
43
  });
@@ -33,7 +33,7 @@ async function downloadTarball(url, storageDir) {
33
33
  strip: 1,
34
34
  filter: (name) => {
35
35
  name = name.replace(storageDir, "");
36
- return !name.startsWith("/templates/") || name.startsWith("/templates/skeleton/");
36
+ return !name.startsWith(path.normalize("/templates/")) || name.startsWith(path.normalize("/templates/skeleton/"));
37
37
  }
38
38
  })
39
39
  );
@@ -4,23 +4,20 @@ import recursiveReaddir from 'recursive-readdir';
4
4
 
5
5
  const VIRTUAL_ROUTES_DIR = "virtual-routes/routes";
6
6
  const VIRTUAL_ROOT = "virtual-routes/virtual-root";
7
- const INDEX_SUFFIX = "/index";
8
7
  async function addVirtualRoutes(config) {
9
8
  const userRouteList = Object.values(config.routes);
10
9
  const distPath = fileURLToPath(new URL("..", import.meta.url));
11
10
  const virtualRoutesPath = path.join(distPath, VIRTUAL_ROUTES_DIR);
12
11
  for (const absoluteFilePath of await recursiveReaddir(virtualRoutesPath)) {
13
12
  const relativeFilePath = path.relative(virtualRoutesPath, absoluteFilePath);
14
- const routePath = fileURLToPath(
15
- new URL(`file:///${relativeFilePath}`)
16
- ).replace(/\.[jt]sx?$/, "");
17
- const isIndex = routePath.endsWith(INDEX_SUFFIX);
18
- const normalizedVirtualRoutePath = isIndex ? routePath.slice(0, -INDEX_SUFFIX.length) || void 0 : routePath.slice(1).replace(/\$/g, ":").replace(/[\[\]]/g, "");
13
+ const routePath = relativeFilePath.replace(/\.[jt]sx?$/, "").replaceAll("\\", "/");
14
+ const isIndex = /(^|\/)index$/.test(routePath);
15
+ const normalizedVirtualRoutePath = isIndex ? routePath.slice(0, -"index".length).replace(/\/$/, "") || void 0 : routePath.replace(/\$/g, ":").replace(/[\[\]]/g, "");
19
16
  const hasUserRoute = userRouteList.some(
20
17
  (r) => r.parentId === "root" && r.path === normalizedVirtualRoutePath
21
18
  );
22
19
  if (!hasUserRoute) {
23
- const id = VIRTUAL_ROUTES_DIR + routePath;
20
+ const id = VIRTUAL_ROUTES_DIR + "/" + routePath;
24
21
  config.routes[id] = {
25
22
  id,
26
23
  parentId: VIRTUAL_ROOT,
@@ -16,10 +16,12 @@ describe("virtual routes", () => {
16
16
  });
17
17
  expect(config.routes[VIRTUAL_ROUTES_DIR + "/index"]).toMatchObject({
18
18
  parentId: VIRTUAL_ROOT,
19
+ path: void 0,
19
20
  file: "../" + VIRTUAL_ROUTES_DIR + "/index.tsx"
20
21
  });
21
22
  expect(config.routes[VIRTUAL_ROUTES_DIR + "/graphiql"]).toMatchObject({
22
23
  parentId: VIRTUAL_ROOT,
24
+ path: "graphiql",
23
25
  file: "../" + VIRTUAL_ROUTES_DIR + "/graphiql.tsx"
24
26
  });
25
27
  });
@@ -0,0 +1,28 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none">
2
+ <style>
3
+ .stroke {
4
+ stroke: #000;
5
+ }
6
+ .fill {
7
+ fill: #000;
8
+ }
9
+ @media (prefers-color-scheme: dark) {
10
+ .stroke {
11
+ stroke: #fff;
12
+ }
13
+ .fill {
14
+ fill: #fff;
15
+ }
16
+ }
17
+ </style>
18
+ <path
19
+ class="stroke"
20
+ fill-rule="evenodd"
21
+ d="M16.1 16.04 1 8.02 6.16 5.3l5.82 3.09 4.88-2.57-5.82-3.1L16.21 0l15.1 8.02-5.17 2.72-5.5-2.91-4.88 2.57 5.5 2.92-5.16 2.72Z"
22
+ />
23
+ <path
24
+ class="fill"
25
+ fill-rule="evenodd"
26
+ d="M16.1 32 1 23.98l5.16-2.72 5.82 3.08 4.88-2.57-5.82-3.08 5.17-2.73 15.1 8.02-5.17 2.72-5.5-2.92-4.88 2.58 5.5 2.92L16.1 32Z"
27
+ />
28
+ </svg>
@@ -0,0 +1,238 @@
1
+ @font-face {
2
+ font-family: 'Inter';
3
+ src: url('../assets/inter-variable-font.ttf') format('truetype-variations');
4
+ }
5
+ @font-face {
6
+ font-family: 'JetBrains Mono';
7
+ src: url('../assets/jetbrainsmono-variable-font.ttf')
8
+ format('truetype-variations');
9
+ }
10
+
11
+ * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ h1,
16
+ h2,
17
+ p {
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ h1 {
23
+ font-size: 3rem;
24
+ line-height: 1.25;
25
+ }
26
+
27
+ h2 {
28
+ font-size: 1.2rem;
29
+ font-weight: 700;
30
+ line-height: 1.4;
31
+ }
32
+
33
+ p {
34
+ font-size: 1rem;
35
+ line-height: 1.4;
36
+ font-weight: 500;
37
+ }
38
+
39
+ body {
40
+ padding: 0;
41
+ margin: 0;
42
+ background: #ffffff;
43
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
44
+ Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
45
+ -webkit-font-smoothing: antialiased;
46
+ min-height: 100vh;
47
+ display: flex;
48
+ flex-direction: column;
49
+ }
50
+
51
+ .hydrogen-virtual-route {
52
+ display: grid;
53
+ grid-template-rows: auto 1fr auto;
54
+ min-height: 100vh;
55
+ }
56
+
57
+ header {
58
+ display: flex;
59
+ flex-direction: row;
60
+ gap: 20px;
61
+ padding: 14px 20px;
62
+ width: 100%;
63
+ height: 48px;
64
+ background: #ffffff;
65
+ align-items: center;
66
+ color: #5c5f62;
67
+ }
68
+
69
+ header nav {
70
+ display: flex;
71
+ flex-direction: row;
72
+ align-items: center;
73
+ gap: 1rem;
74
+ margin-left: auto;
75
+ }
76
+
77
+ header h1 {
78
+ font-size: 0.75rem;
79
+ }
80
+
81
+ header p {
82
+ font-family: 'JetBrains Mono';
83
+ font-weight: 700;
84
+ font-size: 0.875rem;
85
+ border: 1.5px solid #d2d5d8;
86
+ border-radius: 4px;
87
+ padding: 1px;
88
+ white-space: nowrap;
89
+ }
90
+
91
+ main {
92
+ padding: 2rem;
93
+ max-width: 50rem;
94
+ margin: auto;
95
+ }
96
+
97
+ footer {
98
+ background: #f6f6f7;
99
+ }
100
+
101
+ footer div {
102
+ display: block;
103
+ max-width: 50rem;
104
+ padding: 2rem;
105
+ margin: auto;
106
+ }
107
+
108
+ footer a {
109
+ color: #5c5f62;
110
+ text-decoration: none;
111
+ }
112
+
113
+ main > h1 {
114
+ color: #202223;
115
+ font-weight: 900;
116
+ margin-bottom: 24px;
117
+ }
118
+
119
+ main > p {
120
+ margin-bottom: 16px;
121
+ }
122
+
123
+ main > section {
124
+ margin-bottom: 40px;
125
+ }
126
+
127
+ main a {
128
+ color: #475f91;
129
+ font-size: 1rem;
130
+ position: relative;
131
+ font-weight: 500;
132
+ }
133
+
134
+ main a::after {
135
+ content: url("data:image/svg+xml,%3Csvg width='18' height='24' viewBox='0 0 18 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.45312 19.3828H13.9297C15.5703 19.3828 16.3828 18.5703 16.3828 16.9609V7.42188C16.3828 5.8125 15.5703 5 13.9297 5H4.45312C2.82031 5 2 5.80469 2 7.42188V16.9609C2 18.5781 2.82031 19.3828 4.45312 19.3828ZM4.46875 18.125C3.6875 18.125 3.25781 17.7109 3.25781 16.8984V7.48438C3.25781 6.67188 3.6875 6.25781 4.46875 6.25781H13.9141C14.6875 6.25781 15.125 6.67188 15.125 7.48438V16.8984C15.125 17.7109 14.6875 18.125 13.9141 18.125H4.46875ZM11.6406 14.1172C11.9844 14.1172 12.2188 13.8516 12.2188 13.4922V9.80469C12.2188 9.34375 11.9609 9.16406 11.5625 9.16406H7.85938C7.49219 9.16406 7.25781 9.39062 7.25781 9.73438C7.25781 10.0781 7.5 10.3047 7.875 10.3047H9.29688L10.4531 10.1797L9.23438 11.3125L6.35156 14.1953C6.24219 14.3047 6.17188 14.4609 6.17188 14.6172C6.17188 14.9688 6.39844 15.1953 6.74219 15.1953C6.92969 15.1953 7.07812 15.125 7.1875 15.0156L10.0625 12.1406L11.1875 10.9375L11.0703 12.1562V13.5078C11.0703 13.875 11.2969 14.1172 11.6406 14.1172Z' fill='%23475F91'/%3E%3C/svg%3E%0A");
136
+ vertical-align: middle;
137
+ }
138
+
139
+ main {
140
+ display: flex;
141
+ flex-direction: column;
142
+ }
143
+
144
+ .Links ul {
145
+ margin: 0;
146
+ padding: 0 0.5rem;
147
+ list-style: none;
148
+ }
149
+
150
+ .Links li {
151
+ margin-bottom: 1rem;
152
+ }
153
+
154
+ .Links li::before {
155
+ content: '\25fc';
156
+ color: #5c5f62;
157
+ display: inline-block;
158
+ vertical-align: text-bottom;
159
+ width: 1.2rem;
160
+ font-size: 0.8rem;
161
+ }
162
+
163
+ .Links h2 {
164
+ margin-bottom: 0.5rem;
165
+ font-size: 1.5rem;
166
+ color: #44474a;
167
+ font-weight: 700;
168
+ }
169
+
170
+ .Banner {
171
+ box-sizing: border-box;
172
+ display: flex;
173
+ flex-direction: column;
174
+ align-items: flex-start;
175
+ padding: 16px;
176
+ gap: 8px;
177
+ box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.1);
178
+ border-radius: 8px;
179
+ font-style: normal;
180
+ font-size: 1rem;
181
+ line-height: 24px;
182
+ color: #5c5f62;
183
+ border: 1px solid #d2d5d8;
184
+ background: #fafbfb;
185
+ }
186
+
187
+ .Banner div {
188
+ display: flex;
189
+ flex-direction: row;
190
+ align-items: center;
191
+ gap: 16px;
192
+ }
193
+
194
+ .Banner p {
195
+ padding-left: 48px;
196
+ }
197
+
198
+ .Banner code {
199
+ background: #f6f6f7;
200
+ border: 1px solid #d2d5d8;
201
+ font-family: monospace;
202
+ border-radius: 4px;
203
+ padding: 2px;
204
+ }
205
+
206
+ .Banner h2 {
207
+ font-size: 1rem;
208
+ color: #202223;
209
+ }
210
+
211
+ .Banner.ErrorBanner {
212
+ color: #981c06;
213
+ border: 1px solid #fda9a5;
214
+ background: #fee9e8;
215
+ }
216
+
217
+ .Banner.ErrorBanner span {
218
+ background: #fef4f4;
219
+ border: 1px solid #fda9a5;
220
+ border-radius: 4px;
221
+ padding: 2px;
222
+ color: #74180c;
223
+ }
224
+
225
+ .Banner.ErrorBanner a {
226
+ color: #b92409;
227
+ }
228
+
229
+ .Banner.ErrorBanner a::after {
230
+ content: url("data:image/svg+xml,%3Csvg width='18' height='24' viewBox='0 0 18 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.45312 19.3828H13.9297C15.5703 19.3828 16.3828 18.5703 16.3828 16.9609V7.42188C16.3828 5.8125 15.5703 5 13.9297 5H4.45312C2.82031 5 2 5.80469 2 7.42188V16.9609C2 18.5781 2.82031 19.3828 4.45312 19.3828ZM4.46875 18.125C3.6875 18.125 3.25781 17.7109 3.25781 16.8984V7.48438C3.25781 6.67188 3.6875 6.25781 4.46875 6.25781H13.9141C14.6875 6.25781 15.125 6.67188 15.125 7.48438V16.8984C15.125 17.7109 14.6875 18.125 13.9141 18.125H4.46875ZM11.6406 14.1172C11.9844 14.1172 12.2188 13.8516 12.2188 13.4922V9.80469C12.2188 9.34375 11.9609 9.16406 11.5625 9.16406H7.85938C7.49219 9.16406 7.25781 9.39062 7.25781 9.73438C7.25781 10.0781 7.5 10.3047 7.875 10.3047H9.29688L10.4531 10.1797L9.23438 11.3125L6.35156 14.1953C6.24219 14.3047 6.17188 14.4609 6.17188 14.6172C6.17188 14.9688 6.39844 15.1953 6.74219 15.1953C6.92969 15.1953 7.07812 15.125 7.1875 15.0156L10.0625 12.1406L11.1875 10.9375L11.0703 12.1562V13.5078C11.0703 13.875 11.2969 14.1172 11.6406 14.1172Z' fill='%23B92409'/%3E%3C/svg%3E%0A");
231
+ vertical-align: middle;
232
+ color: red;
233
+ }
234
+
235
+ .Banner.ErrorBanner > h2 {
236
+ font-size: 1rem;
237
+ color: #74180c;
238
+ }
@@ -7,6 +7,8 @@ import { IconTwitter } from "../components/IconTwitter.jsx";
7
7
  import { IconBanner } from "../components/IconBanner.jsx";
8
8
  import { IconError } from "../components/IconError.jsx";
9
9
  import favicon from "../assets/favicon.svg";
10
+ import interVariableFont from "../assets/inter-variable-font.ttf";
11
+ import jetbrainsmonoVariableFont from "../assets/jetbrainsmono-variable-font.ttf";
10
12
  const meta = () => {
11
13
  return {
12
14
  title: "Hydrogen",
@@ -20,6 +22,20 @@ const links = () => [
20
22
  rel: "icon",
21
23
  type: "image/svg+xml",
22
24
  href: favicon
25
+ },
26
+ {
27
+ rel: "preload",
28
+ href: interVariableFont,
29
+ as: "font",
30
+ type: "font/ttf",
31
+ crossOrigin: "anonymous"
32
+ },
33
+ {
34
+ rel: "preload",
35
+ href: jetbrainsmonoVariableFont,
36
+ as: "font",
37
+ type: "font/ttf",
38
+ crossOrigin: "anonymous"
23
39
  }
24
40
  ];
25
41
  async function loader({ context }) {
@@ -44,24 +60,31 @@ function Index() {
44
60
  {shopName || "Hydrogen"}
45
61
  </h1>
46
62
  <p>Welcome to your new custom storefront</p>
47
- <section className="Banner">
63
+ {configDone ? null : <section className="Banner">
48
64
  <div>
49
65
  <IconBanner />
50
66
  <h2>Configure storefront token</h2>
51
67
  </div>
52
68
  <p>
53
- {"You\u2019re seeing this because you have not yet configured your storefront token. To get started, edit "}
69
+ {"You\u2019re seeing this because you have not yet configured your storefront token. "}
70
+ <br />
71
+ <br />
72
+ {" To get started, edit "}
54
73
  {` `}
55
74
  <code>.env</code>
56
- {". Then, create your first route with the file "}
75
+ {". Then, create your first route with the file"}
57
76
  {` `}
58
77
  <code>/app/routes/index.jsx</code>
59
78
  {". Learn more about"}
60
79
  {` `}
61
- <a href="https://shopify.dev/custom-storefronts/hydrogen/getting-started/quickstart">{"connecting a\xA0storefront"}</a>
80
+ <a target="_blank" rel="norefferer noopener" href="https://shopify.dev/docs/custom-storefronts/hydrogen/environment-variables">editing environment variables</a>
81
+ {` `}
82
+ {"and"}
83
+ {` `}
84
+ <a target="_blank" rel="norefferer noopener" href="https://shopify.dev/docs/custom-storefronts/hydrogen/building/begin-development#step-4-create-a-route">creating routes</a>
62
85
  {"."}
63
86
  </p>
64
- </section>
87
+ </section>}
65
88
  <ResourcesLinks />
66
89
  </Layout></>;
67
90
  }
@@ -78,9 +101,8 @@ function ErrorPage() {
78
101
  <p>
79
102
  {"Check your domain and API token in your "}
80
103
  <code>.env</code>
81
- {" file. Read the documentation on"}
82
- {` `}
83
- <a target="_blank" rel="norefferer noopener" href="https://shopify.dev/custom-storefronts/hydrogen/getting-started/quickstart">{"how to configure your\xA0storefront"}</a>
104
+ {" file. Learn more about"}
105
+ <a target="_blank" rel="norefferer noopener" href="https://shopify.dev/docs/custom-storefronts/hydrogen/environment-variables">editing environment variables</a>
84
106
  {"."}
85
107
  </p>
86
108
  </section>
@@ -112,9 +134,9 @@ function Layout({
112
134
  <h1>{shopName?.toUpperCase()}</h1>
113
135
  <p>{"\xA0Dev Mode\xA0"}</p>
114
136
  <nav>
115
- <a href="https://discord.com/invite/shopifydevs"><IconDiscord /></a>
116
- <a href="https://github.com/Shopify/hydrogen"><IconGithub /></a>
117
- <a href="https://twitter.com/shopifydevs?lang=en"><IconTwitter /></a>
137
+ <a target="_blank" rel="norefferer noopener" href="https://discord.com/invite/shopifydevs"><IconDiscord /></a>
138
+ <a target="_blank" rel="norefferer noopener" href="https://github.com/Shopify/hydrogen"><IconGithub /></a>
139
+ <a target="_blank" rel="norefferer noopener" href="https://twitter.com/shopifydevs?lang=en"><IconTwitter /></a>
118
140
  </nav>
119
141
  </header>
120
142
  <main>{children}</main>
@@ -1 +1 @@
1
- {"version":"4.0.4","commands":{"hydrogen:build":{"id":"hydrogen:build","description":"Builds a Hydrogen storefront for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"sourcemap":{"name":"sourcemap","type":"boolean","description":"Generate sourcemaps for the build.","allowNo":false},"disable-route-warning":{"name":"disable-route-warning","type":"boolean","description":"Disable warning about missing standard routes.","allowNo":false}},"args":[]},"hydrogen:check":{"id":"hydrogen:check","description":"Returns diagnostic information about a Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"resource","description":"The resource to check. Currently only 'routes' is supported.","required":true,"options":["routes"]}]},"hydrogen:dev":{"id":"hydrogen:dev","description":"Runs Hydrogen storefront in an Oxygen worker for development.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000},"disable-virtual-routes":{"name":"disable-virtual-routes","type":"boolean","description":"Disable rendering fallback routes when a route file doesn't exist","allowNo":false}},"args":[]},"hydrogen:init":{"id":"hydrogen:init","description":"Creates a new Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"language":{"name":"language","type":"option","description":"Sets the template language to use. One of `js` or `ts`.","multiple":false,"default":"js"},"template":{"name":"template","type":"option","description":"Sets the template to use. One of `demo-store` or `hello-world`.","multiple":false}},"args":[]},"hydrogen:preview":{"id":"hydrogen:preview","description":"Runs a Hydrogen storefront in an Oxygen worker for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000}},"args":[]},"hydrogen:generate:route":{"id":"hydrogen:generate:route","description":"Generates a standard Shopify route.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"route","description":"The route to generate. One of home,page,cart,products,collections,policies,robots,sitemap,account,all.","required":true,"options":["home","page","cart","products","collections","policies","robots","sitemap","account","all"]}]},"hydrogen:generate:routes":{"id":"hydrogen:generate:routes","description":"Generates all supported standard shopify routes.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[]}}}
1
+ {"version":"4.0.6","commands":{"hydrogen:build":{"id":"hydrogen:build","description":"Builds a Hydrogen storefront for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"sourcemap":{"name":"sourcemap","type":"boolean","description":"Generate sourcemaps for the build.","allowNo":false},"disable-route-warning":{"name":"disable-route-warning","type":"boolean","description":"Disable warning about missing standard routes.","allowNo":false}},"args":[]},"hydrogen:check":{"id":"hydrogen:check","description":"Returns diagnostic information about a Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"resource","description":"The resource to check. Currently only 'routes' is supported.","required":true,"options":["routes"]}]},"hydrogen:dev":{"id":"hydrogen:dev","description":"Runs Hydrogen storefront in an Oxygen worker for development.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000},"disable-virtual-routes":{"name":"disable-virtual-routes","type":"boolean","description":"Disable rendering fallback routes when a route file doesn't exist","allowNo":false}},"args":[]},"hydrogen:init":{"id":"hydrogen:init","description":"Creates a new Hydrogen storefront.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the new Hydrogen storefront.","multiple":false},"language":{"name":"language","type":"option","description":"Sets the template language to use. One of `js` or `ts`.","multiple":false},"template":{"name":"template","type":"option","description":"Sets the template to use. One of `demo-store` or `hello-world`.","multiple":false},"install-deps":{"name":"install-deps","type":"boolean","description":"Auto install dependencies using the active package manager","allowNo":true}},"args":[]},"hydrogen:preview":{"id":"hydrogen:preview","description":"Runs a Hydrogen storefront in an Oxygen worker for production.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false},"port":{"name":"port","type":"option","description":"Port to run the server on.","multiple":false,"default":3000}},"args":[]},"hydrogen:generate:route":{"id":"hydrogen:generate:route","description":"Generates a standard Shopify route.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[{"name":"route","description":"The route to generate. One of home,page,cart,products,collections,policies,robots,sitemap,account,all.","required":true,"options":["home","page","cart","products","collections","policies","robots","sitemap","account","all"]}]},"hydrogen:generate:routes":{"id":"hydrogen:generate:routes","description":"Generates all supported standard shopify routes.","strict":true,"pluginName":"@shopify/cli-hydrogen","pluginAlias":"@shopify/cli-hydrogen","pluginType":"core","aliases":[],"flags":{"adapter":{"name":"adapter","type":"option","description":"Remix adapter used in the route. The default is `@shopify/remix-oxygen`.","multiple":false},"typescript":{"name":"typescript","type":"boolean","description":"Generate TypeScript files","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Overwrite the destination directory and files if they already exist.","allowNo":false},"path":{"name":"path","type":"option","description":"The path to the directory of the Hydrogen storefront. The default is the current directory.","multiple":false}},"args":[]}}}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public",
5
5
  "@shopify:registry": "https://registry.npmjs.org"
6
6
  },
7
- "version": "4.0.4",
7
+ "version": "4.0.6",
8
8
  "license": "SEE LICENSE IN LICENSE.md",
9
9
  "type": "module",
10
10
  "scripts": {
@@ -13,8 +13,8 @@
13
13
  "typecheck": "tsc --noEmit",
14
14
  "generate:manifest": "oclif manifest",
15
15
  "prepack": "npm run build",
16
- "test": "vitest run",
17
- "test:watch": "vitest"
16
+ "test": "cross-env SHOPIFY_UNIT_TEST=1 vitest run",
17
+ "test:watch": "cross-env SHOPIFY_UNIT_TEST=1 vitest"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/fs-extra": "^9.0.13",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@remix-run/react": "^1.12.0",
31
- "@shopify/hydrogen-react": "^2023.1.4",
32
- "@shopify/remix-oxygen": "^1.0.2"
31
+ "@shopify/hydrogen-react": "^2023.1.5",
32
+ "@shopify/remix-oxygen": "^1.0.3"
33
33
  },
34
34
  "dependencies": {
35
35
  "@oclif/core": "^1.20.4",
@@ -1 +0,0 @@
1
-