@stratum-hq/create 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,12 +1,26 @@
1
1
  {
2
2
  "name": "@stratum-hq/create",
3
- "version": "0.5.0",
4
- "description": "Create a new Stratum multi-tenancy project",
3
+ "version": "0.6.0",
4
+ "description": "Scaffold a multi-tenant SaaS starter for Node.js: PostgreSQL, MySQL, or MongoDB with Express, Fastify, Next.js, Hono, or NestJS",
5
5
  "keywords": [
6
6
  "multi-tenancy",
7
+ "multitenancy",
7
8
  "multi-tenant",
8
9
  "saas",
9
10
  "tenant",
11
+ "tenant-isolation",
12
+ "row-level-security",
13
+ "rls",
14
+ "postgresql",
15
+ "mysql",
16
+ "mongodb",
17
+ "express",
18
+ "fastify",
19
+ "nextjs",
20
+ "hono",
21
+ "nestjs",
22
+ "prisma",
23
+ "drizzle",
10
24
  "create",
11
25
  "scaffolding",
12
26
  "starter",
@@ -40,7 +54,7 @@
40
54
  },
41
55
  "license": "MIT",
42
56
  "author": "Christian Crank",
43
- "homepage": "https://github.com/stratum-hq/Stratum/tree/main/packages/create#readme",
57
+ "homepage": "https://docs.stratum-hq.org/packages/create/",
44
58
  "bugs": "https://github.com/stratum-hq/Stratum/issues",
45
59
  "engines": {
46
60
  "node": ">=20.0.0"
@@ -128,6 +128,15 @@ export { pool };
128
128
  });
129
129
  }
130
130
 
131
+ files.push({
132
+ filename: "src/schema.ts",
133
+ content: drizzleSchema(preset),
134
+ });
135
+
136
+ // drizzle-kit creates tables, so on PostgreSQL it connects as the superuser
137
+ // in DATABASE_SUPERUSER_URL, kept for migrations. A table the app role owned
138
+ // would not be subject to its own RLS policies; init.sql grants the app role
139
+ // access to the tables the superuser creates.
131
140
  files.push({
132
141
  filename: "drizzle.config.ts",
133
142
  content: `import type { Config } from "drizzle-kit";
@@ -137,7 +146,7 @@ export default {
137
146
  out: "./drizzle",
138
147
  ${preset.database === "mysql" ? 'dialect: "mysql",' : 'dialect: "postgresql",'}
139
148
  dbCredentials: {
140
- url: process.env.DATABASE_URL!,
149
+ url: ${preset.database === "mysql" ? "process.env.DATABASE_URL!" : "(process.env.DATABASE_SUPERUSER_URL ?? process.env.DATABASE_URL)!"},
141
150
  },
142
151
  } satisfies Config;
143
152
  `,
@@ -146,6 +155,39 @@ export default {
146
155
  return files;
147
156
  }
148
157
 
158
+ /** src/schema.ts, the Drizzle table definitions that drizzle.config.ts points at. */
159
+ function drizzleSchema(preset: StackPreset): string {
160
+ if (preset.database === "mysql") {
161
+ return `// Drizzle table definitions. drizzle.config.ts reads this file.
162
+ import { mysqlTable, varchar, text, timestamp } from "drizzle-orm/mysql-core";
163
+
164
+ // An example tenant-scoped table: every row carries the tenant it belongs to.
165
+ export const notes = mysqlTable("notes", {
166
+ id: varchar("id", { length: 36 }).primaryKey(),
167
+ tenantId: varchar("tenant_id", { length: 36 }).notNull(),
168
+ body: text("body").notNull(),
169
+ createdAt: timestamp("created_at").notNull().defaultNow(),
170
+ });
171
+ `;
172
+ }
173
+ return `// Drizzle table definitions. drizzle.config.ts reads this file.
174
+ import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
175
+
176
+ // An example tenant-scoped table: every row carries the tenant it belongs to.
177
+ // Stratum's own tables, such as tenants, are created by Stratum, not here.
178
+ export const notes = pgTable(
179
+ "notes",
180
+ {
181
+ id: uuid("id").primaryKey().defaultRandom(),
182
+ tenantId: uuid("tenant_id").notNull(),
183
+ body: text("body").notNull(),
184
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
185
+ },
186
+ (table) => [index("notes_tenant_id_idx").on(table.tenantId)],
187
+ );
188
+ `;
189
+ }
190
+
149
191
  function generateSequelizeSetup(preset: StackPreset): DbSetupFile[] {
150
192
  const dialect = preset.database === "mysql" ? "mysql" : "postgres";
151
193
 
@@ -9,17 +9,37 @@ export function postgresAppRole(dbName: string): string {
9
9
  export const POSTGRES_APP_PASSWORD = "dev_app_password";
10
10
 
11
11
  /**
12
- * SQL that creates the role the application connects as. POSTGRES_USER is a
13
- * superuser, and a superuser (or any BYPASSRLS role) ignores every row-level
14
- * security policy, FORCE included. So the app gets its own role, and the
15
- * superuser is kept for bootstrap and migrations.
12
+ * Name of the login Stratum itself connects as: the library's adminPool,
13
+ * which runs the Stratum migrations. It is a member of the control role of
14
+ * @stratum-hq/lib migration 032, not a superuser.
15
+ */
16
+ export function postgresStratumRole(dbName: string): string {
17
+ return `${dbName}_stratum`;
18
+ }
19
+
20
+ /** Local development password for the generated Stratum login. */
21
+ export const POSTGRES_STRATUM_PASSWORD = "dev_stratum_password";
22
+
23
+ /**
24
+ * SQL that creates the roles of the hardened role model of @stratum-hq/lib
25
+ * (the "Hardening: separate admin and app roles" guide), as docker/init-db.sql
26
+ * does. POSTGRES_USER is a superuser, and a superuser (or any BYPASSRLS
27
+ * role) ignores every row-level security policy, FORCE included. So the app
28
+ * gets its own role, Stratum gets its own login for its tables, and the
29
+ * superuser is kept for bootstrap and the application's own migrations.
16
30
  */
17
31
  export function postgresAppRoleSql(dbName: string, strategy?: string): string {
18
32
  const role = postgresAppRole(dbName);
33
+ const stratum = postgresStratumRole(dbName);
19
34
  let strategyGrant = "";
20
35
  if (strategy === "schema") {
21
36
  strategyGrant = `
22
- -- schema-per-tenant: the app creates one schema per tenant
37
+ -- schema-per-tenant: the app creates one schema per tenant. A schema named
38
+ -- like a login comes first on that login's default search path ("$user",
39
+ -- public), so a schema the app creates could come before public for the
40
+ -- Stratum login or the bootstrap superuser. Both search only public.
41
+ ALTER ROLE ${stratum} IN DATABASE ${dbName} SET search_path = public;
42
+ ALTER ROLE CURRENT_USER IN DATABASE ${dbName} SET search_path = public;
23
43
  GRANT CREATE ON DATABASE ${dbName} TO ${role};
24
44
  `;
25
45
  } else if (strategy === "database") {
@@ -29,15 +49,40 @@ ALTER ROLE ${role} CREATEDB;
29
49
  `;
30
50
  }
31
51
  return `
52
+ -- Stratum's control role (migration 032 of @stratum-hq/lib). Every Stratum
53
+ -- table gets a policy for it, and only its members reach rows across
54
+ -- tenants.
55
+ CREATE ROLE stratum_control NOLOGIN NOSUPERUSER NOBYPASSRLS;
56
+ GRANT USAGE, CREATE ON SCHEMA public TO stratum_control;
57
+
58
+ -- Stratum's own login: pass it to the library as adminPool, which runs the
59
+ -- Stratum migrations, so it owns the Stratum tables. Not a superuser and no
60
+ -- BYPASSRLS: it reaches them as a member of stratum_control. Do not run the
61
+ -- Stratum migrations as the bootstrap superuser: the default privileges
62
+ -- below would give the app role write access to the Stratum tables. If that
63
+ -- happened, "stratum db roles --apply --app-role ${role}" removes it.
64
+ CREATE ROLE ${stratum} WITH LOGIN PASSWORD '${POSTGRES_STRATUM_PASSWORD}' NOSUPERUSER NOBYPASSRLS;
65
+ GRANT stratum_control TO ${stratum} WITH INHERIT TRUE, SET TRUE;
66
+ GRANT CONNECT ON DATABASE ${dbName} TO ${stratum};
67
+ GRANT USAGE, CREATE ON SCHEMA public TO ${stratum};
68
+
32
69
  -- Application role. The app connects as ${role} (DATABASE_URL), never as the
33
- -- bootstrap superuser (DATABASE_ADMIN_URL): a superuser or BYPASSRLS role
34
- -- ignores every row-level security policy, FORCE included. Use the superuser
35
- -- only for bootstrap and migrations.
70
+ -- bootstrap superuser (DATABASE_SUPERUSER_URL): a superuser or BYPASSRLS role
71
+ -- ignores every row-level security policy, FORCE included. It creates no
72
+ -- objects in public: the superuser creates the application's tables (its
73
+ -- migrations), and the app role reads and writes the ones the superuser
74
+ -- creates. It gets nothing on the tables ${stratum} creates; to let it read
75
+ -- Stratum's read list once Stratum has migrated, run:
76
+ -- stratum db roles --apply --admin-role ${stratum} --app-role ${role}
36
77
  CREATE ROLE ${role} WITH LOGIN PASSWORD '${POSTGRES_APP_PASSWORD}' NOSUPERUSER NOBYPASSRLS NOCREATEROLE;
37
78
  GRANT CONNECT ON DATABASE ${dbName} TO ${role};
38
- GRANT USAGE, CREATE ON SCHEMA public TO ${role};
39
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${role};
40
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO ${role};
79
+ GRANT USAGE ON SCHEMA public TO ${role};
80
+ ALTER DEFAULT PRIVILEGES FOR ROLE ${dbName} IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${role};
81
+ ALTER DEFAULT PRIVILEGES FOR ROLE ${dbName} IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO ${role};
82
+
83
+ -- Only roles granted CREATE by name create objects in public. PostgreSQL 15
84
+ -- and later already start this way; older versions grant it to PUBLIC.
85
+ REVOKE CREATE ON SCHEMA public FROM PUBLIC;
41
86
  ${strategyGrant}`;
42
87
  }
43
88
 
@@ -50,8 +50,10 @@ const TENANT_REQUIRED = `{ error: "A bearer token with a tenant_id claim is requ
50
50
  * x-tenant-id. The subdomain is forwarded as x-tenant-slug, never as the ID.
51
51
  */
52
52
  export function nextjsTenantMiddleware(): string {
53
- return `// middleware.ts (place in project root)
54
- // Next.js middleware for Stratum tenant resolution
53
+ return `// src/middleware.ts: Next.js middleware for Stratum tenant resolution
54
+ //
55
+ // Next.js runs middleware only from the directory that holds the app
56
+ // directory: src/middleware.ts for src/app, middleware.ts for app.
55
57
  //
56
58
  // The tenant ID comes only from the tenant_id claim of a bearer token that
57
59
  // verifies with JWT_SECRET, and is forwarded as x-tenant-id. Any copy of the
@@ -127,6 +129,24 @@ export const config = {
127
129
  `;
128
130
  }
129
131
 
132
+ /**
133
+ * The root layout of a generated Next.js app, src/app/layout.tsx. \`next build\`
134
+ * refuses an app directory without one.
135
+ */
136
+ export function nextjsRootLayout(projectName: string): string {
137
+ return `// app/layout.tsx: ${projectName} root layout
138
+ import type { ReactNode } from "react";
139
+
140
+ export default function RootLayout({ children }: { children: ReactNode }) {
141
+ return (
142
+ <html lang="en">
143
+ <body>{children}</body>
144
+ </html>
145
+ );
146
+ }
147
+ `;
148
+ }
149
+
130
150
  export function generateMiddleware(projectName: string, preset: StackPreset): MiddlewareFile[] {
131
151
  switch (preset.framework) {
132
152
  case "express":
@@ -145,10 +165,16 @@ export function generateMiddleware(projectName: string, preset: StackPreset): Mi
145
165
  }
146
166
 
147
167
  function generateExpressMiddleware(projectName: string): MiddlewareFile[] {
148
- return [
149
- {
150
- filename: "src/index.ts",
151
- content: `import express from "express";
168
+ return [{ filename: "src/index.ts", content: expressServer(projectName) }];
169
+ }
170
+
171
+ /**
172
+ * src/index.ts of a generated Express server, for the express template and
173
+ * every express preset: tenant middleware that takes the tenant from a
174
+ * verified JWT, and a /tenants route that requires a tenant.
175
+ */
176
+ export function expressServer(projectName: string): string {
177
+ return `import express from "express";
152
178
  ${VERIFIED_TENANT}
153
179
 
154
180
  const app = express();
@@ -184,16 +210,20 @@ app.get("/tenants", async (req, res) => {
184
210
  app.listen(port, () => {
185
211
  console.log(\`${projectName} running on http://localhost:\${port}\`);
186
212
  });
187
- `,
188
- },
189
- ];
213
+ `;
190
214
  }
191
215
 
192
216
  function generateFastifyMiddleware(projectName: string): MiddlewareFile[] {
193
- return [
194
- {
195
- filename: "src/index.ts",
196
- content: `import Fastify from "fastify";
217
+ return [{ filename: "src/index.ts", content: fastifyServer(projectName) }];
218
+ }
219
+
220
+ /**
221
+ * src/index.ts of a generated Fastify server, for the fastify template and
222
+ * every fastify preset: an onRequest hook that takes the tenant from a
223
+ * verified JWT, and a /tenants route that requires a tenant.
224
+ */
225
+ export function fastifyServer(projectName: string): string {
226
+ return `import Fastify from "fastify";
197
227
  ${VERIFIED_TENANT}
198
228
 
199
229
  const fastify = Fastify({ logger: true });
@@ -228,17 +258,19 @@ fastify.listen({ port, host: "0.0.0.0" }, (err) => {
228
258
  process.exit(1);
229
259
  }
230
260
  });
231
- `,
232
- },
233
- ];
261
+ `;
234
262
  }
235
263
 
236
264
  function generateNextjsMiddleware(projectName: string): MiddlewareFile[] {
237
265
  return [
238
266
  {
239
- filename: "middleware.ts",
267
+ filename: "src/middleware.ts",
240
268
  content: nextjsTenantMiddleware(),
241
269
  },
270
+ {
271
+ filename: "src/app/layout.tsx",
272
+ content: nextjsRootLayout(projectName),
273
+ },
242
274
  {
243
275
  filename: "src/app/page.tsx",
244
276
  content: `// app/page.tsx - ${projectName} root page
@@ -249,7 +281,7 @@ export default function Home() {
249
281
  <p>Multi-tenant app powered by Stratum.</p>
250
282
  <ul>
251
283
  <li>Configure tenants via the Stratum control plane</li>
252
- <li>The tenant comes from a verified JWT in <code>middleware.ts</code></li>
284
+ <li>The tenant comes from a verified JWT in <code>src/middleware.ts</code></li>
253
285
  <li>Use <code>@stratum-hq/lib</code> for tenant resolution</li>
254
286
  </ul>
255
287
  </main>
@@ -2,6 +2,16 @@ import type { StackPreset } from "../matrix.js";
2
2
  import { rootSources } from "./tsconfig.js";
3
3
  import { STRATUM_RANGES } from "../stratum-versions.js";
4
4
 
5
+ /**
6
+ * drizzle-kit 0.31 still depends on the deprecated @esbuild-kit/esm-loader,
7
+ * whose @esbuild-kit/core-utils pins esbuild ~0.18, which has a published
8
+ * advisory (GHSA-67mh-4wv8-2f99). drizzle-kit itself uses esbuild ^0.25, so
9
+ * the override moves the nested copy to that line too.
10
+ */
11
+ const DRIZZLE_KIT_OVERRIDES = {
12
+ "@esbuild-kit/core-utils": { esbuild: "^0.25.4" },
13
+ };
14
+
5
15
  export function generatePresetPackageJson(projectName: string, preset: StackPreset): string {
6
16
  const deps: Record<string, string> = {
7
17
  "@stratum-hq/lib": STRATUM_RANGES["@stratum-hq/lib"],
@@ -14,7 +24,7 @@ export function generatePresetPackageJson(projectName: string, preset: StackPres
14
24
  if (preset.framework === "nestjs") {
15
25
  devDeps["tsc-watch"] = "^7.2.0";
16
26
  } else if (preset.framework !== "nextjs") {
17
- devDeps["tsx"] = "^4.7.0";
27
+ devDeps["tsx"] = "^4.19.3";
18
28
  }
19
29
 
20
30
  // Database driver deps
@@ -40,6 +50,7 @@ export function generatePresetPackageJson(projectName: string, preset: StackPres
40
50
  scripts,
41
51
  dependencies: sortKeys(deps),
42
52
  devDependencies: sortKeys(devDeps),
53
+ ...(preset.orm === "drizzle" ? { overrides: DRIZZLE_KIT_OVERRIDES } : {}),
43
54
  engines: {
44
55
  node: ">=20.0.0",
45
56
  },
@@ -76,7 +87,7 @@ function addDatabaseDeps(deps: Record<string, string>, devDeps: Record<string, s
76
87
  break;
77
88
  case "mysql":
78
89
  if (preset.orm === "drizzle" || preset.orm === "pg") {
79
- deps["mysql2"] = "^3.9.0";
90
+ deps["mysql2"] = "^3.23.1";
80
91
  }
81
92
  break;
82
93
  }
@@ -92,8 +103,8 @@ function addOrmDeps(deps: Record<string, string>, devDeps: Record<string, string
92
103
  }
93
104
  break;
94
105
  case "drizzle":
95
- deps["drizzle-orm"] = "^0.30.0";
96
- devDeps["drizzle-kit"] = "^0.21.0";
106
+ deps["drizzle-orm"] = "^0.45.3";
107
+ devDeps["drizzle-kit"] = "^0.31.11";
97
108
  if (preset.database === "postgres") {
98
109
  deps["pg"] = "^8.11.0";
99
110
  }
@@ -101,7 +112,7 @@ function addOrmDeps(deps: Record<string, string>, devDeps: Record<string, string
101
112
  case "sequelize":
102
113
  deps["sequelize"] = "^6.37.0";
103
114
  if (preset.database === "mysql") {
104
- deps["mysql2"] = "^3.9.0";
115
+ deps["mysql2"] = "^3.23.1";
105
116
  } else {
106
117
  deps["pg"] = "^8.11.0";
107
118
  }
@@ -110,7 +121,7 @@ function addOrmDeps(deps: Record<string, string>, devDeps: Record<string, string
110
121
  deps["knex"] = "^3.1.0";
111
122
  break;
112
123
  case "mongoose":
113
- deps["mongoose"] = "^8.2.0";
124
+ deps["mongoose"] = "^8.24.1";
114
125
  break;
115
126
  case "pg":
116
127
  // pg already handled in addDatabaseDeps
@@ -125,11 +136,11 @@ function addFrameworkDeps(deps: Record<string, string>, devDeps: Record<string,
125
136
  }
126
137
  switch (preset.framework) {
127
138
  case "express":
128
- deps["express"] = "^4.18.0";
139
+ deps["express"] = "^4.22.3";
129
140
  devDeps["@types/express"] = "^4.17.21";
130
141
  break;
131
142
  case "fastify":
132
- deps["fastify"] = "^4.26.0";
143
+ deps["fastify"] = "^5.12.5";
133
144
  break;
134
145
  case "nextjs":
135
146
  deps["next"] = "^15.5.16";
@@ -139,13 +150,13 @@ function addFrameworkDeps(deps: Record<string, string>, devDeps: Record<string,
139
150
  devDeps["@types/react-dom"] = "^19.0.0";
140
151
  break;
141
152
  case "hono":
142
- deps["hono"] = "^4.1.0";
143
- deps["@hono/node-server"] = "^1.8.0";
153
+ deps["hono"] = "^4.13.7";
154
+ deps["@hono/node-server"] = "^1.19.15";
144
155
  break;
145
156
  case "nestjs":
146
- deps["@nestjs/core"] = "^11.0.0";
147
- deps["@nestjs/common"] = "^11.0.0";
148
- deps["@nestjs/platform-express"] = "^11.0.0";
157
+ deps["@nestjs/core"] = "^11.1.18";
158
+ deps["@nestjs/common"] = "^11.1.18";
159
+ deps["@nestjs/platform-express"] = "^11.1.18";
149
160
  deps["reflect-metadata"] = "^0.2.0";
150
161
  deps["rxjs"] = "^7.8.0";
151
162
  break;
@@ -73,12 +73,15 @@ npx prisma db push
73
73
  }
74
74
  if (preset.orm === "drizzle") {
75
75
  return `
76
- ### 3b. Run Drizzle migrations
76
+ ### 3b. Create the tables
77
+
78
+ The tables are defined in \`src/schema.ts\`, which \`drizzle.config.ts\` points at.
79
+ drizzle-kit does not read \`.env\`, so load it first:
77
80
 
78
81
  \`\`\`bash
79
- npx drizzle-kit push
82
+ node --env-file=.env node_modules/drizzle-kit/bin.cjs push
80
83
  \`\`\`
81
- `;
84
+ ${preset.database === "postgres" ? "\nOn PostgreSQL drizzle-kit connects with `DATABASE_SUPERUSER_URL`, the superuser kept for migrations. The app role still reads and writes the new tables, and because it does not own them, their row-level security policies apply to it.\n" : ""}`;
82
85
  }
83
86
  return "";
84
87
  }
@@ -86,23 +89,23 @@ npx drizzle-kit push
86
89
  function getStrategyDescription(strategy: string): string {
87
90
  switch (strategy) {
88
91
  case "rls":
89
- return `- **Row-Level Security** -- PostgreSQL RLS policies filter rows by tenant automatically
92
+ return `- **Row-Level Security**: PostgreSQL RLS policies filter rows by tenant automatically
90
93
  - Each query sets \`app.current_tenant_id\` and RLS enforces isolation
91
94
  - All tenants share one database and schema`;
92
95
  case "schema":
93
- return `- **Schema-per-tenant** -- each tenant gets a dedicated PostgreSQL schema
96
+ return `- **Schema-per-tenant**: each tenant gets a dedicated PostgreSQL schema
94
97
  - Queries are routed to the correct schema via search_path
95
98
  - Shared database, isolated schemas`;
96
99
  case "database":
97
- return `- **Database-per-tenant** -- each tenant gets a fully isolated database
100
+ return `- **Database-per-tenant**: each tenant gets a fully isolated database
98
101
  - Connection routing directs queries to the correct database
99
102
  - Maximum isolation at the cost of more resource usage`;
100
103
  case "collection":
101
- return `- **Collection-per-tenant** -- each tenant gets dedicated MongoDB collections
104
+ return `- **Collection-per-tenant**: each tenant gets dedicated MongoDB collections
102
105
  - Collection names are prefixed or namespaced by tenant ID
103
106
  - Shared database, isolated collections`;
104
107
  case "table-prefix":
105
- return `- **Table-prefix** -- tenant-specific tables with a naming prefix
108
+ return `- **Table-prefix**: tenant-specific tables with a naming prefix
106
109
  - Tables are prefixed with the tenant identifier
107
110
  - Shared database, prefixed table names`;
108
111
  default:
@@ -24,12 +24,15 @@ export function generateTsconfig(framework: Framework, extraSources: string[] =
24
24
  {
25
25
  compilerOptions: {
26
26
  target: "ESNext",
27
- module: "NodeNext",
28
- moduleResolution: "NodeNext",
27
+ // Next.js resolves imports like a bundler: "next/server" has no file
28
+ // extension, which NodeNext refuses in an ES module package.
29
+ ...(framework === "nextjs"
30
+ ? { module: "ESNext", moduleResolution: "Bundler" }
31
+ : { module: "NodeNext", moduleResolution: "NodeNext" }),
29
32
  strict: true,
30
- outDir: "dist",
31
- rootDir,
32
- declaration: true,
33
+ // next build compiles a Next.js app itself and type-checks the files it
34
+ // generates under .next/types, which a rootDir of src would reject.
35
+ ...(framework === "nextjs" ? { noEmit: true } : { outDir: "dist", rootDir, declaration: true }),
33
36
  skipLibCheck: true,
34
37
  esModuleInterop: true,
35
38
  ...(framework === "nestjs"
@@ -39,7 +42,10 @@ export function generateTsconfig(framework: Framework, extraSources: string[] =
39
42
  ? { jsx: "preserve", plugins: [{ name: "next" }] }
40
43
  : {}),
41
44
  },
42
- include: ["src", ...extraSources],
45
+ include:
46
+ framework === "nextjs"
47
+ ? ["next-env.d.ts", "src", ...extraSources, ".next/types/**/*.ts"]
48
+ : ["src", ...extraSources],
43
49
  exclude: ["node_modules", "dist"],
44
50
  },
45
51
  null,