@shopify/cli-hydrogen 0.0.0-next-744c490-20230315110711

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 (81) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +14 -0
  3. package/dist/commands/hydrogen/build.d.ts +23 -0
  4. package/dist/commands/hydrogen/build.js +100 -0
  5. package/dist/commands/hydrogen/check.d.ts +18 -0
  6. package/dist/commands/hydrogen/check.js +35 -0
  7. package/dist/commands/hydrogen/dev.d.ts +15 -0
  8. package/dist/commands/hydrogen/dev.js +134 -0
  9. package/dist/commands/hydrogen/generate/route.d.ts +34 -0
  10. package/dist/commands/hydrogen/generate/route.js +166 -0
  11. package/dist/commands/hydrogen/generate/route.test.d.ts +1 -0
  12. package/dist/commands/hydrogen/generate/route.test.js +126 -0
  13. package/dist/commands/hydrogen/generate/routes.d.ts +16 -0
  14. package/dist/commands/hydrogen/generate/routes.js +22 -0
  15. package/dist/commands/hydrogen/init.d.ts +24 -0
  16. package/dist/commands/hydrogen/init.js +199 -0
  17. package/dist/commands/hydrogen/init.test.d.ts +1 -0
  18. package/dist/commands/hydrogen/init.test.js +79 -0
  19. package/dist/commands/hydrogen/preview.d.ts +17 -0
  20. package/dist/commands/hydrogen/preview.js +34 -0
  21. package/dist/create-app.d.ts +1 -0
  22. package/dist/create-app.js +4 -0
  23. package/dist/generator-templates/routes/[robots.txt].tsx +41 -0
  24. package/dist/generator-templates/routes/[sitemap.xml].tsx +204 -0
  25. package/dist/generator-templates/routes/account/login.tsx +74 -0
  26. package/dist/generator-templates/routes/account/register.tsx +74 -0
  27. package/dist/generator-templates/routes/cart.tsx +41 -0
  28. package/dist/generator-templates/routes/collections/$collectionHandle.tsx +65 -0
  29. package/dist/generator-templates/routes/graphiql.tsx +10 -0
  30. package/dist/generator-templates/routes/index.tsx +7 -0
  31. package/dist/generator-templates/routes/pages/$pageHandle.tsx +74 -0
  32. package/dist/generator-templates/routes/policies/$policyHandle.tsx +87 -0
  33. package/dist/generator-templates/routes/policies/index.tsx +67 -0
  34. package/dist/generator-templates/routes/products/$productHandle.tsx +57 -0
  35. package/dist/hooks/init.d.ts +5 -0
  36. package/dist/hooks/init.js +18 -0
  37. package/dist/utils/check-lockfile.d.ts +3 -0
  38. package/dist/utils/check-lockfile.js +78 -0
  39. package/dist/utils/check-lockfile.test.d.ts +1 -0
  40. package/dist/utils/check-lockfile.test.js +74 -0
  41. package/dist/utils/check-version.d.ts +16 -0
  42. package/dist/utils/check-version.js +55 -0
  43. package/dist/utils/check-version.test.d.ts +1 -0
  44. package/dist/utils/check-version.test.js +60 -0
  45. package/dist/utils/config.d.ts +18 -0
  46. package/dist/utils/config.js +146 -0
  47. package/dist/utils/flags.d.ts +24 -0
  48. package/dist/utils/flags.js +60 -0
  49. package/dist/utils/flags.test.d.ts +1 -0
  50. package/dist/utils/flags.test.js +43 -0
  51. package/dist/utils/log.d.ts +5 -0
  52. package/dist/utils/log.js +20 -0
  53. package/dist/utils/mini-oxygen.d.ts +11 -0
  54. package/dist/utils/mini-oxygen.js +73 -0
  55. package/dist/utils/missing-routes.d.ts +6 -0
  56. package/dist/utils/missing-routes.js +71 -0
  57. package/dist/utils/template-downloader.d.ts +11 -0
  58. package/dist/utils/template-downloader.js +70 -0
  59. package/dist/utils/transpile-ts.d.ts +16 -0
  60. package/dist/utils/transpile-ts.js +169 -0
  61. package/dist/utils/virtual-routes.d.ts +7 -0
  62. package/dist/utils/virtual-routes.js +44 -0
  63. package/dist/utils/virtual-routes.test.d.ts +1 -0
  64. package/dist/utils/virtual-routes.test.js +49 -0
  65. package/dist/virtual-routes/assets/favicon.svg +28 -0
  66. package/dist/virtual-routes/assets/inter-variable-font.woff2 +0 -0
  67. package/dist/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
  68. package/dist/virtual-routes/assets/styles.css +238 -0
  69. package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +7 -0
  70. package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +13 -0
  71. package/dist/virtual-routes/components/IconBanner.jsx +47 -0
  72. package/dist/virtual-routes/components/IconDiscord.jsx +4 -0
  73. package/dist/virtual-routes/components/IconError.jsx +20 -0
  74. package/dist/virtual-routes/components/IconGithub.jsx +4 -0
  75. package/dist/virtual-routes/components/IconTwitter.jsx +4 -0
  76. package/dist/virtual-routes/components/Layout.jsx +6 -0
  77. package/dist/virtual-routes/routes/graphiql.jsx +5 -0
  78. package/dist/virtual-routes/routes/index.jsx +163 -0
  79. package/dist/virtual-routes/virtual-root.jsx +38 -0
  80. package/oclif.manifest.json +1 -0
  81. package/package.json +72 -0
@@ -0,0 +1,71 @@
1
+ import { renderWarning, renderSuccess } from '@shopify/cli-kit/node/ui';
2
+
3
+ const REQUIRED_ROUTES = [
4
+ "",
5
+ "cart",
6
+ "products",
7
+ "products/:productHandle",
8
+ "collections",
9
+ "collections/:collectionHandle",
10
+ "sitemap.xml",
11
+ "robots.txt",
12
+ "pages/:pageHandle",
13
+ "policies/:policyHandle",
14
+ "search",
15
+ "discount/:discountCode",
16
+ "account",
17
+ "account/login",
18
+ "account/register",
19
+ "account/orders/:orderId",
20
+ "account/reset/:id/:token",
21
+ "account/activate/:id/:token"
22
+ ];
23
+ function findMissingRoutes(config) {
24
+ const userRoutes = Object.values(config.routes);
25
+ const requiredRoutes = new Set(REQUIRED_ROUTES);
26
+ for (const requiredRoute of requiredRoutes) {
27
+ for (const userRoute of userRoutes) {
28
+ if (!requiredRoute && !userRoute.path) {
29
+ requiredRoutes.delete(requiredRoute);
30
+ } else if (requiredRoute && userRoute.path) {
31
+ const currentRoute = {
32
+ path: userRoute.path,
33
+ parentId: userRoute.parentId
34
+ };
35
+ while (currentRoute.parentId && currentRoute.parentId !== "root") {
36
+ const parentRoute = userRoutes.find(
37
+ (r) => r.id === currentRoute.parentId
38
+ );
39
+ if (!parentRoute)
40
+ break;
41
+ currentRoute.path = `${parentRoute.path}/${currentRoute.path}`;
42
+ currentRoute.parentId = parentRoute.parentId;
43
+ }
44
+ const reString = "^(:[^\\/\\?]+\\?\\/)?" + requiredRoute.replaceAll(".", "\\.").replace(/:[^/]+/g, ":[^\\/]+") + "$";
45
+ if (new RegExp(reString).test(currentRoute.path)) {
46
+ requiredRoutes.delete(requiredRoute);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return [...requiredRoutes];
52
+ }
53
+ const LINE_LIMIT = 100;
54
+ function logMissingRoutes(routes) {
55
+ if (routes.length) {
56
+ renderWarning({
57
+ headline: "Standard Shopify routes missing",
58
+ body: `Your Hydrogen project is missing ${routes.length} standard Shopify route${routes.length > 1 ? "s" : ""}.
59
+ Including these routes improves compatibility with Shopify\u2019s platform:
60
+
61
+ ` + routes.slice(0, LINE_LIMIT - (routes.length <= LINE_LIMIT ? 0 : 1)).map((route) => `\u2022 /${route}`).join("\n") + (routes.length > LINE_LIMIT ? `
62
+ \u2022 ...and ${routes.length - LINE_LIMIT + 1} more` : "")
63
+ });
64
+ } else {
65
+ renderSuccess({
66
+ headline: "All standard Shopify routes present"
67
+ });
68
+ }
69
+ }
70
+
71
+ export { findMissingRoutes, logMissingRoutes };
@@ -0,0 +1,11 @@
1
+ declare function getLatestReleaseDownloadUrl(): Promise<{
2
+ version: string;
3
+ url: string;
4
+ }>;
5
+ declare function downloadTarball(url: string, storageDir: string): Promise<void>;
6
+ declare function getLatestTemplates(): Promise<{
7
+ version: string;
8
+ templatesDir: string;
9
+ }>;
10
+
11
+ export { downloadTarball, getLatestReleaseDownloadUrl, getLatestTemplates };
@@ -0,0 +1,70 @@
1
+ import path from 'path';
2
+ import { pipeline } from 'stream/promises';
3
+ import gunzipMaybe from 'gunzip-maybe';
4
+ import { extract } from 'tar-fs';
5
+ import { http, file } from '@shopify/cli-kit';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const REPO_RELEASES_URL = `https://api.github.com/repos/shopify/hydrogen/releases/latest`;
9
+ async function getLatestReleaseDownloadUrl() {
10
+ const response = await http.fetch(REPO_RELEASES_URL);
11
+ if (!response.ok || response.status >= 400) {
12
+ throw new Error(
13
+ `Failed to fetch the latest release information. Status ${response.status} ${response.statusText.replace(/\.$/, "")}.` + (response.status === 403 ? `
14
+
15
+ If you are using a VPN, WARP, or similar service, consider disabling it momentarily.` : "")
16
+ );
17
+ }
18
+ const release = await response.json();
19
+ return {
20
+ version: release.name.split("/").pop() ?? release.name,
21
+ url: release.tarball_url
22
+ };
23
+ }
24
+ async function downloadTarball(url, storageDir) {
25
+ const response = await http.fetch(url);
26
+ if (!response.ok || response.status >= 400) {
27
+ throw new Error(
28
+ `Failed to download the latest release files. Status ${response.status} ${response.statusText}}`
29
+ );
30
+ }
31
+ await pipeline(
32
+ response.body,
33
+ gunzipMaybe(),
34
+ extract(storageDir, {
35
+ strip: 1,
36
+ filter: (name) => {
37
+ name = name.replace(storageDir, "");
38
+ return !name.startsWith(path.normalize("/templates/")) || name.startsWith(path.normalize("/templates/skeleton/"));
39
+ }
40
+ })
41
+ );
42
+ }
43
+ async function getLatestTemplates() {
44
+ try {
45
+ const { version, url } = await getLatestReleaseDownloadUrl();
46
+ const templateStoragePath = fileURLToPath(
47
+ new URL("../starter-templates", import.meta.url)
48
+ );
49
+ if (!await file.exists(templateStoragePath)) {
50
+ await file.mkdir(templateStoragePath);
51
+ }
52
+ const templateStorageVersionPath = path.join(templateStoragePath, version);
53
+ if (!await file.exists(templateStorageVersionPath)) {
54
+ await downloadTarball(url, templateStorageVersionPath);
55
+ }
56
+ return {
57
+ version,
58
+ templatesDir: path.join(templateStorageVersionPath, "templates")
59
+ };
60
+ } catch (e) {
61
+ const error = e;
62
+ error.message = `Could not download Hydrogen templates from GitHub.
63
+ Please check your internet connection and the following error:
64
+
65
+ ` + error.message;
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ export { downloadTarball, getLatestReleaseDownloadUrl, getLatestTemplates };
@@ -0,0 +1,16 @@
1
+ import prettier, { Options } from 'prettier';
2
+ import ts, { CompilerOptions } from 'typescript';
3
+
4
+ declare function transpileFile(code: string, config?: Omit<ts.CompilerOptions, "target">): string;
5
+ declare function resolveFormatConfig(filePath?: string): Promise<prettier.Options>;
6
+ declare function format(content: string, config: Options, filePath?: string): string;
7
+ declare function convertConfigToJS(tsConfig: {
8
+ include?: string[];
9
+ compilerOptions?: CompilerOptions;
10
+ }): {
11
+ include?: string[] | undefined;
12
+ compilerOptions?: ts.CompilerOptions | undefined;
13
+ };
14
+ declare function transpileProject(projectDir: string): Promise<void>;
15
+
16
+ export { convertConfigToJS, format, resolveFormatConfig, transpileFile, transpileProject };
@@ -0,0 +1,169 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import prettier from 'prettier';
4
+ import ts from 'typescript';
5
+ import glob from 'fast-glob';
6
+ import { output } from '@shopify/cli-kit';
7
+
8
+ const escapeNewLines = (code) => code.replace(/\n\n/g, "\n/* :newline: */");
9
+ const restoreNewLines = (code) => code.replace(/\/\* :newline: \*\//g, "\n");
10
+ const DEFAULT_TS_CONFIG = {
11
+ lib: ["DOM", "DOM.Iterable", "ES2022"],
12
+ isolatedModules: true,
13
+ esModuleInterop: true,
14
+ resolveJsonModule: true,
15
+ target: "ES2022",
16
+ strict: true,
17
+ allowJs: true,
18
+ forceConsistentCasingInFileNames: true,
19
+ skipLibCheck: true
20
+ };
21
+ function transpileFile(code, config = DEFAULT_TS_CONFIG) {
22
+ const withArtificialNewLines = escapeNewLines(code);
23
+ const compiled = ts.transpileModule(withArtificialNewLines, {
24
+ reportDiagnostics: false,
25
+ compilerOptions: {
26
+ ...config,
27
+ jsx: 1,
28
+ removeComments: false
29
+ }
30
+ });
31
+ return restoreNewLines(compiled.outputText);
32
+ }
33
+ const DEFAULT_PRETTIER_CONFIG = {
34
+ arrowParens: "always",
35
+ singleQuote: true,
36
+ bracketSpacing: false,
37
+ trailingComma: "all"
38
+ };
39
+ async function resolveFormatConfig(filePath = process.cwd()) {
40
+ try {
41
+ return await prettier.resolveConfig(filePath) || DEFAULT_PRETTIER_CONFIG;
42
+ } catch {
43
+ return DEFAULT_PRETTIER_CONFIG;
44
+ }
45
+ }
46
+ function format(content, config, filePath = "") {
47
+ const ext = path.extname(filePath);
48
+ const formattedContent = prettier.format(content, {
49
+ parser: ext === ".tsx" || ext === ".ts" ? "typescript" : "babel",
50
+ ...config
51
+ });
52
+ return formattedContent;
53
+ }
54
+ const DEFAULT_JS_CONFIG = {
55
+ allowJs: true,
56
+ forceConsistentCasingInFileNames: true,
57
+ strict: true,
58
+ lib: ["DOM", "DOM.Iterable", "ES2022"],
59
+ esModuleInterop: true,
60
+ isolatedModules: true,
61
+ jsx: "react-jsx",
62
+ noEmit: true,
63
+ resolveJsonModule: true
64
+ };
65
+ const JS_CONFIG_KEYS = [
66
+ "noLib",
67
+ "target",
68
+ "module",
69
+ "moduleResolution",
70
+ "checkJs",
71
+ "experimentalDecorators",
72
+ "allowSyntheticDefaultImports",
73
+ "baseUrl",
74
+ "paths",
75
+ ...Object.keys(DEFAULT_JS_CONFIG)
76
+ ];
77
+ function convertConfigToJS(tsConfig) {
78
+ const jsConfig = {
79
+ compilerOptions: { ...DEFAULT_JS_CONFIG }
80
+ };
81
+ if (tsConfig.include) {
82
+ jsConfig.include = tsConfig.include.filter((s) => !s.endsWith(".d.ts")).map((s) => s.replace(/\.ts(x?)$/, ".js$1"));
83
+ }
84
+ if (tsConfig.compilerOptions) {
85
+ for (const key of JS_CONFIG_KEYS) {
86
+ if (tsConfig.compilerOptions[key] !== void 0) {
87
+ jsConfig.compilerOptions[key] = tsConfig.compilerOptions[key];
88
+ }
89
+ }
90
+ }
91
+ return jsConfig;
92
+ }
93
+ async function transpileProject(projectDir) {
94
+ const entries = await glob("**/*.+(ts|tsx)", {
95
+ absolute: true,
96
+ cwd: projectDir
97
+ });
98
+ const formatConfig = await resolveFormatConfig();
99
+ for (const entry of entries) {
100
+ if (entry.endsWith(".d.ts")) {
101
+ await fs.rm(entry);
102
+ continue;
103
+ }
104
+ const tsx = await fs.readFile(entry, "utf8");
105
+ const mjs = format(transpileFile(tsx), formatConfig);
106
+ await fs.rm(entry);
107
+ await fs.writeFile(entry.replace(/\.ts(x?)$/, ".js$1"), mjs, "utf8");
108
+ }
109
+ try {
110
+ const remixConfigPath = path.join(projectDir, "remix.config.js");
111
+ let remixConfig = await fs.readFile(remixConfigPath, "utf8");
112
+ remixConfig = remixConfig.replace(/\/server\.ts/gim, "/server.js");
113
+ await fs.writeFile(remixConfigPath, remixConfig);
114
+ } catch (error) {
115
+ output.debug(
116
+ "Could not change TS extensions in remix.config.js:\n" + error.stack
117
+ );
118
+ }
119
+ try {
120
+ const tsConfigPath = path.join(projectDir, "tsconfig.json");
121
+ const tsConfigWithComments = await fs.readFile(tsConfigPath, "utf8");
122
+ const jsConfig = convertConfigToJS(
123
+ JSON.parse(tsConfigWithComments.replace(/^\s*\/\/.*$/gm, ""))
124
+ );
125
+ await fs.rm(tsConfigPath);
126
+ await fs.writeFile(
127
+ path.join(projectDir, "jsconfig.json"),
128
+ JSON.stringify(jsConfig, null, 2),
129
+ "utf8"
130
+ );
131
+ } catch (error) {
132
+ output.debug(
133
+ "Could not transpile tsconfig.json:\n" + error.stack
134
+ );
135
+ }
136
+ try {
137
+ const pkgJson = JSON.parse(
138
+ await fs.readFile(path.join(projectDir, "package.json"), "utf8")
139
+ );
140
+ delete pkgJson.scripts["typecheck"];
141
+ delete pkgJson.devDependencies["typescript"];
142
+ delete pkgJson.devDependencies["@shopify/oxygen-workers-types"];
143
+ for (const key of Object.keys(pkgJson.devDependencies)) {
144
+ if (key.startsWith("@types/")) {
145
+ delete pkgJson.devDependencies[key];
146
+ }
147
+ }
148
+ await fs.writeFile(
149
+ path.join(projectDir, "package.json"),
150
+ JSON.stringify(pkgJson, null, 2)
151
+ );
152
+ } catch (error) {
153
+ output.debug(
154
+ "Could not remove TS dependencies from package.json:\n" + error.stack
155
+ );
156
+ }
157
+ try {
158
+ const eslintrcPath = path.join(projectDir, ".eslintrc.js");
159
+ let eslintrc = await fs.readFile(eslintrcPath, "utf8");
160
+ eslintrc = eslintrc.replace(/\/\*\*[\s*]+@type.+\s+\*\/\s?/gim, "").replace(/\s*,?\s*['"`]plugin:hydrogen\/typescript['"`]/gim, "").replace(/\s+['"`]@typescript-eslint\/.+,/gim, "");
161
+ await fs.writeFile(eslintrcPath, eslintrc);
162
+ } catch (error) {
163
+ output.debug(
164
+ "Could not remove TS rules from .eslintrc:\n" + error.stack
165
+ );
166
+ }
167
+ }
168
+
169
+ export { convertConfigToJS, format, resolveFormatConfig, transpileFile, transpileProject };
@@ -0,0 +1,7 @@
1
+ import { RemixConfig } from '@remix-run/dev/dist/config.js';
2
+
3
+ declare const VIRTUAL_ROUTES_DIR = "virtual-routes/routes";
4
+ declare const VIRTUAL_ROOT = "virtual-routes/virtual-root";
5
+ declare function addVirtualRoutes(config: RemixConfig): Promise<RemixConfig>;
6
+
7
+ export { VIRTUAL_ROOT, VIRTUAL_ROUTES_DIR, addVirtualRoutes };
@@ -0,0 +1,44 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import recursiveReaddir from 'recursive-readdir';
4
+
5
+ const VIRTUAL_ROUTES_DIR = "virtual-routes/routes";
6
+ const VIRTUAL_ROOT = "virtual-routes/virtual-root";
7
+ async function addVirtualRoutes(config) {
8
+ const userRouteList = Object.values(config.routes);
9
+ const distPath = fileURLToPath(new URL("..", import.meta.url));
10
+ const virtualRoutesPath = path.join(distPath, VIRTUAL_ROUTES_DIR);
11
+ for (const absoluteFilePath of await recursiveReaddir(virtualRoutesPath)) {
12
+ const relativeFilePath = path.relative(virtualRoutesPath, absoluteFilePath);
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, "");
16
+ const hasUserRoute = userRouteList.some(
17
+ (r) => r.parentId === "root" && r.path === normalizedVirtualRoutePath
18
+ );
19
+ if (!hasUserRoute) {
20
+ const id = VIRTUAL_ROUTES_DIR + "/" + routePath;
21
+ config.routes[id] = {
22
+ id,
23
+ parentId: VIRTUAL_ROOT,
24
+ path: normalizedVirtualRoutePath,
25
+ index: isIndex || void 0,
26
+ caseSensitive: void 0,
27
+ file: path.relative(config.appDirectory, absoluteFilePath)
28
+ };
29
+ if (!config.routes[VIRTUAL_ROOT]) {
30
+ config.routes[VIRTUAL_ROOT] = {
31
+ id: VIRTUAL_ROOT,
32
+ path: "",
33
+ file: path.relative(
34
+ config.appDirectory,
35
+ path.join(distPath, VIRTUAL_ROOT + ".jsx")
36
+ )
37
+ };
38
+ }
39
+ }
40
+ }
41
+ return config;
42
+ }
43
+
44
+ export { VIRTUAL_ROOT, VIRTUAL_ROUTES_DIR, addVirtualRoutes };
@@ -0,0 +1,49 @@
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
+ });
@@ -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>