create-vitnode-app 1.2.0-canary.6 → 1.2.0-canary.60

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 (69) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +25 -5
  3. package/copy-of-vitnode-app/.vscode/settings.json +11 -0
  4. package/copy-of-vitnode-app/api/.gitignore_template +29 -0
  5. package/copy-of-vitnode-app/api/drizzle.config.ts +12 -0
  6. package/copy-of-vitnode-app/api/src/index.ts +27 -0
  7. package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +23 -0
  8. package/copy-of-vitnode-app/api/tsconfig.json +16 -0
  9. package/copy-of-vitnode-app/api-bun/src/index.ts +16 -0
  10. package/copy-of-vitnode-app/api-single-app/.env.example +8 -0
  11. package/copy-of-vitnode-app/api-single-app/.gitignore_template +37 -0
  12. package/copy-of-vitnode-app/api-single-app/drizzle.config.ts +12 -0
  13. package/copy-of-vitnode-app/api-single-app/src/app/api/[...route]/route.ts +16 -0
  14. package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +18 -0
  15. package/copy-of-vitnode-app/docker/docker-compose.yml +20 -0
  16. package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
  17. package/copy-of-vitnode-app/eslint/eslint.config.mjs +16 -2
  18. package/copy-of-vitnode-app/monorepo/.gitignore_template +41 -0
  19. package/copy-of-vitnode-app/monorepo/apps/api/.env.example +8 -0
  20. package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -0
  21. package/copy-of-vitnode-app/monorepo/turbo.json +40 -0
  22. package/copy-of-vitnode-app/root/.env.example +2 -4
  23. package/copy-of-vitnode-app/root/.gitignore_template +37 -0
  24. package/copy-of-vitnode-app/root/README.md +47 -0
  25. package/copy-of-vitnode-app/root/global.d.ts +9 -0
  26. package/copy-of-vitnode-app/root/next.config.ts +3 -3
  27. package/copy-of-vitnode-app/root/postcss.config.mjs +1 -1
  28. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/[...rest]/page.tsx +1 -1
  29. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +8 -3
  30. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/not-found.tsx +1 -1
  31. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +20 -27
  32. package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +2 -2
  33. package/copy-of-vitnode-app/root/src/app/[locale]/admin/page.tsx +1 -1
  34. package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +20 -17
  35. package/copy-of-vitnode-app/root/src/app/global-error.tsx +8 -11
  36. package/copy-of-vitnode-app/root/src/app/global.css +63 -64
  37. package/copy-of-vitnode-app/root/src/app/layout.tsx +3 -1
  38. package/copy-of-vitnode-app/root/src/proxy.ts +24 -0
  39. package/copy-of-vitnode-app/root/src/vitnode.config.ts +32 -0
  40. package/copy-of-vitnode-app/root/tsconfig.json +1 -1
  41. package/dist/src/create/create-package-json.js +286 -54
  42. package/dist/src/create/create-vitnode.js +161 -27
  43. package/dist/src/helpers/get-available-package-managers.js +6 -6
  44. package/dist/src/helpers/get-package-json.js +2 -2
  45. package/dist/src/helpers/get-package-manager-from-root.js +15 -0
  46. package/dist/src/helpers/init-vitnode.js +21 -0
  47. package/dist/src/helpers/install-dependencies.js +55 -14
  48. package/dist/src/helpers/is-folder-empty.js +25 -26
  49. package/dist/src/helpers/is-online.js +16 -16
  50. package/dist/src/helpers/is-writeable.js +3 -3
  51. package/dist/src/helpers/validate-pkg.js +1 -1
  52. package/dist/src/index.js +29 -26
  53. package/dist/src/plugin/create/create-plugin-vitnode.js +34 -0
  54. package/dist/src/plugin/index.js +17 -6
  55. package/dist/src/plugin/questions.js +14 -0
  56. package/dist/src/plugin/validation.js +72 -0
  57. package/dist/src/prepare/prepare.js +5 -5
  58. package/dist/src/questions.js +52 -15
  59. package/dist/src/validation.js +15 -15
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/eslint.config.mjs +25 -0
  62. package/package.json +9 -9
  63. package/copy-of-vitnode-app/root/.prettierrc.mjs +0 -11
  64. package/copy-of-vitnode-app/root/drizzle.config.ts +0 -12
  65. package/copy-of-vitnode-app/root/gitignore_template +0 -43
  66. package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
  67. package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +0 -18
  68. package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
  69. package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +0 -5
@@ -1,30 +1,71 @@
1
- import { spawn } from 'child_process';
2
- import color from 'picocolors';
3
- import { getOnline } from './is-online.js';
4
- export const installDependencies = async ({ packageManager: pm, }) => {
5
- const packageManager = pm.split('@')[0];
1
+ import { spawn } from "node:child_process";
2
+ import consoleColor from "picocolors";
3
+ import { getOnline } from "./is-online.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 = packageManager === 'npm' ? ['install', '--force'] : ['install'];
22
+ const args = ["install"];
8
23
  if (!isOnline) {
9
- console.log(color.yellow('You appear to be offline.\nFalling back to the local cache.'));
10
- args.push('--offline');
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) => {
28
+ let stdout = "";
29
+ let stderr = "";
13
30
  const child = spawn(packageManager, args, {
14
- stdio: 'ignore',
31
+ stdio: "pipe",
32
+ cwd,
33
+ shell: true,
15
34
  env: {
16
35
  ...process.env,
17
- ADBLOCK: '1',
18
- NODE_ENV: 'development',
19
- DISABLE_OPENCOLLECTIVE: '1',
36
+ ADBLOCK: "1",
37
+ NODE_ENV: "development",
38
+ DISABLE_OPENCOLLECTIVE: "1",
20
39
  },
21
40
  });
22
- child.on('close', code => {
41
+ child.stdout?.on("data", (data) => {
42
+ const output = data.toString();
43
+ stdout += output;
44
+ });
45
+ child.stderr?.on("data", (data) => {
46
+ const output = data.toString();
47
+ stderr += output;
48
+ });
49
+ child.on("close", code => {
23
50
  if (code !== 0) {
24
- reject({ command: `${packageManager} ${args.join(' ')}` });
51
+ console.error(consoleColor.red(`\n❌ Installation failed with exit code: ${code}`));
52
+ if (stderr) {
53
+ console.error(consoleColor.red("Error output:"));
54
+ console.error(stderr);
55
+ }
56
+ if (stdout) {
57
+ console.log(consoleColor.yellow("Standard output:"));
58
+ console.log(stdout);
59
+ }
60
+ printInstallErrorSuggestions(stderr, consoleColor);
61
+ reject(new Error(`Failed to install dependencies using ${packageManager}. Exit code: ${code}\n${stderr || stdout}`));
25
62
  return;
26
63
  }
27
64
  resolve();
28
65
  });
66
+ child.on("error", error => {
67
+ console.error(consoleColor.red(`❌ Failed to start ${packageManager}:`), error.message);
68
+ reject(new Error(`Failed to start ${packageManager}: ${error.message}`));
69
+ });
29
70
  });
30
71
  };
@@ -1,31 +1,30 @@
1
- import { lstatSync, readdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import colors from 'picocolors';
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
- '.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',
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('\nEither try using a new directory name, or remove the files listed above.\n');
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 'child_process';
2
- import { lookup } from 'dns/promises';
3
- import { URL } from 'url';
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('registry.yarnpkg.com');
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 'constants';
2
- import { access } from 'fs/promises';
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
  }
@@ -1,4 +1,4 @@
1
- import validateProjectName from 'validate-npm-package-name';
1
+ import validateProjectName from "validate-npm-package-name";
2
2
  export const validateNpmName = ({ name, }) => {
3
3
  const nameValidation = validateProjectName(name);
4
4
  if (nameValidation.validForNewPackages) {
package/dist/src/index.js CHANGED
@@ -1,51 +1,54 @@
1
1
  #!/usr/bin/env node
2
- import { input } from '@inquirer/prompts';
3
- import { Command, Option } from 'commander';
4
- import { basename, resolve } from '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);
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('uncaughtException', (error) => {
18
- if (error.name === 'ExitPromptError') {
19
- console.log(color.dim('👋 VitNode setup cancelled - see you next time!'));
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('An unexpected error occurred:'));
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('[project-directory]')
31
- .usage(`${color.green('[project-directory]')} [options]`)
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('--package-manager <package-manager>', 'Specify the package manager to use').choices(['npm', 'pnpm', 'bun']));
36
- program.option('--eslint', 'Initialize with eslint config.');
37
- program.option('--skip-install', 'Skip installing packages after initializing the project.');
38
- program.option('--plugin', 'Enable plugin mode.');
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.addOption(new Option("--mode <mode>", "What type of app do you want to create?").choices(["singleApp", "apiMonorepo", "onlyApi"]));
39
+ program.option("--monorepo", "Create project with monorepo structure.");
40
+ program.option("--docker", "Initialize with Docker support.");
41
+ program.option("--plugin", "Create a plugin.");
39
42
  program.parse(process.argv);
40
43
  const opts = program.opts();
41
44
  if (opts.plugin) {
42
- await createPlugin(projectPath);
45
+ await createPlugin({ projectPath, program });
43
46
  return;
44
47
  }
45
48
  if (!projectPath) {
46
49
  projectPath = await input({
47
- message: 'What is your project named?',
48
- default: 'my-vitnode',
50
+ message: "What is your project named?",
51
+ default: "my-vitnode",
49
52
  validate: (name) => {
50
53
  const validation = validateNpmName({ name: basename(resolve(name)) });
51
54
  if (validation.valid)
@@ -0,0 +1,34 @@
1
+ import { existsSync } from "fs";
2
+ import { mkdir } from "fs/promises";
3
+ import ora from "ora";
4
+ import { dirname, join } from "path";
5
+ import color from "picocolors";
6
+ import { fileURLToPath } from "url";
7
+ import { getPackageManagerFromRoot } from "../../helpers/get-package-manager-from-root.js";
8
+ import { installDependencies } from "../../helpers/install-dependencies.js";
9
+ import { isFolderEmpty } from "../../helpers/is-folder-empty.js";
10
+ export const createPluginVitNode = async ({ pluginPath, pluginName, install, root, }) => {
11
+ const packageManager = getPackageManagerFromRoot(process.cwd());
12
+ const spinner = ora(`Creating a new VitNode plugin in ${color.green(pluginPath)}. Using ${color.green(packageManager)}...`).start();
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const templatePath = join(__dirname, "..", "..", "..", "copy-of-vitnode-plugin");
16
+ if (!existsSync(templatePath)) {
17
+ spinner.fail(`\n${color.red("Error!")} Template path ${color.cyan(templatePath)} does not exist.`);
18
+ process.exit(1);
19
+ }
20
+ await mkdir(pluginPath, { recursive: true });
21
+ if (!isFolderEmpty(pluginPath, pluginName)) {
22
+ process.exit(1);
23
+ }
24
+ spinner.text = "Preparing the plugin structure...";
25
+ spinner.text = "Creating package.json...";
26
+ if (install) {
27
+ spinner.text = "Installing dependencies...";
28
+ await installDependencies({
29
+ packageManager,
30
+ cwd: root,
31
+ });
32
+ }
33
+ spinner.succeed(`${color.green("Success!")} Created ${color.cyan(pluginName)} at ${color.cyan(pluginPath)}`);
34
+ };
@@ -1,12 +1,15 @@
1
- import { input } from '@inquirer/prompts';
2
- import { basename, resolve } from 'path';
3
- import { validateNpmName } from '../helpers/validate-pkg.js';
4
- export const createPlugin = async (projectPath) => {
1
+ import { input } from "@inquirer/prompts";
2
+ import { basename, resolve } from "node:path";
3
+ import { validateNpmName } from "../helpers/validate-pkg.js";
4
+ import { createPluginVitNode } from "./create/create-plugin-vitnode.js";
5
+ import { createPluginQuestionsCli } from "./questions.js";
6
+ import { validationProjectForPlugin } from "./validation.js";
7
+ export const createPlugin = async ({ program, projectPath, }) => {
5
8
  let name = projectPath;
6
9
  if (!name) {
7
10
  name = await input({
8
- message: 'What is your plugin named?',
9
- default: 'my-vitnode-plugin',
11
+ message: "What is your plugin named?",
12
+ default: "my-vitnode-plugin",
10
13
  validate: (name) => {
11
14
  const validation = validateNpmName({ name: basename(resolve(name)) });
12
15
  if (validation.valid)
@@ -15,4 +18,12 @@ export const createPlugin = async (projectPath) => {
15
18
  },
16
19
  });
17
20
  }
21
+ const { pluginName, pluginPath } = await validationProjectForPlugin(name);
22
+ const options = await createPluginQuestionsCli(program);
23
+ await createPluginVitNode({
24
+ pluginName,
25
+ pluginPath,
26
+ root: projectPath,
27
+ ...options,
28
+ });
18
29
  };
@@ -0,0 +1,14 @@
1
+ import { confirm } from "@inquirer/prompts";
2
+ import color from "picocolors";
3
+ export const createPluginQuestionsCli = async (program) => {
4
+ const optionsFromProgram = program.opts();
5
+ const options = {
6
+ install: !optionsFromProgram.skipInstall,
7
+ };
8
+ if (optionsFromProgram.skipInstall === undefined) {
9
+ options.install = await confirm({
10
+ message: `Would you like to ${color.blue("Install dependencies")}?`,
11
+ });
12
+ }
13
+ return options;
14
+ };
@@ -0,0 +1,72 @@
1
+ import { program } from "commander";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile } from "node:fs/promises";
4
+ import { basename, join, resolve } from "node:path";
5
+ import color from "picocolors";
6
+ import { isFolderEmpty } from "../helpers/is-folder-empty.js";
7
+ import { isWriteable } from "../helpers/is-writeable.js";
8
+ import { validateNpmName } from "../helpers/validate-pkg.js";
9
+ export const validationProjectForPlugin = async (projectPath) => {
10
+ if (!projectPath) {
11
+ console.log("\nPlease specify the plugin directory:\n" +
12
+ ` ${color.cyan(program.name())} ${color.green("<plugin-directory>")}\n` +
13
+ "For example:\n" +
14
+ ` ${color.cyan(program.name())} ${color.green("my-vitnode-plugin")}\n\n` +
15
+ `Run ${color.cyan(`${program.name()} --help`)} to see all options.`);
16
+ process.exit(1);
17
+ }
18
+ const cwd = process.cwd();
19
+ const turboJsonPath = join(cwd, "turbo.json");
20
+ const packageJsonPath = join(cwd, "package.json");
21
+ if (!existsSync(turboJsonPath)) {
22
+ console.error(`${color.red("Error:")} Could not find ${color.cyan("turbo.json")} in the current directory.`);
23
+ console.error(`Plugins must be created inside a VitNode monorepo structure.`);
24
+ console.error(`\nPlease run this command from the root of your VitNode monorepo project.`);
25
+ process.exit(1);
26
+ }
27
+ if (!existsSync(packageJsonPath)) {
28
+ console.error(`${color.red("Error:")} Could not find ${color.cyan("package.json")} in the current directory.`);
29
+ console.error(`\nPlease run this command from the root of your VitNode monorepo project.`);
30
+ process.exit(1);
31
+ }
32
+ try {
33
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
34
+ if (!packageJson.packageManager) {
35
+ console.error(`${color.red("Error:")} The ${color.cyan("packageManager")} field is not defined in ${color.cyan("package.json")}.`);
36
+ console.error(`\nPlease add a ${color.cyan('"packageManager"')} field to your ${color.cyan("package.json")} file.`);
37
+ console.error(`Example: ${color.green('"packageManager": "pnpm@10.18.3"')}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ catch (error) {
42
+ console.error(`${color.red("Error:")} Failed to read or parse ${color.cyan("package.json")}.`);
43
+ console.error(error);
44
+ process.exit(1);
45
+ }
46
+ const projectName = basename(resolve(projectPath));
47
+ const validation = validateNpmName({ name: projectName });
48
+ if (!validation.valid) {
49
+ console.error(`Could not create a plugin called ${color.red(`"${projectName}"`)} because of npm naming restrictions:`);
50
+ validation.problems.forEach(p => {
51
+ console.error(`${color.red(color.bold("*"))} ${p}`);
52
+ });
53
+ process.exit(1);
54
+ }
55
+ const pluginsDir = join(cwd, "plugins");
56
+ const pluginPath = join(pluginsDir, projectName);
57
+ const pluginName = basename(pluginPath);
58
+ const folderExists = existsSync(pluginPath);
59
+ if (folderExists && !isFolderEmpty(pluginPath, pluginName)) {
60
+ console.error(`The directory ${color.cyan(`plugins/${pluginName}`)} is not empty.`);
61
+ process.exit(1);
62
+ }
63
+ if (!existsSync(pluginsDir)) {
64
+ await mkdir(pluginsDir, { recursive: true });
65
+ }
66
+ if (!(await isWriteable(pluginsDir))) {
67
+ console.error(`The plugins directory is not writable, please check folder permissions and try again.`);
68
+ console.error(`It is likely you do not have write permissions for this folder.`);
69
+ process.exit(1);
70
+ }
71
+ return { pluginName, pluginPath };
72
+ };
@@ -1,12 +1,12 @@
1
- import { existsSync } from 'fs';
2
- import { mkdir } from 'fs/promises';
3
- import { join } from 'path';
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { join } from "node:path";
4
4
  const prepare = async () => {
5
- const toRootPath = join(process.cwd(), 'copy-of-vitnode-app');
5
+ const toRootPath = join(process.cwd(), "copy-of-vitnode-app");
6
6
  if (!existsSync(toRootPath)) {
7
7
  await mkdir(toRootPath);
8
8
  }
9
- const fromRootPath = join(process.cwd(), '..', '..', 'apps', 'web');
9
+ const fromRootPath = join(process.cwd(), "..", "..", "apps", "docs");
10
10
  if (!existsSync(fromRootPath)) {
11
11
  console.error(`\x1b[31mThe path ${fromRootPath} does not exist. Please check the directory structure.\x1b[0m`);
12
12
  process.exit(1);
@@ -1,45 +1,82 @@
1
- import { select } from '@inquirer/prompts';
2
- import { confirm } from '@inquirer/prompts';
3
- import color from 'picocolors';
4
- import { getAvailablePackageManagers } from './helpers/get-available-package-managers.js';
1
+ import { confirm, select } from "@inquirer/prompts";
2
+ import color from "picocolors";
3
+ import { getAvailablePackageManagers } from "./helpers/get-available-package-managers.js";
5
4
  export const createQuestionsCli = async (program) => {
6
5
  const optionsFromProgram = program.opts();
7
6
  const options = {
8
7
  packageManager: optionsFromProgram.packageManager,
9
8
  eslint: optionsFromProgram.eslint,
10
9
  install: !optionsFromProgram.skipInstall,
10
+ docker: optionsFromProgram.docker,
11
+ mode: optionsFromProgram.mode,
12
+ monorepo: optionsFromProgram.monorepo,
11
13
  };
12
14
  if (!optionsFromProgram.packageManager) {
13
15
  const availablePackageManagers = await getAvailablePackageManagers();
14
16
  options.packageManager = await select({
15
- message: `Which ${color.blue('package manager')} do you want to use?`,
17
+ message: `Which ${color.blue("package manager")} do you want to use?`,
16
18
  choices: [
17
19
  {
18
- name: `npm${availablePackageManagers.npm ? `@${availablePackageManagers.npm}` : ''}`,
19
- value: 'npm',
20
- disabled: !availablePackageManagers.npm,
20
+ name: `bun${availablePackageManagers.bun ? `@${availablePackageManagers.bun}` : ""}`,
21
+ value: "bun",
22
+ disabled: !availablePackageManagers.bun,
21
23
  },
22
24
  {
23
- name: `pnpm${availablePackageManagers.pnpm ? `@${availablePackageManagers.pnpm}` : ''}`,
24
- value: 'pnpm',
25
+ name: `pnpm${availablePackageManagers.pnpm ? `@${availablePackageManagers.pnpm}` : ""}`,
26
+ value: "pnpm",
25
27
  disabled: !availablePackageManagers.pnpm,
26
28
  },
27
29
  {
28
- name: `bun${availablePackageManagers.bun ? `@${availablePackageManagers.bun}` : ''}`,
29
- value: 'bun',
30
- disabled: !availablePackageManagers.bun,
30
+ name: `npm${availablePackageManagers.npm ? `@${availablePackageManagers.npm}` : ""}`,
31
+ value: "npm",
32
+ disabled: !availablePackageManagers.npm,
33
+ },
34
+ ],
35
+ });
36
+ }
37
+ if (optionsFromProgram.mode === undefined) {
38
+ options.mode = await select({
39
+ message: `What type of ${color.blue("app")} do you want to create?`,
40
+ choices: [
41
+ {
42
+ name: `Single App - ${color.blue("Next.js")} & ${color.blue("Hono.js")}`,
43
+ description: "Create a single app with Next.js and Hono.js in the same project.",
44
+ value: "singleApp",
45
+ },
46
+ {
47
+ name: `Monorepo App - ${color.blue("Next.js")} & ${color.blue("Hono.js")}`,
48
+ description: "Create a monorepo with both Next.js and Hono.js apps separately.",
49
+ value: "apiMonorepo",
50
+ },
51
+ {
52
+ name: `Only API - ${color.blue("Hono.js")}`,
53
+ description: "Create only an API app using Hono.js without Next.js.",
54
+ value: "onlyApi",
31
55
  },
32
56
  ],
57
+ default: "singleApp",
58
+ });
59
+ }
60
+ if (optionsFromProgram.monorepo === undefined &&
61
+ options.mode !== "apiMonorepo") {
62
+ options.monorepo = await confirm({
63
+ message: `Would you like to use ${color.blue("TurboRepo")} for monorepo management? ${color.red("(Required for plugins development)")}`,
64
+ default: false,
33
65
  });
34
66
  }
35
67
  if (optionsFromProgram.eslint === undefined) {
36
68
  options.eslint = await confirm({
37
- message: `Would you like to use ${color.blue('ESLint')}?`,
69
+ message: `Would you like to use ${color.blue("ESLint & Prettier")}?`,
70
+ });
71
+ }
72
+ if (optionsFromProgram.docker === undefined) {
73
+ options.docker = await confirm({
74
+ message: `Would you like to use ${color.blue("Docker Container")}?`,
38
75
  });
39
76
  }
40
77
  if (optionsFromProgram.skipInstall === undefined) {
41
78
  options.install = await confirm({
42
- message: `Would you like to ${color.blue('Install dependencies')}?`,
79
+ message: `Would you like to ${color.blue("Install dependencies")}?`,
43
80
  });
44
81
  }
45
82
  return options;
@@ -1,16 +1,16 @@
1
- import { program } from 'commander';
2
- import { existsSync } from 'fs';
3
- import { basename, dirname, resolve } from 'path';
4
- import color from 'picocolors';
5
- import { isFolderEmpty } from './helpers/is-folder-empty.js';
6
- import { isWriteable } from './helpers/is-writeable.js';
7
- import { validateNpmName } from './helpers/validate-pkg.js';
1
+ import { program } from "commander";
2
+ import { existsSync } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import color from "picocolors";
5
+ import { isFolderEmpty } from "./helpers/is-folder-empty.js";
6
+ import { isWriteable } from "./helpers/is-writeable.js";
7
+ import { validateNpmName } from "./helpers/validate-pkg.js";
8
8
  export const validationProject = async (projectPath) => {
9
9
  if (!projectPath) {
10
- console.log('\nPlease specify the project directory:\n' +
11
- ` ${color.cyan(program.name())} ${color.green('<project-directory>')}\n` +
12
- 'For example:\n' +
13
- ` ${color.cyan(program.name())} ${color.green('my-vitnode-app')}\n\n` +
10
+ console.log("\nPlease specify the project directory:\n" +
11
+ ` ${color.cyan(program.name())} ${color.green("<project-directory>")}\n` +
12
+ "For example:\n" +
13
+ ` ${color.cyan(program.name())} ${color.green("my-vitnode-app")}\n\n` +
14
14
  `Run ${color.cyan(`${program.name()} --help`)} to see all options.`);
15
15
  process.exit(1);
16
16
  }
@@ -20,7 +20,7 @@ export const validationProject = async (projectPath) => {
20
20
  if (!validation.valid) {
21
21
  console.error(`Could not create a project called ${color.red(`"${projectName}"`)} because of npm naming restrictions:`);
22
22
  validation.problems.forEach(p => {
23
- console.error(`${color.red(color.bold('*'))} ${p}`);
23
+ console.error(`${color.red(color.bold("*"))} ${p}`);
24
24
  });
25
25
  process.exit(1);
26
26
  }
@@ -28,12 +28,12 @@ export const validationProject = async (projectPath) => {
28
28
  const appName = basename(root);
29
29
  const folderExists = existsSync(root);
30
30
  if (folderExists && !isFolderEmpty(root, appName)) {
31
- console.error('The specified directory is not empty.');
31
+ console.error("The specified directory is not empty.");
32
32
  process.exit(1);
33
33
  }
34
34
  if (!(await isWriteable(dirname(root)))) {
35
- console.error('The application path is not writable, please check folder permissions and try again.');
36
- console.error('It is likely you do not have write permissions for this folder.');
35
+ console.error("The application path is not writable, please check folder permissions and try again.");
36
+ console.error("It is likely you do not have write permissions for this folder.");
37
37
  process.exit(1);
38
38
  }
39
39
  return { appName, root };