@shopify/cli-hydrogen 7.1.2 → 8.0.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 (136) hide show
  1. package/dist/commands/hydrogen/build-vite.js +19 -10
  2. package/dist/commands/hydrogen/build.js +10 -2
  3. package/dist/commands/hydrogen/check.js +1 -0
  4. package/dist/commands/hydrogen/codegen.js +1 -0
  5. package/dist/commands/hydrogen/customer-account/push.js +170 -0
  6. package/dist/commands/hydrogen/debug/cpu.js +3 -0
  7. package/dist/commands/hydrogen/deploy.js +121 -36
  8. package/dist/commands/hydrogen/dev-vite.js +128 -59
  9. package/dist/commands/hydrogen/dev.js +108 -51
  10. package/dist/commands/hydrogen/env/list.js +7 -8
  11. package/dist/commands/hydrogen/env/pull.js +17 -1
  12. package/dist/commands/hydrogen/env/{push__unstable.js → push.js} +23 -50
  13. package/dist/commands/hydrogen/generate/route.js +1 -0
  14. package/dist/commands/hydrogen/init.js +45 -17
  15. package/dist/commands/hydrogen/link.js +20 -4
  16. package/dist/commands/hydrogen/list.js +1 -0
  17. package/dist/commands/hydrogen/login.js +1 -0
  18. package/dist/commands/hydrogen/logout.js +1 -0
  19. package/dist/commands/hydrogen/preview.js +31 -16
  20. package/dist/commands/hydrogen/setup/css.js +8 -1
  21. package/dist/commands/hydrogen/setup/markets.js +1 -0
  22. package/dist/commands/hydrogen/setup/vite.js +244 -138
  23. package/dist/commands/hydrogen/setup.js +21 -22
  24. package/dist/commands/hydrogen/shortcut.js +10 -0
  25. package/dist/commands/hydrogen/unlink.js +1 -0
  26. package/dist/commands/hydrogen/upgrade.js +2 -1
  27. package/dist/generator-templates/assets/vite/package.json +3 -4
  28. package/dist/generator-templates/assets/vite/vite.config.js +10 -2
  29. package/dist/generator-templates/starter/CHANGELOG.md +89 -0
  30. package/dist/generator-templates/starter/README.md +3 -44
  31. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +1 -0
  32. package/dist/generator-templates/starter/app/lib/fragments.ts +2 -0
  33. package/dist/generator-templates/starter/app/root.tsx +2 -5
  34. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +1 -1
  35. package/dist/generator-templates/starter/app/routes/account.tsx +1 -1
  36. package/dist/generator-templates/starter/app/routes/collections.all.tsx +160 -0
  37. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +1 -2
  38. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +6 -3
  39. package/dist/generator-templates/starter/{remix.env.d.ts → env.d.ts} +8 -2
  40. package/dist/generator-templates/starter/package.json +14 -9
  41. package/dist/generator-templates/starter/server.ts +2 -1
  42. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +59 -3
  43. package/dist/generator-templates/starter/vite.config.ts +21 -0
  44. package/dist/{commands/hydrogen/init.d.ts → init.d.ts} +11 -3
  45. package/dist/lib/check-lockfile.js +12 -18
  46. package/dist/lib/codegen.js +37 -13
  47. package/dist/lib/common.js +50 -0
  48. package/dist/lib/cpu-profiler.js +4 -1
  49. package/dist/lib/dev-shared.js +97 -0
  50. package/dist/lib/environment-variables.js +51 -30
  51. package/dist/lib/file.js +8 -1
  52. package/dist/lib/flags.js +37 -16
  53. package/dist/lib/graphql/admin/customer-application-update.js +29 -0
  54. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  55. package/dist/lib/graphql/admin/list-environments.js +1 -0
  56. package/dist/lib/graphql/admin/pull-variables.js +4 -4
  57. package/dist/lib/graphql/admin/test-helper.js +37 -0
  58. package/dist/lib/log.js +86 -13
  59. package/dist/lib/mini-oxygen/common.js +19 -33
  60. package/dist/lib/mini-oxygen/index.js +6 -2
  61. package/dist/lib/mini-oxygen/node.js +43 -31
  62. package/dist/lib/mini-oxygen/workerd.js +72 -165
  63. package/dist/lib/missing-routes.js +1 -1
  64. package/dist/lib/onboarding/common.js +82 -70
  65. package/dist/lib/onboarding/local.js +19 -9
  66. package/dist/lib/onboarding/remote.js +35 -30
  67. package/dist/lib/package-managers.js +24 -0
  68. package/dist/lib/remix-config.js +17 -1
  69. package/dist/lib/request-events.js +6 -1
  70. package/dist/lib/setups/i18n/replacers.js +9 -6
  71. package/dist/lib/setups/routes/generate.js +1 -0
  72. package/dist/lib/shell.js +2 -1
  73. package/dist/lib/shopify-config.js +19 -1
  74. package/dist/lib/template-diff.js +36 -15
  75. package/dist/lib/template-downloader.js +35 -5
  76. package/dist/lib/transpile/morph/typedefs.js +5 -2
  77. package/dist/lib/transpile/project.js +8 -4
  78. package/dist/lib/tunneling.js +44 -0
  79. package/dist/lib/virtual-routes.js +1 -1
  80. package/dist/lib/vite-config.js +39 -9
  81. package/oclif.manifest.json +711 -498
  82. package/package.json +32 -24
  83. package/dist/commands/hydrogen/deploy.test.js +0 -553
  84. package/dist/commands/hydrogen/env/list.test.js +0 -148
  85. package/dist/commands/hydrogen/env/pull.test.js +0 -207
  86. package/dist/commands/hydrogen/env/push__unstable.test.js +0 -383
  87. package/dist/commands/hydrogen/generate/route.test.js +0 -43
  88. package/dist/commands/hydrogen/init.test.js +0 -641
  89. package/dist/commands/hydrogen/link.test.js +0 -187
  90. package/dist/commands/hydrogen/list.test.js +0 -111
  91. package/dist/commands/hydrogen/setup.test.js +0 -61
  92. package/dist/commands/hydrogen/shortcut.test.js +0 -30
  93. package/dist/commands/hydrogen/unlink.test.js +0 -36
  94. package/dist/commands/hydrogen/upgrade.test.js +0 -786
  95. package/dist/generator-templates/starter/remix.config.js +0 -24
  96. package/dist/lib/auth.test.js +0 -157
  97. package/dist/lib/check-lockfile.test.js +0 -81
  98. package/dist/lib/check-version.test.js +0 -86
  99. package/dist/lib/environment-variables.test.js +0 -149
  100. package/dist/lib/file.test.js +0 -68
  101. package/dist/lib/flags.test.js +0 -43
  102. package/dist/lib/get-oxygen-deployment-data.test.js +0 -120
  103. package/dist/lib/gid.test.js +0 -15
  104. package/dist/lib/graphql/admin/client.test.js +0 -76
  105. package/dist/lib/graphql/admin/create-storefront.test.js +0 -64
  106. package/dist/lib/graphql/admin/link-storefront.test.js +0 -38
  107. package/dist/lib/graphql/admin/list-environments.test.js +0 -44
  108. package/dist/lib/graphql/admin/list-storefronts.test.js +0 -44
  109. package/dist/lib/graphql/admin/pull-variables.test.js +0 -43
  110. package/dist/lib/graphql/business-platform/user-account.test.js +0 -80
  111. package/dist/lib/log.test.js +0 -92
  112. package/dist/lib/mini-oxygen/assets.js +0 -134
  113. package/dist/lib/mini-oxygen/mini-oxygen.test.js +0 -214
  114. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +0 -227
  115. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +0 -200
  116. package/dist/lib/mini-oxygen/workerd-inspector.js +0 -219
  117. package/dist/lib/missing-routes.test.js +0 -45
  118. package/dist/lib/remix-version-check.test.js +0 -39
  119. package/dist/lib/remix-version-interop.test.js +0 -13
  120. package/dist/lib/setups/i18n/domains.test.js +0 -39
  121. package/dist/lib/setups/i18n/replacers.test.js +0 -261
  122. package/dist/lib/setups/i18n/subdomains.test.js +0 -39
  123. package/dist/lib/setups/i18n/subfolders.test.js +0 -39
  124. package/dist/lib/setups/routes/generate.test.js +0 -296
  125. package/dist/lib/shell.test.js +0 -111
  126. package/dist/lib/shopify-config.test.js +0 -199
  127. package/dist/lib/string.test.js +0 -16
  128. package/dist/lib/virtual-routes.test.js +0 -49
  129. package/dist/lib/vite/hydrogen-middleware.js +0 -82
  130. package/dist/lib/vite/mini-oxygen.js +0 -152
  131. package/dist/lib/vite/plugins.d.ts +0 -27
  132. package/dist/lib/vite/plugins.js +0 -139
  133. package/dist/lib/vite/shared.js +0 -10
  134. package/dist/lib/vite/utils.js +0 -55
  135. package/dist/lib/vite/worker-entry.js +0 -1518
  136. /package/dist/generator-templates/starter/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -1,111 +0,0 @@
1
- import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest';
2
- import { platform, userInfo } from 'node:os';
3
- import { fileExists } from '@shopify/cli-kit/node/fs';
4
- import { getPackageManager } from '@shopify/cli-kit/node/node-package-manager';
5
- import { shellWriteAlias, createPlatformShortcut, getCliCommand } from './shell.js';
6
- import { execAsync } from './process.js';
7
-
8
- vi.mock("node:os");
9
- vi.mock("node:child_process");
10
- vi.mock("@shopify/cli-kit/node/fs");
11
- vi.mock("@shopify/cli-kit/node/node-package-manager");
12
- vi.mock("./process.js", async () => {
13
- const original = await vi.importActual(
14
- "./process.js"
15
- );
16
- return {
17
- ...original,
18
- execAsync: vi.fn()
19
- };
20
- });
21
- vi.mocked(fileExists).mockResolvedValue(false);
22
- vi.mocked(getPackageManager).mockResolvedValue("npm");
23
- describe("shell", () => {
24
- beforeEach(() => {
25
- vi.resetAllMocks();
26
- });
27
- afterEach(() => {
28
- delete process.env.MINGW_PREFIX;
29
- });
30
- describe("shellWriteAlias", () => {
31
- ["bash", "zsh", "fish"].forEach((shell) => {
32
- const alias = "h2";
33
- const command = "command";
34
- it(`writes ${shell} alias to file`, async () => {
35
- await expect(
36
- shellWriteAlias(shell, alias, command)
37
- ).resolves.toBeTruthy();
38
- expect(execAsync).toHaveBeenLastCalledWith(
39
- expect.stringMatching(
40
- new RegExp(
41
- `printf "${command}" ${shell === "fish" ? ">" : ">>"} .*.${shell}`
42
- )
43
- )
44
- );
45
- });
46
- it(`skips writing ${shell} alias when not supported`, async () => {
47
- vi.mocked(execAsync).mockImplementation(
48
- (shellCommand) => shellCommand.startsWith("which") ? Promise.reject(null) : Promise.resolve({ stdout: "stuff", stderr: "" })
49
- );
50
- await expect(
51
- shellWriteAlias(shell, alias, command)
52
- ).resolves.toBeFalsy();
53
- expect(execAsync).not.toHaveBeenLastCalledWith(
54
- expect.stringMatching(/^printf/)
55
- );
56
- });
57
- it(`skips writing ${shell} alias when already aliased`, async () => {
58
- vi.mocked(fileExists).mockResolvedValue(true);
59
- vi.mocked(execAsync).mockImplementation(
60
- (shellCommand) => shellCommand.startsWith("which") || shellCommand.startsWith("grep") ? Promise.resolve({ stdout: "stuff", stderr: "" }) : Promise.reject(null)
61
- );
62
- await expect(
63
- shellWriteAlias(shell, alias, command)
64
- ).resolves.toBeTruthy();
65
- expect(execAsync).not.toHaveBeenLastCalledWith(
66
- expect.stringMatching(/^printf/)
67
- );
68
- });
69
- });
70
- });
71
- describe("createPlatformShortcut", () => {
72
- it("creates aliases for Unix", async () => {
73
- vi.mocked(platform).mockReturnValue("darwin");
74
- const result = await createPlatformShortcut();
75
- expect(result).toEqual(expect.arrayContaining(["zsh", "bash", "fish"]));
76
- });
77
- it("creates aliases for Windows", async () => {
78
- vi.mocked(platform).mockReturnValue("win32");
79
- const result = await createPlatformShortcut();
80
- expect(result).toEqual(
81
- expect.arrayContaining(["PowerShell", "PowerShell 7+"])
82
- );
83
- });
84
- it("creates aliases for Windows in Git Bash", async () => {
85
- process.env.MINGW_PREFIX = "something";
86
- vi.mocked(platform).mockReturnValue("win32");
87
- const result = await createPlatformShortcut();
88
- expect(result).toEqual(expect.arrayContaining(["bash"]));
89
- });
90
- });
91
- describe("getCliCommand", () => {
92
- it("returns the shortcut alias if available", async () => {
93
- vi.mocked(userInfo).mockReturnValue({ shell: "/bin/bash" });
94
- vi.mocked(execAsync).mockImplementation(
95
- (shellCommand) => shellCommand.startsWith("grep") ? Promise.resolve({ stdout: "stuff", stderr: "" }) : Promise.reject(null)
96
- );
97
- await expect(getCliCommand()).resolves.toEqual("h2");
98
- });
99
- it("returns the used package manager command", async () => {
100
- vi.mocked(execAsync).mockImplementation(() => Promise.reject(null));
101
- vi.mocked(getPackageManager).mockRejectedValueOnce(null);
102
- await expect(getCliCommand()).resolves.toEqual("npx shopify hydrogen");
103
- vi.mocked(getPackageManager).mockResolvedValue("npm");
104
- await expect(getCliCommand()).resolves.toEqual("npx shopify hydrogen");
105
- vi.mocked(getPackageManager).mockResolvedValue("yarn");
106
- await expect(getCliCommand()).resolves.toEqual("yarn shopify hydrogen");
107
- vi.mocked(getPackageManager).mockResolvedValue("pnpm");
108
- await expect(getCliCommand()).resolves.toEqual("pnpm shopify hydrogen");
109
- });
110
- });
111
- });
@@ -1,199 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { inTemporaryDirectory, mkdir, writeFile, fileExists, readFile } from '@shopify/cli-kit/node/fs';
3
- import { joinPath, dirname } from '@shopify/cli-kit/node/path';
4
- import { getConfig, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT, resetConfig, setUserAccount, setStorefront, unsetStorefront, ensureShopifyGitIgnore } from './shopify-config.js';
5
-
6
- async function writeExistingConfig(dir, config) {
7
- const existingConfig = config ?? {
8
- shop: "previous-shop",
9
- shopName: "Previous Shop",
10
- email: "email",
11
- storefront: {
12
- id: "gid://shopify/HydrogenStorefront/1",
13
- title: "Hydrogen"
14
- }
15
- };
16
- const filePath = joinPath(dir, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
17
- await mkdir(dirname(filePath));
18
- await writeFile(filePath, JSON.stringify(existingConfig));
19
- expect(JSON.parse(await readFile(filePath))).toStrictEqual(existingConfig);
20
- return { existingConfig, filePath };
21
- }
22
- describe("getConfig()", () => {
23
- describe("when no config exists", () => {
24
- it("returns an empty object", async () => {
25
- await inTemporaryDirectory(async (tmpDir) => {
26
- const config = await getConfig(tmpDir);
27
- expect(config).toStrictEqual({});
28
- });
29
- });
30
- });
31
- describe("when a config exists", () => {
32
- it("returns the config", async () => {
33
- await inTemporaryDirectory(async (tmpDir) => {
34
- const existingConfig = {
35
- shop: "my-shop",
36
- shopName: "My Shop",
37
- email: "email"
38
- };
39
- const filePath = joinPath(tmpDir, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
40
- await mkdir(dirname(filePath));
41
- await writeFile(filePath, JSON.stringify(existingConfig));
42
- const config = await getConfig(tmpDir);
43
- expect(config).toStrictEqual(existingConfig);
44
- });
45
- });
46
- });
47
- });
48
- describe("resetConfig()", () => {
49
- it("writes an empty object", async () => {
50
- await inTemporaryDirectory(async (tmpDir) => {
51
- await writeExistingConfig(tmpDir);
52
- await resetConfig(tmpDir);
53
- const config = await getConfig(tmpDir);
54
- expect(config).toStrictEqual({});
55
- });
56
- });
57
- });
58
- describe("setUserAccount()", () => {
59
- describe("when no config exists", () => {
60
- it("creates a new config file", async () => {
61
- await inTemporaryDirectory(async (tmpDir) => {
62
- const filePath = joinPath(tmpDir, SHOPIFY_DIR, SHOPIFY_DIR_PROJECT);
63
- expect(await fileExists(filePath)).toBeFalsy();
64
- await setUserAccount(tmpDir, {
65
- shop: "new-shop",
66
- shopName: "New Shop",
67
- email: "email"
68
- });
69
- expect(await fileExists(filePath)).toBeTruthy();
70
- });
71
- });
72
- it("returns the new config", async () => {
73
- await inTemporaryDirectory(async (tmpDir) => {
74
- const newConfig = {
75
- shop: "new-shop",
76
- email: "email",
77
- shopName: "New Shop"
78
- };
79
- const config = await setUserAccount(tmpDir, newConfig);
80
- expect(config).toStrictEqual(newConfig);
81
- });
82
- });
83
- });
84
- describe("when a config exists", () => {
85
- it("updates the config file", async () => {
86
- await inTemporaryDirectory(async (tmpDir) => {
87
- const { existingConfig, filePath } = await writeExistingConfig(tmpDir);
88
- const newConfig = {
89
- shop: "new-shop",
90
- shopName: "New Shop",
91
- email: "email"
92
- };
93
- await setUserAccount(tmpDir, newConfig);
94
- expect(JSON.parse(await readFile(filePath))).toStrictEqual({
95
- ...existingConfig,
96
- ...newConfig
97
- });
98
- });
99
- });
100
- it("returns the new config", async () => {
101
- await inTemporaryDirectory(async (tmpDir) => {
102
- const { existingConfig } = await writeExistingConfig(tmpDir);
103
- const newConfig = {
104
- shop: "new-shop",
105
- email: "email",
106
- shopName: "New Shop"
107
- };
108
- const config = await setUserAccount(tmpDir, newConfig);
109
- expect(config).toStrictEqual({
110
- ...existingConfig,
111
- ...newConfig
112
- });
113
- });
114
- });
115
- });
116
- });
117
- describe("setStorefront()", () => {
118
- it("updates the config file", async () => {
119
- await inTemporaryDirectory(async (tmpDir) => {
120
- const { existingConfig, filePath } = await writeExistingConfig(tmpDir);
121
- const newStorefront = {
122
- id: "gid://shopify/HydrogenStorefront/2",
123
- title: "Remix"
124
- };
125
- await setStorefront(tmpDir, newStorefront);
126
- expect(JSON.parse(await readFile(filePath))).toStrictEqual({
127
- ...existingConfig,
128
- storefront: newStorefront
129
- });
130
- });
131
- });
132
- it("returns the new config", async () => {
133
- await inTemporaryDirectory(async (tmpDir) => {
134
- const { existingConfig } = await writeExistingConfig(tmpDir);
135
- const newStorefront = {
136
- id: "gid://shopify/HydrogenStorefront/2",
137
- title: "Remix"
138
- };
139
- const config = await setStorefront(tmpDir, newStorefront);
140
- expect(config).toStrictEqual({
141
- ...existingConfig,
142
- storefront: newStorefront
143
- });
144
- });
145
- });
146
- });
147
- describe("unsetStorefront()", () => {
148
- it("removes the storefront configuration and returns the config", async () => {
149
- await inTemporaryDirectory(async (tmpDir) => {
150
- const { filePath, existingConfig } = await writeExistingConfig(tmpDir);
151
- const config = await unsetStorefront(tmpDir);
152
- expect(config).toStrictEqual({
153
- ...existingConfig,
154
- storefront: void 0
155
- });
156
- const { storefront, ...actualConfig } = existingConfig;
157
- expect(JSON.parse(await readFile(filePath))).toStrictEqual(actualConfig);
158
- });
159
- });
160
- });
161
- describe("ensureShopifyGitIgnore()", () => {
162
- describe("when a .gitignore file already exists", () => {
163
- it("updates the .gitignore file and returns true", async () => {
164
- await inTemporaryDirectory(async (tmpDir) => {
165
- const existingFileContents = "node_modules\r\n";
166
- const filePath = joinPath(tmpDir, ".gitignore");
167
- await writeFile(filePath, JSON.stringify(existingFileContents));
168
- expect(await readFile(filePath)).not.toContain(".shopify");
169
- const result = await ensureShopifyGitIgnore(tmpDir);
170
- expect(await readFile(filePath)).toContain(".shopify");
171
- expect(result).toBeTruthy();
172
- });
173
- });
174
- describe("and the file is already ignoring .shopify", () => {
175
- it("does not update the file and returns false", async () => {
176
- await inTemporaryDirectory(async (tmpDir) => {
177
- const existingFileContents = "node_modules\n.shopify\r\n";
178
- const filePath = joinPath(tmpDir, ".gitignore");
179
- await writeFile(filePath, JSON.stringify(existingFileContents));
180
- const originalFile = await readFile(filePath);
181
- const result = await ensureShopifyGitIgnore(tmpDir);
182
- expect(await readFile(filePath)).toStrictEqual(originalFile);
183
- expect(result).toBeFalsy();
184
- });
185
- });
186
- });
187
- });
188
- describe("when a .gitignore does not exist", () => {
189
- it("creates the .gitignore file and returns true", async () => {
190
- await inTemporaryDirectory(async (tmpDir) => {
191
- const filePath = joinPath(tmpDir, ".gitignore");
192
- expect(await fileExists(filePath)).toBeFalsy();
193
- const result = await ensureShopifyGitIgnore(tmpDir);
194
- expect(await readFile(filePath)).toContain(".shopify");
195
- expect(result).toBeTruthy();
196
- });
197
- });
198
- });
199
- });
@@ -1,16 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { titleize } from './string.js';
3
-
4
- describe("titleize", () => {
5
- const TEST_DIRECTORY_NAMES = {
6
- "demo-storefront": "Demo Storefront",
7
- "nifty \u{1F602} project ": "Nifty Project",
8
- "Hello \u{1F602}": "Hello",
9
- _____: ""
10
- };
11
- it("replaces non-alpha-numeric characters with spaces and capitalizes the first letter of every word", () => {
12
- for (const [input, expected] of Object.entries(TEST_DIRECTORY_NAMES)) {
13
- expect(titleize(input)).toBe(expected);
14
- }
15
- });
16
- });
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { fileURLToPath } from 'url';
3
- import { addVirtualRoutes, VIRTUAL_ROOT, VIRTUAL_ROUTES_DIR } from './virtual-routes.js';
4
-
5
- describe("virtual routes", () => {
6
- it("adds virtual routes", async () => {
7
- const config = {
8
- appDirectory: fileURLToPath(new URL("../virtual-test", import.meta.url)),
9
- routes: {}
10
- };
11
- await addVirtualRoutes(config);
12
- expect(config.routes[VIRTUAL_ROOT]).toMatchObject({
13
- path: "",
14
- id: VIRTUAL_ROOT,
15
- file: "../virtual-routes/virtual-root.jsx"
16
- });
17
- expect(config.routes[VIRTUAL_ROUTES_DIR + "/index"]).toMatchObject({
18
- parentId: VIRTUAL_ROOT,
19
- path: void 0,
20
- file: "../" + VIRTUAL_ROUTES_DIR + "/index.tsx"
21
- });
22
- expect(config.routes[VIRTUAL_ROUTES_DIR + "/graphiql"]).toMatchObject({
23
- parentId: VIRTUAL_ROOT,
24
- path: "graphiql",
25
- file: "../" + VIRTUAL_ROUTES_DIR + "/graphiql.tsx"
26
- });
27
- });
28
- it("skips existing routes", async () => {
29
- const existingIndexRoute = {
30
- id: "routes/index",
31
- index: true,
32
- parentId: "root",
33
- path: void 0,
34
- file: "user-app/routes/index.tsx"
35
- };
36
- const config = {
37
- appDirectory: fileURLToPath(new URL("../virtual-test", import.meta.url)),
38
- routes: {
39
- [existingIndexRoute.id]: existingIndexRoute
40
- }
41
- };
42
- await addVirtualRoutes(config);
43
- expect(config.routes[existingIndexRoute.id]).toMatchObject(
44
- existingIndexRoute
45
- );
46
- expect(config.routes[VIRTUAL_ROUTES_DIR + "/index"]).toBeFalsy();
47
- expect(Object.values(config.routes).length).toBeGreaterThan(2);
48
- });
49
- });
@@ -1,82 +0,0 @@
1
- import { normalizePath } from 'vite';
2
- import path from 'node:path';
3
- import { createRequire } from 'node:module';
4
- import { createFileReadStream } from '@shopify/cli-kit/node/fs';
5
- import { setConstructors, handleDebugNetworkRequest } from '../request-events.js';
6
- import { SUBREQUEST_PROFILER_ENDPOINT } from '../mini-oxygen/common.js';
7
- import { toWeb, pipeFromWeb } from './utils.js';
8
- import { addVirtualRoutes } from '../virtual-routes.js';
9
-
10
- function setupRemixDevServerHooks(viteUrl) {
11
- globalThis["__remix_devServerHooks"] = {
12
- getCriticalCss: (...args) => fetch(new URL("/__vite_critical_css", viteUrl), {
13
- method: "POST",
14
- body: JSON.stringify(args)
15
- }).then((res) => res.json())
16
- };
17
- }
18
- function setupHydrogenMiddleware(viteDevServer, options) {
19
- viteDevServer.middlewares.use(
20
- "/__vite_critical_css",
21
- function h2HandleCriticalCss(req, res) {
22
- toWeb(req).json().then(async (args) => {
23
- const result = await globalThis["__remix_devServerHooks"]?.getCriticalCss?.(...args);
24
- res.writeHead(200, { "Content-Type": "application/json" });
25
- res.end(JSON.stringify(result ?? ""));
26
- });
27
- }
28
- );
29
- if (options.disableVirtualRoutes)
30
- return;
31
- addVirtualRoutesToRemix(viteDevServer);
32
- setConstructors({ Response: globalThis.Response });
33
- viteDevServer.middlewares.use(
34
- SUBREQUEST_PROFILER_ENDPOINT,
35
- function h2HandleSubrequestProfilerEvent(req, res) {
36
- const webResponse = handleDebugNetworkRequest(toWeb(req));
37
- pipeFromWeb(webResponse, res);
38
- }
39
- );
40
- viteDevServer.middlewares.use(
41
- "/graphiql/customer-account.schema.json",
42
- function h2HandleGraphiQLCustomerSchema(req, res) {
43
- const require2 = createRequire(import.meta.url);
44
- const filePath = require2.resolve(
45
- "@shopify/hydrogen/customer-account.schema.json"
46
- );
47
- res.writeHead(200, { "Content-Type": "application/json" });
48
- createFileReadStream(filePath).pipe(res);
49
- }
50
- );
51
- }
52
- let virtualRoutesAdded = false;
53
- async function addVirtualRoutesToRemix(viteDevServer) {
54
- if (virtualRoutesAdded)
55
- return;
56
- const appDirectory = await reloadRemixVirtualRoutes(viteDevServer.config);
57
- viteDevServer.watcher.on("all", (eventName, filepath) => {
58
- const appFileAddedOrRemoved = (eventName === "add" || eventName === "unlink") && normalizePath(filepath).startsWith(normalizePath(appDirectory));
59
- const viteConfigChanged = eventName === "change" && normalizePath(filepath) === normalizePath(viteDevServer.config.configFile ?? "");
60
- if (appFileAddedOrRemoved || viteConfigChanged) {
61
- setTimeout(() => reloadRemixVirtualRoutes(viteDevServer.config), 100);
62
- }
63
- });
64
- virtualRoutesAdded = true;
65
- }
66
- async function reloadRemixVirtualRoutes(config) {
67
- const remixPluginContext = config.__remixPluginContext;
68
- remixPluginContext.remixConfig = { ...remixPluginContext.remixConfig };
69
- remixPluginContext.remixConfig.routes = {
70
- ...remixPluginContext.remixConfig.routes
71
- };
72
- await addVirtualRoutes(remixPluginContext.remixConfig).catch((error) => {
73
- console.debug(
74
- "Could not add virtual routes: " + (error?.stack ?? error?.message ?? error)
75
- );
76
- });
77
- Object.freeze(remixPluginContext.remixConfig.routes);
78
- Object.freeze(remixPluginContext.remixConfig);
79
- return remixPluginContext?.remixConfig?.appDirectory ?? path.join(config.root, "app");
80
- }
81
-
82
- export { setupHydrogenMiddleware, setupRemixDevServerHooks };
@@ -1,152 +0,0 @@
1
- import { fetchModule } from 'vite';
2
- import { fileURLToPath } from 'node:url';
3
- import crypto from 'node:crypto';
4
- import { Miniflare, NoOpLog } from 'miniflare';
5
- import { OXYGEN_HEADERS_MAP, logRequestLine } from '../mini-oxygen/common.js';
6
- import { PRIVATE_WORKERD_INSPECTOR_PORT, OXYGEN_WORKERD_COMPAT_PARAMS } from '../mini-oxygen/workerd.js';
7
- import { findPort } from '../find-port.js';
8
- import { createInspectorConnector } from '../mini-oxygen/workerd-inspector.js';
9
- import { getHmrUrl, toURL, toWeb, pipeFromWeb } from './utils.js';
10
-
11
- const scriptPath = fileURLToPath(new URL("./worker-entry.js", import.meta.url));
12
- const FETCH_MODULE_PATHNAME = "/__vite_fetch_module";
13
- const WARMUP_PATHNAME = "/__vite_warmup";
14
- const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce(
15
- (acc, item) => {
16
- acc[item.name] = item.defaultValue;
17
- return acc;
18
- },
19
- {}
20
- );
21
- async function startMiniOxygenRuntime({
22
- viteDevServer,
23
- env,
24
- services,
25
- debug = false,
26
- inspectorPort,
27
- workerEntryFile,
28
- setupScripts
29
- }) {
30
- const [publicInspectorPort, privateInspectorPort] = await Promise.all([
31
- findPort(inspectorPort),
32
- findPort(PRIVATE_WORKERD_INSPECTOR_PORT)
33
- ]);
34
- const mf = new Miniflare({
35
- cf: false,
36
- verbose: false,
37
- log: new NoOpLog(),
38
- inspectorPort: privateInspectorPort,
39
- handleRuntimeStdio(stdout, stderr) {
40
- stdout.destroy();
41
- stderr.destroy();
42
- },
43
- workers: [
44
- {
45
- name: "oxygen",
46
- modulesRoot: "/",
47
- modules: [{ type: "ESModule", path: scriptPath }],
48
- ...OXYGEN_WORKERD_COMPAT_PARAMS,
49
- serviceBindings: { ...services },
50
- bindings: {
51
- ...env,
52
- __VITE_ROOT: viteDevServer.config.root,
53
- __VITE_RUNTIME_EXECUTE_URL: workerEntryFile,
54
- __VITE_FETCH_MODULE_PATHNAME: FETCH_MODULE_PATHNAME,
55
- __VITE_HMR_URL: getHmrUrl(viteDevServer),
56
- __VITE_WARMUP_PATHNAME: WARMUP_PATHNAME
57
- },
58
- unsafeEvalBinding: "__VITE_UNSAFE_EVAL",
59
- wrappedBindings: {
60
- __VITE_SETUP_ENV: "setup-environment"
61
- }
62
- },
63
- {
64
- name: "setup-environment",
65
- modules: true,
66
- scriptPath,
67
- script: `
68
- const setupScripts = [${setupScripts ?? ""}];
69
- export default (env) => (request) => {
70
- const viteUrl = new URL(request.url).origin;
71
- setupScripts.forEach((setup) => setup?.(viteUrl));
72
- setupScripts.length = 0;
73
- }`
74
- }
75
- ]
76
- });
77
- const warmupWorkerdCache = () => {
78
- let viteUrl = viteDevServer.resolvedUrls?.local[0] ?? viteDevServer.resolvedUrls?.network[0];
79
- if (!viteUrl) {
80
- const address = viteDevServer.httpServer?.address?.();
81
- viteUrl = address && typeof address !== "string" ? `http://localhost:${address.port}` : address ?? void 0;
82
- }
83
- if (viteUrl) {
84
- mf.dispatchFetch(new URL(WARMUP_PATHNAME, viteUrl)).catch(() => {
85
- });
86
- }
87
- };
88
- viteDevServer.httpServer?.listening ? warmupWorkerdCache() : viteDevServer.httpServer?.once("listening", warmupWorkerdCache);
89
- mf.ready.then(() => {
90
- const reconnect = createInspectorConnector({
91
- debug,
92
- sourceMapPath: "",
93
- absoluteBundlePath: "",
94
- privateInspectorPort,
95
- publicInspectorPort
96
- });
97
- return reconnect();
98
- });
99
- return {
100
- ready: mf.ready,
101
- publicInspectorPort,
102
- dispatch: (webRequest) => mf.dispatchFetch(webRequest),
103
- async dispose() {
104
- await mf.dispose();
105
- }
106
- };
107
- }
108
- function setupOxygenMiddleware(viteDevServer, dispatchFetch) {
109
- viteDevServer.middlewares.use(
110
- FETCH_MODULE_PATHNAME,
111
- function o2HandleModuleFetch(req, res) {
112
- const url = toURL(req);
113
- const id = url.searchParams.get("id");
114
- const importer = url.searchParams.get("importer") ?? void 0;
115
- if (id) {
116
- res.setHeader("cache-control", "no-store");
117
- res.setHeader("content-type", "application/json");
118
- fetchModule(viteDevServer, id, importer).then((ssrModule) => res.end(JSON.stringify(ssrModule))).catch((error) => {
119
- console.error("Error during module fetch:", error);
120
- res.writeHead(500, { "Content-Type": "text/plain" });
121
- res.end("Internal server error");
122
- });
123
- } else {
124
- res.statusCode = 400;
125
- res.writeHead(400, { "Content-Type": "text/plain" });
126
- res.end("Invalid request");
127
- }
128
- }
129
- );
130
- viteDevServer.middlewares.use(function o2HandleWorkerRequest(req, res) {
131
- if (!req.headers.host)
132
- throw new Error("Missing host header");
133
- const webRequest = toWeb(req, {
134
- "request-id": crypto.randomUUID(),
135
- ...oxygenHeadersMap
136
- });
137
- const startTimeMs = Date.now();
138
- dispatchFetch(webRequest).then((webResponse) => {
139
- pipeFromWeb(webResponse, res);
140
- logRequestLine(webRequest, {
141
- responseStatus: webResponse.status,
142
- durationMs: Date.now() - startTimeMs
143
- });
144
- }).catch((error) => {
145
- console.error("Error during evaluation:", error);
146
- res.writeHead(500);
147
- res.end();
148
- });
149
- });
150
- }
151
-
152
- export { setupOxygenMiddleware, startMiniOxygenRuntime };
@@ -1,27 +0,0 @@
1
- import { Plugin } from 'vite';
2
-
3
- type HydrogenPluginOptions = {
4
- disableVirtualRoutes?: boolean;
5
- };
6
- type OxygenPluginOptions = {
7
- ssrEntry?: string;
8
- debug?: boolean;
9
- inspectorPort?: number;
10
- env?: Record<string, any>;
11
- };
12
-
13
- /**
14
- * Enables Hydrogen utilities for local development
15
- * such as GraphiQL, Subrequest Profiler, etc.
16
- * It must be used in combination with the `oxygen` plugin and Hydrogen CLI.
17
- * @experimental
18
- */
19
- declare function hydrogen(pluginOptions?: HydrogenPluginOptions): Plugin[];
20
- /**
21
- * Runs backend code in an Oxygen worker instead of Node.js during development.
22
- * It must be placed after `hydrogen` but before `remix` in the Vite plugins list.
23
- * @experimental
24
- */
25
- declare function oxygen(pluginOptions?: OxygenPluginOptions): Plugin[];
26
-
27
- export { hydrogen, oxygen };