@stratum-hq/create 0.2.3 → 0.3.0
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.js +1245 -37
- package/package.json +21 -1
- package/src/__tests__/matrix.test.ts +283 -0
- package/src/__tests__/preset.test.ts +266 -0
- package/src/generators/db-setup.ts +320 -0
- package/src/generators/docker-compose.ts +98 -0
- package/src/generators/init-sql.ts +57 -0
- package/src/generators/middleware.ts +267 -0
- package/src/generators/package-json.ts +174 -0
- package/src/generators/readme.ts +119 -0
- package/src/index.ts +61 -11
- package/src/matrix.ts +186 -0
- package/src/preset-project.ts +148 -0
package/src/index.ts
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
|
+
import crypto from "node:crypto";
|
|
5
6
|
import { execSync } from "child_process";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { parsePresetString, isValidPreset, formatPresetString } from "./matrix.js";
|
|
9
|
+
import { createPresetProject } from "./preset-project.js";
|
|
7
10
|
|
|
8
11
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
12
|
|
|
@@ -12,6 +15,7 @@ export type Template = "express" | "fastify" | "nextjs";
|
|
|
12
15
|
export interface ParsedArgs {
|
|
13
16
|
projectName: string | null;
|
|
14
17
|
template: Template;
|
|
18
|
+
preset: string | null;
|
|
15
19
|
skipInstall: boolean;
|
|
16
20
|
force: boolean;
|
|
17
21
|
}
|
|
@@ -23,8 +27,10 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
23
27
|
const args = [...argv];
|
|
24
28
|
let projectName: string | null = null;
|
|
25
29
|
let template: Template = "express";
|
|
30
|
+
let preset: string | null = null;
|
|
26
31
|
let skipInstall = false;
|
|
27
32
|
let force = false;
|
|
33
|
+
let templateExplicit = false;
|
|
28
34
|
|
|
29
35
|
for (let i = 0; i < args.length; i++) {
|
|
30
36
|
const arg = args[i];
|
|
@@ -36,6 +42,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
36
42
|
const val = args[i + 1];
|
|
37
43
|
if (val === "express" || val === "fastify" || val === "nextjs") {
|
|
38
44
|
template = val;
|
|
45
|
+
templateExplicit = true;
|
|
39
46
|
i++;
|
|
40
47
|
} else {
|
|
41
48
|
console.error(`Unknown template: ${val}. Available: express, fastify, nextjs`);
|
|
@@ -45,16 +52,27 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
45
52
|
const val = arg.slice("--template=".length);
|
|
46
53
|
if (val === "express" || val === "fastify" || val === "nextjs") {
|
|
47
54
|
template = val;
|
|
55
|
+
templateExplicit = true;
|
|
48
56
|
} else {
|
|
49
57
|
console.error(`Unknown template: ${val}. Available: express, fastify, nextjs`);
|
|
50
58
|
process.exit(1);
|
|
51
59
|
}
|
|
60
|
+
} else if (arg === "--preset") {
|
|
61
|
+
preset = args[i + 1] || null;
|
|
62
|
+
i++;
|
|
63
|
+
} else if (arg.startsWith("--preset=")) {
|
|
64
|
+
preset = arg.slice("--preset=".length) || null;
|
|
52
65
|
} else if (!arg.startsWith("--")) {
|
|
53
66
|
projectName = arg;
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
if (preset && templateExplicit) {
|
|
71
|
+
console.error("Error: --template and --preset cannot be used together. Use one or the other.");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { projectName, template, preset, skipInstall, force };
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
// ─── Usage ────────────────────────────────────────────────────────────────────
|
|
@@ -64,13 +82,16 @@ function printUsage(): void {
|
|
|
64
82
|
console.log("");
|
|
65
83
|
console.log("Options:");
|
|
66
84
|
console.log(" --template <express|fastify|nextjs> Framework template (default: express)");
|
|
85
|
+
console.log(" --preset <db-strategy-orm-framework> Full stack preset (e.g. postgres-rls-prisma-express)");
|
|
67
86
|
console.log(" --skip-install Skip npm install");
|
|
68
87
|
console.log(" --force Overwrite existing directory");
|
|
69
88
|
console.log("");
|
|
70
89
|
console.log("Examples:");
|
|
71
90
|
console.log(" npx @stratum-hq/create my-app");
|
|
72
91
|
console.log(" npx @stratum-hq/create my-app --template fastify");
|
|
73
|
-
console.log(" npx @stratum-hq/create my-app --
|
|
92
|
+
console.log(" npx @stratum-hq/create my-app --preset postgres-rls-prisma-express");
|
|
93
|
+
console.log(" npx @stratum-hq/create my-app --preset mongodb-database-mongoose-hono");
|
|
94
|
+
console.log(" npx @stratum-hq/create my-app --preset mysql-table-prefix-sequelize-nestjs");
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
// ─── File generation helpers ──────────────────────────────────────────────────
|
|
@@ -96,11 +117,11 @@ function generatePackageJson(projectName: string, template: Template): string {
|
|
|
96
117
|
fastify: "^4.26.0",
|
|
97
118
|
},
|
|
98
119
|
nextjs: {
|
|
99
|
-
next: "^
|
|
100
|
-
react: "^
|
|
101
|
-
"react-dom": "^
|
|
102
|
-
"@types/react": "^
|
|
103
|
-
"@types/react-dom": "^
|
|
120
|
+
next: "^15.5.16",
|
|
121
|
+
react: "^19.0.0",
|
|
122
|
+
"react-dom": "^19.0.0",
|
|
123
|
+
"@types/react": "^19.0.0",
|
|
124
|
+
"@types/react-dom": "^19.0.0",
|
|
104
125
|
},
|
|
105
126
|
};
|
|
106
127
|
|
|
@@ -178,6 +199,7 @@ COMMENT ON DATABASE ${dbName} IS 'Multi-tenant database for ${projectName}';
|
|
|
178
199
|
|
|
179
200
|
function generateEnv(projectName: string): string {
|
|
180
201
|
const dbName = projectName.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
202
|
+
const jwtSecret = crypto.randomBytes(32).toString("base64url");
|
|
181
203
|
return `# Environment variables for ${projectName}
|
|
182
204
|
# Copy to .env and fill in values
|
|
183
205
|
|
|
@@ -185,7 +207,7 @@ function generateEnv(projectName: string): string {
|
|
|
185
207
|
DATABASE_URL=postgres://${dbName}:dev_password@localhost:5432/${dbName}
|
|
186
208
|
|
|
187
209
|
# Authentication
|
|
188
|
-
JWT_SECRET
|
|
210
|
+
JWT_SECRET=${jwtSecret}
|
|
189
211
|
|
|
190
212
|
# Stratum Control Plane
|
|
191
213
|
STRATUM_URL=http://localhost:3001
|
|
@@ -436,7 +458,7 @@ export function validateProjectName(projectName: string): string | null {
|
|
|
436
458
|
// ─── Main entry point ─────────────────────────────────────────────────────────
|
|
437
459
|
|
|
438
460
|
export function main(argv: string[]): void {
|
|
439
|
-
const { projectName, template, skipInstall, force } = parseArgs(argv);
|
|
461
|
+
const { projectName, template, preset, skipInstall, force } = parseArgs(argv);
|
|
440
462
|
|
|
441
463
|
if (!projectName) {
|
|
442
464
|
printUsage();
|
|
@@ -463,7 +485,26 @@ export function main(argv: string[]): void {
|
|
|
463
485
|
|
|
464
486
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
465
487
|
|
|
466
|
-
|
|
488
|
+
// Preset path: full stack generation
|
|
489
|
+
if (preset) {
|
|
490
|
+
const parsed = parsePresetString(preset);
|
|
491
|
+
if (!parsed) {
|
|
492
|
+
console.error(`Error: Invalid preset string "${preset}".`);
|
|
493
|
+
console.error("Format: {database}-{strategy}-{orm}-{framework}");
|
|
494
|
+
console.error("Example: postgres-rls-prisma-express, mongodb-database-mongoose-hono");
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
if (!isValidPreset(parsed)) {
|
|
498
|
+
console.error(`Error: Invalid preset combination "${preset}".`);
|
|
499
|
+
console.error(`${parsed.database} does not support ${parsed.strategy}/${parsed.orm} together.`);
|
|
500
|
+
console.error("Run with --help to see valid combinations.");
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
createPresetProject(projectName, parsed, targetDir, skipInstall);
|
|
504
|
+
} else {
|
|
505
|
+
// Template path: existing behavior, untouched
|
|
506
|
+
createProject(projectName, template, targetDir, skipInstall);
|
|
507
|
+
}
|
|
467
508
|
|
|
468
509
|
console.log(`\nSuccess! Created ${projectName} at ${targetDir}\n`);
|
|
469
510
|
console.log("Next steps:\n");
|
|
@@ -473,7 +514,16 @@ export function main(argv: string[]): void {
|
|
|
473
514
|
if (skipInstall) {
|
|
474
515
|
console.log(" npm install");
|
|
475
516
|
}
|
|
476
|
-
if (
|
|
517
|
+
if (preset) {
|
|
518
|
+
const parsed = parsePresetString(preset)!;
|
|
519
|
+
if (parsed.framework === "nextjs") {
|
|
520
|
+
console.log(" npm run dev");
|
|
521
|
+
} else if (parsed.framework === "nestjs") {
|
|
522
|
+
console.log(" node --env-file=.env src/main.ts");
|
|
523
|
+
} else {
|
|
524
|
+
console.log(" node --env-file=.env src/index.ts");
|
|
525
|
+
}
|
|
526
|
+
} else if (template === "nextjs") {
|
|
477
527
|
console.log(" npm run dev");
|
|
478
528
|
} else {
|
|
479
529
|
console.log(" node --env-file=.env src/index.ts");
|
package/src/matrix.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// ─── Stack Combination Matrix ────────────────────────────────────────────────
|
|
2
|
+
// Single source of truth for valid Stratum stack combinations.
|
|
3
|
+
// Imported by create CLI, interactive wizard, and web docs.
|
|
4
|
+
|
|
5
|
+
export type Database = "postgres" | "mongodb" | "mysql";
|
|
6
|
+
export type Strategy = "rls" | "schema" | "database" | "collection" | "table-prefix";
|
|
7
|
+
export type Orm = "prisma" | "drizzle" | "sequelize" | "knex" | "mongoose" | "pg";
|
|
8
|
+
export type Framework = "express" | "fastify" | "nextjs" | "hono" | "nestjs" | "none";
|
|
9
|
+
|
|
10
|
+
export interface StackPreset {
|
|
11
|
+
database: Database;
|
|
12
|
+
strategy: Strategy;
|
|
13
|
+
orm: Orm;
|
|
14
|
+
framework: Framework;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ─── Valid combinations by database ──────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const POSTGRES_STRATEGIES: Strategy[] = ["rls", "schema", "database"];
|
|
20
|
+
const POSTGRES_ORMS: Orm[] = ["prisma", "drizzle", "sequelize", "knex", "pg"];
|
|
21
|
+
|
|
22
|
+
const MONGODB_STRATEGIES: Strategy[] = ["database", "collection"];
|
|
23
|
+
const MONGODB_ORMS: Orm[] = ["mongoose"];
|
|
24
|
+
|
|
25
|
+
const MYSQL_STRATEGIES: Strategy[] = ["database", "table-prefix"];
|
|
26
|
+
const MYSQL_ORMS: Orm[] = ["sequelize", "knex", "pg"];
|
|
27
|
+
|
|
28
|
+
const ALL_FRAMEWORKS: Framework[] = ["express", "fastify", "nextjs", "hono", "nestjs", "none"];
|
|
29
|
+
|
|
30
|
+
export interface DatabaseConfig {
|
|
31
|
+
strategies: Strategy[];
|
|
32
|
+
orms: Orm[];
|
|
33
|
+
frameworks: Framework[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const VALID_COMBINATIONS: Record<Database, DatabaseConfig> = {
|
|
37
|
+
postgres: {
|
|
38
|
+
strategies: POSTGRES_STRATEGIES,
|
|
39
|
+
orms: POSTGRES_ORMS,
|
|
40
|
+
frameworks: ALL_FRAMEWORKS,
|
|
41
|
+
},
|
|
42
|
+
mongodb: {
|
|
43
|
+
strategies: MONGODB_STRATEGIES,
|
|
44
|
+
orms: MONGODB_ORMS,
|
|
45
|
+
frameworks: ALL_FRAMEWORKS,
|
|
46
|
+
},
|
|
47
|
+
mysql: {
|
|
48
|
+
strategies: MYSQL_STRATEGIES,
|
|
49
|
+
orms: MYSQL_ORMS,
|
|
50
|
+
frameworks: ALL_FRAMEWORKS,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ─── All valid values (for parsing) ─────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
const ALL_DATABASES: Database[] = ["postgres", "mongodb", "mysql"];
|
|
57
|
+
const ALL_STRATEGIES: Strategy[] = ["rls", "schema", "database", "collection", "table-prefix"];
|
|
58
|
+
const ALL_ORMS: Orm[] = ["prisma", "drizzle", "sequelize", "knex", "mongoose", "pg"];
|
|
59
|
+
|
|
60
|
+
// ─── Validation ──────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export function isValidPreset(preset: StackPreset): boolean {
|
|
63
|
+
const config = VALID_COMBINATIONS[preset.database];
|
|
64
|
+
if (!config) return false;
|
|
65
|
+
if (!config.strategies.includes(preset.strategy)) return false;
|
|
66
|
+
if (!config.orms.includes(preset.orm)) return false;
|
|
67
|
+
if (!config.frameworks.includes(preset.framework)) return false;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Parsing ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export function parsePresetString(s: string): StackPreset | null {
|
|
74
|
+
if (!s || typeof s !== "string") return null;
|
|
75
|
+
|
|
76
|
+
const parts = s.toLowerCase().split("-");
|
|
77
|
+
|
|
78
|
+
// Handle "table-prefix" which contains a hyphen -- it will split into
|
|
79
|
+
// 5 parts: [db, strategy1, "table", "prefix", orm, framework] or similar.
|
|
80
|
+
// We need to reconstruct multi-word tokens.
|
|
81
|
+
// Format: {database}-{strategy}-{orm}-{framework}
|
|
82
|
+
// Strategy "table-prefix" is the only multi-word token.
|
|
83
|
+
|
|
84
|
+
let database: string;
|
|
85
|
+
let strategy: string;
|
|
86
|
+
let orm: string;
|
|
87
|
+
let framework: string;
|
|
88
|
+
|
|
89
|
+
if (parts.length === 4) {
|
|
90
|
+
// Simple case: no hyphenated strategy
|
|
91
|
+
[database, strategy, orm, framework] = parts;
|
|
92
|
+
} else if (parts.length === 5) {
|
|
93
|
+
// "table-prefix" case: mysql-table-prefix-sequelize-express
|
|
94
|
+
database = parts[0];
|
|
95
|
+
strategy = `${parts[1]}-${parts[2]}`;
|
|
96
|
+
orm = parts[3];
|
|
97
|
+
framework = parts[4];
|
|
98
|
+
} else {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate each part is a known value
|
|
103
|
+
if (!ALL_DATABASES.includes(database as Database)) return null;
|
|
104
|
+
if (!ALL_STRATEGIES.includes(strategy as Strategy)) return null;
|
|
105
|
+
if (!ALL_ORMS.includes(orm as Orm)) return null;
|
|
106
|
+
if (!ALL_FRAMEWORKS.includes(framework as Framework)) return null;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
database: database as Database,
|
|
110
|
+
strategy: strategy as Strategy,
|
|
111
|
+
orm: orm as Orm,
|
|
112
|
+
framework: framework as Framework,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Formatting ──────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
export function formatPresetString(preset: StackPreset): string {
|
|
119
|
+
return `${preset.database}-${preset.strategy}-${preset.orm}-${preset.framework}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Wizard helper: narrow valid options given partial selections ─────────────
|
|
123
|
+
|
|
124
|
+
export interface ValidOptions {
|
|
125
|
+
databases: Database[];
|
|
126
|
+
strategies: Strategy[];
|
|
127
|
+
orms: Orm[];
|
|
128
|
+
frameworks: Framework[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getValidOptions(partial: Partial<StackPreset>): ValidOptions {
|
|
132
|
+
let databases: Database[] = [...ALL_DATABASES];
|
|
133
|
+
let strategies: Strategy[] = [];
|
|
134
|
+
let orms: Orm[] = [];
|
|
135
|
+
let frameworks: Framework[] = [];
|
|
136
|
+
|
|
137
|
+
// If database is selected, narrow to that database's config
|
|
138
|
+
if (partial.database) {
|
|
139
|
+
databases = [partial.database];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Collect valid strategies/orms/frameworks from matching databases
|
|
143
|
+
const strategySet = new Set<Strategy>();
|
|
144
|
+
const ormSet = new Set<Orm>();
|
|
145
|
+
const frameworkSet = new Set<Framework>();
|
|
146
|
+
|
|
147
|
+
for (const db of databases) {
|
|
148
|
+
const config = VALID_COMBINATIONS[db];
|
|
149
|
+
|
|
150
|
+
// If strategy is specified, only include this db if it supports it
|
|
151
|
+
if (partial.strategy && !config.strategies.includes(partial.strategy)) continue;
|
|
152
|
+
// If orm is specified, only include this db if it supports it
|
|
153
|
+
if (partial.orm && !config.orms.includes(partial.orm)) continue;
|
|
154
|
+
|
|
155
|
+
for (const s of config.strategies) strategySet.add(s);
|
|
156
|
+
for (const o of config.orms) ormSet.add(o);
|
|
157
|
+
for (const f of config.frameworks) frameworkSet.add(f);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
strategies = [...strategySet];
|
|
161
|
+
orms = [...ormSet];
|
|
162
|
+
frameworks = [...frameworkSet];
|
|
163
|
+
|
|
164
|
+
// Further narrow if specific values are set
|
|
165
|
+
if (partial.strategy) {
|
|
166
|
+
strategies = strategies.filter((s) => s === partial.strategy);
|
|
167
|
+
}
|
|
168
|
+
if (partial.orm) {
|
|
169
|
+
orms = orms.filter((o) => o === partial.orm);
|
|
170
|
+
}
|
|
171
|
+
if (partial.framework) {
|
|
172
|
+
frameworks = frameworks.filter((f) => f === partial.framework);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Narrow databases to only those that support all specified fields
|
|
176
|
+
if (partial.strategy || partial.orm) {
|
|
177
|
+
databases = databases.filter((db) => {
|
|
178
|
+
const config = VALID_COMBINATIONS[db];
|
|
179
|
+
if (partial.strategy && !config.strategies.includes(partial.strategy)) return false;
|
|
180
|
+
if (partial.orm && !config.orms.includes(partial.orm)) return false;
|
|
181
|
+
return true;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { databases, strategies, orms, frameworks };
|
|
186
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import type { StackPreset } from "./matrix.js";
|
|
6
|
+
import { generatePresetDockerCompose } from "./generators/docker-compose.js";
|
|
7
|
+
import { generatePresetInitSql } from "./generators/init-sql.js";
|
|
8
|
+
import { generateDbSetup } from "./generators/db-setup.js";
|
|
9
|
+
import { generateMiddleware } from "./generators/middleware.js";
|
|
10
|
+
import { generatePresetPackageJson } from "./generators/package-json.js";
|
|
11
|
+
import { generatePresetReadme } from "./generators/readme.js";
|
|
12
|
+
|
|
13
|
+
function writeFile(filePath: string, content: string): void {
|
|
14
|
+
const dir = path.dirname(filePath);
|
|
15
|
+
if (!fs.existsSync(dir)) {
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
19
|
+
console.log(` created ${path.relative(process.cwd(), filePath)}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function generatePresetEnv(projectName: string, preset: StackPreset): string {
|
|
23
|
+
const dbName = projectName.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
24
|
+
const jwtSecret = crypto.randomBytes(32).toString("base64url");
|
|
25
|
+
|
|
26
|
+
let dbUrl: string;
|
|
27
|
+
switch (preset.database) {
|
|
28
|
+
case "postgres":
|
|
29
|
+
dbUrl = `postgres://${dbName}:dev_password@localhost:5432/${dbName}`;
|
|
30
|
+
break;
|
|
31
|
+
case "mongodb":
|
|
32
|
+
dbUrl = `mongodb://${dbName}:dev_password@localhost:27017/${dbName}?authSource=admin`;
|
|
33
|
+
break;
|
|
34
|
+
case "mysql":
|
|
35
|
+
dbUrl = `mysql://${dbName}:dev_password@localhost:3306/${dbName}`;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const urlKey = preset.database === "mongodb" ? "MONGODB_URI" : "DATABASE_URL";
|
|
40
|
+
|
|
41
|
+
return `# Environment variables for ${projectName}
|
|
42
|
+
# Copy to .env and fill in values
|
|
43
|
+
|
|
44
|
+
# Database
|
|
45
|
+
${urlKey}=${dbUrl}
|
|
46
|
+
|
|
47
|
+
# Authentication
|
|
48
|
+
JWT_SECRET=${jwtSecret}
|
|
49
|
+
|
|
50
|
+
# Stratum Control Plane
|
|
51
|
+
STRATUM_URL=http://localhost:3001
|
|
52
|
+
STRATUM_API_KEY=sk_test_your_key_here
|
|
53
|
+
|
|
54
|
+
NODE_ENV=development
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createPresetProject(
|
|
59
|
+
projectName: string,
|
|
60
|
+
preset: StackPreset,
|
|
61
|
+
targetDir: string,
|
|
62
|
+
skipInstall: boolean,
|
|
63
|
+
): void {
|
|
64
|
+
const presetLabel = `${preset.database}/${preset.strategy}/${preset.orm}/${preset.framework}`;
|
|
65
|
+
console.log(`\nCreating ${projectName} with preset ${presetLabel}...\n`);
|
|
66
|
+
|
|
67
|
+
// package.json
|
|
68
|
+
writeFile(
|
|
69
|
+
path.join(targetDir, "package.json"),
|
|
70
|
+
generatePresetPackageJson(projectName, preset),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// docker-compose.yml
|
|
74
|
+
writeFile(
|
|
75
|
+
path.join(targetDir, "docker-compose.yml"),
|
|
76
|
+
generatePresetDockerCompose(projectName, preset),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// init.sql (if applicable)
|
|
80
|
+
const initSql = generatePresetInitSql(projectName, preset);
|
|
81
|
+
if (initSql !== null) {
|
|
82
|
+
writeFile(path.join(targetDir, "init.sql"), initSql);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// .env.example
|
|
86
|
+
writeFile(
|
|
87
|
+
path.join(targetDir, ".env.example"),
|
|
88
|
+
generatePresetEnv(projectName, preset),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Database setup files (ORM config, tenant helpers)
|
|
92
|
+
const dbFiles = generateDbSetup(preset);
|
|
93
|
+
for (const file of dbFiles) {
|
|
94
|
+
writeFile(path.join(targetDir, file.filename), file.content);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Framework middleware / server entry point
|
|
98
|
+
const middlewareFiles = generateMiddleware(projectName, preset);
|
|
99
|
+
for (const file of middlewareFiles) {
|
|
100
|
+
writeFile(path.join(targetDir, file.filename), file.content);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// README
|
|
104
|
+
writeFile(
|
|
105
|
+
path.join(targetDir, "README.md"),
|
|
106
|
+
generatePresetReadme(projectName, preset),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// tsconfig.json
|
|
110
|
+
writeFile(
|
|
111
|
+
path.join(targetDir, "tsconfig.json"),
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{
|
|
114
|
+
compilerOptions: {
|
|
115
|
+
target: "ESNext",
|
|
116
|
+
module: "NodeNext",
|
|
117
|
+
moduleResolution: "NodeNext",
|
|
118
|
+
strict: true,
|
|
119
|
+
outDir: "dist",
|
|
120
|
+
rootDir: "src",
|
|
121
|
+
declaration: true,
|
|
122
|
+
skipLibCheck: true,
|
|
123
|
+
esModuleInterop: true,
|
|
124
|
+
...(preset.framework === "nestjs"
|
|
125
|
+
? { experimentalDecorators: true, emitDecoratorMetadata: true }
|
|
126
|
+
: {}),
|
|
127
|
+
...(preset.framework === "nextjs"
|
|
128
|
+
? { jsx: "preserve", plugins: [{ name: "next" }] }
|
|
129
|
+
: {}),
|
|
130
|
+
},
|
|
131
|
+
include: ["src"],
|
|
132
|
+
exclude: ["node_modules", "dist"],
|
|
133
|
+
},
|
|
134
|
+
null,
|
|
135
|
+
2,
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// npm install
|
|
140
|
+
if (!skipInstall) {
|
|
141
|
+
console.log("\nInstalling dependencies...\n");
|
|
142
|
+
try {
|
|
143
|
+
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
144
|
+
} catch {
|
|
145
|
+
console.warn("\nWarning: npm install failed. Run it manually in the project directory.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|