better-ts-stack 1.1.4 → 2.0.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.
- package/LICENSE +21 -0
- package/README.md +113 -82
- package/dist/args.js +90 -0
- package/dist/builder/buildPlanner.js +71 -0
- package/dist/builder/configGenerator.js +19 -10
- package/dist/builder/dependencyInstaller.js +8 -15
- package/dist/builder/fileProcessor.js +47 -44
- package/dist/builder/gitInitializer.js +21 -35
- package/dist/builder/index.js +33 -91
- package/dist/builder/moduleSelector.js +11 -16
- package/dist/builder/templateContext.js +6 -27
- package/dist/index.js +47 -21
- package/dist/modules/registry.js +4 -6
- package/dist/output/nextSteps.js +10 -20
- package/dist/prompts/index.js +22 -59
- package/dist/prompts/stack.js +85 -71
- package/dist/types/index.js +47 -41
- package/dist/ui/index.js +18 -0
- package/dist/ui/messages.js +124 -0
- package/dist/ui/prompts.js +70 -0
- package/dist/ui/theme.js +23 -0
- package/dist/validators/index.js +15 -40
- package/package.json +71 -66
- package/templates/backend/express/.prettierrc +2 -2
- package/templates/backend/express/config.json +40 -44
- package/templates/backend/express/src/{index.ts → index.ts.hbs} +75 -76
- package/templates/backend/nestjs/.prettierignore +52 -0
- package/templates/backend/nestjs/.prettierrc +16 -0
- package/templates/backend/nestjs/config.json +41 -0
- package/templates/backend/nestjs/eslint.config.mjs +40 -0
- package/templates/backend/nestjs/gitignore +36 -0
- package/templates/backend/nestjs/nest-cli.json +9 -0
- package/templates/backend/nestjs/src/app.module.ts.hbs +34 -0
- package/templates/backend/nestjs/src/health/health.controller.ts +12 -0
- package/templates/backend/nestjs/src/main.ts +23 -0
- package/templates/backend/nestjs/tsconfig.eslint.json +9 -0
- package/templates/backend/nestjs/tsconfig.json +25 -0
- package/templates/frontend/nextjs/app/layout.tsx +13 -13
- package/templates/frontend/nextjs/app/page.tsx.hbs +97 -97
- package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
- package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
- package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
- package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
- package/templates/frontend/nextjs/config.json +37 -38
- package/templates/frontend/nextjs/eslint.config.mjs +11 -11
- package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
- package/templates/frontend/tanstack/config.json +45 -48
- package/templates/modules/auth/express/config.json +20 -26
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +43 -57
- package/templates/modules/auth/nestjs/config.json +20 -0
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts +46 -0
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +36 -0
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +22 -0
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +35 -0
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +70 -0
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +27 -0
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +144 -0
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +16 -0
- package/templates/modules/auth/nextjs/config.json +18 -31
- package/templates/modules/auth/tanstack/config.json +2 -16
- package/templates/modules/docker/Dockerfile.hbs +135 -133
- package/templates/modules/docker/config.json +15 -19
- package/templates/modules/drizzle/express/config.json +26 -27
- package/templates/modules/drizzle/nestjs/config.json +26 -0
- package/templates/modules/drizzle/nestjs/drizzle.config.ts +13 -0
- package/templates/modules/drizzle/nestjs/src/database/database.module.ts +30 -0
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +10 -0
- package/templates/modules/drizzle/nextjs/config.json +26 -29
- package/templates/modules/drizzle/tanstack/config.json +5 -5
- package/templates/modules/mongoose/express/config.json +15 -16
- package/templates/modules/mongoose/nestjs/config.json +15 -0
- package/templates/modules/mongoose/nestjs/src/users/user.schema.ts +21 -0
- package/templates/modules/mongoose/nextjs/config.json +15 -16
- package/templates/modules/mongoose/tanstack/config.json +16 -17
- package/templates/modules/prisma/express/config.json +27 -28
- package/templates/modules/prisma/nestjs/config.json +27 -0
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +23 -0
- package/templates/modules/prisma/nestjs/prisma.config.ts +12 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.module.ts +11 -0
- package/templates/modules/prisma/nestjs/src/prisma/prisma.service.ts +17 -0
- package/templates/modules/prisma/nextjs/config.json +28 -29
- package/templates/modules/prisma/tanstack/config.json +5 -6
- package/dist/builder/configGenerator.d.ts +0 -7
- package/dist/builder/configGenerator.d.ts.map +0 -1
- package/dist/builder/configGenerator.js.map +0 -1
- package/dist/builder/dependencyInstaller.d.ts +0 -3
- package/dist/builder/dependencyInstaller.d.ts.map +0 -1
- package/dist/builder/dependencyInstaller.js.map +0 -1
- package/dist/builder/fileProcessor.d.ts +0 -6
- package/dist/builder/fileProcessor.d.ts.map +0 -1
- package/dist/builder/fileProcessor.js.map +0 -1
- package/dist/builder/gitInitializer.d.ts +0 -2
- package/dist/builder/gitInitializer.d.ts.map +0 -1
- package/dist/builder/gitInitializer.js.map +0 -1
- package/dist/builder/index.d.ts +0 -3
- package/dist/builder/index.d.ts.map +0 -1
- package/dist/builder/index.js.map +0 -1
- package/dist/builder/moduleSelector.d.ts +0 -9
- package/dist/builder/moduleSelector.d.ts.map +0 -1
- package/dist/builder/moduleSelector.js.map +0 -1
- package/dist/builder/templateContext.d.ts +0 -21
- package/dist/builder/templateContext.d.ts.map +0 -1
- package/dist/builder/templateContext.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/intro.d.ts +0 -2
- package/dist/intro.d.ts.map +0 -1
- package/dist/intro.js +0 -27
- package/dist/intro.js.map +0 -1
- package/dist/modules/registry.d.ts +0 -6
- package/dist/modules/registry.d.ts.map +0 -1
- package/dist/modules/registry.js.map +0 -1
- package/dist/output/nextSteps.d.ts +0 -4
- package/dist/output/nextSteps.d.ts.map +0 -1
- package/dist/output/nextSteps.js.map +0 -1
- package/dist/prompts/index.d.ts +0 -4
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/stack.d.ts +0 -3
- package/dist/prompts/stack.d.ts.map +0 -1
- package/dist/prompts/stack.js.map +0 -1
- package/dist/types/index.d.ts +0 -159
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/validators/index.d.ts +0 -5
- package/dist/validators/index.d.ts.map +0 -1
- package/dist/validators/index.js.map +0 -1
- package/templates/backend/express/.eslintrc.js +0 -24
package/dist/prompts/stack.js
CHANGED
|
@@ -1,77 +1,91 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ask = ask;
|
|
6
4
|
exports.collectStackChoices = collectStackChoices;
|
|
7
|
-
const
|
|
8
|
-
const consola_1 = __importDefault(require("consola"));
|
|
5
|
+
const core_1 = require("@clack/core");
|
|
9
6
|
const types_1 = require("../types");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
initialValue: "npm",
|
|
52
|
-
}),
|
|
53
|
-
useDocker: () => (0, prompts_1.confirm)({ message: "Use Docker?", initialValue: false }),
|
|
54
|
-
useAuth: async ({ results }) => {
|
|
55
|
-
if (applicationType === "fullstack" && results.databaseType === "none")
|
|
56
|
-
return false;
|
|
57
|
-
return (0, prompts_1.select)({
|
|
58
|
-
message: applicationType === "fullstack"
|
|
59
|
-
? "Add Better Auth?"
|
|
60
|
-
: "Add authentication?",
|
|
61
|
-
options: types_1.authOptions,
|
|
62
|
-
initialValue: false,
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
initGit: () => (0, prompts_1.confirm)({
|
|
66
|
-
message: "Initialize a git repository?",
|
|
67
|
-
initialValue: true,
|
|
68
|
-
}),
|
|
69
|
-
installDeps: () => (0, prompts_1.confirm)({ message: "Install dependencies now?", initialValue: false }),
|
|
70
|
-
}, {
|
|
71
|
-
onCancel: () => {
|
|
72
|
-
(0, prompts_1.cancel)("Operation cancelled.");
|
|
73
|
-
process.exit(0);
|
|
74
|
-
},
|
|
7
|
+
const ui_1 = require("../ui");
|
|
8
|
+
// Returns the preset value when there is one, otherwise asks the user
|
|
9
|
+
async function ask(preset, prompt) {
|
|
10
|
+
if (preset !== undefined)
|
|
11
|
+
return preset;
|
|
12
|
+
// Without a terminal a prompt reads end-of-input and looks like a cancel
|
|
13
|
+
if (!process.stdin.isTTY) {
|
|
14
|
+
throw (0, types_1.buildError)("Cannot ask questions without an interactive terminal. Pass --yes, or give every option as a flag (see --help)", "NOT_INTERACTIVE");
|
|
15
|
+
}
|
|
16
|
+
const answer = await prompt();
|
|
17
|
+
if ((0, core_1.isCancel)(answer)) {
|
|
18
|
+
(0, ui_1.info)("Cancelled. Nothing was created.");
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
return answer;
|
|
22
|
+
}
|
|
23
|
+
async function askFramework() {
|
|
24
|
+
const applicationType = await (0, ui_1.select)({
|
|
25
|
+
message: "Application type",
|
|
26
|
+
options: types_1.applicationTypeOptions,
|
|
27
|
+
initialValue: "backend",
|
|
28
|
+
});
|
|
29
|
+
if ((0, core_1.isCancel)(applicationType))
|
|
30
|
+
return applicationType;
|
|
31
|
+
return applicationType === "backend"
|
|
32
|
+
? (0, ui_1.select)({
|
|
33
|
+
message: "Backend framework",
|
|
34
|
+
options: types_1.backendFrameworkOptions,
|
|
35
|
+
initialValue: "express",
|
|
36
|
+
})
|
|
37
|
+
: (0, ui_1.select)({
|
|
38
|
+
message: "Frontend framework",
|
|
39
|
+
options: types_1.frontendFrameworkOptions,
|
|
40
|
+
initialValue: "nextjs",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function askDatabase() {
|
|
44
|
+
const system = await (0, ui_1.select)({
|
|
45
|
+
message: "Database",
|
|
46
|
+
options: types_1.databaseSystemOptions,
|
|
47
|
+
initialValue: "none",
|
|
75
48
|
});
|
|
49
|
+
if ((0, core_1.isCancel)(system) || system === "none")
|
|
50
|
+
return system;
|
|
51
|
+
const options = system === "mongodb" ? types_1.mongodbOrmOptions : types_1.postgresqlOrmOptions;
|
|
52
|
+
return (0, ui_1.select)({
|
|
53
|
+
message: "ORM",
|
|
54
|
+
options,
|
|
55
|
+
initialValue: options[0].value,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Collects the stack choices, prompting only for those missing from the preset
|
|
59
|
+
async function collectStackChoices(preset) {
|
|
60
|
+
const framework = await ask(preset.framework, askFramework);
|
|
61
|
+
const database = await ask(preset.database, askDatabase);
|
|
62
|
+
const packageManager = await ask(preset.packageManager, () => (0, ui_1.select)({
|
|
63
|
+
message: "Package manager",
|
|
64
|
+
options: types_1.packageManagerOptions,
|
|
65
|
+
initialValue: "npm",
|
|
66
|
+
}));
|
|
67
|
+
const useDocker = await ask(preset.useDocker, () => (0, ui_1.confirm)({ message: "Add Docker?", initialValue: false }));
|
|
68
|
+
// Better Auth stores users and sessions, so full-stack auth needs a database
|
|
69
|
+
const isFullstack = (0, types_1.getApplicationType)(framework) === "fullstack";
|
|
70
|
+
const isAuthAvailable = !isFullstack || database !== "none";
|
|
71
|
+
if (preset.useAuth && !isAuthAvailable) {
|
|
72
|
+
throw (0, types_1.buildError)(`Auth for ${framework} needs a database. Add --database prisma, drizzle, or mongoose`, "INVALID_ARGUMENT");
|
|
73
|
+
}
|
|
74
|
+
const useAuth = isAuthAvailable
|
|
75
|
+
? await ask(preset.useAuth, () => (0, ui_1.confirm)({
|
|
76
|
+
message: isFullstack ? "Add Better Auth?" : "Add JWT authentication?",
|
|
77
|
+
initialValue: false,
|
|
78
|
+
}))
|
|
79
|
+
: false;
|
|
80
|
+
const initGit = await ask(preset.initGit, () => (0, ui_1.confirm)({ message: "Initialize a git repository?", initialValue: true }));
|
|
81
|
+
const installDeps = await ask(preset.installDeps, () => (0, ui_1.confirm)({ message: "Install dependencies now?", initialValue: false }));
|
|
82
|
+
return {
|
|
83
|
+
framework,
|
|
84
|
+
database,
|
|
85
|
+
packageManager,
|
|
86
|
+
useDocker,
|
|
87
|
+
useAuth,
|
|
88
|
+
initGit,
|
|
89
|
+
installDeps,
|
|
90
|
+
};
|
|
76
91
|
}
|
|
77
|
-
//# sourceMappingURL=stack.js.map
|
package/dist/types/index.js
CHANGED
|
@@ -1,77 +1,83 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Core type definitions for the CLI builder
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.
|
|
5
|
-
exports.deriveDatabase = deriveDatabase;
|
|
4
|
+
exports.packageManagerOptions = exports.postgresqlOrmOptions = exports.mongodbOrmOptions = exports.databaseSystemOptions = exports.frontendFrameworkOptions = exports.backendFrameworkOptions = exports.applicationTypeOptions = exports.DEFAULT_PORT = exports.DEFAULT_CHOICES = void 0;
|
|
6
5
|
exports.buildError = buildError;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
exports.isBuildError = isBuildError;
|
|
7
|
+
exports.getApplicationType = getApplicationType;
|
|
8
|
+
// Used for every choice not given as a flag when --yes is passed
|
|
9
|
+
exports.DEFAULT_CHOICES = {
|
|
10
|
+
framework: "express",
|
|
11
|
+
database: "none",
|
|
12
|
+
useDocker: false,
|
|
13
|
+
useAuth: false,
|
|
14
|
+
packageManager: "npm",
|
|
15
|
+
initGit: true,
|
|
16
|
+
installDeps: false,
|
|
17
|
+
};
|
|
18
|
+
// Port the generated server listens on
|
|
19
|
+
exports.DEFAULT_PORT = 3000;
|
|
20
20
|
// Unified error creation function
|
|
21
|
-
function buildError(errorOrMessage, code, context
|
|
21
|
+
function buildError(errorOrMessage, code, context) {
|
|
22
22
|
const message = errorOrMessage instanceof Error
|
|
23
23
|
? errorOrMessage.message
|
|
24
24
|
: String(errorOrMessage);
|
|
25
25
|
const fullMessage = context ? `${context}: ${message}` : message;
|
|
26
26
|
const error = new Error(fullMessage);
|
|
27
|
-
return Object.assign(error, { code
|
|
27
|
+
return Object.assign(error, { code });
|
|
28
|
+
}
|
|
29
|
+
// True for errors created by buildError(), whose message is meant for the user
|
|
30
|
+
function isBuildError(error) {
|
|
31
|
+
return error instanceof Error && "code" in error;
|
|
28
32
|
}
|
|
29
33
|
// Type-safe option definitions
|
|
30
34
|
exports.applicationTypeOptions = [
|
|
31
|
-
{
|
|
32
|
-
|
|
35
|
+
{
|
|
36
|
+
value: "backend",
|
|
37
|
+
label: "Backend API",
|
|
38
|
+
hint: "Express or NestJS",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: "fullstack",
|
|
42
|
+
label: "Full-stack app",
|
|
43
|
+
hint: "Next.js or TanStack Start",
|
|
44
|
+
},
|
|
33
45
|
];
|
|
34
46
|
exports.backendFrameworkOptions = [
|
|
35
47
|
{ value: "express", label: "Express" },
|
|
36
|
-
{ value: "nestjs", label: "NestJS"
|
|
48
|
+
{ value: "nestjs", label: "NestJS" },
|
|
37
49
|
];
|
|
38
50
|
exports.frontendFrameworkOptions = [
|
|
39
|
-
{ value: "nextjs", label: "Next.js 16
|
|
51
|
+
{ value: "nextjs", label: "Next.js 16", hint: "App Router" },
|
|
40
52
|
{
|
|
41
53
|
value: "tanstack",
|
|
42
|
-
label: "TanStack Start
|
|
43
|
-
hint: "
|
|
54
|
+
label: "TanStack Start",
|
|
55
|
+
hint: "React, release candidate",
|
|
44
56
|
},
|
|
45
57
|
];
|
|
46
|
-
// Database
|
|
47
|
-
exports.
|
|
48
|
-
{ value: "none", label: "None
|
|
58
|
+
// Database system options; the ORM is chosen in a second step
|
|
59
|
+
exports.databaseSystemOptions = [
|
|
60
|
+
{ value: "none", label: "None", hint: "skip database setup" },
|
|
49
61
|
{ value: "mongodb", label: "MongoDB" },
|
|
50
62
|
{ value: "postgresql", label: "PostgreSQL" },
|
|
51
63
|
];
|
|
52
64
|
// ORM options for MongoDB
|
|
53
65
|
exports.mongodbOrmOptions = [
|
|
54
|
-
{ value: "mongoose", label: "Mongoose
|
|
66
|
+
{ value: "mongoose", label: "Mongoose", hint: "MongoDB ODM" },
|
|
55
67
|
];
|
|
56
68
|
// ORM options for PostgreSQL
|
|
57
69
|
exports.postgresqlOrmOptions = [
|
|
58
|
-
{ value: "prisma", label: "Prisma
|
|
59
|
-
{ value: "drizzle", label: "Drizzle
|
|
60
|
-
];
|
|
61
|
-
// Legacy database options (for backward compatibility)
|
|
62
|
-
exports.databaseOptions = [
|
|
63
|
-
{ value: "none", label: "None (Skip database setup)" },
|
|
64
|
-
{ value: "prisma", label: "Prisma (Type-safe ORM)" },
|
|
65
|
-
{ value: "mongoose", label: "Mongoose (Standard MongoDB ODM)" },
|
|
66
|
-
{ value: "drizzle", label: "Drizzle (Lightweight ORM)" },
|
|
70
|
+
{ value: "prisma", label: "Prisma", hint: "type-safe ORM" },
|
|
71
|
+
{ value: "drizzle", label: "Drizzle", hint: "lightweight ORM" },
|
|
67
72
|
];
|
|
68
73
|
exports.packageManagerOptions = [
|
|
69
74
|
{ value: "npm", label: "npm" },
|
|
70
75
|
{ value: "pnpm", label: "pnpm" },
|
|
71
76
|
{ value: "bun", label: "bun" },
|
|
72
77
|
];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
// Backend frameworks produce an API server, the others a full-stack app
|
|
79
|
+
function getApplicationType(framework) {
|
|
80
|
+
return exports.backendFrameworkOptions.some((option) => option.value === framework)
|
|
81
|
+
? "backend"
|
|
82
|
+
: "fullstack";
|
|
83
|
+
}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./messages"), exports);
|
|
18
|
+
__exportStar(require("./prompts"), exports);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.error = exports.warn = exports.info = exports.success = void 0;
|
|
7
|
+
exports.showBanner = showBanner;
|
|
8
|
+
exports.showSummary = showSummary;
|
|
9
|
+
exports.showPlan = showPlan;
|
|
10
|
+
exports.showNextSteps = showNextSteps;
|
|
11
|
+
// Everything the CLI prints outside of prompts
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const node_process_1 = require("node:process");
|
|
14
|
+
const types_1 = require("../types");
|
|
15
|
+
const theme_1 = require("./theme");
|
|
16
|
+
// "better-ts-stack" in the figlet Big font
|
|
17
|
+
const WORDMARK = [
|
|
18
|
+
" _ _ _ _ _ _",
|
|
19
|
+
" | | | | | | | | | | | |",
|
|
20
|
+
" | |__ ___| |_| |_ ___ _ __ ______| |_ ___ ______ ___| |_ __ _ ___| | __",
|
|
21
|
+
" | '_ \\ / _ \\ __| __/ _ \\ '__|______| __/ __|______/ __| __/ _` |/ __| |/ /",
|
|
22
|
+
" | |_) | __/ |_| || __/ | | |_\\__ \\ \\__ \\ || (_| | (__| <",
|
|
23
|
+
" |_.__/ \\___|\\__|\\__\\___|_| \\__|___/ |___/\\__\\__,_|\\___|_|\\_\\",
|
|
24
|
+
];
|
|
25
|
+
function write(line = "") {
|
|
26
|
+
process.stdout.write(`${line}\n`);
|
|
27
|
+
}
|
|
28
|
+
const success = (message) => write(`${(0, theme_1.lime)("✓")} ${message}`);
|
|
29
|
+
exports.success = success;
|
|
30
|
+
const info = (message) => write((0, theme_1.dim)(`· ${message}`));
|
|
31
|
+
exports.info = info;
|
|
32
|
+
const warn = (message) => write(`${(0, theme_1.amber)("!")} ${message}`);
|
|
33
|
+
exports.warn = warn;
|
|
34
|
+
const error = (message) => write(`${(0, theme_1.red)("×")} ${message}`);
|
|
35
|
+
exports.error = error;
|
|
36
|
+
const WORDMARK_WIDTH = Math.max(...WORDMARK.map((line) => line.length));
|
|
37
|
+
function greeting() {
|
|
38
|
+
const hour = new Date().getHours();
|
|
39
|
+
if (hour < 12)
|
|
40
|
+
return "Good morning.";
|
|
41
|
+
if (hour < 18)
|
|
42
|
+
return "Good afternoon.";
|
|
43
|
+
return "Good evening.";
|
|
44
|
+
}
|
|
45
|
+
// Opens the session with the wordmark, tagline, and key hints. An interactive
|
|
46
|
+
// session starts on a cleared screen; console.clear() does nothing when piped
|
|
47
|
+
function showBanner(version, isInteractive) {
|
|
48
|
+
if (isInteractive)
|
|
49
|
+
console.clear();
|
|
50
|
+
write();
|
|
51
|
+
if ((process.stdout.columns ?? 80) > WORDMARK_WIDTH) {
|
|
52
|
+
for (const line of WORDMARK)
|
|
53
|
+
write((0, theme_1.lime)(line));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
write((0, theme_1.lime)((0, theme_1.bold)("better-ts-stack")));
|
|
57
|
+
}
|
|
58
|
+
write();
|
|
59
|
+
write((0, theme_1.lavender)((0, theme_1.bold)("TypeScript projects, ready to run.")));
|
|
60
|
+
if (isInteractive) {
|
|
61
|
+
write((0, theme_1.dim)(greeting()));
|
|
62
|
+
write((0, theme_1.lime)((0, theme_1.bold)("Answer a few questions to get started.")));
|
|
63
|
+
write((0, theme_1.dim)(`v${version} · ↑↓ to move · enter to confirm · ctrl+c to cancel`));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
write((0, theme_1.dim)(`v${version}`));
|
|
67
|
+
}
|
|
68
|
+
write();
|
|
69
|
+
}
|
|
70
|
+
// Displays a summary of what will be built
|
|
71
|
+
function showSummary(config, targetDir) {
|
|
72
|
+
const stack = [
|
|
73
|
+
config.framework,
|
|
74
|
+
config.database !== "none" && config.database,
|
|
75
|
+
config.useAuth && "auth",
|
|
76
|
+
config.useDocker && "docker",
|
|
77
|
+
];
|
|
78
|
+
const setup = [
|
|
79
|
+
config.packageManager,
|
|
80
|
+
config.initGit && "git",
|
|
81
|
+
config.installDeps && "install dependencies",
|
|
82
|
+
];
|
|
83
|
+
write(` ${(0, theme_1.amber)((0, theme_1.bold)(config.projectName))}`);
|
|
84
|
+
write(` ${(0, theme_1.pink)("Stack")} ${stack.filter(Boolean).join(" · ")}`);
|
|
85
|
+
write(` ${(0, theme_1.pink)("Setup")} ${setup.filter(Boolean).join(" · ")}`);
|
|
86
|
+
write(` ${(0, theme_1.pink)("Path ")} ${node_path_1.default.relative((0, node_process_1.cwd)(), targetDir) || "."}`);
|
|
87
|
+
write();
|
|
88
|
+
}
|
|
89
|
+
// Prints a titled list, or nothing when the list is empty
|
|
90
|
+
function showSection(title, lines) {
|
|
91
|
+
if (lines.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
write(` ${(0, theme_1.amber)((0, theme_1.bold)(title))} ${(0, theme_1.dim)(`(${lines.length})`)}`);
|
|
94
|
+
for (const line of lines)
|
|
95
|
+
write(` ${line}`);
|
|
96
|
+
write();
|
|
97
|
+
}
|
|
98
|
+
// Formats name/value pairs as two aligned columns
|
|
99
|
+
function toColumns(entries) {
|
|
100
|
+
const width = Math.max(0, ...Object.keys(entries).map((key) => key.length));
|
|
101
|
+
return Object.entries(entries).map(([key, value]) => `${key.padEnd(width)} ${(0, theme_1.dim)(value)}`);
|
|
102
|
+
}
|
|
103
|
+
// Shows everything a build would produce, for --dry-run
|
|
104
|
+
function showPlan(plan) {
|
|
105
|
+
showSection("Files", plan.files);
|
|
106
|
+
showSection("Dependencies", toColumns(plan.dependencies));
|
|
107
|
+
showSection("Dev dependencies", toColumns(plan.devDependencies));
|
|
108
|
+
showSection("Overrides", toColumns(plan.overrides));
|
|
109
|
+
showSection("Scripts", toColumns(plan.scripts));
|
|
110
|
+
showSection("Environment variables", Object.entries(plan.envVars).map(([key, value]) => `${key}=${(0, theme_1.dim)(value)}`));
|
|
111
|
+
showSection("Commands", plan.commands.map(theme_1.lime));
|
|
112
|
+
}
|
|
113
|
+
// Shows the numbered next steps, with the commands to run highlighted
|
|
114
|
+
function showNextSteps(steps) {
|
|
115
|
+
write();
|
|
116
|
+
write(` ${(0, theme_1.amber)((0, theme_1.bold)("Next steps"))}`);
|
|
117
|
+
steps.forEach((step, index) => {
|
|
118
|
+
const isCommand = /^(cd|npm|pnpm|bun) /.test(step);
|
|
119
|
+
write(` ${(0, theme_1.dim)(`${index + 1}.`)} ${isCommand ? (0, theme_1.lime)(step) : step}`);
|
|
120
|
+
});
|
|
121
|
+
write();
|
|
122
|
+
write(` ${(0, theme_1.dim)(`Your app will run at http://localhost:${types_1.DEFAULT_PORT}`)}`);
|
|
123
|
+
write();
|
|
124
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.text = text;
|
|
4
|
+
exports.select = select;
|
|
5
|
+
exports.confirm = confirm;
|
|
6
|
+
// Prompts built on @clack/core, which handles the keyboard while the render
|
|
7
|
+
// functions below draw each state
|
|
8
|
+
const core_1 = require("@clack/core");
|
|
9
|
+
const theme_1 = require("./theme");
|
|
10
|
+
// A prompt waiting for input: "$ Project name"
|
|
11
|
+
const question = (message) => `${(0, theme_1.lime)("$")} ${(0, theme_1.bold)(message)}`;
|
|
12
|
+
// An answered prompt, collapsed to one line: "✓ Project name my-app". The
|
|
13
|
+
// trailing newline leaves a blank line before the next prompt
|
|
14
|
+
const answered = (message, answer) => `${(0, theme_1.lime)("✓")} ${(0, theme_1.dim)(message)} ${answer}\n`;
|
|
15
|
+
const cancelled = (message) => `${(0, theme_1.red)("×")} ${(0, theme_1.dim)(message)}`;
|
|
16
|
+
function text(options) {
|
|
17
|
+
const { message, placeholder } = options;
|
|
18
|
+
return new core_1.TextPrompt({
|
|
19
|
+
validate: options.validate,
|
|
20
|
+
render() {
|
|
21
|
+
if (this.state === "submit")
|
|
22
|
+
return answered(message, this.value ?? "");
|
|
23
|
+
if (this.state === "cancel")
|
|
24
|
+
return cancelled(message);
|
|
25
|
+
// The inverse first character stands in for the cursor on an empty input
|
|
26
|
+
const input = this.userInput
|
|
27
|
+
? this.userInputWithCursor
|
|
28
|
+
: (0, theme_1.inverse)(placeholder[0]) + (0, theme_1.dim)(placeholder.slice(1));
|
|
29
|
+
const error = this.state === "error" ? `\n ${(0, theme_1.red)(this.error)}` : "";
|
|
30
|
+
return `${question(message)}\n ${input}${error}\n`;
|
|
31
|
+
},
|
|
32
|
+
}).prompt();
|
|
33
|
+
}
|
|
34
|
+
function select(options) {
|
|
35
|
+
const { message } = options;
|
|
36
|
+
return new core_1.SelectPrompt({
|
|
37
|
+
options: options.options,
|
|
38
|
+
initialValue: options.initialValue,
|
|
39
|
+
render() {
|
|
40
|
+
if (this.state === "submit")
|
|
41
|
+
return answered(message, this.options[this.cursor].label);
|
|
42
|
+
if (this.state === "cancel")
|
|
43
|
+
return cancelled(message);
|
|
44
|
+
const rows = this.options.map((option, index) => {
|
|
45
|
+
const hint = option.hint ? ` ${(0, theme_1.dim)(option.hint)}` : "";
|
|
46
|
+
return index === this.cursor
|
|
47
|
+
? ` ${(0, theme_1.lime)("›")} ${option.label}${hint}`
|
|
48
|
+
: ` ${(0, theme_1.dim)(option.label)}`;
|
|
49
|
+
});
|
|
50
|
+
return `${question(message)}\n${rows.join("\n")}\n`;
|
|
51
|
+
},
|
|
52
|
+
}).prompt();
|
|
53
|
+
}
|
|
54
|
+
function confirm(options) {
|
|
55
|
+
const { message } = options;
|
|
56
|
+
return new core_1.ConfirmPrompt({
|
|
57
|
+
active: "Yes",
|
|
58
|
+
inactive: "No",
|
|
59
|
+
initialValue: options.initialValue,
|
|
60
|
+
render() {
|
|
61
|
+
if (this.state === "submit")
|
|
62
|
+
return answered(message, this.value ? "Yes" : "No");
|
|
63
|
+
if (this.state === "cancel")
|
|
64
|
+
return cancelled(message);
|
|
65
|
+
// Both choices keep their column; only the marker and color move
|
|
66
|
+
const choice = (label, isActive) => isActive ? (0, theme_1.lime)(`› ${label}`) : (0, theme_1.dim)(` ${label}`);
|
|
67
|
+
return `${question(message)}\n ${choice("Yes", this.value === true)} ${choice("No", this.value === false)}\n`;
|
|
68
|
+
},
|
|
69
|
+
}).prompt();
|
|
70
|
+
}
|
package/dist/ui/theme.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inverse = exports.dim = exports.bold = exports.red = exports.pink = exports.lime = exports.lavender = exports.amber = void 0;
|
|
4
|
+
// Colors and text styles shared by every piece of CLI output
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
// Plain text is used when the output is piped, NO_COLOR is set, or the
|
|
7
|
+
// terminal has fewer than 256 colors
|
|
8
|
+
const hasColors = process.stdout.isTTY && process.stdout.hasColors(256);
|
|
9
|
+
// Wraps text in a color from the 256-color palette
|
|
10
|
+
function color(code) {
|
|
11
|
+
return (text) => hasColors ? `\x1b[38;5;${code}m${text}\x1b[39m` : text;
|
|
12
|
+
}
|
|
13
|
+
exports.amber = color(214); // Brand: wordmark and headings
|
|
14
|
+
exports.lavender = color(183); // Tagline
|
|
15
|
+
exports.lime = color(148); // Prompts, success, and commands to run
|
|
16
|
+
exports.pink = color(211); // Labels
|
|
17
|
+
exports.red = color(203); // Errors
|
|
18
|
+
const bold = (text) => (0, node_util_1.styleText)("bold", text);
|
|
19
|
+
exports.bold = bold;
|
|
20
|
+
const dim = (text) => (0, node_util_1.styleText)("dim", text);
|
|
21
|
+
exports.dim = dim;
|
|
22
|
+
const inverse = (text) => (0, node_util_1.styleText)("inverse", text);
|
|
23
|
+
exports.inverse = inverse;
|
package/dist/validators/index.js
CHANGED
|
@@ -1,43 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.projectNameSchema = void 0;
|
|
37
7
|
exports.validateProjectName = validateProjectName;
|
|
8
|
+
exports.validateProjectPath = validateProjectPath;
|
|
38
9
|
exports.validateDirectoryEmpty = validateDirectoryEmpty;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
41
12
|
const zod_1 = require("zod");
|
|
42
13
|
// Validates project name: lowercase letters, numbers, and hyphens
|
|
43
14
|
exports.projectNameSchema = zod_1.z
|
|
@@ -56,18 +27,22 @@ function validateProjectName(name) {
|
|
|
56
27
|
const firstError = result.error.issues[0];
|
|
57
28
|
return firstError.message;
|
|
58
29
|
}
|
|
30
|
+
// Checks a project path such as "my-app", "apps/api", or "."; the last folder
|
|
31
|
+
// name becomes the package name
|
|
32
|
+
function validateProjectPath(projectPath) {
|
|
33
|
+
return validateProjectName(node_path_1.default.basename(node_path_1.default.resolve(projectPath)));
|
|
34
|
+
}
|
|
59
35
|
// Ensures a directory is empty or does not exist before starting build
|
|
60
36
|
async function validateDirectoryEmpty(dirPath) {
|
|
61
|
-
const exists = await
|
|
37
|
+
const exists = await fs_extra_1.default.pathExists(dirPath);
|
|
62
38
|
if (!exists)
|
|
63
39
|
return null;
|
|
64
|
-
const stats = await
|
|
40
|
+
const stats = await fs_extra_1.default.stat(dirPath);
|
|
65
41
|
if (!stats.isDirectory()) {
|
|
66
42
|
return `Path "${dirPath}" exists but is not a directory`;
|
|
67
43
|
}
|
|
68
|
-
const files = await
|
|
44
|
+
const files = await fs_extra_1.default.readdir(dirPath);
|
|
69
45
|
if (files.length === 0)
|
|
70
46
|
return null;
|
|
71
|
-
return `Directory "${
|
|
47
|
+
return `Directory "${node_path_1.default.basename(dirPath)}" already exists and is not empty`;
|
|
72
48
|
}
|
|
73
|
-
//# sourceMappingURL=index.js.map
|