create-vitnode-app 1.2.0-canary.9 → 2.0.0-canary.10
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 +1 -1
- package/README.md +59 -18
- package/copy-of-vitnode-app/.vscode/settings.json +10 -0
- package/copy-of-vitnode-app/README.md +62 -0
- package/copy-of-vitnode-app/api/.gitignore_template +32 -0
- package/copy-of-vitnode-app/api/drizzle.config.ts +12 -0
- package/copy-of-vitnode-app/api/src/index.ts +27 -0
- package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +39 -0
- package/copy-of-vitnode-app/api/tsconfig.json +17 -0
- package/copy-of-vitnode-app/api-bun/src/index.ts +16 -0
- package/copy-of-vitnode-app/api-single-app/drizzle.config.ts +12 -0
- package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +10 -0
- package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +11 -0
- package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +29 -0
- package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +28 -0
- package/copy-of-vitnode-app/docker/docker-compose.yml +32 -0
- package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
- package/copy-of-vitnode-app/eslint/eslint.config.mjs +18 -2
- package/copy-of-vitnode-app/{root → eslint-react}/.prettierrc.mjs +1 -1
- package/copy-of-vitnode-app/eslint-react/eslint.config.mjs +19 -0
- package/copy-of-vitnode-app/monorepo/.gitignore_template +41 -0
- package/copy-of-vitnode-app/monorepo/apps/api/.env.example +12 -0
- package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -0
- package/copy-of-vitnode-app/monorepo/turbo.json +53 -0
- package/copy-of-vitnode-app/root/.env.example +9 -5
- package/copy-of-vitnode-app/root/.gitignore_template +41 -0
- package/copy-of-vitnode-app/root/global.d.ts +10 -0
- package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +21 -0
- package/copy-of-vitnode-app/root/src/components/main-footer.tsx +44 -0
- package/copy-of-vitnode-app/root/src/components/main-header.tsx +4 -0
- package/copy-of-vitnode-app/root/src/lib/i18n.ts +37 -0
- package/copy-of-vitnode-app/root/src/locales/app.ts +4 -0
- package/copy-of-vitnode-app/root/src/locales/packages.ts +10 -0
- package/copy-of-vitnode-app/root/src/router.tsx +71 -0
- package/copy-of-vitnode-app/root/src/routes/__root.tsx +109 -0
- package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +20 -0
- package/copy-of-vitnode-app/root/src/routes/_admin.tsx +65 -0
- package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +196 -0
- package/copy-of-vitnode-app/root/src/routes/_main.tsx +29 -0
- package/copy-of-vitnode-app/root/src/server/messages.server.ts +8 -0
- package/copy-of-vitnode-app/root/src/start.ts +5 -0
- package/copy-of-vitnode-app/root/src/styles.css +149 -0
- package/copy-of-vitnode-app/root/src/vitnode.config.ts +20 -0
- package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +12 -0
- package/copy-of-vitnode-app/root/tsconfig.json +29 -16
- package/copy-of-vitnode-app/root/tsr.config.json +3 -0
- package/copy-of-vitnode-app/root/vite.config.ts +22 -0
- package/copy-of-vitnode-plugin/root/.swcrc +26 -0
- package/copy-of-vitnode-plugin/root/global.d.ts +21 -0
- package/copy-of-vitnode-plugin/root/npmignore.template +17 -0
- package/copy-of-vitnode-plugin/root/tsconfig.build.json +5 -0
- package/copy-of-vitnode-plugin/root/tsconfig.json +25 -0
- package/dist/src/create/create-package-json.js +266 -56
- package/dist/src/create/create-vitnode.js +191 -28
- package/dist/src/create/package-versions.js +49 -0
- package/dist/src/helpers/get-available-package-managers.js +6 -6
- package/dist/src/helpers/get-package-json.js +2 -2
- package/dist/src/helpers/get-package-manager-from-root.js +15 -0
- package/dist/src/helpers/get-vitnode-package-version.js +9 -0
- package/dist/src/helpers/init-git.js +38 -0
- package/dist/src/helpers/init-vitnode.js +33 -0
- package/dist/src/helpers/install-dependencies.js +55 -15
- package/dist/src/helpers/is-folder-empty.js +25 -26
- package/dist/src/helpers/is-online.js +16 -16
- package/dist/src/helpers/is-writeable.js +3 -3
- package/dist/src/helpers/spawn-command.js +7 -0
- package/dist/src/helpers/validate-pkg.js +1 -1
- package/dist/src/helpers/with-If.js +1 -0
- package/dist/src/index.js +30 -26
- package/dist/src/plugin/create/add-plugin-to-config.js +231 -0
- package/dist/src/plugin/create/add-plugin-to-workspace.js +74 -0
- package/dist/src/plugin/create/create-package-json.js +55 -0
- package/dist/src/plugin/create/create-plugin-vitnode.js +101 -0
- package/dist/src/plugin/create/route-templates.js +186 -0
- package/dist/src/plugin/index.js +17 -6
- package/dist/src/plugin/questions.js +14 -0
- package/dist/src/plugin/validation.js +76 -0
- package/dist/src/prepare/prepare.js +11 -8
- package/dist/src/questions.js +59 -15
- package/dist/src/validation.js +15 -15
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +22 -12
- package/copy-of-vitnode-app/root/README.md +0 -1
- package/copy-of-vitnode-app/root/drizzle.config.ts +0 -12
- package/copy-of-vitnode-app/root/gitignore_template +0 -43
- package/copy-of-vitnode-app/root/next.config.ts +0 -11
- package/copy-of-vitnode-app/root/postcss.config.mjs +0 -8
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/[...rest]/page.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +0 -10
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/not-found.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +0 -113
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +0 -10
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/admin/page.tsx +0 -5
- package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +0 -36
- package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +0 -18
- package/copy-of-vitnode-app/root/src/app/favicon.ico +0 -0
- package/copy-of-vitnode-app/root/src/app/global-error.tsx +0 -27
- package/copy-of-vitnode-app/root/src/app/global.css +0 -128
- package/copy-of-vitnode-app/root/src/app/layout.tsx +0 -9
- package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
- package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +0 -5
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export const getPackageManagerFromRoot = (rootPath) => {
|
|
4
|
+
try {
|
|
5
|
+
const packageJsonPath = join(rootPath, "package.json");
|
|
6
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
7
|
+
if (packageJson.packageManager) {
|
|
8
|
+
return packageJson.packageManager.split("@")[0];
|
|
9
|
+
}
|
|
10
|
+
return "npm";
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return "npm";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
export const getVitnodePackageVersion = async () => {
|
|
7
|
+
const cliPkg = JSON.parse(await readFile(join(__dirname, "..", "..", "..", "package.json"), "utf-8"));
|
|
8
|
+
return `^${cliPkg.version}`;
|
|
9
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { spawnCommand } from "./spawn-command.js";
|
|
4
|
+
export const DEFAULT_GIT_BRANCH = "main";
|
|
5
|
+
const runGit = async (args, cwd) => new Promise(resolve => {
|
|
6
|
+
const child = spawnCommand("git", args, { cwd, stdio: "ignore" });
|
|
7
|
+
child.on("error", () => {
|
|
8
|
+
resolve(false);
|
|
9
|
+
});
|
|
10
|
+
child.on("close", code => {
|
|
11
|
+
resolve(code === 0);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
export const initGitRepository = async ({ root, branch = DEFAULT_GIT_BRANCH, commitMessage = "Initial commit from Create VitNode App", }) => {
|
|
15
|
+
if (!(await runGit(["--version"], root))) {
|
|
16
|
+
return "git-unavailable";
|
|
17
|
+
}
|
|
18
|
+
if (await runGit(["rev-parse", "--is-inside-work-tree"], root)) {
|
|
19
|
+
return "already-in-repository";
|
|
20
|
+
}
|
|
21
|
+
const removeGitDirectory = async () => {
|
|
22
|
+
await rm(join(root, ".git"), { force: true, recursive: true });
|
|
23
|
+
};
|
|
24
|
+
const initialized = (await runGit(["init", "--initial-branch", branch], root)) ||
|
|
25
|
+
((await runGit(["init"], root)) &&
|
|
26
|
+
(await runGit(["symbolic-ref", "HEAD", `refs/heads/${branch}`], root)));
|
|
27
|
+
if (!initialized) {
|
|
28
|
+
await removeGitDirectory();
|
|
29
|
+
return "failed";
|
|
30
|
+
}
|
|
31
|
+
const committed = (await runGit(["add", "-A"], root)) &&
|
|
32
|
+
(await runGit(["commit", "-m", commitMessage], root));
|
|
33
|
+
if (!committed) {
|
|
34
|
+
await removeGitDirectory();
|
|
35
|
+
return "failed";
|
|
36
|
+
}
|
|
37
|
+
return "created";
|
|
38
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import color from "picocolors";
|
|
2
|
+
import { spawnCommand } from "./spawn-command.js";
|
|
3
|
+
export const generateMigrationsVitnode = async ({ packageManager: pm, cwd, }) => {
|
|
4
|
+
const packageManager = pm.split("@")[0];
|
|
5
|
+
const args = ["vitnode", "migrate", "--generate"];
|
|
6
|
+
await new Promise((resolve, reject) => {
|
|
7
|
+
let output = "";
|
|
8
|
+
const child = spawnCommand(packageManager, args, {
|
|
9
|
+
cwd,
|
|
10
|
+
env: process.env,
|
|
11
|
+
stdio: "pipe",
|
|
12
|
+
});
|
|
13
|
+
child.stdout?.on("data", (data) => {
|
|
14
|
+
output += data.toString();
|
|
15
|
+
});
|
|
16
|
+
child.stderr?.on("data", (data) => {
|
|
17
|
+
output += data.toString();
|
|
18
|
+
});
|
|
19
|
+
child.on("error", error => {
|
|
20
|
+
reject(new Error(`Failed to start ${packageManager}: ${error.message}\nRun "${packageManager} vitnode db:prepare" in the new project once your database is running.`));
|
|
21
|
+
});
|
|
22
|
+
child.on("close", code => {
|
|
23
|
+
if (code === 0) {
|
|
24
|
+
resolve();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (output.trim().length > 0) {
|
|
28
|
+
console.error(color.red(output.trim()));
|
|
29
|
+
}
|
|
30
|
+
reject(new Error(`"${packageManager} vitnode migrate --generate" exited with code ${String(code)}. Your project was created - run "${packageManager} dev" once your database is running and it will migrate itself.`));
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
};
|
|
@@ -1,30 +1,70 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import consoleColor from "picocolors";
|
|
2
|
+
import { getOnline } from "./is-online.js";
|
|
3
|
+
import { spawnCommand } from "./spawn-command.js";
|
|
4
|
+
function printInstallErrorSuggestions(stderr, color) {
|
|
5
|
+
if (stderr.includes("ENOTFOUND") || stderr.includes("network")) {
|
|
6
|
+
console.error(color.yellow("💡 Network error detected. Please check your internet connection."));
|
|
7
|
+
}
|
|
8
|
+
else if (stderr.includes("EACCES") ||
|
|
9
|
+
stderr.includes("permission denied")) {
|
|
10
|
+
console.error(color.yellow("💡 Permission error detected. Try running with elevated privileges or check file permissions."));
|
|
11
|
+
}
|
|
12
|
+
else if (stderr.includes("ENOSPC")) {
|
|
13
|
+
console.error(color.yellow("💡 Disk space error detected. Please free up some disk space."));
|
|
14
|
+
}
|
|
15
|
+
else if (stderr.includes("ERR_PNPM_PEER_DEP_ISSUES")) {
|
|
16
|
+
console.error(color.yellow("💡 Peer dependency issues detected. Consider using --force flag or resolve conflicts manually."));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const installDependencies = async ({ packageManager: pm, cwd, }) => {
|
|
20
|
+
const packageManager = pm.split("@")[0];
|
|
6
21
|
const isOnline = await getOnline();
|
|
7
|
-
const args =
|
|
22
|
+
const args = ["install"];
|
|
8
23
|
if (!isOnline) {
|
|
9
|
-
console.log(
|
|
10
|
-
args.push(
|
|
24
|
+
console.log(consoleColor.yellow("You appear to be offline.\nFalling back to the local cache."));
|
|
25
|
+
args.push("--offline");
|
|
11
26
|
}
|
|
12
27
|
return new Promise((resolve, reject) => {
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
let stdout = "";
|
|
29
|
+
let stderr = "";
|
|
30
|
+
const child = spawnCommand(packageManager, args, {
|
|
31
|
+
stdio: "pipe",
|
|
32
|
+
cwd,
|
|
15
33
|
env: {
|
|
16
34
|
...process.env,
|
|
17
|
-
ADBLOCK:
|
|
18
|
-
NODE_ENV:
|
|
19
|
-
DISABLE_OPENCOLLECTIVE:
|
|
35
|
+
ADBLOCK: "1",
|
|
36
|
+
NODE_ENV: "development",
|
|
37
|
+
DISABLE_OPENCOLLECTIVE: "1",
|
|
20
38
|
},
|
|
21
39
|
});
|
|
22
|
-
child.on(
|
|
40
|
+
child.stdout?.on("data", (data) => {
|
|
41
|
+
const output = data.toString();
|
|
42
|
+
stdout += output;
|
|
43
|
+
});
|
|
44
|
+
child.stderr?.on("data", (data) => {
|
|
45
|
+
const output = data.toString();
|
|
46
|
+
stderr += output;
|
|
47
|
+
});
|
|
48
|
+
child.on("close", code => {
|
|
23
49
|
if (code !== 0) {
|
|
24
|
-
|
|
50
|
+
console.error(consoleColor.red(`\n❌ Installation failed with exit code: ${code}`));
|
|
51
|
+
if (stderr) {
|
|
52
|
+
console.error(consoleColor.red("Error output:"));
|
|
53
|
+
console.error(stderr);
|
|
54
|
+
}
|
|
55
|
+
if (stdout) {
|
|
56
|
+
console.log(consoleColor.yellow("Standard output:"));
|
|
57
|
+
console.log(stdout);
|
|
58
|
+
}
|
|
59
|
+
printInstallErrorSuggestions(stderr, consoleColor);
|
|
60
|
+
reject(new Error(`Failed to install dependencies using ${packageManager}. Exit code: ${code}\n${stderr || stdout}`));
|
|
25
61
|
return;
|
|
26
62
|
}
|
|
27
63
|
resolve();
|
|
28
64
|
});
|
|
65
|
+
child.on("error", error => {
|
|
66
|
+
console.error(consoleColor.red(`❌ Failed to start ${packageManager}:`), error.message);
|
|
67
|
+
reject(new Error(`Failed to start ${packageManager}: ${error.message}`));
|
|
68
|
+
});
|
|
29
69
|
});
|
|
30
70
|
};
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
import { lstatSync, readdirSync } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import colors from
|
|
1
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import colors from "picocolors";
|
|
4
4
|
export function isFolderEmpty(root, name) {
|
|
5
5
|
const validFiles = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
".DS_Store",
|
|
7
|
+
".git",
|
|
8
|
+
".gitattributes",
|
|
9
|
+
".gitignore",
|
|
10
|
+
".gitlab-ci.yml",
|
|
11
|
+
".hg",
|
|
12
|
+
".hgcheck",
|
|
13
|
+
".hgignore",
|
|
14
|
+
".idea",
|
|
15
|
+
".npmignore",
|
|
16
|
+
".travis.yml",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"Thumbs.db",
|
|
19
|
+
"docs",
|
|
20
|
+
"mkdocs.yml",
|
|
21
|
+
"npm-debug.log",
|
|
22
|
+
"yarn-debug.log",
|
|
23
|
+
"yarn-error.log",
|
|
24
|
+
"yarnrc.yml",
|
|
25
|
+
".yarn",
|
|
26
26
|
];
|
|
27
|
-
const conflicts = readdirSync(root).filter(file => !validFiles.includes(file)
|
|
28
|
-
!file.endsWith('.iml'));
|
|
27
|
+
const conflicts = readdirSync(root).filter(file => !(validFiles.includes(file) || file.endsWith(".iml")));
|
|
29
28
|
if (conflicts.length > 0) {
|
|
30
29
|
console.log(`The directory ${colors.green(name)} contains files that could conflict:\n`);
|
|
31
30
|
for (const file of conflicts) {
|
|
@@ -42,7 +41,7 @@ export function isFolderEmpty(root, name) {
|
|
|
42
41
|
console.log(file);
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
console.log(
|
|
44
|
+
console.log("\nEither try using a new directory name, or remove the files listed above.\n");
|
|
46
45
|
return false;
|
|
47
46
|
}
|
|
48
47
|
return true;
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { execSync } from
|
|
2
|
-
import { lookup } from
|
|
3
|
-
import { URL } from
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
function getProxy() {
|
|
5
|
+
if (process.env.https_proxy) {
|
|
6
|
+
return process.env.https_proxy;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const httpsProxy = execSync("npm config get https-proxy").toString().trim();
|
|
10
|
+
return httpsProxy !== "null" ? httpsProxy : undefined;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
4
16
|
export async function getOnline() {
|
|
5
17
|
try {
|
|
6
|
-
await lookup(
|
|
18
|
+
await lookup("registry.yarnpkg.com");
|
|
7
19
|
return true;
|
|
8
20
|
}
|
|
9
21
|
catch {
|
|
@@ -24,15 +36,3 @@ export async function getOnline() {
|
|
|
24
36
|
}
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
|
-
function getProxy() {
|
|
28
|
-
if (process.env.https_proxy) {
|
|
29
|
-
return process.env.https_proxy;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const httpsProxy = execSync('npm config get https-proxy').toString().trim();
|
|
33
|
-
return httpsProxy !== 'null' ? httpsProxy : undefined;
|
|
34
|
-
}
|
|
35
|
-
catch (_) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { W_OK } from
|
|
2
|
-
import { access } from
|
|
1
|
+
import { W_OK } from "node:constants";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
3
|
export async function isWriteable(directory) {
|
|
4
4
|
try {
|
|
5
5
|
await access(directory, W_OK);
|
|
6
6
|
return true;
|
|
7
7
|
}
|
|
8
|
-
catch
|
|
8
|
+
catch {
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export const spawnCommand = (command, args = [], options = {}) => {
|
|
3
|
+
const spawnOptions = { ...options, shell: false };
|
|
4
|
+
return process.platform === "win32"
|
|
5
|
+
? spawn("cmd.exe", ["/c", command, ...args], spawnOptions)
|
|
6
|
+
: spawn(command, args, spawnOptions);
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const withIf = (cond, obj) => (cond ? obj : {});
|
package/dist/src/index.js
CHANGED
|
@@ -1,51 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { input } from
|
|
3
|
-
import { Command, Option } from
|
|
4
|
-
import { basename, resolve } from
|
|
5
|
-
import color from
|
|
6
|
-
import { createVitNode } from
|
|
7
|
-
import { packageJson } from
|
|
8
|
-
import { validateNpmName } from
|
|
9
|
-
import { createPlugin } from
|
|
10
|
-
import { createQuestionsCli } from
|
|
11
|
-
import { validationProject } from
|
|
12
|
-
const [major] = process.versions.node.split(
|
|
2
|
+
import { input } from "@inquirer/prompts";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
|
+
import { basename, resolve } from "node:path";
|
|
5
|
+
import color from "picocolors";
|
|
6
|
+
import { createVitNode } from "./create/create-vitnode.js";
|
|
7
|
+
import { packageJson } from "./helpers/get-package-json.js";
|
|
8
|
+
import { validateNpmName } from "./helpers/validate-pkg.js";
|
|
9
|
+
import { createPlugin } from "./plugin/index.js";
|
|
10
|
+
import { createQuestionsCli } from "./questions.js";
|
|
11
|
+
import { validationProject } from "./validation.js";
|
|
12
|
+
const [major] = process.versions.node.split(".").map(Number);
|
|
13
13
|
if (major < 20) {
|
|
14
14
|
console.error(color.red(`\nError: VitNode requires Node.js version 20 or higher.\nYou are currently using Node.js ${process.versions.node}\n`));
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
|
-
process.on(
|
|
18
|
-
if (error.name ===
|
|
19
|
-
console.log(color.dim(
|
|
17
|
+
process.on("uncaughtException", (error) => {
|
|
18
|
+
if (error.name === "ExitPromptError") {
|
|
19
|
+
console.log(color.dim("👋 VitNode setup cancelled - see you next time!"));
|
|
20
20
|
process.exit(0);
|
|
21
21
|
}
|
|
22
|
-
console.error(color.red(
|
|
22
|
+
console.error(color.red("An unexpected error occurred:"));
|
|
23
23
|
console.error(color.red(error.stack ?? error.message));
|
|
24
24
|
process.exit(1);
|
|
25
25
|
});
|
|
26
26
|
const init = async () => {
|
|
27
|
-
let projectPath =
|
|
27
|
+
let projectPath = "";
|
|
28
28
|
const program = new Command()
|
|
29
|
-
.version(packageJson.version)
|
|
30
|
-
.argument(
|
|
31
|
-
.usage(`${color.green(
|
|
29
|
+
.version(packageJson.version ?? "0.1.0")
|
|
30
|
+
.argument("[project-directory]")
|
|
31
|
+
.usage(`${color.green("[project-directory]")} [options]`)
|
|
32
32
|
.action(name => {
|
|
33
33
|
projectPath = name;
|
|
34
34
|
});
|
|
35
|
-
program.addOption(new Option(
|
|
36
|
-
program.option(
|
|
37
|
-
program.option(
|
|
38
|
-
program.option(
|
|
35
|
+
program.addOption(new Option("--package-manager <package-manager>", "Specify the package manager to use").choices(["npm", "pnpm", "bun"]));
|
|
36
|
+
program.option("--eslint", "Initialize with ESLint & Prettier config.");
|
|
37
|
+
program.option("--skip-install", "Skip installing packages after initializing the project.");
|
|
38
|
+
program.option("--skip-git", "Skip initializing a git repository in the project.");
|
|
39
|
+
program.addOption(new Option("--mode <mode>", "What type of app do you want to create?").choices(["singleApp", "apiMonorepo", "onlyApi"]));
|
|
40
|
+
program.option("--monorepo", "Create project with monorepo structure.");
|
|
41
|
+
program.option("--docker", "Initialize with Docker support.");
|
|
42
|
+
program.option("--plugin", "Create a plugin.");
|
|
39
43
|
program.parse(process.argv);
|
|
40
44
|
const opts = program.opts();
|
|
41
45
|
if (opts.plugin) {
|
|
42
|
-
await createPlugin(projectPath);
|
|
46
|
+
await createPlugin({ projectPath, program });
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
49
|
if (!projectPath) {
|
|
46
50
|
projectPath = await input({
|
|
47
|
-
message:
|
|
48
|
-
default:
|
|
51
|
+
message: "What is your project named?",
|
|
52
|
+
default: "my-vitnode",
|
|
49
53
|
validate: (name) => {
|
|
50
54
|
const validation = validateNpmName({ name: basename(resolve(name)) });
|
|
51
55
|
if (validation.valid)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { basename, dirname, join } from "path";
|
|
3
|
+
import { pluginApiVariableName, pluginVariableName, } from "./route-templates.js";
|
|
4
|
+
const SKIPPED_DIRECTORIES = new Set([
|
|
5
|
+
".git",
|
|
6
|
+
".next",
|
|
7
|
+
".nitro",
|
|
8
|
+
".output",
|
|
9
|
+
".turbo",
|
|
10
|
+
".vercel",
|
|
11
|
+
"build",
|
|
12
|
+
"coverage",
|
|
13
|
+
"dist",
|
|
14
|
+
"node_modules",
|
|
15
|
+
]);
|
|
16
|
+
const CONFIG_BUILDERS = {
|
|
17
|
+
"vitnode.api.config.ts": {
|
|
18
|
+
builder: "buildApiConfig",
|
|
19
|
+
subpath: "config.api",
|
|
20
|
+
},
|
|
21
|
+
"vitnode.config.ts": { builder: "buildConfig", subpath: "config" },
|
|
22
|
+
};
|
|
23
|
+
const endOfStringLiteral = (source, start) => {
|
|
24
|
+
const quote = source[start];
|
|
25
|
+
let index = start + 1;
|
|
26
|
+
while (index < source.length) {
|
|
27
|
+
const char = source[index];
|
|
28
|
+
if (char === "\\") {
|
|
29
|
+
index += 2;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (char === quote)
|
|
33
|
+
return index + 1;
|
|
34
|
+
index += 1;
|
|
35
|
+
}
|
|
36
|
+
return source.length;
|
|
37
|
+
};
|
|
38
|
+
const closingBracketOf = (source, open) => {
|
|
39
|
+
let depth = 0;
|
|
40
|
+
let index = open;
|
|
41
|
+
while (index < source.length) {
|
|
42
|
+
const char = source[index];
|
|
43
|
+
if (char === "/" && source[index + 1] === "/") {
|
|
44
|
+
const newline = source.indexOf("\n", index);
|
|
45
|
+
index = newline === -1 ? source.length : newline;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === "/" && source[index + 1] === "*") {
|
|
49
|
+
const end = source.indexOf("*/", index + 2);
|
|
50
|
+
index = end === -1 ? source.length : end + 2;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
54
|
+
index = endOfStringLiteral(source, index);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === "[" || char === "(" || char === "{") {
|
|
58
|
+
depth += 1;
|
|
59
|
+
}
|
|
60
|
+
else if (char === "]" || char === ")" || char === "}") {
|
|
61
|
+
depth -= 1;
|
|
62
|
+
if (depth === 0)
|
|
63
|
+
return char === "]" ? index : -1;
|
|
64
|
+
}
|
|
65
|
+
index += 1;
|
|
66
|
+
}
|
|
67
|
+
return -1;
|
|
68
|
+
};
|
|
69
|
+
const findPluginsArray = (source, from) => {
|
|
70
|
+
const key = /\bplugins\s*:\s*\[/g;
|
|
71
|
+
key.lastIndex = from;
|
|
72
|
+
const match = key.exec(source);
|
|
73
|
+
if (!match)
|
|
74
|
+
return null;
|
|
75
|
+
const open = match.index + match[0].length - 1;
|
|
76
|
+
const close = closingBracketOf(source, open);
|
|
77
|
+
return close === -1 ? null : { close, open };
|
|
78
|
+
};
|
|
79
|
+
const readImports = (source) => {
|
|
80
|
+
const statements = [];
|
|
81
|
+
let offset = 0;
|
|
82
|
+
let open = null;
|
|
83
|
+
for (const line of source.split("\n")) {
|
|
84
|
+
const lineStart = offset;
|
|
85
|
+
offset += line.length + 1;
|
|
86
|
+
open ??= /^import\b/.test(line)
|
|
87
|
+
? { isType: /^import\s+type\b/.test(line), start: lineStart }
|
|
88
|
+
: null;
|
|
89
|
+
if (!open)
|
|
90
|
+
continue;
|
|
91
|
+
const specifier = /\bfrom\s+['"]([^'"]+)['"]/.exec(line)?.[1] ??
|
|
92
|
+
/^import\s+['"]([^'"]+)['"]/.exec(line)?.[1];
|
|
93
|
+
if (specifier === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
const end = lineStart + line.length;
|
|
96
|
+
statements.push({
|
|
97
|
+
end,
|
|
98
|
+
isType: open.isType,
|
|
99
|
+
specifier,
|
|
100
|
+
start: open.start,
|
|
101
|
+
text: source.slice(open.start, end),
|
|
102
|
+
});
|
|
103
|
+
open = null;
|
|
104
|
+
}
|
|
105
|
+
return statements;
|
|
106
|
+
};
|
|
107
|
+
const importStyleOf = (source) => {
|
|
108
|
+
const match = /\bfrom\s+(['"])[^'"\n]+\1(;?)/.exec(source);
|
|
109
|
+
return { quote: match?.[1] ?? '"', semicolon: match?.[2] ?? ";" };
|
|
110
|
+
};
|
|
111
|
+
const withImport = (source, { factory, module }) => {
|
|
112
|
+
const { quote, semicolon } = importStyleOf(source);
|
|
113
|
+
const statement = `import { ${factory} } from ${quote}${module}${quote}${semicolon}`;
|
|
114
|
+
const imports = readImports(source);
|
|
115
|
+
if (imports.length === 0) {
|
|
116
|
+
return `${statement}\n\n${source}`;
|
|
117
|
+
}
|
|
118
|
+
const before = imports.find(entry => !entry.isType && entry.specifier > module);
|
|
119
|
+
if (before) {
|
|
120
|
+
return `${source.slice(0, before.start)}${statement}\n${source.slice(before.start)}`;
|
|
121
|
+
}
|
|
122
|
+
const last = imports[imports.length - 1];
|
|
123
|
+
return `${source.slice(0, last.end)}\n${statement}${source.slice(last.end)}`;
|
|
124
|
+
};
|
|
125
|
+
const withEntry = (source, { close, open }, call) => {
|
|
126
|
+
const body = source.slice(open + 1, close);
|
|
127
|
+
const head = source.slice(0, open + 1);
|
|
128
|
+
const tail = source.slice(close);
|
|
129
|
+
if (body.trim() === "") {
|
|
130
|
+
if (!body.includes("\n"))
|
|
131
|
+
return `${head}${call}${tail}`;
|
|
132
|
+
const indent = /\n([ \t]*)$/.exec(body)?.[1] ?? "";
|
|
133
|
+
return `${head}\n${indent} ${call},${body}${tail}`;
|
|
134
|
+
}
|
|
135
|
+
if (!body.includes("\n")) {
|
|
136
|
+
const separator = body.trimEnd().endsWith(",") ? " " : ", ";
|
|
137
|
+
return `${head}${body}${separator}${call}${tail}`;
|
|
138
|
+
}
|
|
139
|
+
const entries = body.trimEnd();
|
|
140
|
+
const trailing = body.slice(entries.length);
|
|
141
|
+
const indent = /^[ \t]*(?=\S)/m.exec(body.slice(body.indexOf("\n") + 1))?.[0] ?? " ";
|
|
142
|
+
const comma = entries.endsWith(",") ? "" : ",";
|
|
143
|
+
return `${head}${entries}${comma}\n${indent}${call},${trailing}${tail}`;
|
|
144
|
+
};
|
|
145
|
+
export const registerPluginInSource = (source, { builder, factory, module }) => {
|
|
146
|
+
const builderAt = source.indexOf(`${builder}(`);
|
|
147
|
+
if (builderAt === -1)
|
|
148
|
+
return { source, status: "no-config-call" };
|
|
149
|
+
const range = findPluginsArray(source, builderAt);
|
|
150
|
+
if (!range)
|
|
151
|
+
return { source, status: "no-plugins-array" };
|
|
152
|
+
const called = new RegExp(`\\b${factory}\\s*\\(`);
|
|
153
|
+
const hasEntry = called.test(source.slice(range.open + 1, range.close));
|
|
154
|
+
const hasImport = readImports(source).some(entry => entry.specifier === module &&
|
|
155
|
+
new RegExp(`\\b${factory}\\b`).test(entry.text));
|
|
156
|
+
if (hasEntry && hasImport)
|
|
157
|
+
return { source, status: "already-registered" };
|
|
158
|
+
const withCall = hasEntry ? source : withEntry(source, range, `${factory}()`);
|
|
159
|
+
return {
|
|
160
|
+
source: hasImport ? withCall : withImport(withCall, { factory, module }),
|
|
161
|
+
status: "registered",
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const readEntries = async (dir) => {
|
|
165
|
+
try {
|
|
166
|
+
return await readdir(dir, { withFileTypes: true });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const findConfigFiles = async (dir, skipDir, results = []) => {
|
|
173
|
+
for (const entry of await readEntries(dir)) {
|
|
174
|
+
const fullPath = join(dir, entry.name);
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
if (SKIPPED_DIRECTORIES.has(entry.name) || fullPath === skipDir)
|
|
177
|
+
continue;
|
|
178
|
+
await findConfigFiles(fullPath, skipDir, results);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (entry.isFile() && CONFIG_BUILDERS[entry.name]) {
|
|
182
|
+
results.push(fullPath);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return results;
|
|
186
|
+
};
|
|
187
|
+
const ownedByVitNodePackage = async (file, rootPath) => {
|
|
188
|
+
let current = dirname(file);
|
|
189
|
+
while (current.startsWith(rootPath)) {
|
|
190
|
+
try {
|
|
191
|
+
const pkg = JSON.parse(await readFile(join(current, "package.json"), "utf-8"));
|
|
192
|
+
return Boolean(pkg.dependencies?.["@vitnode/core"] ??
|
|
193
|
+
pkg.devDependencies?.["@vitnode/core"]);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
const parent = dirname(current);
|
|
197
|
+
if (parent === current)
|
|
198
|
+
return false;
|
|
199
|
+
current = parent;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
};
|
|
204
|
+
export const addPluginToConfig = async ({ pluginName, pluginPath, rootPath, }) => {
|
|
205
|
+
const files = await findConfigFiles(rootPath, pluginPath);
|
|
206
|
+
const registrations = [];
|
|
207
|
+
for (const file of files.sort()) {
|
|
208
|
+
if (!(await ownedByVitNodePackage(file, rootPath)))
|
|
209
|
+
continue;
|
|
210
|
+
const target = CONFIG_BUILDERS[basename(file)];
|
|
211
|
+
const isApi = target.subpath === "config.api";
|
|
212
|
+
try {
|
|
213
|
+
const source = await readFile(file, "utf-8");
|
|
214
|
+
const { source: next, status } = registerPluginInSource(source, {
|
|
215
|
+
builder: target.builder,
|
|
216
|
+
factory: isApi
|
|
217
|
+
? pluginApiVariableName(pluginName)
|
|
218
|
+
: pluginVariableName(pluginName),
|
|
219
|
+
module: `${pluginName}/${target.subpath}`,
|
|
220
|
+
});
|
|
221
|
+
if (status === "registered") {
|
|
222
|
+
await writeFile(file, next, "utf-8");
|
|
223
|
+
}
|
|
224
|
+
registrations.push({ file, status });
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return registrations;
|
|
231
|
+
};
|