@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.
- package/LICENSE.md +7 -0
- package/README.md +14 -0
- package/dist/commands/hydrogen/build.d.ts +23 -0
- package/dist/commands/hydrogen/build.js +100 -0
- package/dist/commands/hydrogen/check.d.ts +18 -0
- package/dist/commands/hydrogen/check.js +35 -0
- package/dist/commands/hydrogen/dev.d.ts +15 -0
- package/dist/commands/hydrogen/dev.js +134 -0
- package/dist/commands/hydrogen/generate/route.d.ts +34 -0
- package/dist/commands/hydrogen/generate/route.js +166 -0
- package/dist/commands/hydrogen/generate/route.test.d.ts +1 -0
- package/dist/commands/hydrogen/generate/route.test.js +126 -0
- package/dist/commands/hydrogen/generate/routes.d.ts +16 -0
- package/dist/commands/hydrogen/generate/routes.js +22 -0
- package/dist/commands/hydrogen/init.d.ts +24 -0
- package/dist/commands/hydrogen/init.js +199 -0
- package/dist/commands/hydrogen/init.test.d.ts +1 -0
- package/dist/commands/hydrogen/init.test.js +79 -0
- package/dist/commands/hydrogen/preview.d.ts +17 -0
- package/dist/commands/hydrogen/preview.js +34 -0
- package/dist/create-app.d.ts +1 -0
- package/dist/create-app.js +4 -0
- package/dist/generator-templates/routes/[robots.txt].tsx +41 -0
- package/dist/generator-templates/routes/[sitemap.xml].tsx +204 -0
- package/dist/generator-templates/routes/account/login.tsx +74 -0
- package/dist/generator-templates/routes/account/register.tsx +74 -0
- package/dist/generator-templates/routes/cart.tsx +41 -0
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +65 -0
- package/dist/generator-templates/routes/graphiql.tsx +10 -0
- package/dist/generator-templates/routes/index.tsx +7 -0
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +74 -0
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +87 -0
- package/dist/generator-templates/routes/policies/index.tsx +67 -0
- package/dist/generator-templates/routes/products/$productHandle.tsx +57 -0
- package/dist/hooks/init.d.ts +5 -0
- package/dist/hooks/init.js +18 -0
- package/dist/utils/check-lockfile.d.ts +3 -0
- package/dist/utils/check-lockfile.js +78 -0
- package/dist/utils/check-lockfile.test.d.ts +1 -0
- package/dist/utils/check-lockfile.test.js +74 -0
- package/dist/utils/check-version.d.ts +16 -0
- package/dist/utils/check-version.js +55 -0
- package/dist/utils/check-version.test.d.ts +1 -0
- package/dist/utils/check-version.test.js +60 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.js +146 -0
- package/dist/utils/flags.d.ts +24 -0
- package/dist/utils/flags.js +60 -0
- package/dist/utils/flags.test.d.ts +1 -0
- package/dist/utils/flags.test.js +43 -0
- package/dist/utils/log.d.ts +5 -0
- package/dist/utils/log.js +20 -0
- package/dist/utils/mini-oxygen.d.ts +11 -0
- package/dist/utils/mini-oxygen.js +73 -0
- package/dist/utils/missing-routes.d.ts +6 -0
- package/dist/utils/missing-routes.js +71 -0
- package/dist/utils/template-downloader.d.ts +11 -0
- package/dist/utils/template-downloader.js +70 -0
- package/dist/utils/transpile-ts.d.ts +16 -0
- package/dist/utils/transpile-ts.js +169 -0
- package/dist/utils/virtual-routes.d.ts +7 -0
- package/dist/utils/virtual-routes.js +44 -0
- package/dist/utils/virtual-routes.test.d.ts +1 -0
- package/dist/utils/virtual-routes.test.js +49 -0
- package/dist/virtual-routes/assets/favicon.svg +28 -0
- package/dist/virtual-routes/assets/inter-variable-font.woff2 +0 -0
- package/dist/virtual-routes/assets/jetbrainsmono-variable-font.woff2 +0 -0
- package/dist/virtual-routes/assets/styles.css +238 -0
- package/dist/virtual-routes/components/HydrogenLogoBaseBW.jsx +7 -0
- package/dist/virtual-routes/components/HydrogenLogoBaseColor.jsx +13 -0
- package/dist/virtual-routes/components/IconBanner.jsx +47 -0
- package/dist/virtual-routes/components/IconDiscord.jsx +4 -0
- package/dist/virtual-routes/components/IconError.jsx +20 -0
- package/dist/virtual-routes/components/IconGithub.jsx +4 -0
- package/dist/virtual-routes/components/IconTwitter.jsx +4 -0
- package/dist/virtual-routes/components/Layout.jsx +6 -0
- package/dist/virtual-routes/routes/graphiql.jsx +5 -0
- package/dist/virtual-routes/routes/index.jsx +163 -0
- package/dist/virtual-routes/virtual-root.jsx +38 -0
- package/oclif.manifest.json +1 -0
- 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 @@
|
|
|
1
|
+
|
|
@@ -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>
|
|
Binary file
|
|
Binary file
|