stackkit-cli 0.4.3 → 0.4.5
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 +26 -2
- package/bin/stackkit.js +10 -1
- package/dist/commands/add.js +128 -10
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.js +483 -0
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.js +59 -38
- package/dist/index.js +11 -13
- package/dist/types/index.d.ts +29 -2
- package/dist/utils/config-utils.d.ts +2 -0
- package/dist/utils/config-utils.js +88 -0
- package/dist/utils/detect.js +12 -2
- package/dist/utils/env-editor.d.ts +0 -1
- package/dist/utils/env-editor.js +50 -25
- package/dist/utils/files.d.ts +8 -0
- package/dist/utils/files.js +51 -0
- package/dist/utils/js-conversion.d.ts +1 -0
- package/dist/utils/js-conversion.js +244 -0
- package/dist/utils/module-utils.d.ts +2 -0
- package/dist/utils/module-utils.js +461 -0
- package/dist/utils/package-manager.js +15 -31
- package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
- package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
- package/modules/auth/authjs/files/lib/auth.ts +41 -0
- package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
- package/modules/auth/authjs/module.json +95 -0
- package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
- package/modules/auth/better-auth/files/lib/auth.ts +75 -0
- package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
- package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
- package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +63 -0
- package/modules/auth/better-auth/module.json +191 -0
- package/modules/database/mongoose/files/lib/db.ts +68 -0
- package/modules/database/mongoose/files/models/User.ts +34 -0
- package/modules/database/mongoose/module.json +55 -0
- package/modules/database/prisma/files/lib/prisma.ts +4 -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 +122 -0
- package/package.json +1 -1
- package/templates/express/.env.example +1 -10
- package/templates/express/package.json +15 -21
- package/templates/express/src/app.ts +9 -29
- package/templates/express/src/config/env.ts +3 -14
- package/templates/express/src/features/health/health.controller.ts +18 -0
- package/templates/express/src/features/health/health.route.ts +9 -0
- package/templates/express/src/features/health/health.service.ts +6 -0
- package/templates/express/src/middlewares/error.middleware.ts +2 -2
- package/templates/express/src/server.ts +1 -1
- package/templates/express/template.json +1 -5
- package/templates/express/tsconfig.json +0 -1
- package/templates/nextjs/lib/env.ts +8 -0
- package/templates/nextjs/package.json +7 -7
- package/templates/react-vite/.env.example +1 -2
- package/templates/react-vite/.prettierignore +4 -0
- package/templates/react-vite/.prettierrc +9 -0
- package/templates/react-vite/README.md +22 -0
- package/templates/react-vite/package.json +16 -16
- package/templates/react-vite/src/router.tsx +0 -12
- package/templates/react-vite/vite.config.ts +0 -6
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/init.js +0 -157
- package/dist/utils/code-inject.d.ts +0 -14
- package/dist/utils/code-inject.js +0 -70
- package/dist/utils/json-editor.d.ts +0 -8
- package/dist/utils/json-editor.js +0 -45
- package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +0 -13
- package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +0 -15
- package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +0 -15
- 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/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-express/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-express/module.json +0 -61
- package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +0 -24
- package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +0 -26
- package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/lib/auth.ts +0 -26
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-mongodb-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/files/schemas/prisma-postgresql-schema.prisma +0 -72
- package/modules/auth/better-auth-nextjs/module.json +0 -62
- package/modules/auth/better-auth-react/files/lib/auth-client.ts +0 -9
- package/modules/auth/better-auth-react/module.json +0 -28
- package/modules/auth/clerk-express/files/lib/auth.ts +0 -7
- package/modules/auth/clerk-express/module.json +0 -34
- package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +0 -5
- package/modules/auth/clerk-nextjs/files/middleware.ts +0 -9
- package/modules/auth/clerk-nextjs/module.json +0 -64
- package/modules/auth/clerk-react/files/lib/auth-provider.tsx +0 -15
- package/modules/auth/clerk-react/module.json +0 -28
- package/modules/database/mongoose-mongodb/files/lib/db.ts +0 -40
- package/modules/database/mongoose-mongodb/module.json +0 -55
- 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 -60
- 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 -60
- package/templates/react-vite/src/api/services/user.service.ts +0 -18
- package/templates/react-vite/src/pages/UserProfile.tsx +0 -40
- package/templates/react-vite/src/types/user.d.ts +0 -6
- /package/modules/auth/{better-auth-nextjs → better-auth}/files/api/auth/[...all]/route.ts +0 -0
package/dist/types/index.d.ts
CHANGED
|
@@ -12,10 +12,37 @@ export interface ModuleMetadata {
|
|
|
12
12
|
description: string;
|
|
13
13
|
category: "auth" | "database" | "ui" | "other";
|
|
14
14
|
supportedFrameworks: string[];
|
|
15
|
-
dependencies:
|
|
16
|
-
|
|
15
|
+
dependencies: {
|
|
16
|
+
common?: Record<string, string>;
|
|
17
|
+
providers?: Record<string, Record<string, string>>;
|
|
18
|
+
} | Record<string, string>;
|
|
19
|
+
devDependencies?: {
|
|
20
|
+
common?: Record<string, string>;
|
|
21
|
+
providers?: Record<string, Record<string, string>>;
|
|
22
|
+
} | Record<string, string>;
|
|
17
23
|
envVars: EnvVar[];
|
|
18
24
|
patches: ModulePatch[];
|
|
25
|
+
frameworkPatches?: Record<string, {
|
|
26
|
+
[file: string]: {
|
|
27
|
+
merge?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
postInstall?: string[];
|
|
31
|
+
databaseAdapters?: {
|
|
32
|
+
common?: {
|
|
33
|
+
dependencies?: Record<string, string>;
|
|
34
|
+
devDependencies?: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
providers?: Record<string, Record<string, {
|
|
37
|
+
dependencies?: Record<string, string>;
|
|
38
|
+
devDependencies?: Record<string, string>;
|
|
39
|
+
}>>;
|
|
40
|
+
};
|
|
41
|
+
frameworkConfigs?: Record<string, {
|
|
42
|
+
dependencies?: Record<string, string>;
|
|
43
|
+
devDependencies?: Record<string, string>;
|
|
44
|
+
patches?: ModulePatch[];
|
|
45
|
+
}>;
|
|
19
46
|
}
|
|
20
47
|
export interface EnvVar {
|
|
21
48
|
key: string;
|
|
@@ -0,0 +1,88 @@
|
|
|
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.deepMerge = deepMerge;
|
|
7
|
+
exports.applyFrameworkPatches = applyFrameworkPatches;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
function deepMerge(target, source) {
|
|
11
|
+
const result = { ...target };
|
|
12
|
+
for (const key in source) {
|
|
13
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
14
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result[key] = source[key];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
async function applyFrameworkPatches(targetDir, patches) {
|
|
23
|
+
for (const [filename, patchConfig] of Object.entries(patches)) {
|
|
24
|
+
const filePath = (0, path_1.join)(targetDir, filename);
|
|
25
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
26
|
+
if (patchConfig && typeof patchConfig === "object" && "merge" in patchConfig) {
|
|
27
|
+
const fileContent = await fs_extra_1.default.readJson(filePath);
|
|
28
|
+
const merged = deepMerge(fileContent, patchConfig.merge);
|
|
29
|
+
await fs_extra_1.default.writeJson(filePath, merged, { spaces: 2 });
|
|
30
|
+
}
|
|
31
|
+
else if (patchConfig && typeof patchConfig === "object" && "replace" in patchConfig) {
|
|
32
|
+
let fileContent = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
33
|
+
const replaceConfig = patchConfig;
|
|
34
|
+
for (const [oldStr, newStr] of Object.entries(replaceConfig.replace)) {
|
|
35
|
+
fileContent = fileContent.replace(new RegExp(oldStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newStr);
|
|
36
|
+
}
|
|
37
|
+
await fs_extra_1.default.writeFile(filePath, fileContent);
|
|
38
|
+
}
|
|
39
|
+
else if (patchConfig &&
|
|
40
|
+
typeof patchConfig === "object" &&
|
|
41
|
+
("operations" in patchConfig ||
|
|
42
|
+
("type" in patchConfig && patchConfig.type === "patch-file"))) {
|
|
43
|
+
let fileContent = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
44
|
+
const operations = patchConfig.operations;
|
|
45
|
+
for (const operation of operations) {
|
|
46
|
+
if (operation && typeof operation === "object" && "type" in operation) {
|
|
47
|
+
const op = operation;
|
|
48
|
+
if (op.type === "add-import" && "imports" in op && Array.isArray(op.imports)) {
|
|
49
|
+
const imports = op.imports;
|
|
50
|
+
// Add imports at the top after existing imports
|
|
51
|
+
const importLines = imports.join("\n");
|
|
52
|
+
// Find the last import statement
|
|
53
|
+
const lines = fileContent.split("\n");
|
|
54
|
+
let lastImportIndex = -1;
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
if (lines[i].trim().startsWith("import")) {
|
|
57
|
+
lastImportIndex = i;
|
|
58
|
+
}
|
|
59
|
+
else if (lines[i].trim() !== "" && !lines[i].trim().startsWith("//")) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (lastImportIndex >= 0) {
|
|
64
|
+
lines.splice(lastImportIndex + 1, 0, "", importLines);
|
|
65
|
+
fileContent = lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (op.type === "add-code" && "after" in op && "code" in op) {
|
|
69
|
+
const after = op.after;
|
|
70
|
+
const code = op.code;
|
|
71
|
+
const afterIndex = fileContent.indexOf(after);
|
|
72
|
+
if (afterIndex >= 0) {
|
|
73
|
+
const insertPos = afterIndex + after.length;
|
|
74
|
+
fileContent = fileContent.slice(0, insertPos) + code + fileContent.slice(insertPos);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (op.type === "replace" && "oldString" in op && "newString" in op) {
|
|
78
|
+
const oldString = op.oldString;
|
|
79
|
+
const newString = op.newString;
|
|
80
|
+
fileContent = fileContent.replace(oldString, newString);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await fs_extra_1.default.writeFile(filePath, fileContent);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/utils/detect.js
CHANGED
|
@@ -54,7 +54,18 @@ async function detectProjectInfo(targetDir) {
|
|
|
54
54
|
}
|
|
55
55
|
// Detect TypeScript vs JavaScript
|
|
56
56
|
const tsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "tsconfig.json"));
|
|
57
|
-
const
|
|
57
|
+
const jsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "jsconfig.json"));
|
|
58
|
+
let language;
|
|
59
|
+
if (tsconfigExists) {
|
|
60
|
+
language = "ts";
|
|
61
|
+
}
|
|
62
|
+
else if (jsconfigExists) {
|
|
63
|
+
language = "js";
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Default to TypeScript if neither exists
|
|
67
|
+
language = "ts";
|
|
68
|
+
}
|
|
58
69
|
// Detect package manager
|
|
59
70
|
const yarnLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "yarn.lock"));
|
|
60
71
|
const pnpmLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "pnpm-lock.yaml"));
|
|
@@ -73,7 +84,6 @@ async function detectProjectInfo(targetDir) {
|
|
|
73
84
|
const hasAuth = !!(packageJson.dependencies?.["next-auth"] ||
|
|
74
85
|
packageJson.dependencies?.["better-auth"] ||
|
|
75
86
|
packageJson.dependencies?.["@auth/core"] ||
|
|
76
|
-
packageJson.dependencies?.["@clerk/nextjs"] ||
|
|
77
87
|
packageJson.dependencies?.["@kinde-oss/kinde-auth-nextjs"] ||
|
|
78
88
|
packageJson.dependencies?.["passport"]);
|
|
79
89
|
const hasPrisma = !!(packageJson.dependencies?.["@prisma/client"] || packageJson.devDependencies?.["prisma"]);
|
package/dist/utils/env-editor.js
CHANGED
|
@@ -13,9 +13,7 @@ const ENV_MARKER_END = "# End StackKit";
|
|
|
13
13
|
async function addEnvVariables(projectRoot, variables, options = {}) {
|
|
14
14
|
const envExamplePath = path_1.default.join(projectRoot, ".env.example");
|
|
15
15
|
const envPath = path_1.default.join(projectRoot, ".env");
|
|
16
|
-
// Add to .env.example
|
|
17
16
|
await appendToEnvFile(envExamplePath, variables, "example", options);
|
|
18
|
-
// Add to .env if it exists or create it
|
|
19
17
|
const envExists = await fs_extra_1.default.pathExists(envPath);
|
|
20
18
|
if (envExists || options.force) {
|
|
21
19
|
await appendToEnvFile(envPath, variables, "local", options);
|
|
@@ -23,11 +21,28 @@ async function addEnvVariables(projectRoot, variables, options = {}) {
|
|
|
23
21
|
logger_1.logger.success("Environment variables added");
|
|
24
22
|
}
|
|
25
23
|
async function appendToEnvFile(filePath, variables, fileType, options = {}) {
|
|
24
|
+
// Validate environment variable keys
|
|
25
|
+
for (const variable of variables) {
|
|
26
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(variable.key)) {
|
|
27
|
+
throw new Error(`Invalid environment variable key: ${variable.key}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
26
30
|
let content = "";
|
|
27
31
|
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
28
32
|
content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
29
33
|
}
|
|
30
|
-
//
|
|
34
|
+
// If force, remove existing keys first to avoid duplicates
|
|
35
|
+
if (options.force) {
|
|
36
|
+
const keysToRemove = variables.map((v) => v.key);
|
|
37
|
+
await removeFromEnvFile(filePath, keysToRemove);
|
|
38
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
39
|
+
content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
content = "";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Check if variables already exist (after potential removal)
|
|
31
46
|
const existingKeys = new Set();
|
|
32
47
|
const lines = content.split("\n");
|
|
33
48
|
for (const line of lines) {
|
|
@@ -36,15 +51,7 @@ async function appendToEnvFile(filePath, variables, fileType, options = {}) {
|
|
|
36
51
|
existingKeys.add(match[1]);
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
|
-
const newVariables = variables.filter((v) =>
|
|
40
|
-
if (existingKeys.has(v.key)) {
|
|
41
|
-
if (!options.force) {
|
|
42
|
-
logger_1.logger.warn(`Variable ${v.key} already exists in ${filePath}`);
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return true;
|
|
47
|
-
});
|
|
54
|
+
const newVariables = variables.filter((v) => !existingKeys.has(v.key));
|
|
48
55
|
if (newVariables.length === 0) {
|
|
49
56
|
return;
|
|
50
57
|
}
|
|
@@ -56,13 +63,11 @@ async function appendToEnvFile(filePath, variables, fileType, options = {}) {
|
|
|
56
63
|
content += "\n";
|
|
57
64
|
content += `${ENV_MARKER_START} Added by StackKit\n`;
|
|
58
65
|
for (const variable of newVariables) {
|
|
59
|
-
|
|
60
|
-
content += `# ${variable.description}\n`;
|
|
61
|
-
}
|
|
62
|
-
const value = fileType === "example" ? variable.value || "" : variable.value || "";
|
|
66
|
+
const value = fileType === "example" ? (variable.value || "") : (variable.value || "");
|
|
63
67
|
content += `${variable.key}=${value}\n`;
|
|
64
68
|
}
|
|
65
69
|
content += `${ENV_MARKER_END}\n`;
|
|
70
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
66
71
|
await fs_extra_1.default.writeFile(filePath, content, "utf-8");
|
|
67
72
|
}
|
|
68
73
|
async function removeEnvVariables(projectRoot, keys) {
|
|
@@ -77,15 +82,35 @@ async function removeFromEnvFile(filePath, keys) {
|
|
|
77
82
|
if (!(await fs_extra_1.default.pathExists(filePath))) {
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
try {
|
|
86
|
+
const content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
87
|
+
const lines = content.split("\n");
|
|
88
|
+
const newLines = [];
|
|
89
|
+
let inStackKitBlock = false;
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
if (line.includes(ENV_MARKER_START)) {
|
|
92
|
+
inStackKitBlock = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (line.includes(ENV_MARKER_END)) {
|
|
96
|
+
inStackKitBlock = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
100
|
+
if (match && keys.includes(match[1])) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!inStackKitBlock || !line.startsWith("#")) {
|
|
104
|
+
newLines.push(line);
|
|
105
|
+
}
|
|
87
106
|
}
|
|
88
|
-
newLines.
|
|
107
|
+
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
|
|
108
|
+
newLines.pop();
|
|
109
|
+
}
|
|
110
|
+
await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger_1.logger.error(`Failed to remove env variables from ${filePath}: ${error}`);
|
|
114
|
+
throw error;
|
|
89
115
|
}
|
|
90
|
-
await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
|
|
91
116
|
}
|
package/dist/utils/files.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export interface PackageJsonConfig {
|
|
2
|
+
dependencies?: Record<string, string>;
|
|
3
|
+
devDependencies?: Record<string, string>;
|
|
4
|
+
scripts?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare function copyBaseFramework(templatesDir: string, targetDir: string, framework: string): Promise<void>;
|
|
7
|
+
export declare function mergePackageJson(targetDir: string, config: PackageJsonConfig): Promise<void>;
|
|
8
|
+
export declare function mergeEnvFile(targetDir: string, envVars: Record<string, string>): Promise<void>;
|
|
1
9
|
export declare function copyTemplate(templatePath: string, targetPath: string, projectName: string): Promise<void>;
|
|
2
10
|
export declare function createFile(targetPath: string, content: string, options?: {
|
|
3
11
|
force?: boolean;
|
package/dist/utils/files.js
CHANGED
|
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.copyBaseFramework = copyBaseFramework;
|
|
7
|
+
exports.mergePackageJson = mergePackageJson;
|
|
8
|
+
exports.mergeEnvFile = mergeEnvFile;
|
|
6
9
|
exports.copyTemplate = copyTemplate;
|
|
7
10
|
exports.createFile = createFile;
|
|
8
11
|
exports.readFile = readFile;
|
|
@@ -10,6 +13,54 @@ exports.fileExists = fileExists;
|
|
|
10
13
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
14
|
const path_1 = __importDefault(require("path"));
|
|
12
15
|
const logger_1 = require("./logger");
|
|
16
|
+
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
17
|
+
const baseDir = path_1.default.join(templatesDir, framework);
|
|
18
|
+
if (!(await fs_extra_1.default.pathExists(baseDir))) {
|
|
19
|
+
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
20
|
+
}
|
|
21
|
+
await fs_extra_1.default.copy(baseDir, targetDir, {
|
|
22
|
+
filter: (src) => {
|
|
23
|
+
const basename = path_1.default.basename(src);
|
|
24
|
+
return !["template.json", "config.json", "node_modules", ".git"].includes(basename);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async function mergePackageJson(targetDir, config) {
|
|
29
|
+
const pkgPath = path_1.default.join(targetDir, "package.json");
|
|
30
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath))) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
34
|
+
if (config.dependencies) {
|
|
35
|
+
pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
|
|
36
|
+
}
|
|
37
|
+
if (config.devDependencies) {
|
|
38
|
+
pkg.devDependencies = { ...pkg.devDependencies, ...config.devDependencies };
|
|
39
|
+
}
|
|
40
|
+
if (config.scripts) {
|
|
41
|
+
pkg.scripts = { ...pkg.scripts, ...config.scripts };
|
|
42
|
+
}
|
|
43
|
+
await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
44
|
+
}
|
|
45
|
+
async function mergeEnvFile(targetDir, envVars) {
|
|
46
|
+
const envPath = path_1.default.join(targetDir, ".env");
|
|
47
|
+
let existingEnv = "";
|
|
48
|
+
if (await fs_extra_1.default.pathExists(envPath)) {
|
|
49
|
+
existingEnv = await fs_extra_1.default.readFile(envPath, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
const envLines = existingEnv.split("\n").filter((line) => line.trim() !== "");
|
|
52
|
+
// Add new variables
|
|
53
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
54
|
+
const existingIndex = envLines.findIndex((line) => line.startsWith(`${key}=`));
|
|
55
|
+
if (existingIndex !== -1) {
|
|
56
|
+
envLines[existingIndex] = `${key}=${value}`;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
envLines.push(`${key}=${value}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
await fs_extra_1.default.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
|
|
63
|
+
}
|
|
13
64
|
async function copyTemplate(templatePath, targetPath, projectName) {
|
|
14
65
|
if (!(await fs_extra_1.default.pathExists(templatePath))) {
|
|
15
66
|
throw new Error(`Template not found: ${templatePath}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function convertToJavaScript(targetDir: string, framework: string): Promise<void>;
|
|
@@ -0,0 +1,244 @@
|
|
|
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.convertToJavaScript = convertToJavaScript;
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const baseDirs = {
|
|
11
|
+
express: "./src",
|
|
12
|
+
"react-vite": "./src",
|
|
13
|
+
nextjs: ".",
|
|
14
|
+
};
|
|
15
|
+
async function convertToJavaScript(targetDir, framework) {
|
|
16
|
+
const tsFiles = [
|
|
17
|
+
"tsconfig.json",
|
|
18
|
+
"tsconfig.app.json",
|
|
19
|
+
"tsconfig.node.json",
|
|
20
|
+
"next-env.d.ts",
|
|
21
|
+
"vite-env.d.ts",
|
|
22
|
+
];
|
|
23
|
+
for (const file of tsFiles) {
|
|
24
|
+
const filePath = path_1.default.join(targetDir, file);
|
|
25
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
26
|
+
await fs_extra_1.default.remove(filePath);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const removeDtsFiles = async (dir) => {
|
|
30
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
33
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
34
|
+
await removeDtsFiles(fullPath);
|
|
35
|
+
}
|
|
36
|
+
else if (entry.isFile() && entry.name.endsWith(".d.ts")) {
|
|
37
|
+
await fs_extra_1.default.remove(fullPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
await removeDtsFiles(targetDir);
|
|
42
|
+
const babel = require("@babel/core");
|
|
43
|
+
const transpileAllTsFiles = async (dir) => {
|
|
44
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
47
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
48
|
+
await transpileAllTsFiles(fullPath);
|
|
49
|
+
}
|
|
50
|
+
else if (entry.isFile()) {
|
|
51
|
+
if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
52
|
+
const code = await fs_extra_1.default.readFile(fullPath, "utf8");
|
|
53
|
+
const isTsx = entry.name.endsWith(".tsx");
|
|
54
|
+
const outFile = fullPath.replace(/\.tsx$/, ".jsx").replace(/\.ts$/, ".js");
|
|
55
|
+
const presets = [
|
|
56
|
+
[
|
|
57
|
+
require.resolve("@babel/preset-typescript"),
|
|
58
|
+
{
|
|
59
|
+
onlyRemoveTypeImports: true,
|
|
60
|
+
allowDeclareFields: true,
|
|
61
|
+
allowNamespaces: true,
|
|
62
|
+
optimizeForSpeed: true,
|
|
63
|
+
allExtensions: true,
|
|
64
|
+
isTSX: isTsx,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
require.resolve("@babel/preset-env"),
|
|
69
|
+
{
|
|
70
|
+
targets: { node: "18" },
|
|
71
|
+
modules: false,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
];
|
|
75
|
+
if (isTsx) {
|
|
76
|
+
presets.push([
|
|
77
|
+
require.resolve("@babel/preset-react"),
|
|
78
|
+
{
|
|
79
|
+
runtime: "classic",
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
// Use recast + Babel AST transform (same approach as transform.tools)
|
|
84
|
+
try {
|
|
85
|
+
const recast = require("recast");
|
|
86
|
+
const { transformFromAstSync } = require("@babel/core");
|
|
87
|
+
const transformTypescript = require("@babel/plugin-transform-typescript");
|
|
88
|
+
// getBabelOptions may be exported as default or directly
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
let getBabelOptions = require("recast/parsers/_babel_options");
|
|
91
|
+
if (getBabelOptions && getBabelOptions.default)
|
|
92
|
+
getBabelOptions = getBabelOptions.default;
|
|
93
|
+
const babelParser = require("recast/parsers/babel").parser;
|
|
94
|
+
const ast = recast.parse(code, {
|
|
95
|
+
parser: {
|
|
96
|
+
parse: (source, options) => {
|
|
97
|
+
const babelOptions = getBabelOptions(options || {});
|
|
98
|
+
// ensure typescript and jsx handling
|
|
99
|
+
if (isTsx) {
|
|
100
|
+
babelOptions.plugins.push("typescript", "jsx");
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
babelOptions.plugins.push("typescript");
|
|
104
|
+
}
|
|
105
|
+
return babelParser.parse(source, babelOptions);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const opts = {
|
|
110
|
+
cloneInputAst: false,
|
|
111
|
+
code: false,
|
|
112
|
+
ast: true,
|
|
113
|
+
plugins: [transformTypescript],
|
|
114
|
+
configFile: false,
|
|
115
|
+
};
|
|
116
|
+
const { ast: transformedAST } = transformFromAstSync(ast, code, opts);
|
|
117
|
+
const resultCode = recast.print(transformedAST).code;
|
|
118
|
+
await fs_extra_1.default.writeFile(outFile, resultCode, "utf8");
|
|
119
|
+
await fs_extra_1.default.remove(fullPath);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// ignore recast errors, fall back to babel
|
|
124
|
+
}
|
|
125
|
+
const result = await babel.transformAsync(code, {
|
|
126
|
+
filename: entry.name,
|
|
127
|
+
presets,
|
|
128
|
+
comments: true,
|
|
129
|
+
retainLines: true,
|
|
130
|
+
compact: false,
|
|
131
|
+
babelrc: false,
|
|
132
|
+
configFile: false,
|
|
133
|
+
});
|
|
134
|
+
await fs_extra_1.default.writeFile(outFile, result.code, "utf8");
|
|
135
|
+
await fs_extra_1.default.remove(fullPath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
await transpileAllTsFiles(targetDir);
|
|
141
|
+
const baseDir = baseDirs[framework];
|
|
142
|
+
if (baseDir) {
|
|
143
|
+
const replaceAliases = async (dir) => {
|
|
144
|
+
const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const full = path_1.default.join(dir, entry.name);
|
|
147
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
148
|
+
await replaceAliases(full);
|
|
149
|
+
}
|
|
150
|
+
else if (entry.isFile() && (entry.name.endsWith(".js") || entry.name.endsWith(".jsx"))) {
|
|
151
|
+
const content = await fs_extra_1.default.readFile(full, "utf-8");
|
|
152
|
+
if (content.includes("@/")) {
|
|
153
|
+
const fileDir = path_1.default.dirname(full);
|
|
154
|
+
const newContent = content.replace(/from ['"]@\/([^'"]*)['"]/g, (match, p1) => {
|
|
155
|
+
const resolved = path_1.default.resolve(baseDir, p1);
|
|
156
|
+
let relPath = path_1.default.relative(fileDir, resolved);
|
|
157
|
+
if (!relPath.startsWith("."))
|
|
158
|
+
relPath = "./" + relPath;
|
|
159
|
+
return `from '${relPath}'`;
|
|
160
|
+
});
|
|
161
|
+
await fs_extra_1.default.writeFile(full, newContent, "utf-8");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
await replaceAliases(targetDir);
|
|
167
|
+
}
|
|
168
|
+
const templatesRoot = path_1.default.join(__dirname, "..", "..", "..", "templates");
|
|
169
|
+
const templateName = framework;
|
|
170
|
+
let fileReplacements = [];
|
|
171
|
+
let jsScripts = null;
|
|
172
|
+
if (templateName) {
|
|
173
|
+
const templateJsonPath = path_1.default.join(templatesRoot, templateName, "template.json");
|
|
174
|
+
if (await fs_extra_1.default.pathExists(templateJsonPath)) {
|
|
175
|
+
try {
|
|
176
|
+
const templateJson = await fs_extra_1.default.readJson(templateJsonPath);
|
|
177
|
+
if (Array.isArray(templateJson.fileReplacements)) {
|
|
178
|
+
fileReplacements = templateJson.fileReplacements;
|
|
179
|
+
}
|
|
180
|
+
if (templateJson.jsScripts) {
|
|
181
|
+
jsScripts = templateJson.jsScripts;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// ignore errors reading template.json
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const rep of fileReplacements) {
|
|
190
|
+
const filePath = path_1.default.join(targetDir, rep.file);
|
|
191
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
192
|
+
let content = await fs_extra_1.default.readFile(filePath, "utf8");
|
|
193
|
+
if (rep.from && rep.to) {
|
|
194
|
+
content = content.replace(rep.from, rep.to);
|
|
195
|
+
await fs_extra_1.default.writeFile(filePath, content, "utf8");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (jsScripts) {
|
|
200
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
201
|
+
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
202
|
+
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
203
|
+
packageJson.scripts = { ...packageJson.scripts, ...jsScripts };
|
|
204
|
+
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const jsconfig = path_1.default.join(targetDir, "jsconfig.json");
|
|
208
|
+
if (!(await fs_extra_1.default.pathExists(jsconfig))) {
|
|
209
|
+
for (const tmpl of await fs_extra_1.default.readdir(templatesRoot, { withFileTypes: true })) {
|
|
210
|
+
if (tmpl.isDirectory()) {
|
|
211
|
+
const templateJsconfig = path_1.default.join(templatesRoot, tmpl.name, "jsconfig.json");
|
|
212
|
+
if (await fs_extra_1.default.pathExists(templateJsconfig)) {
|
|
213
|
+
await fs_extra_1.default.copy(templateJsconfig, jsconfig);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const srcDir = path_1.default.join(targetDir, "src");
|
|
220
|
+
if (await fs_extra_1.default.pathExists(srcDir)) {
|
|
221
|
+
const srcFiles = await fs_extra_1.default.readdir(srcDir);
|
|
222
|
+
for (const file of srcFiles) {
|
|
223
|
+
if ((file.endsWith(".js") || file.endsWith(".jsx")) &&
|
|
224
|
+
file.replace(/\.(js|jsx)$/, ".ts") &&
|
|
225
|
+
srcFiles.includes(file.replace(/\.(js|jsx)$/, ".ts"))) {
|
|
226
|
+
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.(js|jsx)$/, ".ts")));
|
|
227
|
+
}
|
|
228
|
+
if (file.endsWith(".jsx") && srcFiles.includes(file.replace(/\.jsx$/, ".tsx"))) {
|
|
229
|
+
await fs_extra_1.default.remove(path_1.default.join(srcDir, file.replace(/\.jsx$/, ".tsx")));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const packageJsonPath = path_1.default.join(targetDir, "package.json");
|
|
234
|
+
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
235
|
+
const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
|
|
236
|
+
if (packageJson.devDependencies) {
|
|
237
|
+
delete packageJson.devDependencies["typescript"];
|
|
238
|
+
delete packageJson.devDependencies["@types/node"];
|
|
239
|
+
delete packageJson.devDependencies["@types/react"];
|
|
240
|
+
delete packageJson.devDependencies["@types/react-dom"];
|
|
241
|
+
}
|
|
242
|
+
await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function mergeDatabaseConfig(templatesDir: string, targetDir: string, database: string, framework: string, dbProvider?: string): Promise<string[]>;
|
|
2
|
+
export declare function mergeAuthConfig(templatesDir: string, targetDir: string, framework: string, auth: string, database?: string, dbProvider?: string): Promise<void>;
|