stackkit-cli 0.4.2 → 0.4.4
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/README.md +17 -10
- package/bin/stackkit.js +10 -1
- package/dist/commands/add.js +164 -25
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +34 -29
- package/dist/commands/list.js +12 -12
- package/dist/index.js +25 -23
- package/dist/types/index.d.ts +28 -16
- package/dist/utils/code-inject.d.ts +1 -1
- package/dist/utils/code-inject.js +6 -6
- package/dist/utils/detect.d.ts +1 -1
- package/dist/utils/detect.js +48 -44
- package/dist/utils/env-editor.js +20 -20
- package/dist/utils/files.js +4 -4
- package/dist/utils/json-editor.d.ts +3 -3
- package/dist/utils/json-editor.js +10 -14
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +8 -8
- package/dist/utils/package-manager.d.ts +2 -2
- package/dist/utils/package-manager.js +33 -26
- package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
- package/modules/auth/better-auth/files/lib/auth.ts +13 -0
- package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +63 -0
- package/modules/auth/better-auth/module.json +54 -0
- package/modules/auth/{clerk-express/files/lib → clerk/files/express}/auth.ts +1 -1
- package/modules/auth/{clerk-nextjs/files/lib → clerk/files/nextjs}/auth-provider.tsx +1 -1
- package/modules/auth/clerk/files/nextjs/middleware.ts +9 -0
- package/modules/auth/{clerk-react/files/lib → clerk/files/react}/auth-provider.tsx +2 -2
- package/modules/auth/clerk/module.json +115 -0
- package/modules/database/mongoose-mongodb/files/lib/db.ts +45 -7
- package/modules/database/mongoose-mongodb/files/models/User.ts +39 -0
- package/modules/database/mongoose-mongodb/module.json +59 -7
- package/modules/database/prisma/files/lib/prisma.ts +6 -0
- package/modules/database/prisma/files/prisma/schema.prisma +8 -0
- package/modules/database/prisma/files/prisma.config.ts +12 -0
- package/modules/database/prisma/module.json +140 -0
- package/package.json +1 -1
- package/templates/express/.env.example +3 -0
- package/templates/express/eslint.config.cjs +42 -0
- package/templates/express/package.json +33 -0
- package/templates/express/src/app.ts +51 -0
- package/templates/express/src/config/env.ts +12 -0
- package/templates/express/src/features/auth/auth.controller.ts +48 -0
- package/templates/express/src/features/auth/auth.route.ts +10 -0
- package/templates/express/src/features/auth/auth.service.ts +21 -0
- package/templates/express/src/middlewares/error.middleware.ts +18 -0
- package/templates/{bases/express-base → express}/src/server.ts +3 -3
- package/templates/express/template.json +40 -0
- package/templates/express/tsconfig.json +30 -0
- package/templates/{bases/nextjs-base → nextjs}/app/layout.tsx +1 -5
- package/templates/nextjs/app/page.tsx +57 -0
- package/templates/{bases/nextjs-base → nextjs}/package.json +2 -1
- package/templates/{bases/nextjs-base → nextjs}/template.json +13 -1
- package/templates/react-vite/.env.example +2 -0
- package/templates/react-vite/README.md +85 -0
- package/templates/react-vite/eslint.config.js +23 -0
- package/templates/{bases/react-vite-base → react-vite}/index.html +1 -0
- package/templates/{bases/react-vite-base → react-vite}/package.json +16 -2
- package/templates/react-vite/src/api/client.ts +47 -0
- package/templates/react-vite/src/api/services/user.service.ts +18 -0
- package/templates/react-vite/src/components/ErrorBoundary.tsx +51 -0
- package/templates/react-vite/src/components/Layout.tsx +13 -0
- package/templates/react-vite/src/components/Loading.tsx +8 -0
- package/templates/react-vite/src/components/SEO.tsx +49 -0
- package/templates/react-vite/src/config/constants.ts +5 -0
- package/templates/react-vite/src/hooks/index.ts +64 -0
- package/templates/react-vite/src/index.css +1 -0
- package/templates/react-vite/src/lib/queryClient.ts +12 -0
- package/templates/react-vite/src/main.tsx +22 -0
- package/templates/react-vite/src/pages/About.tsx +78 -0
- package/templates/react-vite/src/pages/Home.tsx +49 -0
- package/templates/react-vite/src/pages/NotFound.tsx +24 -0
- package/templates/react-vite/src/pages/UserProfile.tsx +40 -0
- package/templates/react-vite/src/router.tsx +33 -0
- package/templates/react-vite/src/types/api.d.ts +20 -0
- package/templates/react-vite/src/types/user.d.ts +6 -0
- package/templates/react-vite/src/utils/helpers.ts +51 -0
- package/templates/react-vite/src/utils/storage.ts +35 -0
- package/templates/react-vite/src/vite-env.d.ts +11 -0
- package/templates/react-vite/template.json +46 -0
- package/templates/react-vite/tsconfig.json +4 -0
- package/templates/react-vite/vite.config.ts +13 -0
- package/modules/auth/better-auth-express/files/lib/auth.ts +0 -16
- package/modules/auth/better-auth-express/files/routes/auth.ts +0 -12
- package/modules/auth/better-auth-express/module.json +0 -38
- package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +0 -5
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
- package/modules/auth/better-auth-nextjs/module.json +0 -41
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
- package/modules/auth/better-auth-react/module.json +0 -26
- package/modules/auth/clerk-express/module.json +0 -20
- package/modules/auth/clerk-nextjs/files/middleware.ts +0 -9
- package/modules/auth/clerk-nextjs/module.json +0 -28
- package/modules/auth/clerk-react/module.json +0 -19
- package/modules/database/drizzle-postgresql/files/drizzle.config.ts +0 -10
- package/modules/database/drizzle-postgresql/files/lib/db.ts +0 -7
- package/modules/database/drizzle-postgresql/files/lib/schema.ts +0 -8
- package/modules/database/drizzle-postgresql/module.json +0 -35
- package/modules/database/prisma-mongodb/files/lib/db.ts +0 -9
- package/modules/database/prisma-mongodb/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-mongodb/module.json +0 -36
- package/modules/database/prisma-postgresql/files/lib/db.ts +0 -9
- package/modules/database/prisma-postgresql/files/prisma/schema.prisma +0 -17
- package/modules/database/prisma-postgresql/module.json +0 -36
- package/templates/bases/express-base/.env.example +0 -2
- package/templates/bases/express-base/package.json +0 -23
- package/templates/bases/express-base/src/app.ts +0 -34
- package/templates/bases/express-base/src/config/env.ts +0 -14
- package/templates/bases/express-base/src/middlewares/error.middleware.ts +0 -12
- package/templates/bases/express-base/template.json +0 -7
- package/templates/bases/express-base/tsconfig.json +0 -14
- package/templates/bases/nextjs-base/app/page.tsx +0 -65
- package/templates/bases/react-vite-base/README.md +0 -73
- package/templates/bases/react-vite-base/eslint.config.js +0 -23
- package/templates/bases/react-vite-base/src/App.css +0 -42
- package/templates/bases/react-vite-base/src/App.tsx +0 -35
- package/templates/bases/react-vite-base/src/index.css +0 -68
- package/templates/bases/react-vite-base/src/main.tsx +0 -10
- package/templates/bases/react-vite-base/template.json +0 -19
- package/templates/bases/react-vite-base/tsconfig.json +0 -7
- package/templates/bases/react-vite-base/vite.config.ts +0 -7
- /package/templates/{bases/nextjs-base → nextjs}/README.md +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/app/favicon.ico +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/app/globals.css +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/eslint.config.mjs +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/next.config.ts +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/postcss.config.mjs +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/file.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/globe.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/next.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/vercel.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/public/window.svg +0 -0
- /package/templates/{bases/nextjs-base → nextjs}/tsconfig.json +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/public/vite.svg +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/src/assets/react.svg +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/tsconfig.app.json +0 -0
- /package/templates/{bases/react-vite-base → react-vite}/tsconfig.node.json +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# stackkit-cli
|
|
2
2
|
|
|
3
|
-
Add modules to existing projects.
|
|
3
|
+
Add authentication and database modules to existing projects.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -15,18 +15,25 @@ npx stackkit-cli add database
|
|
|
15
15
|
npx stackkit-cli list
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Features
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
- **Auto-detects** your framework (Next.js, Express, React)
|
|
21
|
+
- **Shows compatible modules** for your project
|
|
22
|
+
- **Installs dependencies** automatically
|
|
23
|
+
- **Configures everything** - files, env vars, and setup
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
List all available modules.
|
|
25
|
+
## Available Modules
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
### Authentication
|
|
28
|
+
|
|
29
|
+
- Better Auth (Next.js, Express, React)
|
|
30
|
+
- Clerk (Next.js, Express, React)
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
### Database
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
- Prisma with PostgreSQL or MongoDB (Next.js, Express)
|
|
35
|
+
- Mongoose with MongoDB (Next.js, Express)
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
Full documentation: [stackkit.dev](https://stackkit.dev) | [GitHub](https://github.com/tariqul420/stackkit)
|
package/bin/stackkit.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
// eslint-disable-next-line
|
|
5
|
+
require("ts-node/register");
|
|
6
|
+
// eslint-disable-next-line
|
|
7
|
+
require("../src/index.ts");
|
|
8
|
+
} catch {
|
|
9
|
+
// eslint-disable-next-line
|
|
10
|
+
require("../dist/index.js");
|
|
11
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.addCommand = addCommand;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
8
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
10
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
@@ -17,43 +18,136 @@ async function addCommand(module, options) {
|
|
|
17
18
|
try {
|
|
18
19
|
const projectRoot = process.cwd();
|
|
19
20
|
// Detect project info
|
|
20
|
-
const spinner = logger_1.logger.startSpinner(
|
|
21
|
+
const spinner = logger_1.logger.startSpinner("Detecting project...");
|
|
21
22
|
const projectInfo = await (0, detect_1.detectProjectInfo)(projectRoot);
|
|
22
23
|
spinner.succeed(`Detected ${projectInfo.framework} (${projectInfo.router} router, ${projectInfo.language})`);
|
|
23
24
|
// Load module metadata
|
|
24
|
-
const modulesDir = path_1.default.join(__dirname,
|
|
25
|
+
const modulesDir = path_1.default.join(__dirname, "..", "..", "modules");
|
|
25
26
|
const moduleMetadata = await loadModuleMetadata(modulesDir, module, options.provider);
|
|
26
27
|
if (!moduleMetadata) {
|
|
27
28
|
logger_1.logger.error(`Module "${module}" not found`);
|
|
28
29
|
process.exit(1);
|
|
29
30
|
}
|
|
31
|
+
// For database modules, ensure provider is selected
|
|
32
|
+
let selectedProvider = options.provider;
|
|
33
|
+
if (moduleMetadata.category === "database" && !selectedProvider) {
|
|
34
|
+
if (typeof moduleMetadata.dependencies === "object" &&
|
|
35
|
+
"providers" in moduleMetadata.dependencies) {
|
|
36
|
+
const providers = Object.keys(moduleMetadata.dependencies.providers || {});
|
|
37
|
+
if (providers.length > 0) {
|
|
38
|
+
const { provider } = await inquirer_1.default.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: "list",
|
|
41
|
+
name: "provider",
|
|
42
|
+
message: "Select database provider:",
|
|
43
|
+
choices: providers.map((p) => ({ name: p, value: p })),
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
selectedProvider = provider;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Merge dependencies based on provider
|
|
51
|
+
const mergedDeps = {};
|
|
52
|
+
const mergedDevDeps = {};
|
|
53
|
+
if (typeof moduleMetadata.dependencies === "object" &&
|
|
54
|
+
!("common" in moduleMetadata.dependencies)) {
|
|
55
|
+
// Already flat
|
|
56
|
+
Object.assign(mergedDeps, moduleMetadata.dependencies);
|
|
57
|
+
}
|
|
58
|
+
else if (typeof moduleMetadata.dependencies === "object" &&
|
|
59
|
+
"common" in moduleMetadata.dependencies) {
|
|
60
|
+
Object.assign(mergedDeps, moduleMetadata.dependencies.common);
|
|
61
|
+
if (selectedProvider &&
|
|
62
|
+
typeof moduleMetadata.dependencies === "object" &&
|
|
63
|
+
"providers" in moduleMetadata.dependencies &&
|
|
64
|
+
typeof moduleMetadata.dependencies.providers === "object" &&
|
|
65
|
+
selectedProvider in moduleMetadata.dependencies.providers) {
|
|
66
|
+
Object.assign(mergedDeps, moduleMetadata.dependencies.providers[selectedProvider]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (moduleMetadata.devDependencies) {
|
|
70
|
+
if (typeof moduleMetadata.devDependencies === "object" &&
|
|
71
|
+
!("common" in moduleMetadata.devDependencies)) {
|
|
72
|
+
Object.assign(mergedDevDeps, moduleMetadata.devDependencies);
|
|
73
|
+
}
|
|
74
|
+
else if (typeof moduleMetadata.devDependencies === "object" &&
|
|
75
|
+
"common" in moduleMetadata.devDependencies) {
|
|
76
|
+
Object.assign(mergedDevDeps, moduleMetadata.devDependencies.common);
|
|
77
|
+
if (selectedProvider &&
|
|
78
|
+
typeof moduleMetadata.devDependencies === "object" &&
|
|
79
|
+
"providers" in moduleMetadata.devDependencies &&
|
|
80
|
+
typeof moduleMetadata.devDependencies.providers === "object" &&
|
|
81
|
+
selectedProvider in moduleMetadata.devDependencies.providers) {
|
|
82
|
+
Object.assign(mergedDevDeps, moduleMetadata.devDependencies.providers[selectedProvider]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Update metadata with merged deps
|
|
87
|
+
moduleMetadata.dependencies = mergedDeps;
|
|
88
|
+
moduleMetadata.devDependencies = mergedDevDeps;
|
|
89
|
+
// Set variables for replacements
|
|
90
|
+
const variables = {};
|
|
91
|
+
if (selectedProvider) {
|
|
92
|
+
variables.provider = selectedProvider;
|
|
93
|
+
if (selectedProvider === "postgresql") {
|
|
94
|
+
variables.connectionString = "postgresql://user:password@localhost:5432/mydb?schema=public";
|
|
95
|
+
}
|
|
96
|
+
else if (selectedProvider === "mongodb") {
|
|
97
|
+
variables.connectionString = "mongodb://localhost:27017/mydb";
|
|
98
|
+
}
|
|
99
|
+
else if (selectedProvider === "mysql") {
|
|
100
|
+
variables.connectionString = "mysql://user:password@localhost:3306/mydb";
|
|
101
|
+
}
|
|
102
|
+
else if (selectedProvider === "sqlite") {
|
|
103
|
+
variables.connectionString = "file:./dev.db";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
30
106
|
// Check if framework is supported
|
|
31
107
|
if (!moduleMetadata.supportedFrameworks.includes(projectInfo.framework)) {
|
|
32
|
-
logger_1.logger.error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(
|
|
108
|
+
logger_1.logger.error(`Module "${module}" does not support ${projectInfo.framework}. Supported: ${moduleMetadata.supportedFrameworks.join(", ")}`);
|
|
33
109
|
process.exit(1);
|
|
34
110
|
}
|
|
35
111
|
// Check for conflicts
|
|
36
|
-
if (module ===
|
|
37
|
-
logger_1.logger.warn(
|
|
112
|
+
if (module === "auth" && projectInfo.hasAuth && !options.force) {
|
|
113
|
+
logger_1.logger.warn("Auth library already detected in this project");
|
|
38
114
|
const { proceed } = await inquirer_1.default.prompt([
|
|
39
115
|
{
|
|
40
|
-
type:
|
|
41
|
-
name:
|
|
42
|
-
message:
|
|
116
|
+
type: "confirm",
|
|
117
|
+
name: "proceed",
|
|
118
|
+
message: "Continue anyway? (use --force to skip this prompt)",
|
|
43
119
|
default: false,
|
|
44
120
|
},
|
|
45
121
|
]);
|
|
46
122
|
if (!proceed) {
|
|
47
|
-
logger_1.logger.info(
|
|
123
|
+
logger_1.logger.info("Cancelled");
|
|
48
124
|
process.exit(0);
|
|
49
125
|
}
|
|
50
126
|
}
|
|
51
127
|
if (options.dryRun) {
|
|
52
|
-
logger_1.logger.warn(
|
|
128
|
+
logger_1.logger.warn("Dry run mode - no changes will be made");
|
|
53
129
|
logger_1.logger.newLine();
|
|
54
130
|
}
|
|
55
131
|
// Apply module patches
|
|
56
132
|
await applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, module, options);
|
|
133
|
+
// Apply framework patches
|
|
134
|
+
if (moduleMetadata.frameworkPatches && !options.dryRun) {
|
|
135
|
+
await applyFrameworkPatches(projectRoot, moduleMetadata.frameworkPatches, projectInfo.framework);
|
|
136
|
+
}
|
|
137
|
+
// Run post-install commands
|
|
138
|
+
if (moduleMetadata.postInstall && moduleMetadata.postInstall.length > 0 && !options.dryRun) {
|
|
139
|
+
const postInstallSpinner = logger_1.logger.startSpinner("Running post-install commands...");
|
|
140
|
+
try {
|
|
141
|
+
for (const command of moduleMetadata.postInstall) {
|
|
142
|
+
(0, child_process_1.execSync)(command, { cwd: projectRoot, stdio: "pipe" });
|
|
143
|
+
}
|
|
144
|
+
postInstallSpinner.succeed("Post-install commands completed");
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
postInstallSpinner.fail("Failed to run post-install commands");
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
57
151
|
// Add dependencies
|
|
58
152
|
if (Object.keys(moduleMetadata.dependencies).length > 0 && options.install !== false) {
|
|
59
153
|
const deps = Object.entries(moduleMetadata.dependencies).map(([name, version]) => `${name}@${version}`);
|
|
@@ -61,7 +155,7 @@ async function addCommand(module, options) {
|
|
|
61
155
|
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, deps, false);
|
|
62
156
|
}
|
|
63
157
|
else {
|
|
64
|
-
logger_1.logger.info(`Would add dependencies: ${deps.join(
|
|
158
|
+
logger_1.logger.info(`Would add dependencies: ${deps.join(", ")}`);
|
|
65
159
|
}
|
|
66
160
|
}
|
|
67
161
|
// Add dev dependencies
|
|
@@ -73,16 +167,21 @@ async function addCommand(module, options) {
|
|
|
73
167
|
await (0, package_manager_1.addDependencies)(projectRoot, projectInfo.packageManager, devDeps, true);
|
|
74
168
|
}
|
|
75
169
|
else {
|
|
76
|
-
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(
|
|
170
|
+
logger_1.logger.info(`Would add dev dependencies: ${devDeps.join(", ")}`);
|
|
77
171
|
}
|
|
78
172
|
}
|
|
79
173
|
// Add environment variables
|
|
80
174
|
if (moduleMetadata.envVars.length > 0) {
|
|
175
|
+
// Replace variables in envVars
|
|
176
|
+
const processedEnvVars = moduleMetadata.envVars.map((envVar) => ({
|
|
177
|
+
...envVar,
|
|
178
|
+
value: envVar.value?.replace(/\{\{(\w+)\}\}/g, (match, key) => variables[key] || match),
|
|
179
|
+
}));
|
|
81
180
|
if (!options.dryRun) {
|
|
82
|
-
await (0, env_editor_1.addEnvVariables)(projectRoot,
|
|
181
|
+
await (0, env_editor_1.addEnvVariables)(projectRoot, processedEnvVars, { force: options.force });
|
|
83
182
|
}
|
|
84
183
|
else {
|
|
85
|
-
logger_1.logger.log(` ${chalk_1.default.dim(
|
|
184
|
+
logger_1.logger.log(` ${chalk_1.default.dim("~")} .env.example`);
|
|
86
185
|
}
|
|
87
186
|
}
|
|
88
187
|
logger_1.logger.newLine();
|
|
@@ -90,7 +189,7 @@ async function addCommand(module, options) {
|
|
|
90
189
|
logger_1.logger.newLine();
|
|
91
190
|
// Print next steps
|
|
92
191
|
if (moduleMetadata.envVars.some((v) => v.required)) {
|
|
93
|
-
logger_1.logger.log(
|
|
192
|
+
logger_1.logger.log("Next: Fill in environment variables in .env");
|
|
94
193
|
}
|
|
95
194
|
logger_1.logger.newLine();
|
|
96
195
|
}
|
|
@@ -120,7 +219,7 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
120
219
|
const moduleStat = await fs_extra_1.default.stat(modulePath);
|
|
121
220
|
if (!moduleStat.isDirectory())
|
|
122
221
|
continue;
|
|
123
|
-
const metadataPath = path_1.default.join(modulePath,
|
|
222
|
+
const metadataPath = path_1.default.join(modulePath, "module.json");
|
|
124
223
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
125
224
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
126
225
|
// If provider is specified, match by directory name (exact match)
|
|
@@ -136,13 +235,15 @@ async function loadModuleMetadata(modulesDir, moduleName, provider) {
|
|
|
136
235
|
}
|
|
137
236
|
return null;
|
|
138
237
|
}
|
|
238
|
+
// (removed duplicate import)
|
|
139
239
|
async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modulesDir, moduleName, options) {
|
|
240
|
+
// Find the module path
|
|
140
241
|
const moduleBasePath = await findModulePath(modulesDir, moduleName, options.provider);
|
|
141
242
|
if (!moduleBasePath) {
|
|
142
|
-
throw new Error(
|
|
243
|
+
throw new Error("Module files not found");
|
|
143
244
|
}
|
|
144
245
|
for (const patch of moduleMetadata.patches) {
|
|
145
|
-
if (patch.type ===
|
|
246
|
+
if (patch.type === "create-file") {
|
|
146
247
|
const filePatch = patch;
|
|
147
248
|
// Check conditions
|
|
148
249
|
if (filePatch.condition) {
|
|
@@ -153,18 +254,18 @@ async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modu
|
|
|
153
254
|
continue; // Skip this patch
|
|
154
255
|
}
|
|
155
256
|
}
|
|
156
|
-
const sourceFile = path_1.default.join(moduleBasePath,
|
|
257
|
+
const sourceFile = path_1.default.join(moduleBasePath, "files", filePatch.source);
|
|
157
258
|
let destFile = path_1.default.join(projectRoot, filePatch.destination);
|
|
158
259
|
// Replace placeholders in destination
|
|
159
260
|
destFile = destFile
|
|
160
|
-
.replace(
|
|
161
|
-
.replace(
|
|
261
|
+
.replace("{{router}}", (0, detect_1.getRouterBasePath)(projectInfo))
|
|
262
|
+
.replace("{{lib}}", (0, detect_1.getLibPath)(projectInfo));
|
|
162
263
|
if (!options.dryRun) {
|
|
163
264
|
if (await (0, files_1.fileExists)(sourceFile)) {
|
|
164
|
-
const content = await fs_extra_1.default.readFile(sourceFile,
|
|
265
|
+
const content = await fs_extra_1.default.readFile(sourceFile, "utf-8");
|
|
165
266
|
await (0, files_1.createFile)(destFile, content, { force: options.force });
|
|
166
267
|
const relativePath = path_1.default.relative(projectRoot, destFile);
|
|
167
|
-
logger_1.logger.log(` ${chalk_1.default.green(
|
|
268
|
+
logger_1.logger.log(` ${chalk_1.default.green("+")} ${relativePath}`);
|
|
168
269
|
}
|
|
169
270
|
else {
|
|
170
271
|
logger_1.logger.warn(`Source file not found: ${filePatch.source}`);
|
|
@@ -172,7 +273,7 @@ async function applyModulePatches(projectRoot, projectInfo, moduleMetadata, modu
|
|
|
172
273
|
}
|
|
173
274
|
else {
|
|
174
275
|
const relativePath = path_1.default.relative(projectRoot, destFile);
|
|
175
|
-
logger_1.logger.log(` ${chalk_1.default.dim(
|
|
276
|
+
logger_1.logger.log(` ${chalk_1.default.dim("+")} ${relativePath}`);
|
|
176
277
|
}
|
|
177
278
|
}
|
|
178
279
|
}
|
|
@@ -190,7 +291,7 @@ async function findModulePath(modulesDir, moduleName, provider) {
|
|
|
190
291
|
const moduleStat = await fs_extra_1.default.stat(modulePath);
|
|
191
292
|
if (!moduleStat.isDirectory())
|
|
192
293
|
continue;
|
|
193
|
-
const metadataPath = path_1.default.join(modulePath,
|
|
294
|
+
const metadataPath = path_1.default.join(modulePath, "module.json");
|
|
194
295
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
195
296
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
196
297
|
// If provider is specified, match by directory name (exact match)
|
|
@@ -206,3 +307,41 @@ async function findModulePath(modulesDir, moduleName, provider) {
|
|
|
206
307
|
}
|
|
207
308
|
return null;
|
|
208
309
|
}
|
|
310
|
+
async function applyFrameworkPatches(projectRoot, patches, framework) {
|
|
311
|
+
const frameworkKey = framework;
|
|
312
|
+
const frameworkPatches = patches[frameworkKey];
|
|
313
|
+
if (!frameworkPatches)
|
|
314
|
+
return;
|
|
315
|
+
for (const [filename, patchConfig] of Object.entries(frameworkPatches)) {
|
|
316
|
+
const filePath = path_1.default.join(projectRoot, filename);
|
|
317
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
318
|
+
const fileContent = await fs_extra_1.default.readJson(filePath);
|
|
319
|
+
if (patchConfig.merge) {
|
|
320
|
+
const merged = deepMerge(fileContent, patchConfig.merge);
|
|
321
|
+
await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
|
|
322
|
+
const relativePath = path_1.default.relative(projectRoot, filePath);
|
|
323
|
+
logger_1.logger.log(` ${chalk_1.default.blue("~")} ${relativePath}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function deepMerge(target, source) {
|
|
329
|
+
const output = { ...target };
|
|
330
|
+
for (const key in source) {
|
|
331
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
332
|
+
if (target[key]) {
|
|
333
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
output[key] = source[key];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (Array.isArray(source[key])) {
|
|
340
|
+
output[key] = Array.from(new Set([...(target[key] || []), ...source[key]]));
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
output[key] = source[key];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return output;
|
|
347
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -15,24 +15,24 @@ const package_manager_1 = require("../utils/package-manager");
|
|
|
15
15
|
async function initCommand(projectName, options) {
|
|
16
16
|
try {
|
|
17
17
|
// Validate package manager option
|
|
18
|
-
if (options.pm && ![
|
|
19
|
-
logger_1.logger.error(`Invalid package manager: ${options.pm}. Use npm, yarn, or
|
|
18
|
+
if (options.pm && !["npm", "yarn", "pnpm", "bun"].includes(options.pm)) {
|
|
19
|
+
logger_1.logger.error(`Invalid package manager: ${options.pm}. Use npm, yarn, pnpm, or bun.`);
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
// Get available templates
|
|
23
|
-
const templatesDir = path_1.default.join(__dirname,
|
|
23
|
+
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
24
24
|
const templates = await getAvailableTemplates(templatesDir);
|
|
25
25
|
if (templates.length === 0) {
|
|
26
|
-
logger_1.logger.error(
|
|
26
|
+
logger_1.logger.error("No templates found");
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
// Prompt for project details if not using --yes
|
|
30
30
|
let answers;
|
|
31
31
|
if (options.yes) {
|
|
32
32
|
answers = {
|
|
33
|
-
projectName: projectName ||
|
|
33
|
+
projectName: projectName || "my-app",
|
|
34
34
|
template: options.template || templates[0].name,
|
|
35
|
-
packageManager: options.pm ||
|
|
35
|
+
packageManager: options.pm || "pnpm",
|
|
36
36
|
install: options.install !== false,
|
|
37
37
|
git: options.git !== false,
|
|
38
38
|
};
|
|
@@ -40,23 +40,23 @@ async function initCommand(projectName, options) {
|
|
|
40
40
|
else {
|
|
41
41
|
const prompted = await inquirer_1.default.prompt([
|
|
42
42
|
{
|
|
43
|
-
type:
|
|
44
|
-
name:
|
|
45
|
-
message:
|
|
46
|
-
default: projectName ||
|
|
43
|
+
type: "input",
|
|
44
|
+
name: "projectName",
|
|
45
|
+
message: "Project name:",
|
|
46
|
+
default: projectName || "my-app",
|
|
47
47
|
when: !projectName,
|
|
48
48
|
validate: (input) => {
|
|
49
49
|
const validation = (0, validate_npm_package_name_1.default)(input);
|
|
50
50
|
if (!validation.validForNewPackages) {
|
|
51
|
-
return validation.errors?.[0] ||
|
|
51
|
+
return validation.errors?.[0] || "Invalid package name";
|
|
52
52
|
}
|
|
53
53
|
return true;
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
|
-
type:
|
|
58
|
-
name:
|
|
59
|
-
message:
|
|
57
|
+
type: "list",
|
|
58
|
+
name: "template",
|
|
59
|
+
message: "Select a template:",
|
|
60
60
|
choices: templates.map((t) => ({
|
|
61
61
|
name: t.displayName,
|
|
62
62
|
value: t.name,
|
|
@@ -64,24 +64,29 @@ async function initCommand(projectName, options) {
|
|
|
64
64
|
when: !options.template,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
type:
|
|
68
|
-
name:
|
|
69
|
-
message:
|
|
70
|
-
choices: [
|
|
71
|
-
|
|
67
|
+
type: "list",
|
|
68
|
+
name: "packageManager",
|
|
69
|
+
message: "Select a package manager:",
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: "pnpm (recommended)", value: "pnpm" },
|
|
72
|
+
{ name: "npm", value: "npm" },
|
|
73
|
+
{ name: "yarn", value: "yarn" },
|
|
74
|
+
{ name: "bun", value: "bun" },
|
|
75
|
+
],
|
|
76
|
+
default: "pnpm",
|
|
72
77
|
when: !options.pm,
|
|
73
78
|
},
|
|
74
79
|
{
|
|
75
|
-
type:
|
|
76
|
-
name:
|
|
77
|
-
message:
|
|
80
|
+
type: "confirm",
|
|
81
|
+
name: "install",
|
|
82
|
+
message: "Install dependencies?",
|
|
78
83
|
default: true,
|
|
79
84
|
when: options.install !== false,
|
|
80
85
|
},
|
|
81
86
|
{
|
|
82
|
-
type:
|
|
83
|
-
name:
|
|
84
|
-
message:
|
|
87
|
+
type: "confirm",
|
|
88
|
+
name: "git",
|
|
89
|
+
message: "Initialize git repository?",
|
|
85
90
|
default: true,
|
|
86
91
|
when: options.git !== false,
|
|
87
92
|
},
|
|
@@ -98,7 +103,7 @@ async function initCommand(projectName, options) {
|
|
|
98
103
|
// Check if directory exists
|
|
99
104
|
if (await fs_extra_1.default.pathExists(targetDir)) {
|
|
100
105
|
logger_1.logger.error(`Directory "${answers.projectName}" already exists`);
|
|
101
|
-
logger_1.logger.info(
|
|
106
|
+
logger_1.logger.info("Please choose a different name or remove the existing directory.");
|
|
102
107
|
process.exit(1);
|
|
103
108
|
}
|
|
104
109
|
// Validate template exists
|
|
@@ -123,11 +128,11 @@ async function initCommand(projectName, options) {
|
|
|
123
128
|
logger_1.logger.success(`Created ${chalk_1.default.bold(answers.projectName)}`);
|
|
124
129
|
logger_1.logger.newLine();
|
|
125
130
|
logger_1.logger.log(`Next steps:`);
|
|
126
|
-
logger_1.logger.log(` ${chalk_1.default.cyan(
|
|
131
|
+
logger_1.logger.log(` ${chalk_1.default.cyan("cd")} ${answers.projectName}`);
|
|
127
132
|
if (!answers.install) {
|
|
128
133
|
logger_1.logger.log(` ${chalk_1.default.cyan(answers.packageManager)} install`);
|
|
129
134
|
}
|
|
130
|
-
logger_1.logger.log(` ${chalk_1.default.cyan(answers.packageManager)} ${answers.packageManager ===
|
|
135
|
+
logger_1.logger.log(` ${chalk_1.default.cyan(answers.packageManager)} ${answers.packageManager === "npm" ? "run " : ""}dev`);
|
|
131
136
|
logger_1.logger.newLine();
|
|
132
137
|
}
|
|
133
138
|
catch (error) {
|
|
@@ -142,7 +147,7 @@ async function getAvailableTemplates(templatesDir) {
|
|
|
142
147
|
const templateDirs = await fs_extra_1.default.readdir(templatesDir);
|
|
143
148
|
const templates = [];
|
|
144
149
|
for (const dir of templateDirs) {
|
|
145
|
-
const metadataPath = path_1.default.join(templatesDir, dir,
|
|
150
|
+
const metadataPath = path_1.default.join(templatesDir, dir, "template.json");
|
|
146
151
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
147
152
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
148
153
|
templates.push(metadata);
|
package/dist/commands/list.js
CHANGED
|
@@ -15,28 +15,28 @@ async function listCommand(options) {
|
|
|
15
15
|
logger_1.logger.newLine();
|
|
16
16
|
// List templates
|
|
17
17
|
if (showTemplates) {
|
|
18
|
-
const templatesDir = path_1.default.join(__dirname,
|
|
18
|
+
const templatesDir = path_1.default.join(__dirname, "..", "..", "templates");
|
|
19
19
|
const templates = await getAvailableTemplates(templatesDir);
|
|
20
|
-
logger_1.logger.log(chalk_1.default.bold.cyan(
|
|
20
|
+
logger_1.logger.log(chalk_1.default.bold.cyan("▸ TEMPLATES") + chalk_1.default.gray(` (${templates.length})`));
|
|
21
21
|
logger_1.logger.newLine();
|
|
22
22
|
if (templates.length === 0) {
|
|
23
|
-
logger_1.logger.log(chalk_1.default.dim(
|
|
23
|
+
logger_1.logger.log(chalk_1.default.dim(" No templates available"));
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
|
-
templates.forEach((template
|
|
27
|
-
logger_1.logger.log(` ${chalk_1.default.cyan(
|
|
26
|
+
templates.forEach((template) => {
|
|
27
|
+
logger_1.logger.log(` ${chalk_1.default.cyan("•")} ${template.displayName}`);
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
logger_1.logger.newLine();
|
|
31
31
|
}
|
|
32
32
|
// List modules
|
|
33
33
|
if (showModules) {
|
|
34
|
-
const modulesDir = path_1.default.join(__dirname,
|
|
34
|
+
const modulesDir = path_1.default.join(__dirname, "..", "..", "modules");
|
|
35
35
|
const modules = await getAvailableModules(modulesDir);
|
|
36
|
-
logger_1.logger.log(chalk_1.default.bold.cyan(
|
|
36
|
+
logger_1.logger.log(chalk_1.default.bold.cyan("▸ MODULES") + chalk_1.default.gray(` (${modules.length})`));
|
|
37
37
|
if (modules.length === 0) {
|
|
38
38
|
logger_1.logger.newLine();
|
|
39
|
-
logger_1.logger.log(chalk_1.default.dim(
|
|
39
|
+
logger_1.logger.log(chalk_1.default.dim(" No modules available"));
|
|
40
40
|
logger_1.logger.newLine();
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
@@ -50,9 +50,9 @@ async function listCommand(options) {
|
|
|
50
50
|
}, {});
|
|
51
51
|
for (const [category, mods] of Object.entries(grouped)) {
|
|
52
52
|
logger_1.logger.newLine();
|
|
53
|
-
logger_1.logger.log(` ${chalk_1.default.yellow(
|
|
53
|
+
logger_1.logger.log(` ${chalk_1.default.yellow("→")} ${chalk_1.default.bold.yellow(category.toUpperCase())} ${chalk_1.default.dim(`(${mods.length})`)}`);
|
|
54
54
|
mods.forEach((mod) => {
|
|
55
|
-
logger_1.logger.log(` ${chalk_1.default.cyan(
|
|
55
|
+
logger_1.logger.log(` ${chalk_1.default.cyan("•")} ${mod.displayName}`);
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
logger_1.logger.newLine();
|
|
@@ -71,7 +71,7 @@ async function getAvailableTemplates(templatesDir) {
|
|
|
71
71
|
const templateDirs = await fs_extra_1.default.readdir(templatesDir);
|
|
72
72
|
const templates = [];
|
|
73
73
|
for (const dir of templateDirs) {
|
|
74
|
-
const metadataPath = path_1.default.join(templatesDir, dir,
|
|
74
|
+
const metadataPath = path_1.default.join(templatesDir, dir, "template.json");
|
|
75
75
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
76
76
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
77
77
|
templates.push(metadata);
|
|
@@ -92,7 +92,7 @@ async function getAvailableModules(modulesDir) {
|
|
|
92
92
|
continue;
|
|
93
93
|
const moduleDirs = await fs_extra_1.default.readdir(categoryPath);
|
|
94
94
|
for (const moduleDir of moduleDirs) {
|
|
95
|
-
const metadataPath = path_1.default.join(categoryPath, moduleDir,
|
|
95
|
+
const metadataPath = path_1.default.join(categoryPath, moduleDir, "module.json");
|
|
96
96
|
if (await fs_extra_1.default.pathExists(metadataPath)) {
|
|
97
97
|
const metadata = await fs_extra_1.default.readJSON(metadataPath);
|
|
98
98
|
modules.push(metadata);
|
package/dist/index.js
CHANGED
|
@@ -11,39 +11,41 @@ const init_1 = require("./commands/init");
|
|
|
11
11
|
const list_1 = require("./commands/list");
|
|
12
12
|
const program = new commander_1.Command();
|
|
13
13
|
program
|
|
14
|
-
.name(
|
|
15
|
-
.description(
|
|
16
|
-
.version(
|
|
14
|
+
.name("stackkit")
|
|
15
|
+
.description("Production-ready project generator and module CLI")
|
|
16
|
+
.version("0.3.2");
|
|
17
17
|
// Init command
|
|
18
18
|
program
|
|
19
|
-
.command(
|
|
20
|
-
.description(
|
|
21
|
-
.option(
|
|
22
|
-
.option(
|
|
23
|
-
.option(
|
|
24
|
-
.option(
|
|
25
|
-
.option(
|
|
19
|
+
.command("init [project-name]")
|
|
20
|
+
.description("Create a new project from a template")
|
|
21
|
+
.option("-t, --template <template>", "Template to use")
|
|
22
|
+
.option("--pm <pm>", "Package manager to use (npm, yarn, pnpm, bun)")
|
|
23
|
+
.option("--no-install", "Skip installing dependencies")
|
|
24
|
+
.option("--no-git", "Skip git initialization")
|
|
25
|
+
.option("-y, --yes", "Skip prompts and use defaults")
|
|
26
26
|
.action(init_1.initCommand);
|
|
27
27
|
// List command
|
|
28
28
|
program
|
|
29
|
-
.command(
|
|
30
|
-
.description(
|
|
31
|
-
.option(
|
|
32
|
-
.option(
|
|
29
|
+
.command("list")
|
|
30
|
+
.description("List available templates and modules")
|
|
31
|
+
.option("-t, --templates", "List only templates")
|
|
32
|
+
.option("-m, --modules", "List only modules")
|
|
33
33
|
.action(list_1.listCommand);
|
|
34
34
|
// Add command
|
|
35
35
|
program
|
|
36
|
-
.command(
|
|
37
|
-
.description(
|
|
38
|
-
.option(
|
|
39
|
-
.option(
|
|
40
|
-
.option(
|
|
41
|
-
.option(
|
|
36
|
+
.command("add <module>")
|
|
37
|
+
.description("Add a module to your existing project")
|
|
38
|
+
.option("--provider <provider>", "Specific provider/variant to use")
|
|
39
|
+
.option("--force", "Overwrite existing files")
|
|
40
|
+
.option("--dry-run", "Show what would be changed without making changes")
|
|
41
|
+
.option("--no-install", "Skip installing dependencies")
|
|
42
42
|
.action(add_1.addCommand);
|
|
43
43
|
// Error handling
|
|
44
|
-
program.on(
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
program.on("command:*", () => {
|
|
45
|
+
// Use logger for error and info
|
|
46
|
+
// logger.error and logger.log are not available here since logger is not imported, so fallback to process.stderr
|
|
47
|
+
process.stderr.write(chalk_1.default.red(`\nInvalid command: ${program.args.join(" ")}\n`));
|
|
48
|
+
process.stderr.write(chalk_1.default.yellow("Run stackkit --help for a list of available commands.\n"));
|
|
47
49
|
process.exit(1);
|
|
48
50
|
});
|
|
49
51
|
program.parse();
|