stackkit 0.2.6 → 0.2.8
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 +87 -12
- package/dist/cli/create.js +1 -2
- package/dist/cli/list.js +73 -83
- package/dist/index.js +25 -44
- package/dist/lib/constants.d.ts +110 -0
- package/dist/lib/constants.js +112 -0
- package/dist/lib/env/env-editor.js +41 -47
- package/dist/lib/fs/files.d.ts +0 -1
- package/dist/lib/fs/files.js +12 -40
- package/dist/lib/generation/code-generator.d.ts +4 -1
- package/dist/lib/generation/code-generator.js +36 -10
- package/dist/lib/pm/package-manager.d.ts +1 -1
- package/dist/lib/pm/package-manager.js +130 -14
- package/dist/lib/ui/logger.d.ts +8 -1
- package/dist/lib/ui/logger.js +60 -3
- package/dist/lib/utils/fs-helpers.d.ts +12 -0
- package/dist/lib/utils/fs-helpers.js +61 -0
- package/dist/lib/utils/json-loader.d.ts +6 -0
- package/dist/lib/utils/json-loader.js +34 -0
- package/dist/lib/utils/module-loader.d.ts +9 -0
- package/dist/lib/utils/module-loader.js +98 -0
- package/dist/lib/utils/package-root.d.ts +1 -0
- package/dist/lib/utils/package-root.js +75 -2
- package/dist/lib/utils/path-resolver.d.ts +9 -0
- package/dist/lib/utils/path-resolver.js +44 -0
- package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +3 -0
- package/modules/auth/authjs/files/nextjs/proxy.ts +1 -0
- package/modules/auth/authjs/files/shared/lib/auth.ts +119 -0
- package/modules/auth/authjs/files/{prisma → shared/prisma}/schema.prisma +11 -1
- package/modules/auth/authjs/generator.json +18 -8
- package/modules/auth/better-auth/files/express/middlewares/authorize.ts +54 -0
- package/modules/auth/better-auth/files/express/types/express.d.ts +16 -0
- package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +31 -0
- package/modules/auth/better-auth/files/nextjs/proxy.ts +34 -0
- package/modules/auth/better-auth/files/{lib → shared/lib}/auth-client.ts +1 -1
- package/modules/auth/better-auth/files/{lib → shared/lib}/auth.ts +46 -20
- package/modules/auth/better-auth/files/{prisma → shared/prisma}/schema.prisma +11 -2
- package/modules/auth/better-auth/generator.json +74 -19
- package/modules/database/mongoose/generator.json +16 -2
- package/modules/database/prisma/files/lib/prisma.ts +1 -1
- package/modules/database/prisma/files/prisma/schema.prisma +1 -2
- package/modules/database/prisma/generator.json +8 -1
- package/package.json +2 -2
- package/templates/express/env.example +2 -1
- package/templates/express/package.json +3 -4
- package/templates/express/src/app.ts +18 -25
- package/templates/express/src/config/cors.ts +12 -0
- package/templates/express/src/config/helmet.ts +5 -0
- package/templates/express/src/config/logger.ts +6 -0
- package/templates/express/src/config/rate-limit.ts +11 -0
- package/templates/express/src/{features → modules}/health/health.route.ts +1 -1
- package/templates/express/src/routes/index.ts +12 -0
- package/templates/express/src/shared/errors/api-error.ts +14 -0
- package/templates/express/src/shared/errors/error-codes.ts +9 -0
- package/templates/express/src/shared/logger/logger.ts +20 -0
- package/templates/express/src/{middlewares → shared/middlewares}/error.middleware.ts +1 -1
- package/templates/express/src/shared/middlewares/not-found.middleware.ts +9 -0
- package/templates/express/src/shared/utils/async-handler.ts +9 -0
- package/templates/express/src/shared/utils/pagination.ts +6 -0
- package/templates/express/src/shared/utils/response.ts +9 -0
- package/templates/express/tsconfig.json +9 -3
- package/templates/react/src/app/layouts/dashboard-layout.tsx +8 -0
- package/templates/react/src/app/layouts/public-layout.tsx +5 -0
- package/templates/react/src/app/providers.tsx +20 -0
- package/templates/react/src/app/router.tsx +21 -0
- package/templates/react/src/{pages/About.tsx → features/about/pages/about.tsx} +1 -1
- package/templates/react/src/{pages/Home.tsx → features/home/pages/home.tsx} +1 -1
- package/templates/react/src/main.tsx +2 -2
- package/templates/react/src/{api/client.ts → shared/api/http.ts} +1 -1
- package/templates/react/src/{pages/NotFound.tsx → shared/pages/not-found.tsx} +1 -1
- package/dist/lib/git-utils.d.ts +0 -1
- package/dist/lib/git-utils.js +0 -29
- package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +0 -2
- package/modules/auth/authjs/files/lib/auth.ts +0 -22
- /package/modules/auth/better-auth/files/{api → nextjs/api}/auth/[...all]/route.ts +0 -0
- /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-service.ts +0 -0
- /package/modules/auth/better-auth/files/{lib → shared/lib/email}/email-templates.ts +0 -0
- /package/templates/express/src/{features → modules}/health/health.controller.ts +0 -0
- /package/templates/express/src/{features → modules}/health/health.service.ts +0 -0
- /package/templates/react/src/{components/ErrorBoundary.tsx → shared/components/error-boundary.tsx} +0 -0
- /package/templates/react/src/{components/Layout.tsx → shared/components/layout.tsx} +0 -0
- /package/templates/react/src/{components/Loading.tsx → shared/components/loading.tsx} +0 -0
- /package/templates/react/src/{components/SEO.tsx → shared/components/seo.tsx} +0 -0
- /package/templates/react/src/{lib/queryClient.ts → shared/lib/query-client.ts} +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_LANGUAGE = exports.DEFAULT_PACKAGE_MANAGER = exports.PACKAGE_NAME = exports.CLI_COLORS = exports.SUCCESS_MESSAGES = exports.ERROR_MESSAGES = exports.DISPLAY_NAMES = exports.ROUTER_TYPES = exports.ENV_PATTERNS = exports.RETRY_CONFIG = exports.TIMEOUTS = exports.EXCLUDE_FROM_COPY = exports.ENV_FILES = exports.FILE_NAMES = exports.DIRECTORY_NAMES = exports.MODULE_CATEGORIES = exports.LANGUAGES = exports.LOCK_FILES_ARRAY = exports.LOCK_FILES = exports.PACKAGE_MANAGERS = void 0;
|
|
4
|
+
exports.PACKAGE_MANAGERS = {
|
|
5
|
+
PNPM: "pnpm",
|
|
6
|
+
NPM: "npm",
|
|
7
|
+
YARN: "yarn",
|
|
8
|
+
BUN: "bun",
|
|
9
|
+
};
|
|
10
|
+
exports.LOCK_FILES = {
|
|
11
|
+
[exports.PACKAGE_MANAGERS.PNPM]: "pnpm-lock.yaml",
|
|
12
|
+
[exports.PACKAGE_MANAGERS.YARN]: "yarn.lock",
|
|
13
|
+
[exports.PACKAGE_MANAGERS.BUN]: "bun.lockb",
|
|
14
|
+
[exports.PACKAGE_MANAGERS.NPM]: "package-lock.json",
|
|
15
|
+
};
|
|
16
|
+
// Array of lock files with package managers for iteration
|
|
17
|
+
exports.LOCK_FILES_ARRAY = [
|
|
18
|
+
{ file: exports.LOCK_FILES.pnpm, pm: exports.PACKAGE_MANAGERS.PNPM },
|
|
19
|
+
{ file: exports.LOCK_FILES.yarn, pm: exports.PACKAGE_MANAGERS.YARN },
|
|
20
|
+
{ file: exports.LOCK_FILES.bun, pm: exports.PACKAGE_MANAGERS.BUN },
|
|
21
|
+
{ file: exports.LOCK_FILES.npm, pm: exports.PACKAGE_MANAGERS.NPM },
|
|
22
|
+
];
|
|
23
|
+
exports.LANGUAGES = {
|
|
24
|
+
TYPESCRIPT: "typescript",
|
|
25
|
+
JAVASCRIPT: "javascript",
|
|
26
|
+
};
|
|
27
|
+
exports.MODULE_CATEGORIES = {
|
|
28
|
+
DATABASE: "database",
|
|
29
|
+
AUTH: "auth",
|
|
30
|
+
FRAMEWORK: "framework",
|
|
31
|
+
};
|
|
32
|
+
exports.DIRECTORY_NAMES = {
|
|
33
|
+
MODULES: "modules",
|
|
34
|
+
TEMPLATES: "templates",
|
|
35
|
+
NODE_MODULES: "node_modules",
|
|
36
|
+
GIT: ".git",
|
|
37
|
+
PRISMA: "prisma",
|
|
38
|
+
FILES: "files",
|
|
39
|
+
SRC: "src",
|
|
40
|
+
DIST: "dist",
|
|
41
|
+
BIN: "bin",
|
|
42
|
+
};
|
|
43
|
+
exports.FILE_NAMES = {
|
|
44
|
+
PACKAGE_JSON: "package.json",
|
|
45
|
+
TSCONFIG_JSON: "tsconfig.json",
|
|
46
|
+
MODULE_JSON: "module.json",
|
|
47
|
+
GENERATOR_JSON: "generator.json",
|
|
48
|
+
TEMPLATE_JSON: "template.json",
|
|
49
|
+
CONFIG_JSON: "config.json",
|
|
50
|
+
ENV: ".env",
|
|
51
|
+
ENV_LOCAL: ".env.local",
|
|
52
|
+
ENV_EXAMPLE: ".env.example",
|
|
53
|
+
GITIGNORE: ".gitignore",
|
|
54
|
+
README: "README.md",
|
|
55
|
+
SCHEMA_PRISMA: "schema.prisma",
|
|
56
|
+
};
|
|
57
|
+
exports.ENV_FILES = [exports.FILE_NAMES.ENV, exports.FILE_NAMES.ENV_LOCAL];
|
|
58
|
+
exports.EXCLUDE_FROM_COPY = [
|
|
59
|
+
exports.FILE_NAMES.TEMPLATE_JSON,
|
|
60
|
+
exports.FILE_NAMES.CONFIG_JSON,
|
|
61
|
+
exports.DIRECTORY_NAMES.NODE_MODULES,
|
|
62
|
+
exports.DIRECTORY_NAMES.GIT,
|
|
63
|
+
];
|
|
64
|
+
exports.TIMEOUTS = {
|
|
65
|
+
PACKAGE_INSTALL: 300000, // 5 minutes in milliseconds
|
|
66
|
+
GIT_INIT: 30000, // 30 seconds
|
|
67
|
+
RETRY_DELAY_BASE: 1000, // 1 second base delay for retries
|
|
68
|
+
};
|
|
69
|
+
exports.RETRY_CONFIG = {
|
|
70
|
+
MAX_ATTEMPTS: 2,
|
|
71
|
+
PACKAGE_ROOT_MAX_ATTEMPTS: 10,
|
|
72
|
+
};
|
|
73
|
+
exports.ENV_PATTERNS = {
|
|
74
|
+
KEY: /^[A-Z_][A-Z0-9_]*$/,
|
|
75
|
+
COMMENT: /^\s*#/,
|
|
76
|
+
};
|
|
77
|
+
exports.ROUTER_TYPES = {
|
|
78
|
+
APP: "app",
|
|
79
|
+
PAGES: "pages",
|
|
80
|
+
};
|
|
81
|
+
exports.DISPLAY_NAMES = {
|
|
82
|
+
[exports.MODULE_CATEGORIES.DATABASE]: "Database",
|
|
83
|
+
[exports.MODULE_CATEGORIES.AUTH]: "Auth",
|
|
84
|
+
[exports.MODULE_CATEGORIES.FRAMEWORK]: "Framework",
|
|
85
|
+
};
|
|
86
|
+
exports.ERROR_MESSAGES = {
|
|
87
|
+
NO_PACKAGE_JSON: "No package.json found in current directory or any parent directory.",
|
|
88
|
+
INVALID_DIRECTORY: "Target directory already exists and is not empty.",
|
|
89
|
+
INVALID_PROJECT_NAME: "Invalid project name. Please use a valid npm package name.",
|
|
90
|
+
UNKNOWN_MODULE_TYPE: (module) => `Unknown module type "${module}". Use "${exports.MODULE_CATEGORIES.DATABASE}" or "${exports.MODULE_CATEGORIES.AUTH}", or specify a provider directly.`,
|
|
91
|
+
MODULE_NOT_FOUND: (moduleName) => `Module "${moduleName}" not found.`,
|
|
92
|
+
TEMPLATE_NOT_FOUND: (framework) => `Base template not found for framework: ${framework}`,
|
|
93
|
+
GIT_INIT_FAILED: "Failed to initialize git repository",
|
|
94
|
+
PACKAGE_INSTALL_FAILED: "Failed to install dependencies",
|
|
95
|
+
};
|
|
96
|
+
exports.SUCCESS_MESSAGES = {
|
|
97
|
+
PROJECT_CREATED: "✨ Project created successfully!",
|
|
98
|
+
MODULE_ADDED: "✨ Module added successfully!",
|
|
99
|
+
DEPENDENCIES_INSTALLED: "✓ Dependencies installed",
|
|
100
|
+
GIT_INITIALIZED: "✓ Git repository initialized",
|
|
101
|
+
};
|
|
102
|
+
exports.CLI_COLORS = {
|
|
103
|
+
PRIMARY: "cyan",
|
|
104
|
+
SUCCESS: "green",
|
|
105
|
+
WARNING: "yellow",
|
|
106
|
+
ERROR: "red",
|
|
107
|
+
INFO: "blue",
|
|
108
|
+
HEADER: "magenta",
|
|
109
|
+
};
|
|
110
|
+
exports.PACKAGE_NAME = "stackkit";
|
|
111
|
+
exports.DEFAULT_PACKAGE_MANAGER = exports.PACKAGE_MANAGERS.NPM;
|
|
112
|
+
exports.DEFAULT_LANGUAGE = exports.LANGUAGES.TYPESCRIPT;
|
|
@@ -7,94 +7,88 @@ exports.addEnvVariables = addEnvVariables;
|
|
|
7
7
|
exports.removeEnvVariables = removeEnvVariables;
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const constants_1 = require("../constants");
|
|
10
11
|
const logger_1 = require("../ui/logger");
|
|
11
12
|
async function addEnvVariables(projectRoot, variables, options = {}) {
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
await appendToEnvFile(envPath, variables,
|
|
13
|
+
validateEnvVariables(variables);
|
|
14
|
+
const envExamplePath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV_EXAMPLE);
|
|
15
|
+
const envPath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV);
|
|
16
|
+
await appendToEnvFile(envExamplePath, variables, options);
|
|
17
|
+
if ((await fs_extra_1.default.pathExists(envPath)) || options.force) {
|
|
18
|
+
await appendToEnvFile(envPath, variables, options);
|
|
18
19
|
}
|
|
19
20
|
logger_1.logger.success("Environment variables added");
|
|
20
21
|
}
|
|
21
|
-
async function
|
|
22
|
-
|
|
22
|
+
async function removeEnvVariables(projectRoot, keys) {
|
|
23
|
+
const envExamplePath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV_EXAMPLE);
|
|
24
|
+
const envPath = path_1.default.join(projectRoot, constants_1.FILE_NAMES.ENV);
|
|
25
|
+
await removeFromEnvFile(envExamplePath, keys);
|
|
26
|
+
if (await fs_extra_1.default.pathExists(envPath)) {
|
|
27
|
+
await removeFromEnvFile(envPath, keys);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function validateEnvVariables(variables) {
|
|
23
31
|
for (const variable of variables) {
|
|
24
|
-
if (
|
|
25
|
-
throw new Error(`Invalid environment variable key: ${variable.key}`
|
|
32
|
+
if (!constants_1.ENV_PATTERNS.KEY.test(variable.key)) {
|
|
33
|
+
throw new Error(`Invalid environment variable key: ${variable.key}. ` +
|
|
34
|
+
`Must match pattern: ${constants_1.ENV_PATTERNS.KEY}`);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
}
|
|
38
|
+
async function appendToEnvFile(filePath, variables, options = {}) {
|
|
28
39
|
let content = "";
|
|
29
40
|
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
30
41
|
content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
31
42
|
}
|
|
32
|
-
// If force, remove existing keys first to avoid duplicates
|
|
33
43
|
if (options.force) {
|
|
34
44
|
const keysToRemove = variables.map((v) => v.key);
|
|
35
45
|
await removeFromEnvFile(filePath, keysToRemove);
|
|
36
|
-
|
|
37
|
-
content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
content = "";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// Check if variables already exist (after potential removal)
|
|
44
|
-
const existingKeys = new Set();
|
|
45
|
-
const lines = content.split("\n");
|
|
46
|
-
for (const line of lines) {
|
|
47
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
48
|
-
if (match) {
|
|
49
|
-
existingKeys.add(match[1]);
|
|
50
|
-
}
|
|
46
|
+
content = (await fs_extra_1.default.pathExists(filePath)) ? await fs_extra_1.default.readFile(filePath, "utf-8") : "";
|
|
51
47
|
}
|
|
48
|
+
const existingKeys = extractExistingKeys(content);
|
|
52
49
|
const newVariables = variables.filter((v) => !existingKeys.has(v.key));
|
|
53
|
-
if (newVariables.length === 0)
|
|
50
|
+
if (newVariables.length === 0)
|
|
54
51
|
return;
|
|
55
|
-
}
|
|
56
|
-
// Ensure file ends with newline
|
|
57
52
|
if (content && !content.endsWith("\n")) {
|
|
58
53
|
content += "\n";
|
|
59
54
|
}
|
|
60
|
-
// Append variables
|
|
61
55
|
for (const variable of newVariables) {
|
|
62
|
-
|
|
63
|
-
content += `${variable.key}=${value}\n`;
|
|
56
|
+
content += `${variable.key}=${variable.value || ""}\n`;
|
|
64
57
|
}
|
|
65
58
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
66
59
|
await fs_extra_1.default.writeFile(filePath, content, "utf-8");
|
|
67
60
|
}
|
|
68
|
-
async function removeEnvVariables(projectRoot, keys) {
|
|
69
|
-
const envExamplePath = path_1.default.join(projectRoot, ".env.example");
|
|
70
|
-
const envPath = path_1.default.join(projectRoot, ".env");
|
|
71
|
-
await removeFromEnvFile(envExamplePath, keys);
|
|
72
|
-
if (await fs_extra_1.default.pathExists(envPath)) {
|
|
73
|
-
await removeFromEnvFile(envPath, keys);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
61
|
async function removeFromEnvFile(filePath, keys) {
|
|
77
|
-
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
62
|
+
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
78
63
|
return;
|
|
79
|
-
}
|
|
80
64
|
try {
|
|
81
65
|
const content = await fs_extra_1.default.readFile(filePath, "utf-8");
|
|
82
66
|
const lines = content.split("\n");
|
|
83
67
|
const newLines = [];
|
|
84
68
|
for (const line of lines) {
|
|
85
69
|
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
86
|
-
if (match
|
|
87
|
-
|
|
70
|
+
if (!match || !keys.includes(match[1])) {
|
|
71
|
+
newLines.push(line);
|
|
88
72
|
}
|
|
89
|
-
newLines.push(line);
|
|
90
73
|
}
|
|
91
74
|
while (newLines.length > 0 && newLines[newLines.length - 1].trim() === "") {
|
|
92
75
|
newLines.pop();
|
|
93
76
|
}
|
|
94
|
-
await fs_extra_1.default.writeFile(filePath, newLines.join("\n"), "utf-8");
|
|
77
|
+
await fs_extra_1.default.writeFile(filePath, newLines.join("\n") + (newLines.length > 0 ? "\n" : ""), "utf-8");
|
|
95
78
|
}
|
|
96
79
|
catch (error) {
|
|
97
|
-
logger_1.logger.error(`Failed to remove env variables from ${filePath}
|
|
80
|
+
logger_1.logger.error(`Failed to remove env variables from ${filePath}`);
|
|
98
81
|
throw error;
|
|
99
82
|
}
|
|
100
83
|
}
|
|
84
|
+
function extractExistingKeys(content) {
|
|
85
|
+
const existingKeys = new Set();
|
|
86
|
+
const lines = content.split("\n");
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
89
|
+
if (match) {
|
|
90
|
+
existingKeys.add(match[1]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return existingKeys;
|
|
94
|
+
}
|
package/dist/lib/fs/files.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ export interface PackageJsonConfig {
|
|
|
6
6
|
export declare function copyBaseFramework(templatesDir: string, targetDir: string, framework: string): Promise<void>;
|
|
7
7
|
export declare function mergePackageJson(targetDir: string, config: PackageJsonConfig): Promise<void>;
|
|
8
8
|
export declare function mergeEnvFile(targetDir: string, envVars: Record<string, string>): Promise<void>;
|
|
9
|
-
export declare function copyTemplate(templatePath: string, targetPath: string, projectName: string): Promise<void>;
|
|
10
9
|
export declare function createFile(targetPath: string, content: string, options?: {
|
|
11
10
|
force?: boolean;
|
|
12
11
|
}): Promise<void>;
|
package/dist/lib/fs/files.js
CHANGED
|
@@ -6,12 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.copyBaseFramework = copyBaseFramework;
|
|
7
7
|
exports.mergePackageJson = mergePackageJson;
|
|
8
8
|
exports.mergeEnvFile = mergeEnvFile;
|
|
9
|
-
exports.copyTemplate = copyTemplate;
|
|
10
9
|
exports.createFile = createFile;
|
|
11
10
|
exports.readFile = readFile;
|
|
12
11
|
exports.fileExists = fileExists;
|
|
13
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const constants_1 = require("../constants");
|
|
15
15
|
const logger_1 = require("../ui/logger");
|
|
16
16
|
async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
17
17
|
const baseDir = path_1.default.join(templatesDir, framework);
|
|
@@ -19,17 +19,13 @@ async function copyBaseFramework(templatesDir, targetDir, framework) {
|
|
|
19
19
|
throw new Error(`Base template not found for framework: ${framework}\n` + `Expected at: ${baseDir}`);
|
|
20
20
|
}
|
|
21
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
|
-
},
|
|
22
|
+
filter: (src) => !constants_1.EXCLUDE_FROM_COPY.includes(path_1.default.basename(src)),
|
|
26
23
|
});
|
|
27
24
|
}
|
|
28
25
|
async function mergePackageJson(targetDir, config) {
|
|
29
|
-
const pkgPath = path_1.default.join(targetDir,
|
|
30
|
-
if (!(await fs_extra_1.default.pathExists(pkgPath)))
|
|
26
|
+
const pkgPath = path_1.default.join(targetDir, constants_1.FILE_NAMES.PACKAGE_JSON);
|
|
27
|
+
if (!(await fs_extra_1.default.pathExists(pkgPath)))
|
|
31
28
|
return;
|
|
32
|
-
}
|
|
33
29
|
const pkg = await fs_extra_1.default.readJson(pkgPath);
|
|
34
30
|
if (config.dependencies) {
|
|
35
31
|
pkg.dependencies = { ...pkg.dependencies, ...config.dependencies };
|
|
@@ -43,46 +39,22 @@ async function mergePackageJson(targetDir, config) {
|
|
|
43
39
|
await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
44
40
|
}
|
|
45
41
|
async function mergeEnvFile(targetDir, envVars) {
|
|
46
|
-
const envPath = path_1.default.join(targetDir,
|
|
47
|
-
let
|
|
42
|
+
const envPath = path_1.default.join(targetDir, constants_1.FILE_NAMES.ENV);
|
|
43
|
+
let content = "";
|
|
48
44
|
if (await fs_extra_1.default.pathExists(envPath)) {
|
|
49
|
-
|
|
45
|
+
content = await fs_extra_1.default.readFile(envPath, "utf-8");
|
|
50
46
|
}
|
|
51
|
-
const
|
|
52
|
-
// Add new variables
|
|
47
|
+
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
53
48
|
for (const [key, value] of Object.entries(envVars)) {
|
|
54
|
-
const existingIndex =
|
|
49
|
+
const existingIndex = lines.findIndex((line) => line.startsWith(`${key}=`));
|
|
55
50
|
if (existingIndex !== -1) {
|
|
56
|
-
|
|
51
|
+
lines[existingIndex] = `${key}=${value}`;
|
|
57
52
|
}
|
|
58
53
|
else {
|
|
59
|
-
|
|
54
|
+
lines.push(`${key}=${value}`);
|
|
60
55
|
}
|
|
61
56
|
}
|
|
62
|
-
await fs_extra_1.default.writeFile(envPath,
|
|
63
|
-
}
|
|
64
|
-
async function copyTemplate(templatePath, targetPath, projectName) {
|
|
65
|
-
if (!(await fs_extra_1.default.pathExists(templatePath))) {
|
|
66
|
-
throw new Error(`Template not found: ${templatePath}`);
|
|
67
|
-
}
|
|
68
|
-
// Create target directory
|
|
69
|
-
await fs_extra_1.default.ensureDir(targetPath);
|
|
70
|
-
// Copy all files
|
|
71
|
-
await fs_extra_1.default.copy(templatePath, targetPath, {
|
|
72
|
-
filter: (src) => {
|
|
73
|
-
const basename = path_1.default.basename(src);
|
|
74
|
-
// Skip template.json metadata file and node_modules
|
|
75
|
-
return basename !== "template.json" && basename !== "node_modules";
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
// Update package.json with project name
|
|
79
|
-
const packageJsonPath = path_1.default.join(targetPath, "package.json");
|
|
80
|
-
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
|
|
81
|
-
const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
|
|
82
|
-
packageJson.name = projectName;
|
|
83
|
-
await fs_extra_1.default.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
84
|
-
}
|
|
85
|
-
logger_1.logger.success(`Template copied to ${targetPath}`);
|
|
57
|
+
await fs_extra_1.default.writeFile(envPath, lines.join("\n") + "\n", "utf-8");
|
|
86
58
|
}
|
|
87
59
|
async function createFile(targetPath, content, options = {}) {
|
|
88
60
|
const exists = await fs_extra_1.default.pathExists(targetPath);
|
|
@@ -4,6 +4,8 @@ export interface GenerationContext {
|
|
|
4
4
|
database?: string;
|
|
5
5
|
auth?: string;
|
|
6
6
|
features?: string[];
|
|
7
|
+
combo?: string;
|
|
8
|
+
prismaProvider?: string;
|
|
7
9
|
[key: string]: unknown;
|
|
8
10
|
}
|
|
9
11
|
export interface TemplateCondition {
|
|
@@ -31,7 +33,7 @@ export interface PatchOperation {
|
|
|
31
33
|
type: "add-import" | "add-code" | "replace-code" | "add-to-top" | "add-to-bottom";
|
|
32
34
|
condition?: TemplateCondition;
|
|
33
35
|
imports?: string[];
|
|
34
|
-
code?: string;
|
|
36
|
+
code?: string | string[];
|
|
35
37
|
after?: string;
|
|
36
38
|
before?: string;
|
|
37
39
|
replace?: string;
|
|
@@ -58,6 +60,7 @@ export declare class AdvancedCodeGenerator {
|
|
|
58
60
|
loadGenerators(modulesPath: string): Promise<void>;
|
|
59
61
|
private evaluateCondition;
|
|
60
62
|
private processTemplate;
|
|
63
|
+
private renderHeadingFromExpr;
|
|
61
64
|
private processVariableDefinitions;
|
|
62
65
|
private processTemplateRecursive;
|
|
63
66
|
generate(selectedModules: {
|
|
@@ -108,6 +108,13 @@ class AdvancedCodeGenerator {
|
|
|
108
108
|
content = content.replace(/\n{3,}/g, "\n\n");
|
|
109
109
|
return content;
|
|
110
110
|
}
|
|
111
|
+
renderHeadingFromExpr(expr, context) {
|
|
112
|
+
const depthVar = expr.substring(8).trim();
|
|
113
|
+
const depthVal = context[depthVar];
|
|
114
|
+
const n = parseInt(String(depthVal || "1"), 10) || 1;
|
|
115
|
+
const level = Math.max(1, Math.min(n, 6));
|
|
116
|
+
return "#".repeat(level);
|
|
117
|
+
}
|
|
111
118
|
processVariableDefinitions(content, context) {
|
|
112
119
|
let result = content;
|
|
113
120
|
let index = 0;
|
|
@@ -223,7 +230,8 @@ class AdvancedCodeGenerator {
|
|
|
223
230
|
break;
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
|
-
|
|
233
|
+
const chosen = (result || defaultCase || "").trim();
|
|
234
|
+
return this.processTemplateRecursive(chosen, context);
|
|
227
235
|
});
|
|
228
236
|
// Handle variable replacement with advanced expressions
|
|
229
237
|
content = content.replace(/\{\{([^}]+)\}\}/g, (match, varExpr) => {
|
|
@@ -252,15 +260,19 @@ class AdvancedCodeGenerator {
|
|
|
252
260
|
const [caseVal, result] = caseStr.split(":").map((s) => s.trim());
|
|
253
261
|
const cleanCaseVal = caseVal.replace(/['"]/g, "");
|
|
254
262
|
if (cleanCaseVal === actualVal || cleanCaseVal === "default") {
|
|
255
|
-
return result.replace(/['"]/g, "");
|
|
263
|
+
return this.processTemplateRecursive(result.replace(/['"]/g, ""), context);
|
|
256
264
|
}
|
|
257
265
|
}
|
|
258
266
|
return "";
|
|
259
267
|
}
|
|
268
|
+
// Handle heading helper {{heading:depthVar}} -> '#', '##', ... up to '######'
|
|
269
|
+
if (trimmedExpr.startsWith("heading:")) {
|
|
270
|
+
return this.renderHeadingFromExpr(trimmedExpr, context);
|
|
271
|
+
}
|
|
260
272
|
// Handle feature flags {{feature:name}}
|
|
261
273
|
if (trimmedExpr.startsWith("feature:")) {
|
|
262
|
-
const featureName = trimmedExpr.substring(8);
|
|
263
|
-
const features = context.features
|
|
274
|
+
const featureName = trimmedExpr.substring(8).trim();
|
|
275
|
+
const features = Array.isArray(context.features) ? context.features : [];
|
|
264
276
|
return features.includes(featureName) ? "true" : "false";
|
|
265
277
|
}
|
|
266
278
|
// Handle conditional expressions {{if condition then:value else:value}}
|
|
@@ -290,6 +302,8 @@ class AdvancedCodeGenerator {
|
|
|
290
302
|
...selectedModules,
|
|
291
303
|
features,
|
|
292
304
|
};
|
|
305
|
+
// Derived combined key to simplify template conditionals (e.g. "prisma:express")
|
|
306
|
+
context.combo = `${context.database || ""}:${context.framework || ""}`;
|
|
293
307
|
// Set default prismaProvider if database is prisma but no provider specified
|
|
294
308
|
if (selectedModules.database === "prisma" && !context.prismaProvider) {
|
|
295
309
|
const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
@@ -505,9 +519,6 @@ class AdvancedCodeGenerator {
|
|
|
505
519
|
if (processed.content) {
|
|
506
520
|
processed.content = this.processTemplate(processed.content, context);
|
|
507
521
|
}
|
|
508
|
-
if (processed.destination) {
|
|
509
|
-
processed.destination = this.processTemplate(processed.destination, context);
|
|
510
|
-
}
|
|
511
522
|
// Process templates in patch operations
|
|
512
523
|
if (processed.operations) {
|
|
513
524
|
processed.operations = processed.operations.map((op) => {
|
|
@@ -516,7 +527,14 @@ class AdvancedCodeGenerator {
|
|
|
516
527
|
processedOp.imports = processedOp.imports.map((imp) => this.processTemplate(imp, context));
|
|
517
528
|
}
|
|
518
529
|
if (processedOp.code) {
|
|
519
|
-
|
|
530
|
+
if (Array.isArray(processedOp.code)) {
|
|
531
|
+
processedOp.code = processedOp.code
|
|
532
|
+
.map((c) => this.processTemplate(c, context))
|
|
533
|
+
.join("\n");
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
processedOp.code = this.processTemplate(processedOp.code, context);
|
|
537
|
+
}
|
|
520
538
|
}
|
|
521
539
|
if (processedOp.after) {
|
|
522
540
|
processedOp.after = this.processTemplate(processedOp.after, context);
|
|
@@ -648,7 +666,10 @@ class AdvancedCodeGenerator {
|
|
|
648
666
|
break;
|
|
649
667
|
case "add-code":
|
|
650
668
|
if (patchOp.code) {
|
|
651
|
-
|
|
669
|
+
let codeValue = patchOp.code;
|
|
670
|
+
if (Array.isArray(codeValue))
|
|
671
|
+
codeValue = codeValue.join("\n");
|
|
672
|
+
const processedCode = this.processTemplate(codeValue, context);
|
|
652
673
|
// Skip insertion if the exact code already exists in the file
|
|
653
674
|
const codeTrimmed = processedCode.trim();
|
|
654
675
|
if (codeTrimmed && content.includes(codeTrimmed)) {
|
|
@@ -696,7 +717,10 @@ class AdvancedCodeGenerator {
|
|
|
696
717
|
break;
|
|
697
718
|
case "replace-code":
|
|
698
719
|
if (patchOp.code && patchOp.replace) {
|
|
699
|
-
|
|
720
|
+
let codeValue2 = patchOp.code;
|
|
721
|
+
if (Array.isArray(codeValue2))
|
|
722
|
+
codeValue2 = codeValue2.join("\n");
|
|
723
|
+
const processedCode = this.processTemplate(codeValue2, context);
|
|
700
724
|
const replacePattern = this.processTemplate(patchOp.replace, context);
|
|
701
725
|
content = content.replace(replacePattern, processedCode);
|
|
702
726
|
}
|
|
@@ -845,6 +869,8 @@ class AdvancedCodeGenerator {
|
|
|
845
869
|
...selectedModules,
|
|
846
870
|
features,
|
|
847
871
|
};
|
|
872
|
+
// Derived combined key to simplify template conditionals (e.g. "prisma:express")
|
|
873
|
+
context.combo = `${context.database || ""}:${context.framework || ""}`;
|
|
848
874
|
if (selectedModules.database === "prisma" && !context.prismaProvider) {
|
|
849
875
|
const providers = (0, shared_1.getPrismaProvidersFromGenerator)((0, package_root_1.getPackageRoot)());
|
|
850
876
|
if (providers && providers.length > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type PackageManager = "npm" | "yarn" | "pnpm" | "bun";
|
|
2
2
|
export declare function detectPackageManager(cwd: string): Promise<PackageManager>;
|
|
3
|
-
export declare function installDependencies(cwd: string, pm: PackageManager): Promise<void>;
|
|
3
|
+
export declare function installDependencies(cwd: string, pm: PackageManager, maxRetries?: number): Promise<void>;
|
|
4
4
|
export declare function addDependencies(cwd: string, pm: PackageManager, packages: string[], dev?: boolean): Promise<void>;
|
|
5
5
|
export declare function initGit(cwd: string): Promise<void>;
|