@stratum-hq/create 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratum-hq/create",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Create a new Stratum multi-tenancy project",
5
5
  "keywords": [
6
6
  "multi-tenancy",
@@ -16,7 +16,7 @@
16
16
  "stratum"
17
17
  ],
18
18
  "bin": {
19
- "create-stratum": "./dist/index.js"
19
+ "create-stratum": "./dist/bin.js"
20
20
  },
21
21
  "exports": {
22
22
  ".": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "type": "module",
26
26
  "scripts": {
27
- "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@stratum-hq/* && esbuild src/matrix.ts --bundle --platform=node --format=esm --outfile=dist/matrix.js --external:@stratum-hq/*",
27
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@stratum-hq/* && esbuild src/matrix.ts --bundle --platform=node --format=esm --outfile=dist/matrix.js --external:@stratum-hq/* && esbuild src/bin.ts --platform=node --format=esm --outfile=dist/bin.js",
28
28
  "typecheck": "tsc --noEmit",
29
29
  "lint": "eslint .",
30
30
  "test": "vitest run"
package/src/bin.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ // The bin calls main() without a check on process.argv[1]. npm runs the bin
4
+ // through a symlink, so a compare of argv[1] with the module path is false.
5
+ import { main } from "./index.js";
6
+
7
+ main(process.argv.slice(2));
@@ -203,60 +203,120 @@ export default config;
203
203
  },
204
204
  {
205
205
  filename: "src/stratum-knex.ts",
206
- content: `// Knex with Stratum tenant-scoped queries
206
+ content: preset.database === "postgres" ? KNEX_POSTGRES_SCOPE : knexColumnScope(preset.database),
207
+ },
208
+ ];
209
+ }
210
+
211
+ // PostgreSQL does not accept a bind parameter in SET, so the generated code
212
+ // calls set_config. The third argument (is_local = true) ends the setting with
213
+ // the transaction, so a pooled connection never keeps another tenant's ID.
214
+ const KNEX_POSTGRES_SCOPE = `// Knex with Stratum tenant-scoped queries
215
+ import createKnex, { type Knex } from "knex";
216
+ import config from "../knexfile.js";
217
+
218
+ const knex = createKnex(config);
219
+
220
+ // Run fn in a transaction that sets app.current_tenant_id, the setting that
221
+ // the RLS policies read. The setting ends with the transaction, so run every
222
+ // tenant query through trx, not through knex.
223
+ export async function withTenantScope<T>(
224
+ tenantId: string,
225
+ fn: (trx: Knex.Transaction) => Promise<T>,
226
+ ): Promise<T> {
227
+ return knex.transaction(async (trx) => {
228
+ await trx.raw("SELECT set_config('app.current_tenant_id', ?, true)", [tenantId]);
229
+ return fn(trx);
230
+ });
231
+ }
232
+
233
+ // Usage:
234
+ // const orders = await withTenantScope(currentTenantId, (trx) => trx("orders").select());
235
+
236
+ export { knex };
237
+ `;
238
+
239
+ function knexColumnScope(database: string): string {
240
+ return `// Knex with Stratum tenant-scoped queries
207
241
  import Knex from "knex";
208
242
  import config from "../knexfile.js";
209
243
 
210
244
  const knex = Knex(config);
211
245
 
212
246
  // Create a tenant-scoped query builder.
213
- // For RLS strategy, set the session variable before queries.
214
247
  export async function withTenantScope(tenantId: string, fn: (db: typeof knex) => Promise<void>) {
215
- ${preset.database === "postgres" ? ` await knex.raw("SET app.current_tenant = ?", [tenantId]);` : ` // For ${preset.database}, scope queries by tenant_id column`}
216
- try {
217
- await fn(knex);
218
- } finally {
219
- ${preset.database === "postgres" ? ` await knex.raw("RESET app.current_tenant");` : ` // Scope cleanup not needed for column-based isolation`}
220
- }
248
+ // For ${database}, scope queries by tenant_id column
249
+ await fn(knex);
221
250
  }
222
251
 
223
252
  export { knex };
224
- `,
225
- },
226
- ];
253
+ `;
254
+ }
255
+
256
+ // @stratum-hq/mongodb has no Mongoose connection helper. The generated code uses
257
+ // Mongoose directly and copies the tenant names of the @stratum-hq/mongodb
258
+ // adapters, so MongoDatabaseAdapter and MongoCollectionAdapter find the same
259
+ // data, for example for purgeTenantData.
260
+ function generateMongooseSetup(preset: StackPreset): DbSetupFile[] {
261
+ const tenantAccess =
262
+ preset.strategy === "collection"
263
+ ? `// Return the tenant's model for a base collection. The tenant's documents are
264
+ // in the collection {baseCollection}_{tenantSlug}, the name that
265
+ // MongoCollectionAdapter from @stratum-hq/mongodb uses.
266
+ export function getTenantModel<T>(
267
+ baseCollection: string,
268
+ schema: mongoose.Schema<T>,
269
+ tenantSlug: string,
270
+ ) {
271
+ const name = \`\${baseCollection}_\${assertSlug(tenantSlug)}\`;
272
+ return mainConnection.models[name] ?? mainConnection.model(name, schema, name);
273
+ }
274
+
275
+ // Usage:
276
+ // const Order = getTenantModel("orders", OrderSchema, "tenant_abc");
277
+ // const orders = await Order.find();`
278
+ : `// Return a connection to the tenant's own database. The database name is
279
+ // stratum_tenant_{tenantSlug}, the name that MongoDatabaseAdapter from
280
+ // @stratum-hq/mongodb uses. useCache returns the same connection on each call.
281
+ export function getTenantConnection(tenantSlug: string) {
282
+ return mainConnection.useDb(\`stratum_tenant_\${assertSlug(tenantSlug)}\`, { useCache: true });
227
283
  }
228
284
 
229
- function generateMongooseSetup(_preset: StackPreset): DbSetupFile[] {
285
+ // Usage:
286
+ // const conn = getTenantConnection("tenant_abc");
287
+ // const Order = conn.model("Order", OrderSchema);
288
+ // const orders = await Order.find();`;
289
+
230
290
  return [
231
291
  {
232
292
  filename: "src/stratum-mongoose.ts",
233
293
  content: `// Mongoose with Stratum multi-tenant support
234
294
  import mongoose from "mongoose";
235
- import { createTenantConnection } from "@stratum-hq/mongodb";
236
295
 
237
296
  // Main connection (used for tenant metadata)
238
297
  const mainConnection = mongoose.createConnection(
239
298
  process.env.MONGODB_URI || "mongodb://localhost:27017/main",
240
299
  );
241
300
 
242
- // Create a tenant-scoped connection.
243
- // Each tenant gets its own database (database-per-tenant strategy)
244
- // or collection prefix (collection strategy).
245
- export function getTenantConnection(tenantId: string) {
246
- return createTenantConnection(mainConnection, tenantId);
301
+ // A tenant slug becomes part of a MongoDB name, so it must match the Stratum
302
+ // slug rule: a lowercase letter, then lowercase letters, digits or underscores.
303
+ const SLUG_PATTERN = /^[a-z][a-z0-9_]{0,62}$/;
304
+
305
+ function assertSlug(tenantSlug: string): string {
306
+ if (!SLUG_PATTERN.test(tenantSlug)) {
307
+ throw new Error(\`Invalid tenant slug: "\${tenantSlug}"\`);
308
+ }
309
+ return tenantSlug;
247
310
  }
248
311
 
312
+ ${tenantAccess}
313
+
249
314
  // Define schemas that work across tenant connections
250
315
  export const TenantSchema = new mongoose.Schema({
251
316
  name: { type: String, required: true },
252
317
  createdAt: { type: Date, default: Date.now },
253
318
  });
254
319
 
255
- // Usage:
256
- // const conn = getTenantConnection("tenant-abc");
257
- // const Order = conn.model("Order", OrderSchema);
258
- // const orders = await Order.find();
259
-
260
320
  export { mainConnection };
261
321
  `,
262
322
  },
@@ -276,7 +336,11 @@ const pool = mysql.createPool({
276
336
  });
277
337
 
278
338
  // Execute a query scoped to a tenant by filtering on tenant_id
279
- export async function tenantQuery(tenantId: string, sql: string, params: unknown[] = []) {
339
+ export async function tenantQuery(
340
+ tenantId: string,
341
+ sql: string,
342
+ params: (string | number | bigint | boolean | Date | null)[] = [],
343
+ ) {
280
344
  const [rows] = await pool.execute(sql, [...params, tenantId]);
281
345
  return rows;
282
346
  }
@@ -302,9 +366,10 @@ const pool = new Pool({
302
366
  });
303
367
 
304
368
  // Create a tenant-scoped pool.
305
- // Sets app.current_tenant on each connection for RLS enforcement.
369
+ // Each query runs in a transaction that sets app.current_tenant_id, the
370
+ // setting that the RLS policies read.
306
371
  export function getTenantPool(tenantId: string) {
307
- return createTenantPool(pool, tenantId);
372
+ return createTenantPool(pool, () => tenantId);
308
373
  }
309
374
 
310
375
  // Usage:
@@ -65,8 +65,11 @@ function generatePostgresInit(projectName: string, dbName: string, strategy: str
65
65
  -- ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
66
66
  -- ALTER TABLE your_table FORCE ROW LEVEL SECURITY;
67
67
  -- CREATE POLICY tenant_isolation ON your_table
68
- -- USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
68
+ -- USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);
69
69
  --
70
+ -- A pooled connection reads the setting as '' after a tenant transaction ends,
71
+ -- or as NULL before the first one. NULLIF makes both return no rows; a bare
72
+ -- ::uuid cast of '' raises an error.
70
73
  -- FORCE makes the policy apply to the table owner too; without it, a table
71
74
  -- created by the application role is not isolated for that role.
72
75
  -- The Stratum db-adapters package sets app.current_tenant_id automatically.
@@ -1,16 +1,24 @@
1
1
  import type { StackPreset } from "../matrix.js";
2
+ import { rootSources } from "./tsconfig.js";
3
+ import { STRATUM_RANGES } from "../stratum-versions.js";
2
4
 
3
5
  export function generatePresetPackageJson(projectName: string, preset: StackPreset): string {
4
6
  const deps: Record<string, string> = {
5
- "@stratum-hq/lib": "^0.2.0",
7
+ "@stratum-hq/lib": STRATUM_RANGES["@stratum-hq/lib"],
6
8
  };
7
9
  const devDeps: Record<string, string> = {
8
10
  typescript: "^5.3.0",
9
11
  "@types/node": "^20.11.0",
10
12
  };
13
+ // NestJS injection needs the decorator metadata that tsc emits and tsx does not.
14
+ if (preset.framework === "nestjs") {
15
+ devDeps["tsc-watch"] = "^7.2.0";
16
+ } else if (preset.framework !== "nextjs") {
17
+ devDeps["tsx"] = "^4.7.0";
18
+ }
11
19
 
12
20
  // Database driver deps
13
- addDatabaseDeps(deps, preset);
21
+ addDatabaseDeps(deps, devDeps, preset);
14
22
 
15
23
  // ORM deps
16
24
  addOrmDeps(deps, devDeps, preset);
@@ -41,9 +49,14 @@ export function generatePresetPackageJson(projectName: string, preset: StackPres
41
49
  );
42
50
  }
43
51
 
44
- function addDatabaseDeps(deps: Record<string, string>, preset: StackPreset): void {
52
+ function addDatabaseDeps(deps: Record<string, string>, devDeps: Record<string, string>, preset: StackPreset): void {
45
53
  switch (preset.database) {
46
54
  case "postgres":
55
+ // The generated code for these ORMs imports pg itself. The strict tsc
56
+ // build fails with TS7016 when the pg types are not installed.
57
+ if (preset.orm === "pg" || preset.orm === "prisma" || preset.orm === "drizzle") {
58
+ devDeps["@types/pg"] = "^8.11.0";
59
+ }
47
60
  if (preset.orm !== "prisma" && preset.orm !== "drizzle" && preset.orm !== "sequelize") {
48
61
  deps["pg"] = "^8.11.0";
49
62
  }
@@ -139,19 +152,19 @@ function addFrameworkDeps(deps: Record<string, string>, devDeps: Record<string,
139
152
 
140
153
  function addStratumDeps(deps: Record<string, string>, preset: StackPreset): void {
141
154
  if (preset.database === "postgres" && preset.orm !== "mongoose") {
142
- deps["@stratum-hq/db-adapters"] = "^0.2.0";
155
+ deps["@stratum-hq/db-adapters"] = STRATUM_RANGES["@stratum-hq/db-adapters"];
143
156
  }
144
157
  if (preset.database === "mongodb") {
145
- deps["@stratum-hq/mongodb"] = "^0.2.0";
158
+ deps["@stratum-hq/mongodb"] = STRATUM_RANGES["@stratum-hq/mongodb"];
146
159
  }
147
160
  if (preset.database === "mysql") {
148
- deps["@stratum-hq/mysql"] = "^0.2.0";
161
+ deps["@stratum-hq/mysql"] = STRATUM_RANGES["@stratum-hq/mysql"];
149
162
  }
150
163
  if (preset.framework === "hono") {
151
- deps["@stratum-hq/hono"] = "^0.2.0";
164
+ deps["@stratum-hq/hono"] = STRATUM_RANGES["@stratum-hq/hono"];
152
165
  }
153
166
  if (preset.framework === "nestjs") {
154
- deps["@stratum-hq/nestjs"] = "^0.2.0";
167
+ deps["@stratum-hq/nestjs"] = STRATUM_RANGES["@stratum-hq/nestjs"];
155
168
  }
156
169
  }
157
170
 
@@ -159,10 +172,16 @@ function getScripts(preset: StackPreset): Record<string, string> {
159
172
  if (preset.framework === "nextjs") {
160
173
  return { dev: "next dev", build: "next build", start: "next start" };
161
174
  }
162
- if (preset.framework === "nestjs") {
163
- return { dev: "node --watch src/main.ts", build: "tsc", start: "node dist/main.js" };
164
- }
165
- return { dev: "node --watch src/index.js", build: "tsc", start: "node dist/index.js" };
175
+ const entry = preset.framework === "nestjs" ? "main" : "index";
176
+ // When tsc compiles from the project root, src/ is emitted to dist/src/.
177
+ const emittedDir = rootSources(preset).length > 0 ? "dist/src" : "dist";
178
+ // Node 20 cannot run a .ts file. NestJS compiles with tsc, which keeps the
179
+ // decorator metadata its injection needs; the other frameworks run through tsx.
180
+ const dev =
181
+ preset.framework === "nestjs"
182
+ ? `tsc-watch --onSuccess "node --env-file=.env ${emittedDir}/${entry}.js"`
183
+ : `tsx watch --env-file=.env src/${entry}.ts`;
184
+ return { dev, build: "tsc", start: `node ${emittedDir}/${entry}.js` };
166
185
  }
167
186
 
168
187
  function sortKeys(obj: Record<string, string>): Record<string, string> {
@@ -1,7 +1,6 @@
1
1
  import type { StackPreset } from "../matrix.js";
2
2
 
3
3
  export function generatePresetReadme(projectName: string, preset: StackPreset): string {
4
- const devCmd = getDevCommand(preset);
5
4
  const dbStartCmd = getDbStartInfo(preset);
6
5
  const dbSetupNote = getDbSetupNote(preset);
7
6
 
@@ -37,7 +36,7 @@ ${dbSetupNote}
37
36
  ### 4. Run the app
38
37
 
39
38
  \`\`\`bash
40
- ${devCmd}
39
+ npm run dev
41
40
  \`\`\`
42
41
 
43
42
  ## Multi-tenancy
@@ -50,13 +49,6 @@ See the [Stratum docs](https://github.com/stratum-hq/Stratum) for full reference
50
49
  `;
51
50
  }
52
51
 
53
- function getDevCommand(preset: StackPreset): string {
54
- if (preset.framework === "nextjs") return "npm run dev";
55
- if (preset.framework === "nestjs") return "node --env-file=.env src/main.ts";
56
- if (preset.framework === "none") return "node --env-file=.env src/index.ts";
57
- return "node --env-file=.env src/index.ts";
58
- }
59
-
60
52
  function getDbStartInfo(preset: StackPreset): string {
61
53
  switch (preset.database) {
62
54
  case "postgres":
@@ -95,7 +87,7 @@ function getStrategyDescription(strategy: string): string {
95
87
  switch (strategy) {
96
88
  case "rls":
97
89
  return `- **Row-Level Security** -- PostgreSQL RLS policies filter rows by tenant automatically
98
- - Each query sets \`app.current_tenant\` and RLS enforces isolation
90
+ - Each query sets \`app.current_tenant_id\` and RLS enforces isolation
99
91
  - All tenants share one database and schema`;
100
92
  case "schema":
101
93
  return `- **Schema-per-tenant** -- each tenant gets a dedicated PostgreSQL schema
@@ -0,0 +1,48 @@
1
+ import type { Framework, StackPreset } from "../matrix.js";
2
+
3
+ /**
4
+ * Return the source files that a preset writes at the project root and that code in `src/` imports.
5
+ *
6
+ * @param preset - The stack preset of the generated project.
7
+ */
8
+ export function rootSources(preset: StackPreset): string[] {
9
+ // The knex CLI reads knexfile.ts from the project root, and src/stratum-knex.ts imports it.
10
+ return preset.orm === "knex" ? ["knexfile.ts"] : [];
11
+ }
12
+
13
+ /**
14
+ * Return the tsconfig.json content for a generated project.
15
+ *
16
+ * @param framework - The framework of the project. It adds the compiler options that the framework needs.
17
+ * @param extraSources - Source files at the project root that code in `src/` imports.
18
+ * When there are any, tsc compiles from the project root, so the output of `src/` goes to `dist/src/`.
19
+ */
20
+ export function generateTsconfig(framework: Framework, extraSources: string[] = []): string {
21
+ // tsc rejects an imported file outside rootDir with TS6059.
22
+ const rootDir = extraSources.length > 0 ? "." : "src";
23
+ return JSON.stringify(
24
+ {
25
+ compilerOptions: {
26
+ target: "ESNext",
27
+ module: "NodeNext",
28
+ moduleResolution: "NodeNext",
29
+ strict: true,
30
+ outDir: "dist",
31
+ rootDir,
32
+ declaration: true,
33
+ skipLibCheck: true,
34
+ esModuleInterop: true,
35
+ ...(framework === "nestjs"
36
+ ? { experimentalDecorators: true, emitDecoratorMetadata: true }
37
+ : {}),
38
+ ...(framework === "nextjs"
39
+ ? { jsx: "preserve", plugins: [{ name: "next" }] }
40
+ : {}),
41
+ },
42
+ include: ["src", ...extraSources],
43
+ exclude: ["node_modules", "dist"],
44
+ },
45
+ null,
46
+ 2,
47
+ );
48
+ }
package/src/index.ts CHANGED
@@ -1,13 +1,12 @@
1
- #!/usr/bin/env node
2
-
3
1
  import * as fs from "fs";
4
2
  import * as path from "path";
5
3
  import crypto from "node:crypto";
6
4
  import { execSync } from "child_process";
7
- import { fileURLToPath } from "url";
8
5
  import { parsePresetString, isValidPreset } from "./matrix.js";
9
6
  import { createPresetProject } from "./preset-project.js";
7
+ import { STRATUM_RANGES } from "./stratum-versions.js";
10
8
  import { postgresAppRole, postgresAppRoleSql, POSTGRES_APP_PASSWORD } from "./generators/init-sql.js";
9
+ import { generateTsconfig } from "./generators/tsconfig.js";
11
10
 
12
11
  // ─── Types ────────────────────────────────────────────────────────────────────
13
12
 
@@ -127,7 +126,7 @@ function generatePackageJson(projectName: string, template: Template): string {
127
126
  };
128
127
 
129
128
  const deps = {
130
- "@stratum-hq/lib": "^0.2.0",
129
+ "@stratum-hq/lib": STRATUM_RANGES["@stratum-hq/lib"],
131
130
  pg: "^8.11.0",
132
131
  ...frameworkDeps[template],
133
132
  };
@@ -141,11 +140,13 @@ function generatePackageJson(projectName: string, template: Template): string {
141
140
  scripts:
142
141
  template === "nextjs"
143
142
  ? { dev: "next dev", build: "next build", start: "next start" }
144
- : { dev: "node --watch src/index.js", build: "tsc", start: "node dist/index.js" },
143
+ : // Node 20 cannot run a .ts file, so dev runs the source through tsx.
144
+ { dev: "tsx watch --env-file=.env src/index.ts", build: "tsc", start: "node dist/index.js" },
145
145
  dependencies: deps,
146
146
  devDependencies: {
147
147
  typescript: "^5.3.0",
148
148
  "@types/node": "^20.11.0",
149
+ ...(template === "nextjs" ? {} : { tsx: "^4.7.0", "@types/pg": "^8.11.0" }),
149
150
  },
150
151
  engines: {
151
152
  node: ">=20.0.0",
@@ -337,9 +338,6 @@ export const config = {
337
338
  }
338
339
 
339
340
  function generateReadme(projectName: string, template: Template): string {
340
- const devCmd =
341
- template === "nextjs" ? "npm run dev" : "node --env-file=.env src/index.ts";
342
-
343
341
  return `# ${projectName}
344
342
 
345
343
  A multi-tenant application built with [Stratum](https://github.com/stratum-hq/Stratum).
@@ -368,7 +366,7 @@ npm install
368
366
  ### 4. Run the app
369
367
 
370
368
  \`\`\`bash
371
- ${devCmd}
369
+ npm run dev
372
370
  \`\`\`
373
371
 
374
372
  ## Project structure
@@ -379,7 +377,7 @@ ${projectName}/
379
377
  ├── docker-compose.yml
380
378
  ├── init.sql # DB extensions (uuid-ossp, ltree)
381
379
  ├── .env.example
382
- └── package.json
380
+ ${template === "nextjs" ? "" : "├── tsconfig.json\n"}└── package.json
383
381
  \`\`\`
384
382
 
385
383
  ## Multi-tenancy
@@ -422,8 +420,10 @@ export function createProject(
422
420
  // Server starter file
423
421
  if (template === "express") {
424
422
  writeFile(path.join(targetDir, "src", "index.ts"), generateExpressServer(projectName));
423
+ writeFile(path.join(targetDir, "tsconfig.json"), generateTsconfig(template));
425
424
  } else if (template === "fastify") {
426
425
  writeFile(path.join(targetDir, "src", "index.ts"), generateFastifyServer(projectName));
426
+ writeFile(path.join(targetDir, "tsconfig.json"), generateTsconfig(template));
427
427
  } else if (template === "nextjs") {
428
428
  writeFile(path.join(targetDir, "src", "app", "page.tsx"), generateNextjsPage(projectName));
429
429
  writeFile(path.join(targetDir, "middleware.ts"), generateNextjsMiddleware());
@@ -520,25 +520,6 @@ export function main(argv: string[]): void {
520
520
  if (skipInstall) {
521
521
  console.log(" npm install");
522
522
  }
523
- if (preset) {
524
- const parsed = parsePresetString(preset)!;
525
- if (parsed.framework === "nextjs") {
526
- console.log(" npm run dev");
527
- } else if (parsed.framework === "nestjs") {
528
- console.log(" node --env-file=.env src/main.ts");
529
- } else {
530
- console.log(" node --env-file=.env src/index.ts");
531
- }
532
- } else if (template === "nextjs") {
533
- console.log(" npm run dev");
534
- } else {
535
- console.log(" node --env-file=.env src/index.ts");
536
- }
523
+ console.log(" npm run dev");
537
524
  console.log("");
538
525
  }
539
-
540
- // Run when executed directly (not when imported by tests)
541
- const __filename = fileURLToPath(import.meta.url);
542
- if (process.argv[1] === __filename) {
543
- main(process.argv.slice(2));
544
- }
@@ -9,6 +9,7 @@ import { generateDbSetup } from "./generators/db-setup.js";
9
9
  import { generateMiddleware } from "./generators/middleware.js";
10
10
  import { generatePresetPackageJson } from "./generators/package-json.js";
11
11
  import { generatePresetReadme } from "./generators/readme.js";
12
+ import { generateTsconfig, rootSources } from "./generators/tsconfig.js";
12
13
 
13
14
  function writeFile(filePath: string, content: string): void {
14
15
  const dir = path.dirname(filePath);
@@ -114,31 +115,7 @@ export function createPresetProject(
114
115
  // tsconfig.json
115
116
  writeFile(
116
117
  path.join(targetDir, "tsconfig.json"),
117
- JSON.stringify(
118
- {
119
- compilerOptions: {
120
- target: "ESNext",
121
- module: "NodeNext",
122
- moduleResolution: "NodeNext",
123
- strict: true,
124
- outDir: "dist",
125
- rootDir: "src",
126
- declaration: true,
127
- skipLibCheck: true,
128
- esModuleInterop: true,
129
- ...(preset.framework === "nestjs"
130
- ? { experimentalDecorators: true, emitDecoratorMetadata: true }
131
- : {}),
132
- ...(preset.framework === "nextjs"
133
- ? { jsx: "preserve", plugins: [{ name: "next" }] }
134
- : {}),
135
- },
136
- include: ["src"],
137
- exclude: ["node_modules", "dist"],
138
- },
139
- null,
140
- 2,
141
- ),
118
+ generateTsconfig(preset.framework, rootSources(preset)),
142
119
  );
143
120
 
144
121
  // npm install
@@ -0,0 +1,20 @@
1
+ // esbuild inlines these files when it bundles the CLI, so the published package carries
2
+ // the versions of the workspace it was built from. The ranges are fixed at create's build
3
+ // time. When a pinned package gets a major bump, or a minor bump while at 0.x, release
4
+ // create again, or new projects keep the old range.
5
+ import hono from "../../hono/package.json" with { type: "json" };
6
+ import dbAdapters from "../../db-adapters/package.json" with { type: "json" };
7
+ import lib from "../../lib/package.json" with { type: "json" };
8
+ import mongodb from "../../mongodb/package.json" with { type: "json" };
9
+ import mysql from "../../mysql/package.json" with { type: "json" };
10
+ import nestjs from "../../nestjs/package.json" with { type: "json" };
11
+
12
+ /** Caret ranges on the current workspace versions of the packages a generated project uses. */
13
+ export const STRATUM_RANGES = {
14
+ "@stratum-hq/db-adapters": `^${dbAdapters.version}`,
15
+ "@stratum-hq/hono": `^${hono.version}`,
16
+ "@stratum-hq/lib": `^${lib.version}`,
17
+ "@stratum-hq/mongodb": `^${mongodb.version}`,
18
+ "@stratum-hq/mysql": `^${mysql.version}`,
19
+ "@stratum-hq/nestjs": `^${nestjs.version}`,
20
+ } as const;
package/turbo.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "extends": ["//"],
4
+ "tasks": {
5
+ "build": {
6
+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/packages/*/package.json"]
7
+ },
8
+ "typecheck": {
9
+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/packages/*/package.json"]
10
+ },
11
+ "test": {
12
+ "inputs": [
13
+ "$TURBO_DEFAULT$",
14
+ "$TURBO_ROOT$/packages/*/package.json",
15
+ "$TURBO_ROOT$/packages/{core,db-adapters,mysql,mongodb}/src/**"
16
+ ]
17
+ }
18
+ }
19
+ }