create-vitnode-app 1.2.0-canary.9 → 2.0.0-canary.1

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 (110) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +52 -18
  3. package/copy-of-vitnode-app/.vscode/settings.json +10 -0
  4. package/copy-of-vitnode-app/README.md +40 -0
  5. package/copy-of-vitnode-app/api/.gitignore_template +32 -0
  6. package/copy-of-vitnode-app/api/drizzle.config.ts +12 -0
  7. package/copy-of-vitnode-app/api/src/i18n.ts +38 -0
  8. package/copy-of-vitnode-app/api/src/index.ts +27 -0
  9. package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +40 -0
  10. package/copy-of-vitnode-app/api/tsconfig.json +17 -0
  11. package/copy-of-vitnode-app/api-bun/src/index.ts +16 -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/routes/api/$.ts +32 -0
  14. package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +22 -0
  15. package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +29 -0
  16. package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +37 -0
  17. package/copy-of-vitnode-app/docker/docker-compose.yml +30 -0
  18. package/copy-of-vitnode-app/eslint/.prettierrc.mjs +1 -1
  19. package/copy-of-vitnode-app/eslint/eslint.config.mjs +18 -2
  20. package/copy-of-vitnode-app/{root → eslint-react}/.prettierrc.mjs +1 -1
  21. package/copy-of-vitnode-app/eslint-react/eslint.config.mjs +19 -0
  22. package/copy-of-vitnode-app/monorepo/.gitignore_template +41 -0
  23. package/copy-of-vitnode-app/monorepo/apps/api/.env.example +12 -0
  24. package/copy-of-vitnode-app/monorepo/apps/web/.env.example +12 -0
  25. package/copy-of-vitnode-app/monorepo/turbo.json +53 -0
  26. package/copy-of-vitnode-app/root/.env.example +16 -5
  27. package/copy-of-vitnode-app/root/.gitignore_template +41 -0
  28. package/copy-of-vitnode-app/root/global.d.ts +28 -0
  29. package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +72 -0
  30. package/copy-of-vitnode-app/root/src/components/main-header.tsx +33 -0
  31. package/copy-of-vitnode-app/root/src/i18n.ts +36 -0
  32. package/copy-of-vitnode-app/root/src/lib/admin-auth.ts +11 -0
  33. package/copy-of-vitnode-app/root/src/lib/admin-nav.ts +43 -0
  34. package/copy-of-vitnode-app/root/src/lib/admin-search.ts +37 -0
  35. package/copy-of-vitnode-app/root/src/lib/auth.ts +66 -0
  36. package/copy-of-vitnode-app/root/src/lib/content-registry.ts +51 -0
  37. package/copy-of-vitnode-app/root/src/lib/document-headers.ts +128 -0
  38. package/copy-of-vitnode-app/root/src/lib/i18n/runtime.ts +75 -0
  39. package/copy-of-vitnode-app/root/src/lib/i18n/shared.ts +20 -0
  40. package/copy-of-vitnode-app/root/src/lib/navigation.ts +24 -0
  41. package/copy-of-vitnode-app/root/src/lib/page-head.ts +19 -0
  42. package/copy-of-vitnode-app/root/src/locales/app.ts +28 -0
  43. package/copy-of-vitnode-app/root/src/locales/packages.ts +43 -0
  44. package/copy-of-vitnode-app/root/src/router.tsx +227 -0
  45. package/copy-of-vitnode-app/root/src/routes/__root.tsx +212 -0
  46. package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +70 -0
  47. package/copy-of-vitnode-app/root/src/routes/_admin.tsx +258 -0
  48. package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +60 -0
  49. package/copy-of-vitnode-app/root/src/routes/_main.tsx +64 -0
  50. package/copy-of-vitnode-app/root/src/server/messages.server.ts +29 -0
  51. package/copy-of-vitnode-app/root/src/start.ts +88 -0
  52. package/copy-of-vitnode-app/root/src/styles.css +171 -0
  53. package/copy-of-vitnode-app/root/src/vitnode.config.ts +58 -0
  54. package/copy-of-vitnode-app/root/src/vitnode.shell.config.ts +35 -0
  55. package/copy-of-vitnode-app/root/tsconfig.json +24 -16
  56. package/copy-of-vitnode-app/root/tsr.config.json +3 -0
  57. package/copy-of-vitnode-app/root/vite.config.ts +119 -0
  58. package/copy-of-vitnode-plugin/root/.swcrc +26 -0
  59. package/copy-of-vitnode-plugin/root/global.d.ts +21 -0
  60. package/copy-of-vitnode-plugin/root/npmignore.template +17 -0
  61. package/copy-of-vitnode-plugin/root/tsconfig.build.json +5 -0
  62. package/copy-of-vitnode-plugin/root/tsconfig.json +25 -0
  63. package/dist/src/create/create-package-json.js +266 -56
  64. package/dist/src/create/create-vitnode.js +177 -28
  65. package/dist/src/create/package-versions.js +49 -0
  66. package/dist/src/helpers/get-available-package-managers.js +6 -6
  67. package/dist/src/helpers/get-package-json.js +2 -2
  68. package/dist/src/helpers/get-package-manager-from-root.js +15 -0
  69. package/dist/src/helpers/get-vitnode-package-version.js +9 -0
  70. package/dist/src/helpers/init-vitnode.js +34 -0
  71. package/dist/src/helpers/install-dependencies.js +55 -14
  72. package/dist/src/helpers/is-folder-empty.js +25 -26
  73. package/dist/src/helpers/is-online.js +16 -16
  74. package/dist/src/helpers/is-writeable.js +3 -3
  75. package/dist/src/helpers/validate-pkg.js +1 -1
  76. package/dist/src/helpers/with-If.js +1 -0
  77. package/dist/src/index.js +29 -26
  78. package/dist/src/plugin/create/add-plugin-to-workspace.js +74 -0
  79. package/dist/src/plugin/create/create-package-json.js +54 -0
  80. package/dist/src/plugin/create/create-plugin-vitnode.js +80 -0
  81. package/dist/src/plugin/create/route-templates.js +144 -0
  82. package/dist/src/plugin/index.js +17 -6
  83. package/dist/src/plugin/questions.js +14 -0
  84. package/dist/src/plugin/validation.js +76 -0
  85. package/dist/src/prepare/prepare.js +11 -8
  86. package/dist/src/questions.js +52 -15
  87. package/dist/src/validation.js +15 -15
  88. package/dist/tsconfig.build.tsbuildinfo +1 -0
  89. package/package.json +22 -12
  90. package/copy-of-vitnode-app/root/README.md +0 -1
  91. package/copy-of-vitnode-app/root/drizzle.config.ts +0 -12
  92. package/copy-of-vitnode-app/root/gitignore_template +0 -43
  93. package/copy-of-vitnode-app/root/next.config.ts +0 -11
  94. package/copy-of-vitnode-app/root/postcss.config.mjs +0 -8
  95. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/[...rest]/page.tsx +0 -5
  96. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/layout.tsx +0 -10
  97. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/not-found.tsx +0 -5
  98. package/copy-of-vitnode-app/root/src/app/[locale]/(main)/page.tsx +0 -113
  99. package/copy-of-vitnode-app/root/src/app/[locale]/admin/(auth)/layout.tsx +0 -10
  100. package/copy-of-vitnode-app/root/src/app/[locale]/admin/not-found.tsx +0 -5
  101. package/copy-of-vitnode-app/root/src/app/[locale]/admin/page.tsx +0 -5
  102. package/copy-of-vitnode-app/root/src/app/[locale]/layout.tsx +0 -36
  103. package/copy-of-vitnode-app/root/src/app/api/[...route]/route.ts +0 -18
  104. package/copy-of-vitnode-app/root/src/app/favicon.ico +0 -0
  105. package/copy-of-vitnode-app/root/src/app/global-error.tsx +0 -27
  106. package/copy-of-vitnode-app/root/src/app/global.css +0 -128
  107. package/copy-of-vitnode-app/root/src/app/layout.tsx +0 -9
  108. package/copy-of-vitnode-app/root/src/app/not-found.tsx +0 -13
  109. package/copy-of-vitnode-app/root/src/vitnode.api.config.ts +0 -5
  110. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,144 @@
1
+ export const routeSlugFor = (pluginName) => pluginName.includes("/")
2
+ ? pluginName.slice(pluginName.indexOf("/") + 1)
3
+ : pluginName;
4
+ export const pluginRoutesTemplate = (pluginName) => {
5
+ const slug = routeSlugFor(pluginName);
6
+ return `import { definePluginRoutes, lazy, page } from "@vitnode/core/routing";
7
+
8
+ /**
9
+ * The routes this plugin contributes to whatever app installs it.
10
+ *
11
+ * Browser-safe data: a path, and the module that renders it. \`lazy\` keeps that
12
+ * \`import()\` a literal the bundler can follow *without running it*, so your page
13
+ * gets a chunk of its own and is fetched when somebody navigates to it - not
14
+ * before. Never import a page into this file: a component named here is in the
15
+ * initial bundle of every page on the site, which is why VitNode refuses one.
16
+ *
17
+ * Your page is never copied into the application either. The app holds one static
18
+ * import of this tree, and nothing else.
19
+ *
20
+ * Add a route by adding a \`page()\`. \`path\` is the public URL, written in
21
+ * VitNode's own spelling: a dynamic segment is \`:id\`, never Next's \`[id]\` and
22
+ * never TanStack's \`$id\`. To nest pages inside a shared frame, wrap them in a
23
+ * \`layout()\` and give each child a path relative to it.
24
+ */
25
+ export const routes = definePluginRoutes([
26
+ page("/${slug}", {
27
+ component: lazy(() => import("./pages/home-page")),
28
+ }),
29
+ ]);
30
+ `;
31
+ };
32
+ export const pluginRouteModuleTemplate = (pluginName) => `import { useTranslations } from "use-intl";
33
+
34
+ /**
35
+ * The page \`routes.ts\` declares.
36
+ *
37
+ * Keep it framework-neutral. This module is compiled into the package's own
38
+ * \`dist\` and imported by whichever app installed the plugin, so anything from
39
+ * a router or a host-bound i18n package pins the plugin to one kind of host.
40
+ * \`use-intl\` - which is what VitNode itself renders through - and plain JSX are
41
+ * pinned to neither.
42
+ *
43
+ * No \`<main>\`: the application shell owns the document's one \`main\` landmark,
44
+ * and a page that renders a second gives a screen reader two to choose between.
45
+ * A page owns its container - width, padding, vertical rhythm - and nothing
46
+ * above it.
47
+ *
48
+ * To give this route a loader, page metadata or a breadcrumb, add a \`route\`
49
+ * export beside the default one:
50
+ *
51
+ * import { definePluginRoute } from "@vitnode/core/routing";
52
+ *
53
+ * export const route = definePluginRoute({
54
+ * load: ({ context, params }) => fetchThing(context.locale, params.id),
55
+ * head: ({ loaderData }) => ({ title: loaderData?.title }),
56
+ * });
57
+ */
58
+ const HomePage = () => {
59
+ const t = useTranslations("${pluginName}");
60
+
61
+ return (
62
+ <div className="container mx-auto flex max-w-2xl flex-col gap-4 p-4">
63
+ <h1 className="text-2xl font-semibold tracking-tight text-balance">
64
+ {t("home.title")}
65
+ </h1>
66
+
67
+ <p className="text-muted-foreground leading-relaxed text-pretty">
68
+ {t("home.desc")}
69
+ </p>
70
+ </div>
71
+ );
72
+ };
73
+
74
+ export default HomePage;
75
+ `;
76
+ export const pluginMessagesTemplate = (pluginName) => `${JSON.stringify({
77
+ [pluginName]: {
78
+ home: {
79
+ desc: "This page ships inside the plugin and is served by the app that installed it.",
80
+ title: "Hello from your plugin",
81
+ },
82
+ },
83
+ }, null, 2)}\n`;
84
+ export const pluginMessagesBarrelTemplate = () => `import type { LocaleMessagesMap } from "@vitnode/core/lib/i18n/types";
85
+
86
+ /**
87
+ * Every language this plugin ships. Add a file next to this one and a line here
88
+ * to add another; apps pick it up with no copy step, because they read this
89
+ * package's own \`dist\` rather than a copy of it.
90
+ */
91
+ const messages: LocaleMessagesMap = {
92
+ en: async () => await import("./en.json", { with: { type: "json" } }),
93
+ };
94
+
95
+ export default messages;
96
+ `;
97
+ export const pluginVariableName = (pluginName) => {
98
+ const camel = routeSlugFor(pluginName)
99
+ .split(/[^A-Za-z0-9]+/)
100
+ .filter(Boolean)
101
+ .map((part, index) => index === 0 ? part : `${part[0].toUpperCase()}${part.slice(1)}`)
102
+ .join("");
103
+ const safe = /^[A-Za-z]/.test(camel) ? camel : `vitnode${camel}`;
104
+ const stem = safe.replace(/plugin$/i, "");
105
+ return `${stem === "" ? safe : stem}Plugin`;
106
+ };
107
+ export const pluginConfigTemplate = (pluginName) => `import { buildPlugin } from "@vitnode/core/lib/plugin";
108
+
109
+ import messages from "./locales";
110
+ import { routes } from "./routes";
111
+
112
+ /**
113
+ * This plugin, as an application registers it.
114
+ *
115
+ * \`pluginId\` is the package name, and that is not a convention - it is how this
116
+ * plugin's route tree is imported (\`${pluginName}/routes\`) and how its messages
117
+ * are namespaced. The two cannot drift because they are one string.
118
+ *
119
+ * Add this to an app's \`src/vitnode.config.ts\` \`plugins\` array. A plugin that
120
+ * is installed but not listed there contributes nothing - no directory is ever
121
+ * scanned - so this is the only switch.
122
+ */
123
+ export const ${pluginVariableName(pluginName)} = () =>
124
+ buildPlugin({
125
+ pluginId: "${pluginName}",
126
+ messages,
127
+ routes,
128
+ });
129
+ `;
130
+ export const pluginPackageExports = () => ({
131
+ "./locales/*.json": "./src/locales/*.json",
132
+ "./*": {
133
+ import: "./dist/src/*.js",
134
+ types: "./dist/src/*.d.ts",
135
+ default: "./dist/src/*.js",
136
+ },
137
+ });
138
+ export const pluginRouteScaffold = (pluginName) => ({
139
+ "src/config.tsx": pluginConfigTemplate(pluginName),
140
+ "src/locales/en.json": pluginMessagesTemplate(pluginName),
141
+ "src/locales/index.ts": pluginMessagesBarrelTemplate(),
142
+ "src/pages/home-page.tsx": pluginRouteModuleTemplate(pluginName),
143
+ "src/routes.ts": pluginRoutesTemplate(pluginName),
144
+ });
@@ -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, eslint } = await validationProjectForPlugin(name);
22
+ const options = await createPluginQuestionsCli(program);
23
+ await createPluginVitNode({
24
+ pluginName,
25
+ pluginPath,
26
+ eslint,
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,76 @@
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
+ let eslint = false;
33
+ try {
34
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
35
+ if (!packageJson.packageManager) {
36
+ console.error(`${color.red("Error:")} The ${color.cyan("packageManager")} field is not defined in ${color.cyan("package.json")}.`);
37
+ console.error(`\nPlease add a ${color.cyan('"packageManager"')} field to your ${color.cyan("package.json")} file.`);
38
+ console.error(`Example: ${color.green('"packageManager": "pnpm@10.18.3"')}`);
39
+ process.exit(1);
40
+ }
41
+ eslint = !!(packageJson.dependencies?.eslint ??
42
+ packageJson.devDependencies?.eslint ??
43
+ false);
44
+ }
45
+ catch (error) {
46
+ console.error(`${color.red("Error:")} Failed to read or parse ${color.cyan("package.json")}.`);
47
+ console.error(error);
48
+ process.exit(1);
49
+ }
50
+ const projectName = basename(resolve(projectPath));
51
+ const validation = validateNpmName({ name: projectName });
52
+ if (!validation.valid) {
53
+ console.error(`Could not create a plugin called ${color.red(`"${projectName}"`)} because of npm naming restrictions:`);
54
+ validation.problems.forEach(p => {
55
+ console.error(`${color.red(color.bold("*"))} ${p}`);
56
+ });
57
+ process.exit(1);
58
+ }
59
+ const pluginsDir = join(cwd, "plugins");
60
+ const pluginPath = join(pluginsDir, projectName);
61
+ const pluginName = basename(pluginPath);
62
+ const folderExists = existsSync(pluginPath);
63
+ if (folderExists && !isFolderEmpty(pluginPath, pluginName)) {
64
+ console.error(`The directory ${color.cyan(`plugins/${pluginName}`)} is not empty.`);
65
+ process.exit(1);
66
+ }
67
+ if (!existsSync(pluginsDir)) {
68
+ await mkdir(pluginsDir, { recursive: true });
69
+ }
70
+ if (!(await isWriteable(pluginsDir))) {
71
+ console.error(`The plugins directory is not writable, please check folder permissions and try again.`);
72
+ console.error(`It is likely you do not have write permissions for this folder.`);
73
+ process.exit(1);
74
+ }
75
+ return { pluginName, pluginPath, eslint };
76
+ };
@@ -1,12 +1,15 @@
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');
6
- if (!existsSync(toRootPath)) {
7
- await mkdir(toRootPath);
8
- }
9
- const fromRootPath = join(process.cwd(), '..', '..', 'apps', 'web');
5
+ const toRootPaths = ["copy-of-vitnode-app", "copy-of-vitnode-plugin"];
6
+ await Promise.all(toRootPaths.map(async (path) => {
7
+ const toRootPath = join(process.cwd(), path);
8
+ if (!existsSync(toRootPath)) {
9
+ await mkdir(toRootPath);
10
+ }
11
+ }));
12
+ const fromRootPath = join(process.cwd(), "..", "..", "apps", "web");
10
13
  if (!existsSync(fromRootPath)) {
11
14
  console.error(`\x1b[31mThe path ${fromRootPath} does not exist. Please check the directory structure.\x1b[0m`);
12
15
  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("TanStack Start")} & ${color.blue("Hono.js")}`,
43
+ description: "One app: a TanStack Start frontend with the Hono API mounted inside it at /api.",
44
+ value: "singleApp",
45
+ },
46
+ {
47
+ name: `Monorepo App - ${color.blue("TanStack Start")} & ${color.blue("Hono.js")}`,
48
+ description: "Two apps: a TanStack Start frontend and a Hono API served separately.",
49
+ value: "apiMonorepo",
50
+ },
51
+ {
52
+ name: `Only API - ${color.blue("Hono.js")}`,
53
+ description: "Just the Hono API, with no frontend app.",
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 };