create-better-t-stack 3.7.3-canary.8e47571f → 3.7.3-canary.8e4d5716
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/package.json +19 -23
- package/src/cli.ts +3 -0
- package/src/constants.ts +188 -0
- package/src/helpers/addons/addons-setup.ts +226 -0
- package/src/helpers/addons/examples-setup.ts +104 -0
- package/src/helpers/addons/fumadocs-setup.ts +103 -0
- package/src/helpers/addons/ruler-setup.ts +139 -0
- package/src/helpers/addons/starlight-setup.ts +51 -0
- package/src/helpers/addons/tauri-setup.ts +96 -0
- package/src/helpers/addons/ultracite-setup.ts +232 -0
- package/src/helpers/addons/vite-pwa-setup.ts +59 -0
- package/src/helpers/core/add-addons.ts +85 -0
- package/src/helpers/core/add-deployment.ts +102 -0
- package/src/helpers/core/api-setup.ts +280 -0
- package/src/helpers/core/auth-setup.ts +203 -0
- package/src/helpers/core/backend-setup.ts +73 -0
- package/src/helpers/core/command-handlers.ts +354 -0
- package/src/helpers/core/convex-codegen.ts +14 -0
- package/src/helpers/core/create-project.ts +133 -0
- package/src/helpers/core/create-readme.ts +687 -0
- package/src/helpers/core/db-setup.ts +184 -0
- package/src/helpers/core/detect-project-config.ts +41 -0
- package/src/helpers/core/env-setup.ts +449 -0
- package/src/helpers/core/git.ts +31 -0
- package/src/helpers/core/install-dependencies.ts +32 -0
- package/src/helpers/core/payments-setup.ts +48 -0
- package/src/helpers/core/post-installation.ts +383 -0
- package/src/helpers/core/project-config.ts +246 -0
- package/src/helpers/core/runtime-setup.ts +76 -0
- package/src/helpers/core/template-manager.ts +917 -0
- package/src/helpers/core/workspace-setup.ts +184 -0
- package/src/helpers/database-providers/d1-setup.ts +28 -0
- package/src/helpers/database-providers/docker-compose-setup.ts +50 -0
- package/src/helpers/database-providers/mongodb-atlas-setup.ts +186 -0
- package/src/helpers/database-providers/neon-setup.ts +243 -0
- package/src/helpers/database-providers/planetscale-setup.ts +78 -0
- package/src/helpers/database-providers/prisma-postgres-setup.ts +196 -0
- package/src/helpers/database-providers/supabase-setup.ts +218 -0
- package/src/helpers/database-providers/turso-setup.ts +309 -0
- package/src/helpers/deployment/alchemy/alchemy-combined-setup.ts +80 -0
- package/src/helpers/deployment/alchemy/alchemy-next-setup.ts +51 -0
- package/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts +104 -0
- package/src/helpers/deployment/alchemy/alchemy-react-router-setup.ts +32 -0
- package/src/helpers/deployment/alchemy/alchemy-solid-setup.ts +32 -0
- package/src/helpers/deployment/alchemy/alchemy-svelte-setup.ts +98 -0
- package/src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts +33 -0
- package/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts +98 -0
- package/src/helpers/deployment/alchemy/env-dts-setup.ts +76 -0
- package/src/helpers/deployment/alchemy/index.ts +7 -0
- package/src/helpers/deployment/server-deploy-setup.ts +55 -0
- package/src/helpers/deployment/web-deploy-setup.ts +58 -0
- package/src/index.ts +253 -0
- package/src/prompts/addons.ts +178 -0
- package/src/prompts/api.ts +49 -0
- package/src/prompts/auth.ts +84 -0
- package/src/prompts/backend.ts +83 -0
- package/src/prompts/config-prompts.ts +138 -0
- package/src/prompts/database-setup.ts +112 -0
- package/src/prompts/database.ts +57 -0
- package/src/prompts/examples.ts +64 -0
- package/src/prompts/frontend.ts +118 -0
- package/src/prompts/git.ts +16 -0
- package/src/prompts/install.ts +16 -0
- package/src/prompts/orm.ts +53 -0
- package/src/prompts/package-manager.ts +32 -0
- package/src/prompts/payments.ts +50 -0
- package/src/prompts/project-name.ts +86 -0
- package/src/prompts/runtime.ts +47 -0
- package/src/prompts/server-deploy.ts +91 -0
- package/src/prompts/web-deploy.ts +107 -0
- package/src/types.ts +2 -0
- package/src/utils/add-package-deps.ts +57 -0
- package/src/utils/analytics.ts +39 -0
- package/src/utils/better-auth-plugin-setup.ts +71 -0
- package/src/utils/biome-formatter.ts +82 -0
- package/src/utils/bts-config.ts +122 -0
- package/src/utils/command-exists.ts +16 -0
- package/src/utils/compatibility-rules.ts +319 -0
- package/src/utils/compatibility.ts +11 -0
- package/src/utils/config-processing.ts +130 -0
- package/src/utils/config-validation.ts +470 -0
- package/src/utils/display-config.ts +96 -0
- package/src/utils/docker-utils.ts +70 -0
- package/src/utils/errors.ts +32 -0
- package/src/utils/generate-reproducible-command.ts +53 -0
- package/src/utils/get-latest-cli-version.ts +11 -0
- package/src/utils/get-package-manager.ts +13 -0
- package/src/utils/open-url.ts +25 -0
- package/src/utils/package-runner.ts +23 -0
- package/src/utils/project-directory.ts +102 -0
- package/src/utils/project-name-validation.ts +43 -0
- package/src/utils/render-title.ts +48 -0
- package/src/utils/setup-catalogs.ts +192 -0
- package/src/utils/sponsors.ts +101 -0
- package/src/utils/telemetry.ts +19 -0
- package/src/utils/template-processor.ts +64 -0
- package/src/utils/templates.ts +94 -0
- package/src/utils/ts-morph.ts +26 -0
- package/src/validation.ts +117 -0
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -8
- package/dist/index.d.mts +0 -347
- package/dist/index.mjs +0 -4
- package/dist/src-CxVxLS85.mjs +0 -7077
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isCancel, multiselect } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { API, Backend, Database, Examples, Frontend } from "../types";
|
|
4
|
+
import { isExampleAIAllowed, isExampleTodoAllowed } from "../utils/compatibility-rules";
|
|
5
|
+
import { exitCancelled } from "../utils/errors";
|
|
6
|
+
|
|
7
|
+
export async function getExamplesChoice(
|
|
8
|
+
examples?: Examples[],
|
|
9
|
+
database?: Database,
|
|
10
|
+
frontends?: Frontend[],
|
|
11
|
+
backend?: Backend,
|
|
12
|
+
api?: API,
|
|
13
|
+
) {
|
|
14
|
+
if (examples !== undefined) return examples;
|
|
15
|
+
|
|
16
|
+
if (api === "none") {
|
|
17
|
+
if (backend === "convex") {
|
|
18
|
+
return ["todo"];
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (backend === "convex") {
|
|
24
|
+
return ["todo"];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (backend === "none") {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (database === "none") return [];
|
|
32
|
+
|
|
33
|
+
let response: Examples[] | symbol = [];
|
|
34
|
+
const options: { value: Examples; label: string; hint: string }[] = [];
|
|
35
|
+
|
|
36
|
+
if (isExampleTodoAllowed(backend, database)) {
|
|
37
|
+
options.push({
|
|
38
|
+
value: "todo" as const,
|
|
39
|
+
label: "Todo App",
|
|
40
|
+
hint: "A simple CRUD example app",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isExampleAIAllowed(backend, frontends ?? [])) {
|
|
45
|
+
options.push({
|
|
46
|
+
value: "ai" as const,
|
|
47
|
+
label: "AI Chat",
|
|
48
|
+
hint: "A simple AI chat interface using AI SDK",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.length === 0) return [];
|
|
53
|
+
|
|
54
|
+
response = await multiselect<Examples>({
|
|
55
|
+
message: "Include examples",
|
|
56
|
+
options: options,
|
|
57
|
+
required: false,
|
|
58
|
+
initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex)),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
62
|
+
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { isCancel, multiselect, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Backend, Frontend } from "../types";
|
|
4
|
+
import { isFrontendAllowedWithBackend } from "../utils/compatibility-rules";
|
|
5
|
+
import { exitCancelled } from "../utils/errors";
|
|
6
|
+
|
|
7
|
+
export async function getFrontendChoice(
|
|
8
|
+
frontendOptions?: Frontend[],
|
|
9
|
+
backend?: Backend,
|
|
10
|
+
auth?: string,
|
|
11
|
+
) {
|
|
12
|
+
if (frontendOptions !== undefined) return frontendOptions;
|
|
13
|
+
|
|
14
|
+
const frontendTypes = await multiselect({
|
|
15
|
+
message: "Select project type",
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
value: "web",
|
|
19
|
+
label: "Web",
|
|
20
|
+
hint: "React, Vue or Svelte Web Application",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "native",
|
|
24
|
+
label: "Native",
|
|
25
|
+
hint: "Create a React Native/Expo app",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
required: false,
|
|
29
|
+
initialValues: ["web"],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
33
|
+
|
|
34
|
+
const result: Frontend[] = [];
|
|
35
|
+
|
|
36
|
+
if (frontendTypes.includes("web")) {
|
|
37
|
+
const allWebOptions = [
|
|
38
|
+
{
|
|
39
|
+
value: "tanstack-router" as const,
|
|
40
|
+
label: "TanStack Router",
|
|
41
|
+
hint: "Modern and scalable routing for React Applications",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
value: "react-router" as const,
|
|
45
|
+
label: "React Router",
|
|
46
|
+
hint: "A user‑obsessed, standards‑focused, multi‑strategy router",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: "next" as const,
|
|
50
|
+
label: "Next.js",
|
|
51
|
+
hint: "The React Framework for the Web",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: "nuxt" as const,
|
|
55
|
+
label: "Nuxt",
|
|
56
|
+
hint: "The Progressive Web Framework for Vue.js",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: "svelte" as const,
|
|
60
|
+
label: "Svelte",
|
|
61
|
+
hint: "web development for the rest of us",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: "solid" as const,
|
|
65
|
+
label: "Solid",
|
|
66
|
+
hint: "Simple and performant reactivity for building user interfaces",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: "tanstack-start" as const,
|
|
70
|
+
label: "TanStack Start",
|
|
71
|
+
hint: "SSR, Server Functions, API Routes and more with TanStack Router",
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const webOptions = allWebOptions.filter((option) =>
|
|
76
|
+
isFrontendAllowedWithBackend(option.value, backend, auth),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const webFramework = await select<Frontend>({
|
|
80
|
+
message: "Choose web",
|
|
81
|
+
options: webOptions,
|
|
82
|
+
initialValue: DEFAULT_CONFIG.frontend[0],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (isCancel(webFramework)) return exitCancelled("Operation cancelled");
|
|
86
|
+
|
|
87
|
+
result.push(webFramework);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (frontendTypes.includes("native")) {
|
|
91
|
+
const nativeFramework = await select<Frontend>({
|
|
92
|
+
message: "Choose native",
|
|
93
|
+
options: [
|
|
94
|
+
{
|
|
95
|
+
value: "native-bare" as const,
|
|
96
|
+
label: "Bare",
|
|
97
|
+
hint: "Bare Expo without styling library",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
value: "native-uniwind" as const,
|
|
101
|
+
label: "Uniwind",
|
|
102
|
+
hint: "Fastest Tailwind bindings for React Native with HeroUI Native",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
value: "native-unistyles" as const,
|
|
106
|
+
label: "Unistyles",
|
|
107
|
+
hint: "Consistent styling for React Native",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
initialValue: "native-bare",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (isCancel(nativeFramework)) return exitCancelled("Operation cancelled");
|
|
114
|
+
result.push(nativeFramework);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import { exitCancelled } from "../utils/errors";
|
|
4
|
+
|
|
5
|
+
export async function getGitChoice(git?: boolean) {
|
|
6
|
+
if (git !== undefined) return git;
|
|
7
|
+
|
|
8
|
+
const response = await confirm({
|
|
9
|
+
message: "Initialize git repository?",
|
|
10
|
+
initialValue: DEFAULT_CONFIG.git,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
14
|
+
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import { exitCancelled } from "../utils/errors";
|
|
4
|
+
|
|
5
|
+
export async function getinstallChoice(install?: boolean) {
|
|
6
|
+
if (install !== undefined) return install;
|
|
7
|
+
|
|
8
|
+
const response = await confirm({
|
|
9
|
+
message: "Install dependencies?",
|
|
10
|
+
initialValue: DEFAULT_CONFIG.install,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
14
|
+
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Backend, Database, ORM, Runtime } from "../types";
|
|
4
|
+
import { exitCancelled } from "../utils/errors";
|
|
5
|
+
|
|
6
|
+
const ormOptions = {
|
|
7
|
+
prisma: {
|
|
8
|
+
value: "prisma" as const,
|
|
9
|
+
label: "Prisma",
|
|
10
|
+
hint: "Powerful, feature-rich ORM",
|
|
11
|
+
},
|
|
12
|
+
mongoose: {
|
|
13
|
+
value: "mongoose" as const,
|
|
14
|
+
label: "Mongoose",
|
|
15
|
+
hint: "Elegant object modeling tool",
|
|
16
|
+
},
|
|
17
|
+
drizzle: {
|
|
18
|
+
value: "drizzle" as const,
|
|
19
|
+
label: "Drizzle",
|
|
20
|
+
hint: "Lightweight and performant TypeScript ORM",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export async function getORMChoice(
|
|
25
|
+
orm: ORM | undefined,
|
|
26
|
+
hasDatabase: boolean,
|
|
27
|
+
database?: Database,
|
|
28
|
+
backend?: Backend,
|
|
29
|
+
runtime?: Runtime,
|
|
30
|
+
) {
|
|
31
|
+
if (backend === "convex") {
|
|
32
|
+
return "none";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!hasDatabase) return "none";
|
|
36
|
+
if (orm !== undefined) return orm;
|
|
37
|
+
|
|
38
|
+
const options =
|
|
39
|
+
database === "mongodb"
|
|
40
|
+
? [ormOptions.prisma, ormOptions.mongoose]
|
|
41
|
+
: [ormOptions.drizzle, ormOptions.prisma];
|
|
42
|
+
|
|
43
|
+
const response = await select<ORM>({
|
|
44
|
+
message: "Select ORM",
|
|
45
|
+
options,
|
|
46
|
+
initialValue:
|
|
47
|
+
database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
51
|
+
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import type { PackageManager } from "../types";
|
|
3
|
+
import { exitCancelled } from "../utils/errors";
|
|
4
|
+
import { getUserPkgManager } from "../utils/get-package-manager";
|
|
5
|
+
|
|
6
|
+
export async function getPackageManagerChoice(packageManager?: PackageManager) {
|
|
7
|
+
if (packageManager !== undefined) return packageManager;
|
|
8
|
+
|
|
9
|
+
const detectedPackageManager = getUserPkgManager();
|
|
10
|
+
|
|
11
|
+
const response = await select<PackageManager>({
|
|
12
|
+
message: "Choose package manager",
|
|
13
|
+
options: [
|
|
14
|
+
{ value: "npm", label: "npm", hint: "Node Package Manager" },
|
|
15
|
+
{
|
|
16
|
+
value: "pnpm",
|
|
17
|
+
label: "pnpm",
|
|
18
|
+
hint: "Fast, disk space efficient package manager",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "bun",
|
|
22
|
+
label: "bun",
|
|
23
|
+
hint: "All-in-one JavaScript runtime & toolkit",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
initialValue: detectedPackageManager,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
30
|
+
|
|
31
|
+
return response;
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Auth, Backend, Frontend, Payments } from "../types";
|
|
4
|
+
import { splitFrontends } from "../utils/compatibility-rules";
|
|
5
|
+
import { exitCancelled } from "../utils/errors";
|
|
6
|
+
|
|
7
|
+
export async function getPaymentsChoice(
|
|
8
|
+
payments?: Payments,
|
|
9
|
+
auth?: Auth,
|
|
10
|
+
backend?: Backend,
|
|
11
|
+
frontends?: Frontend[],
|
|
12
|
+
) {
|
|
13
|
+
if (payments !== undefined) return payments;
|
|
14
|
+
|
|
15
|
+
if (backend === "none") {
|
|
16
|
+
return "none" as Payments;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isPolarCompatible =
|
|
20
|
+
auth === "better-auth" &&
|
|
21
|
+
backend !== "convex" &&
|
|
22
|
+
(frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
|
|
23
|
+
|
|
24
|
+
if (!isPolarCompatible) {
|
|
25
|
+
return "none" as Payments;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const options = [
|
|
29
|
+
{
|
|
30
|
+
value: "polar" as Payments,
|
|
31
|
+
label: "Polar",
|
|
32
|
+
hint: "Turn your software into a business. 6 lines of code.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: "none" as Payments,
|
|
36
|
+
label: "None",
|
|
37
|
+
hint: "No payments integration",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const response = await select<Payments>({
|
|
42
|
+
message: "Select payments provider",
|
|
43
|
+
options,
|
|
44
|
+
initialValue: DEFAULT_CONFIG.payments,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
48
|
+
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isCancel, text } from "@clack/prompts";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
7
|
+
import { ProjectNameSchema } from "../types";
|
|
8
|
+
import { exitCancelled } from "../utils/errors";
|
|
9
|
+
|
|
10
|
+
function isPathWithinCwd(targetPath: string) {
|
|
11
|
+
const resolved = path.resolve(targetPath);
|
|
12
|
+
const rel = path.relative(process.cwd(), resolved);
|
|
13
|
+
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function validateDirectoryName(name: string) {
|
|
17
|
+
if (name === ".") return undefined;
|
|
18
|
+
|
|
19
|
+
const result = ProjectNameSchema.safeParse(name);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
return result.error.issues[0]?.message || "Invalid project name";
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getProjectName(initialName?: string) {
|
|
27
|
+
if (initialName) {
|
|
28
|
+
if (initialName === ".") {
|
|
29
|
+
return initialName;
|
|
30
|
+
}
|
|
31
|
+
const finalDirName = path.basename(initialName);
|
|
32
|
+
const validationError = validateDirectoryName(finalDirName);
|
|
33
|
+
if (!validationError) {
|
|
34
|
+
const projectDir = path.resolve(process.cwd(), initialName);
|
|
35
|
+
if (isPathWithinCwd(projectDir)) {
|
|
36
|
+
return initialName;
|
|
37
|
+
}
|
|
38
|
+
consola.error(pc.red("Project path must be within current directory"));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let isValid = false;
|
|
43
|
+
let projectPath = "";
|
|
44
|
+
let defaultName: string = DEFAULT_CONFIG.projectName;
|
|
45
|
+
let counter = 1;
|
|
46
|
+
|
|
47
|
+
while (
|
|
48
|
+
(await fs.pathExists(path.resolve(process.cwd(), defaultName))) &&
|
|
49
|
+
(await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0
|
|
50
|
+
) {
|
|
51
|
+
defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
|
|
52
|
+
counter++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
while (!isValid) {
|
|
56
|
+
const response = await text({
|
|
57
|
+
message: "Enter your project name or path (relative to current directory)",
|
|
58
|
+
placeholder: defaultName,
|
|
59
|
+
initialValue: initialName,
|
|
60
|
+
defaultValue: defaultName,
|
|
61
|
+
validate: (value) => {
|
|
62
|
+
const nameToUse = String(value ?? "").trim() || defaultName;
|
|
63
|
+
|
|
64
|
+
const finalDirName = path.basename(nameToUse);
|
|
65
|
+
const validationError = validateDirectoryName(finalDirName);
|
|
66
|
+
if (validationError) return validationError;
|
|
67
|
+
|
|
68
|
+
if (nameToUse !== ".") {
|
|
69
|
+
const projectDir = path.resolve(process.cwd(), nameToUse);
|
|
70
|
+
if (!isPathWithinCwd(projectDir)) {
|
|
71
|
+
return "Project path must be within current directory";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return undefined;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled.");
|
|
80
|
+
|
|
81
|
+
projectPath = response || defaultName;
|
|
82
|
+
isValid = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return projectPath;
|
|
86
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Backend, Runtime } from "../types";
|
|
4
|
+
import { exitCancelled } from "../utils/errors";
|
|
5
|
+
|
|
6
|
+
export async function getRuntimeChoice(runtime?: Runtime, backend?: Backend) {
|
|
7
|
+
if (backend === "convex" || backend === "none" || backend === "self") {
|
|
8
|
+
return "none";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (runtime !== undefined) return runtime;
|
|
12
|
+
|
|
13
|
+
const runtimeOptions: Array<{
|
|
14
|
+
value: Runtime;
|
|
15
|
+
label: string;
|
|
16
|
+
hint: string;
|
|
17
|
+
}> = [
|
|
18
|
+
{
|
|
19
|
+
value: "bun",
|
|
20
|
+
label: "Bun",
|
|
21
|
+
hint: "Fast all-in-one JavaScript runtime",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: "node",
|
|
25
|
+
label: "Node.js",
|
|
26
|
+
hint: "Traditional Node.js runtime",
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (backend === "hono") {
|
|
31
|
+
runtimeOptions.push({
|
|
32
|
+
value: "workers",
|
|
33
|
+
label: "Cloudflare Workers",
|
|
34
|
+
hint: "Edge runtime on Cloudflare's global network",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const response = await select<Runtime>({
|
|
39
|
+
message: "Select runtime",
|
|
40
|
+
options: runtimeOptions,
|
|
41
|
+
initialValue: DEFAULT_CONFIG.runtime,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
45
|
+
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Backend, Runtime, ServerDeploy, WebDeploy } from "../types";
|
|
4
|
+
import { exitCancelled } from "../utils/errors";
|
|
5
|
+
|
|
6
|
+
type DeploymentOption = {
|
|
7
|
+
value: ServerDeploy;
|
|
8
|
+
label: string;
|
|
9
|
+
hint: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function getDeploymentDisplay(deployment: ServerDeploy): {
|
|
13
|
+
label: string;
|
|
14
|
+
hint: string;
|
|
15
|
+
} {
|
|
16
|
+
if (deployment === "alchemy") {
|
|
17
|
+
return {
|
|
18
|
+
label: "Alchemy",
|
|
19
|
+
hint: "Deploy to Cloudflare Workers using Alchemy",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
label: deployment,
|
|
24
|
+
hint: `Add ${deployment} deployment`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function getServerDeploymentChoice(
|
|
29
|
+
deployment?: ServerDeploy,
|
|
30
|
+
runtime?: Runtime,
|
|
31
|
+
backend?: Backend,
|
|
32
|
+
_webDeploy?: WebDeploy,
|
|
33
|
+
) {
|
|
34
|
+
if (deployment !== undefined) return deployment;
|
|
35
|
+
|
|
36
|
+
if (backend === "none" || backend === "convex") {
|
|
37
|
+
return "none";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (backend !== "hono") {
|
|
41
|
+
return "none";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Auto-select alchemy for workers runtime since it's the only valid option
|
|
45
|
+
if (runtime === "workers") {
|
|
46
|
+
return "alchemy";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return "none";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function getServerDeploymentToAdd(
|
|
53
|
+
runtime?: Runtime,
|
|
54
|
+
existingDeployment?: ServerDeploy,
|
|
55
|
+
backend?: Backend,
|
|
56
|
+
) {
|
|
57
|
+
if (backend !== "hono") {
|
|
58
|
+
return "none";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const options: DeploymentOption[] = [];
|
|
62
|
+
|
|
63
|
+
if (runtime === "workers") {
|
|
64
|
+
if (existingDeployment !== "alchemy") {
|
|
65
|
+
const { label, hint } = getDeploymentDisplay("alchemy");
|
|
66
|
+
options.push({
|
|
67
|
+
value: "alchemy",
|
|
68
|
+
label,
|
|
69
|
+
hint,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (existingDeployment && existingDeployment !== "none") {
|
|
75
|
+
return "none";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.length === 0) {
|
|
79
|
+
return "none";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const response = await select<ServerDeploy>({
|
|
83
|
+
message: "Select server deployment",
|
|
84
|
+
options,
|
|
85
|
+
initialValue: DEFAULT_CONFIG.serverDeploy,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
89
|
+
|
|
90
|
+
return response;
|
|
91
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isCancel, select } from "@clack/prompts";
|
|
2
|
+
import { DEFAULT_CONFIG } from "../constants";
|
|
3
|
+
import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
|
|
4
|
+
import { WEB_FRAMEWORKS } from "../utils/compatibility";
|
|
5
|
+
import { exitCancelled } from "../utils/errors";
|
|
6
|
+
|
|
7
|
+
function hasWebFrontend(frontends: Frontend[]) {
|
|
8
|
+
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type DeploymentOption = {
|
|
12
|
+
value: WebDeploy;
|
|
13
|
+
label: string;
|
|
14
|
+
hint: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function getDeploymentDisplay(deployment: WebDeploy): {
|
|
18
|
+
label: string;
|
|
19
|
+
hint: string;
|
|
20
|
+
} {
|
|
21
|
+
if (deployment === "alchemy") {
|
|
22
|
+
return {
|
|
23
|
+
label: "Alchemy",
|
|
24
|
+
hint: "Deploy to Cloudflare Workers using Alchemy",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
label: deployment,
|
|
29
|
+
hint: `Add ${deployment} deployment`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getDeploymentChoice(
|
|
34
|
+
deployment?: WebDeploy,
|
|
35
|
+
_runtime?: Runtime,
|
|
36
|
+
_backend?: Backend,
|
|
37
|
+
frontend: Frontend[] = [],
|
|
38
|
+
) {
|
|
39
|
+
if (deployment !== undefined) return deployment;
|
|
40
|
+
if (!hasWebFrontend(frontend)) {
|
|
41
|
+
return "none";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const availableDeployments = ["alchemy", "none"];
|
|
45
|
+
|
|
46
|
+
const options: DeploymentOption[] = availableDeployments.map((deploy) => {
|
|
47
|
+
const { label, hint } = getDeploymentDisplay(deploy as WebDeploy);
|
|
48
|
+
return {
|
|
49
|
+
value: deploy as WebDeploy,
|
|
50
|
+
label,
|
|
51
|
+
hint,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const response = await select<WebDeploy>({
|
|
56
|
+
message: "Select web deployment",
|
|
57
|
+
options,
|
|
58
|
+
initialValue: DEFAULT_CONFIG.webDeploy,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
62
|
+
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function getDeploymentToAdd(frontend: Frontend[], existingDeployment?: WebDeploy) {
|
|
67
|
+
if (!hasWebFrontend(frontend)) {
|
|
68
|
+
return "none";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const options: DeploymentOption[] = [];
|
|
72
|
+
|
|
73
|
+
if (existingDeployment !== "alchemy") {
|
|
74
|
+
const { label, hint } = getDeploymentDisplay("alchemy");
|
|
75
|
+
options.push({
|
|
76
|
+
value: "alchemy",
|
|
77
|
+
label,
|
|
78
|
+
hint,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (existingDeployment && existingDeployment !== "none") {
|
|
83
|
+
return "none";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (options.length > 0) {
|
|
87
|
+
options.push({
|
|
88
|
+
value: "none",
|
|
89
|
+
label: "None",
|
|
90
|
+
hint: "Skip deployment setup",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.length === 0) {
|
|
95
|
+
return "none";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const response = await select<WebDeploy>({
|
|
99
|
+
message: "Select web deployment",
|
|
100
|
+
options,
|
|
101
|
+
initialValue: DEFAULT_CONFIG.webDeploy,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
105
|
+
|
|
106
|
+
return response;
|
|
107
|
+
}
|
package/src/types.ts
ADDED