@danceroutine/tango-codegen 0.1.0 → 1.0.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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/chunk-BkvOhyD0.js +12 -0
  4. package/dist/{generators/repository → commands}/index.d.ts +1 -1
  5. package/dist/commands/index.js +4 -0
  6. package/dist/commands/registerCodegenCommands.d.ts +5 -0
  7. package/dist/commands/runInitCommand.d.ts +13 -0
  8. package/dist/commands/runInstall.d.ts +2 -0
  9. package/dist/commands/runNewCommand.d.ts +16 -0
  10. package/dist/commands-C2Xr9uRE.js +186 -0
  11. package/dist/commands-C2Xr9uRE.js.map +1 -0
  12. package/dist/frameworks/contracts/FrameworkScaffoldStrategy.d.ts +39 -0
  13. package/dist/frameworks/contracts/template/ScaffoldTemplate.d.ts +22 -0
  14. package/dist/frameworks/contracts/template/TemplateBuilder.d.ts +40 -0
  15. package/dist/frameworks/contracts/template/implementation/ScaffoldTemplateDescriptor.d.ts +10 -0
  16. package/dist/frameworks/index.d.ts +11 -0
  17. package/dist/frameworks/index.js +3 -0
  18. package/dist/frameworks/registry/FrameworkScaffoldRegistry.d.ts +23 -0
  19. package/dist/frameworks/scaffold/scaffoldProject.d.ts +17 -0
  20. package/dist/frameworks/strategies/express/ExpressScaffoldStrategy.d.ts +14 -0
  21. package/dist/frameworks/strategies/express/templates/appSource.d.ts +6 -0
  22. package/dist/frameworks/strategies/express/templates/bootstrap.d.ts +6 -0
  23. package/dist/frameworks/strategies/express/templates/models.d.ts +10 -0
  24. package/dist/frameworks/strategies/express/templates/openapi.d.ts +6 -0
  25. package/dist/frameworks/strategies/express/templates/packageJson.d.ts +6 -0
  26. package/dist/frameworks/strategies/express/templates/readme.d.ts +6 -0
  27. package/dist/frameworks/strategies/express/templates/serializers.d.ts +10 -0
  28. package/dist/frameworks/strategies/express/templates/tangoConfig.d.ts +6 -0
  29. package/dist/frameworks/strategies/express/templates/tangoRegister.d.ts +6 -0
  30. package/dist/frameworks/strategies/express/templates/tsconfig.d.ts +6 -0
  31. package/dist/frameworks/strategies/express/templates/tsconfigBuild.d.ts +6 -0
  32. package/dist/frameworks/strategies/express/templates/viewSet.d.ts +6 -0
  33. package/dist/frameworks/strategies/next/NextScaffoldStrategy.d.ts +14 -0
  34. package/dist/frameworks/strategies/next/templates/bootstrap.d.ts +6 -0
  35. package/dist/frameworks/strategies/next/templates/healthRoute.d.ts +6 -0
  36. package/dist/frameworks/strategies/next/templates/layout.d.ts +6 -0
  37. package/dist/frameworks/strategies/next/templates/models.d.ts +11 -0
  38. package/dist/frameworks/strategies/next/templates/openapi.d.ts +6 -0
  39. package/dist/frameworks/strategies/next/templates/openapiRoute.d.ts +6 -0
  40. package/dist/frameworks/strategies/next/templates/packageJson.d.ts +6 -0
  41. package/dist/frameworks/strategies/next/templates/page.d.ts +6 -0
  42. package/dist/frameworks/strategies/next/templates/readme.d.ts +6 -0
  43. package/dist/frameworks/strategies/next/templates/serializers.d.ts +10 -0
  44. package/dist/frameworks/strategies/next/templates/tangoConfig.d.ts +6 -0
  45. package/dist/frameworks/strategies/next/templates/todoRoute.d.ts +6 -0
  46. package/dist/frameworks/strategies/next/templates/tsconfig.d.ts +6 -0
  47. package/dist/frameworks/strategies/next/templates/viewSet.d.ts +6 -0
  48. package/dist/frameworks-Bp_9BOt2.js +1366 -0
  49. package/dist/frameworks-Bp_9BOt2.js.map +1 -0
  50. package/dist/generators/index.d.ts +0 -2
  51. package/dist/generators/index.js +2 -2
  52. package/dist/generators/migration/generateMigrationFromModels.d.ts +6 -0
  53. package/dist/generators/model/generateModelInterface.d.ts +3 -0
  54. package/dist/generators/viewset/generateViewSet.d.ts +3 -0
  55. package/dist/{generators-CDXkQAkq.js → generators-C9UeakCq.js} +18 -55
  56. package/dist/generators-C9UeakCq.js.map +1 -0
  57. package/dist/index.d.ts +6 -2
  58. package/dist/index.js +4 -7
  59. package/dist/mappers/fieldType.d.ts +6 -0
  60. package/package.json +60 -48
  61. package/dist/generators/repository/generateRepository.d.ts +0 -1
  62. package/dist/generators-CDXkQAkq.js.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/mappers/fieldType.js +0 -32
  65. package/dist/version.d.ts +0 -1
@@ -0,0 +1,1366 @@
1
+ import { __export } from "./chunk-BkvOhyD0.js";
2
+ import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, resolve } from "node:path";
4
+
5
+ //#region src/frameworks/contracts/template/ScaffoldTemplate.ts
6
+ const SCAFFOLD_TEMPLATE_CATEGORY = {
7
+ FRAMEWORK: "framework",
8
+ TANGO: "tango",
9
+ INIT_ONLY: "init-only"
10
+ };
11
+
12
+ //#endregion
13
+ //#region src/frameworks/contracts/template/implementation/ScaffoldTemplateDescriptor.ts
14
+ var ScaffoldTemplateDescriptor = class {
15
+ constructor(template, category) {
16
+ this.template = template;
17
+ this.category = category;
18
+ }
19
+ get path() {
20
+ return this.template.getPath();
21
+ }
22
+ render(ctx) {
23
+ return this.template.setContext(ctx).build();
24
+ }
25
+ shouldEmit(mode) {
26
+ if (mode === "new") return this.category === SCAFFOLD_TEMPLATE_CATEGORY.FRAMEWORK || this.category === SCAFFOLD_TEMPLATE_CATEGORY.TANGO;
27
+ return this.category === SCAFFOLD_TEMPLATE_CATEGORY.TANGO || this.category === SCAFFOLD_TEMPLATE_CATEGORY.INIT_ONLY;
28
+ }
29
+ };
30
+
31
+ //#endregion
32
+ //#region package.json
33
+ var name = "@danceroutine/tango-codegen";
34
+ var version$1 = "1.0.0";
35
+ var description = "CLI for generating repositories, types, migrations, and OpenAPI specs for Tango";
36
+ var type = "module";
37
+ var main = "./dist/index.js";
38
+ var types = "./dist/index.d.ts";
39
+ var exports = {
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js"
43
+ },
44
+ "./domain": {
45
+ "types": "./dist/domain/index.d.ts",
46
+ "import": "./dist/domain/index.js"
47
+ },
48
+ "./generators": {
49
+ "types": "./dist/generators/index.d.ts",
50
+ "import": "./dist/generators/index.js"
51
+ },
52
+ "./frameworks": {
53
+ "types": "./dist/frameworks/index.d.ts",
54
+ "import": "./dist/frameworks/index.js"
55
+ },
56
+ "./commands": {
57
+ "types": "./dist/commands/index.d.ts",
58
+ "import": "./dist/commands/index.js"
59
+ }
60
+ };
61
+ var files = ["dist"];
62
+ var scripts = {
63
+ "build": "tsdown",
64
+ "test": "vitest run --coverage",
65
+ "test:watch": "vitest",
66
+ "typecheck": "pnpm run typecheck:prod && pnpm run typecheck:test",
67
+ "typecheck:prod": "tsc --noEmit -p tsconfig.json",
68
+ "typecheck:test": "tsc --noEmit -p tsconfig.tests.json"
69
+ };
70
+ var keywords = [
71
+ "tango",
72
+ "codegen",
73
+ "cli",
74
+ "generator",
75
+ "migrations"
76
+ ];
77
+ var author = "Pedro Del Moral Lopez";
78
+ var license = "MIT";
79
+ var repository = {
80
+ "type": "git",
81
+ "url": "https://github.com/danceroutine/tango.git",
82
+ "directory": "packages/codegen"
83
+ };
84
+ var dependencies = {
85
+ "@danceroutine/tango-core": "workspace:*",
86
+ "yargs": "^17.7.2"
87
+ };
88
+ var devDependencies = {
89
+ "@types/yargs": "^17.0.33",
90
+ "@types/node": "^22.9.0",
91
+ "tsdown": "^0.4.0",
92
+ "typescript": "^5.6.3",
93
+ "vitest": "^4.0.6"
94
+ };
95
+ var package_default = {
96
+ name,
97
+ version: version$1,
98
+ description,
99
+ type,
100
+ main,
101
+ types,
102
+ exports,
103
+ files,
104
+ scripts,
105
+ keywords,
106
+ author,
107
+ license,
108
+ repository,
109
+ dependencies,
110
+ devDependencies
111
+ };
112
+
113
+ //#endregion
114
+ //#region src/frameworks/contracts/template/TemplateBuilder.ts
115
+ const { version } = package_default;
116
+ var TemplateBuilder = class TemplateBuilder {
117
+ name;
118
+ _context;
119
+ constructor(options) {
120
+ this.name = options.name;
121
+ }
122
+ /**
123
+ * One-liner to install Tango + dialect deps and Tango CLI (for init success message).
124
+ */
125
+ static getTangoInstallOneLiner(packageManager, dialect, framework) {
126
+ const deps = TemplateBuilder.getTangoDependencyEntriesFor(dialect, framework);
127
+ const devDeps = TemplateBuilder.getTangoDevDependencyEntriesFor();
128
+ const depList = Object.entries(deps).map(([pkg, ver]) => `${pkg}@${ver}`).join(" ");
129
+ const devList = Object.entries(devDeps).map(([pkg, ver]) => `${pkg}@${ver}`).join(" ");
130
+ const addCmd = packageManager === "npm" ? "npm install" : `${packageManager} add`;
131
+ const addDevCmd = packageManager === "npm" ? "npm install -D" : `${packageManager} add -D`;
132
+ return `${addCmd} ${depList} && ${addDevCmd} ${devList}`;
133
+ }
134
+ /**
135
+ * Shorthand for static content (no subclass needed). Returns a BoundTemplate that add* methods accept.
136
+ */
137
+ static createStaticTemplate(fileName, template) {
138
+ class TransientStaticTemplateBuilder extends TemplateBuilder {
139
+ resolveTemplate(_context) {
140
+ return typeof template === "string" ? template : template();
141
+ }
142
+ }
143
+ return new TransientStaticTemplateBuilder({ name: fileName });
144
+ }
145
+ /** Tango package version (semver range) for scaffolded dependency entries. */
146
+ static getTangoVersion() {
147
+ return `^${version}`;
148
+ }
149
+ static getTangoDependencyEntriesFor(dialect, framework) {
150
+ const v = TemplateBuilder.getTangoVersion();
151
+ const core = {
152
+ "@danceroutine/tango-core": v,
153
+ "@danceroutine/tango-schema": v,
154
+ "@danceroutine/tango-orm": v,
155
+ "@danceroutine/tango-resources": v,
156
+ "@danceroutine/tango-migrations": v,
157
+ "@danceroutine/tango-openapi": v,
158
+ "@danceroutine/tango-config": v
159
+ };
160
+ const adapter = framework === "express" ? { "@danceroutine/tango-adapters-express": v } : { "@danceroutine/tango-adapters-next": v };
161
+ const dialectDeps = dialect === "sqlite" ? { "better-sqlite3": "^11.10.0" } : { pg: "^8.16.3" };
162
+ return {
163
+ ...core,
164
+ ...adapter,
165
+ ...dialectDeps
166
+ };
167
+ }
168
+ static getTangoDevDependencyEntriesFor() {
169
+ return { "@danceroutine/tango-cli": TemplateBuilder.getTangoVersion() };
170
+ }
171
+ /** Bind context and return this for chaining. Use before passing to add* methods. */
172
+ setContext(context) {
173
+ this._context = context;
174
+ return this;
175
+ }
176
+ getPath() {
177
+ return this.name;
178
+ }
179
+ build() {
180
+ if (this._context === undefined) throw new Error("TemplateBuilder: context not bound. Call .setContext(context) before .build().");
181
+ return this.resolveTemplate(this._context);
182
+ }
183
+ getTangoDependencyEntries(context) {
184
+ return TemplateBuilder.getTangoDependencyEntriesFor(context.dialect, context.framework);
185
+ }
186
+ getTangoDevDependencyEntries(_context) {
187
+ return TemplateBuilder.getTangoDevDependencyEntriesFor();
188
+ }
189
+ };
190
+
191
+ //#endregion
192
+ //#region src/frameworks/contracts/FrameworkScaffoldStrategy.ts
193
+ const SUPPORTED_FRAMEWORK = {
194
+ EXPRESS: "express",
195
+ NEXT: "next"
196
+ };
197
+ const PACKAGE_MANAGER = {
198
+ PNPM: "pnpm",
199
+ NPM: "npm",
200
+ YARN: "yarn",
201
+ BUN: "bun"
202
+ };
203
+ const SCAFFOLD_DATABASE_DIALECT = {
204
+ SQLITE: "sqlite",
205
+ POSTGRES: "postgres"
206
+ };
207
+ var FrameworkScaffoldStrategy = class {
208
+ /**
209
+ * One-liner to install Tango + dialect deps and Tango CLI, for init success message.
210
+ */
211
+ getTangoInstallOneLiner(packageManager, context) {
212
+ return TemplateBuilder.getTangoInstallOneLiner(packageManager, context.dialect, context.framework);
213
+ }
214
+ addFrameworkTemplate(template) {
215
+ return this.createTemplate(template, SCAFFOLD_TEMPLATE_CATEGORY.FRAMEWORK);
216
+ }
217
+ addTangoTemplate(template) {
218
+ return this.createTemplate(template, SCAFFOLD_TEMPLATE_CATEGORY.TANGO);
219
+ }
220
+ addInitOnlyTemplate(template) {
221
+ return this.createTemplate(template, SCAFFOLD_TEMPLATE_CATEGORY.INIT_ONLY);
222
+ }
223
+ createTemplate(template, category) {
224
+ return new ScaffoldTemplateDescriptor(template, category);
225
+ }
226
+ };
227
+
228
+ //#endregion
229
+ //#region src/frameworks/strategies/express/templates/packageJson.ts
230
+ var PackageJsonTemplateBuilder$1 = class extends TemplateBuilder {
231
+ constructor() {
232
+ super({ name: "package.json" });
233
+ }
234
+ resolveTemplate(context) {
235
+ const deps = this.getTangoDependencyEntries(context);
236
+ const devDeps = this.getTangoDevDependencyEntries(context);
237
+ return JSON.stringify({
238
+ name: context.projectName,
239
+ private: true,
240
+ type: "module",
241
+ scripts: {
242
+ predev: "tango migrate --config ./tango.config.ts",
243
+ dev: "tsx watch src/index.ts",
244
+ prestart: "tango migrate --config ./tango.config.ts",
245
+ build: "tsc -p tsconfig.build.json",
246
+ start: "node dist/index.js",
247
+ typecheck: "tsc --noEmit",
248
+ "setup:schema": "node -e \"require('node:fs').mkdirSync('./.data',{recursive:true})\" && tango migrate --config ./tango.config.ts",
249
+ "make:migrations": "tango make:migrations --config ./tango.config.ts --models ./src/models/index.ts --name \"\${npm_config_name:-manual_change}\"",
250
+ prebootstrap: "node -e \"require('node:fs').mkdirSync('./.data',{recursive:true})\" && tango migrate --config ./tango.config.ts",
251
+ bootstrap: "tsx src/bootstrap.ts"
252
+ },
253
+ dependencies: {
254
+ ...deps,
255
+ express: "^4.21.2",
256
+ zod: "^4.0.0"
257
+ },
258
+ devDependencies: {
259
+ ...devDeps,
260
+ "@types/express": "^5.0.0",
261
+ "@types/node": "^22.9.0",
262
+ tsx: "^4.20.6",
263
+ typescript: "^5.6.3"
264
+ }
265
+ }, null, 4);
266
+ }
267
+ };
268
+
269
+ //#endregion
270
+ //#region src/frameworks/strategies/express/templates/tsconfig.ts
271
+ var TsConfigTemplateBuilder$1 = class extends TemplateBuilder {
272
+ constructor() {
273
+ super({ name: "tsconfig.json" });
274
+ }
275
+ resolveTemplate(_context) {
276
+ return JSON.stringify({
277
+ compilerOptions: {
278
+ target: "ES2022",
279
+ lib: ["ES2022", "DOM"],
280
+ module: "NodeNext",
281
+ moduleResolution: "NodeNext",
282
+ strict: true,
283
+ noEmit: true,
284
+ esModuleInterop: true,
285
+ skipLibCheck: true,
286
+ resolveJsonModule: true,
287
+ types: ["node"]
288
+ },
289
+ include: [
290
+ "src",
291
+ "migrations/**/*.ts",
292
+ "tango.config.ts"
293
+ ],
294
+ exclude: ["node_modules", "dist"]
295
+ }, null, 4);
296
+ }
297
+ };
298
+
299
+ //#endregion
300
+ //#region src/frameworks/strategies/express/templates/tsconfigBuild.ts
301
+ var TsConfigBuildTemplateBuilder = class extends TemplateBuilder {
302
+ constructor() {
303
+ super({ name: "tsconfig.build.json" });
304
+ }
305
+ resolveTemplate(_context) {
306
+ return JSON.stringify({
307
+ extends: "./tsconfig.json",
308
+ compilerOptions: {
309
+ noEmit: false,
310
+ outDir: "./dist",
311
+ rootDir: ".",
312
+ sourceMap: true,
313
+ declaration: false
314
+ },
315
+ include: ["src", "tango.config.ts"],
316
+ exclude: [
317
+ "node_modules",
318
+ "dist",
319
+ "migrations",
320
+ "**/*.test.ts"
321
+ ]
322
+ }, null, 4);
323
+ }
324
+ };
325
+
326
+ //#endregion
327
+ //#region src/frameworks/strategies/express/templates/tangoConfig.ts
328
+ var TangoConfigTemplateBuilder$1 = class extends TemplateBuilder {
329
+ constructor() {
330
+ super({ name: "tango.config.ts" });
331
+ }
332
+ resolveTemplate(context) {
333
+ if (context.dialect === "sqlite") return `import { defineConfig } from '@danceroutine/tango-config';
334
+
335
+ export default defineConfig({
336
+ current: (process.env.NODE_ENV || 'development') as 'development' | 'test' | 'production',
337
+ environments: {
338
+ development: {
339
+ name: 'development',
340
+ db: {
341
+ adapter: 'sqlite',
342
+ filename: process.env.TANGO_SQLITE_FILENAME || './.data/${context.projectName}.sqlite',
343
+ maxConnections: 1,
344
+ },
345
+ migrations: { dir: './migrations', online: false },
346
+ },
347
+ test: {
348
+ name: 'test',
349
+ db: {
350
+ adapter: 'sqlite',
351
+ filename: process.env.TANGO_SQLITE_FILENAME || ':memory:',
352
+ maxConnections: 1,
353
+ },
354
+ migrations: { dir: './migrations', online: false },
355
+ },
356
+ production: {
357
+ name: 'production',
358
+ db: {
359
+ adapter: 'sqlite',
360
+ filename: process.env.TANGO_SQLITE_FILENAME || './.data/${context.projectName}.sqlite',
361
+ maxConnections: 1,
362
+ },
363
+ migrations: { dir: './migrations', online: false },
364
+ },
365
+ },
366
+ });
367
+ `;
368
+ return `import { defineConfig } from '@danceroutine/tango-config';
369
+
370
+ export default defineConfig({
371
+ current: (process.env.NODE_ENV || 'development') as 'development' | 'test' | 'production',
372
+ environments: {
373
+ development: {
374
+ name: 'development',
375
+ db: {
376
+ adapter: 'postgres',
377
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}',
378
+ maxConnections: 10,
379
+ },
380
+ migrations: { dir: './migrations', online: true },
381
+ },
382
+ test: {
383
+ name: 'test',
384
+ db: {
385
+ adapter: 'postgres',
386
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}_test',
387
+ maxConnections: 5,
388
+ },
389
+ migrations: { dir: './migrations', online: true },
390
+ },
391
+ production: {
392
+ name: 'production',
393
+ db: {
394
+ adapter: 'postgres',
395
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}',
396
+ maxConnections: 20,
397
+ },
398
+ migrations: { dir: './migrations', online: true },
399
+ },
400
+ },
401
+ });
402
+ `;
403
+ }
404
+ };
405
+
406
+ //#endregion
407
+ //#region src/frameworks/strategies/express/templates/appSource.ts
408
+ var AppSourceTemplateBuilder = class extends TemplateBuilder {
409
+ constructor() {
410
+ super({ name: "src/index.ts" });
411
+ }
412
+ resolveTemplate(_context) {
413
+ return `import express from 'express';
414
+ import { seedExampleData } from './bootstrap.js';
415
+ import { registerTango } from './tango.js';
416
+
417
+ async function main(): Promise<void> {
418
+ const app = express();
419
+ app.use(express.json());
420
+
421
+ if (process.env.AUTO_BOOTSTRAP === 'true') {
422
+ await seedExampleData(Number(process.env.SEED_TODOS_COUNT || '100'));
423
+ }
424
+
425
+ app.get('/health', (_req, res) => {
426
+ res.json({ ok: true });
427
+ });
428
+
429
+ await registerTango(app);
430
+
431
+ const port = Number(process.env.PORT || '3000');
432
+ app.listen(port, () => {
433
+ console.log(\`Tango Express app listening on http://localhost:\${String(port)}\`);
434
+ });
435
+ }
436
+
437
+ // oxlint-disable-next-line unicorn/prefer-top-level-await
438
+ main().catch((error: unknown) => {
439
+ console.error('Failed to start app:', error);
440
+ process.exit(1);
441
+ });
442
+ `;
443
+ }
444
+ };
445
+
446
+ //#endregion
447
+ //#region src/frameworks/strategies/express/templates/models.ts
448
+ var TodoModelTemplateBuilder$1 = class extends TemplateBuilder {
449
+ constructor() {
450
+ super({ name: "src/models/TodoModel.ts" });
451
+ }
452
+ resolveTemplate(_context) {
453
+ return `import { z } from 'zod';
454
+ import '@danceroutine/tango-orm/runtime';
455
+ import { Model, t } from '@danceroutine/tango-schema';
456
+
457
+ export const TodoReadSchema = z.object({
458
+ id: z.number(),
459
+ title: z.string().min(1),
460
+ completed: z.coerce.boolean(),
461
+ createdAt: z.string(),
462
+ updatedAt: z.string(),
463
+ });
464
+
465
+ export const TodoCreateSchema = z.object({
466
+ title: z.string().min(1),
467
+ completed: z.boolean().optional().default(false),
468
+ });
469
+
470
+ export const TodoUpdateSchema = TodoCreateSchema.partial();
471
+
472
+ export type Todo = z.output<typeof TodoReadSchema>;
473
+
474
+ export const TodoModel = Model({
475
+ namespace: 'app',
476
+ name: 'Todo',
477
+ schema: TodoReadSchema.extend({
478
+ id: t.primaryKey(z.number().int()),
479
+ title: z.string().min(1),
480
+ completed: t.default(z.coerce.boolean(), 'false'),
481
+ createdAt: t.default(z.string(), { now: true }),
482
+ updatedAt: t.default(z.string(), { now: true }),
483
+ }),
484
+ });
485
+ `;
486
+ }
487
+ };
488
+ var ModelsBarrelTemplateBuilder$1 = class extends TemplateBuilder {
489
+ constructor() {
490
+ super({ name: "src/models/index.ts" });
491
+ }
492
+ resolveTemplate(_context) {
493
+ return `export { TodoReadSchema, TodoCreateSchema, TodoUpdateSchema, TodoModel, type Todo } from './TodoModel.js';
494
+ `;
495
+ }
496
+ };
497
+
498
+ //#endregion
499
+ //#region src/frameworks/strategies/express/templates/serializers.ts
500
+ var TodoSerializerTemplateBuilder$1 = class extends TemplateBuilder {
501
+ constructor() {
502
+ super({ name: "src/serializers/TodoSerializer.ts" });
503
+ }
504
+ resolveTemplate(_context) {
505
+ return `import { ModelSerializer } from '@danceroutine/tango-resources';
506
+ import { TodoCreateSchema, TodoModel, TodoReadSchema, TodoUpdateSchema, type Todo } from '../models/index.js';
507
+
508
+ export class TodoSerializer extends ModelSerializer<
509
+ Todo,
510
+ typeof TodoCreateSchema,
511
+ typeof TodoUpdateSchema,
512
+ typeof TodoReadSchema
513
+ > {
514
+ static readonly model = TodoModel;
515
+ static readonly createSchema = TodoCreateSchema;
516
+ static readonly updateSchema = TodoUpdateSchema;
517
+ static readonly outputSchema = TodoReadSchema;
518
+ }
519
+ `;
520
+ }
521
+ };
522
+ var SerializersBarrelTemplateBuilder$1 = class extends TemplateBuilder {
523
+ constructor() {
524
+ super({ name: "src/serializers/index.ts" });
525
+ }
526
+ resolveTemplate(_context) {
527
+ return `export { TodoSerializer } from './TodoSerializer.js';
528
+ `;
529
+ }
530
+ };
531
+
532
+ //#endregion
533
+ //#region src/frameworks/strategies/express/templates/openapi.ts
534
+ var OpenAPITemplateBuilder$1 = class extends TemplateBuilder {
535
+ constructor() {
536
+ super({ name: "src/openapi.ts" });
537
+ }
538
+ resolveTemplate(_context) {
539
+ return `import { describeViewSet, generateOpenAPISpec, type OpenAPISpec } from '@danceroutine/tango-openapi';
540
+ import { TodoViewSet } from './viewsets/TodoViewSet.js';
541
+
542
+ export function createOpenAPISpec(): OpenAPISpec {
543
+ return generateOpenAPISpec({
544
+ title: 'Tango Express Todo API',
545
+ version: '1.0.0',
546
+ description: 'OpenAPI document generated from Tango resource instances.',
547
+ resources: [describeViewSet({ basePath: '/api/todos', resource: new TodoViewSet() })],
548
+ });
549
+ }
550
+ `;
551
+ }
552
+ };
553
+
554
+ //#endregion
555
+ //#region src/frameworks/strategies/express/templates/viewSet.ts
556
+ var ViewSetTemplateBuilder$1 = class extends TemplateBuilder {
557
+ constructor() {
558
+ super({ name: "src/viewsets/TodoViewSet.ts" });
559
+ }
560
+ resolveTemplate(_context) {
561
+ return `import { FilterSet, ModelViewSet } from '@danceroutine/tango-resources';
562
+ import { type Todo } from '../models/index.js';
563
+ import { TodoSerializer } from '../serializers/index.js';
564
+
565
+ export class TodoViewSet extends ModelViewSet<Todo, typeof TodoSerializer> {
566
+ constructor() {
567
+ super({
568
+ serializer: TodoSerializer,
569
+ filters: FilterSet.define<Todo>({
570
+ fields: {
571
+ completed: true,
572
+ },
573
+ aliases: {
574
+ q: { fields: ['title'], lookup: 'icontains' },
575
+ },
576
+ }),
577
+ orderingFields: ['id', 'createdAt', 'updatedAt', 'title'],
578
+ searchFields: ['title'],
579
+ });
580
+ }
581
+ }
582
+ `;
583
+ }
584
+ };
585
+
586
+ //#endregion
587
+ //#region src/frameworks/strategies/express/templates/bootstrap.ts
588
+ var BootstrapTemplateBuilder$1 = class extends TemplateBuilder {
589
+ constructor() {
590
+ super({ name: "src/bootstrap.ts" });
591
+ }
592
+ resolveTemplate(_context) {
593
+ return `import { TodoModel, type Todo } from './models/index.js';
594
+
595
+ export async function seedExampleData(count = 100): Promise<void> {
596
+ const existing = await TodoModel.objects.query().count();
597
+ if (existing >= count) {
598
+ console.log(\`[bootstrap] Skipped; already have \${existing} todos.\`);
599
+ return;
600
+ }
601
+
602
+ const rows: Array<Partial<Todo>> = [];
603
+ for (let index = existing; index < count; index += 1) {
604
+ const now = new Date(Date.now() - index * 60_000).toISOString();
605
+ rows.push({
606
+ title: \`Seeded todo #\${index + 1}\`,
607
+ completed: index % 3 === 0,
608
+ createdAt: now,
609
+ updatedAt: now,
610
+ });
611
+ }
612
+
613
+ await TodoModel.objects.bulkCreate(rows);
614
+ console.log(\`[bootstrap] Seeded \${rows.length} todos.\`);
615
+ }
616
+
617
+ if (import.meta.url === \`file://\${process.argv[1]}\`) {
618
+ const count = Number(process.env.SEED_TODOS_COUNT || '100');
619
+ // oxlint-disable-next-line unicorn/prefer-top-level-await
620
+ seedExampleData(Number.isFinite(count) ? count : 100).catch((error: unknown) => {
621
+ console.error('[bootstrap] Failed:', error);
622
+ process.exit(1);
623
+ });
624
+ }
625
+ `;
626
+ }
627
+ };
628
+
629
+ //#endregion
630
+ //#region src/frameworks/strategies/express/templates/readme.ts
631
+ var ReadmeTemplateBuilder$1 = class extends TemplateBuilder {
632
+ constructor() {
633
+ super({ name: "README.md" });
634
+ }
635
+ resolveTemplate(context) {
636
+ return `# ${context.projectName}
637
+
638
+ This project was scaffolded by \`tango new --framework express\`.
639
+
640
+ Express still owns the server and route registration; Tango owns model metadata, \`Model.objects\`, serializers, migrations, querying, and resource behavior.
641
+
642
+ ## First-time setup
643
+
644
+ Generate your first migration from the scaffolded models, then start the app:
645
+
646
+ \`\`\`bash
647
+ ${context.packageManager} run make:migrations --name initial
648
+ ${context.packageManager} run dev
649
+ \`\`\`
650
+
651
+ ## Scripts
652
+
653
+ - \`${context.packageManager} run dev\`
654
+ - \`${context.packageManager} run make:migrations --name add_field\`
655
+ - \`${context.packageManager} run setup:schema\`
656
+ - \`${context.packageManager} run bootstrap\`
657
+ - \`${context.packageManager} run typecheck\`
658
+
659
+ ## Useful endpoints
660
+
661
+ - \`GET /health\`
662
+ - \`GET /api/openapi.json\`
663
+ - \`GET|POST|PATCH|DELETE /api/todos...\`
664
+
665
+ ## Project layout
666
+
667
+ - \`tango.config.ts\` Tango configuration
668
+ - \`src/tango.ts\` Express registration helper for Tango routes
669
+ - \`src/models/\` schemas and Tango model metadata; importing the model module enables \`Model.objects\`
670
+ - \`src/serializers/\` serializer-backed API contracts for Tango resources
671
+ - \`src/viewsets/\` CRUD resources backed by \`Model.objects\`
672
+ - \`src/openapi.ts\` OpenAPI document generation
673
+ - \`src/bootstrap.ts\` seed utility for a larger demo dataset
674
+ - \`migrations/\` checked-in Tango migrations
675
+ `;
676
+ }
677
+ };
678
+
679
+ //#endregion
680
+ //#region src/frameworks/strategies/express/templates/tangoRegister.ts
681
+ var TangoRegisterTemplateBuilder = class extends TemplateBuilder {
682
+ constructor() {
683
+ super({ name: "src/tango.ts" });
684
+ }
685
+ resolveTemplate(_context) {
686
+ return `import type { Application } from 'express';
687
+ import { ExpressAdapter } from '@danceroutine/tango-adapters-express/adapter';
688
+ import { createOpenAPISpec } from './openapi.js';
689
+ import { TodoViewSet } from './viewsets/TodoViewSet.js';
690
+
691
+ /**
692
+ * Register Tango API routes and OpenAPI spec on an existing Express app.
693
+ * Use from your app entry (for example, \`index.ts\`): \`await registerTango(app);\`
694
+ */
695
+ export async function registerTango(app: Application): Promise<void> {
696
+ const adapter = new ExpressAdapter();
697
+ adapter.registerViewSet(app, '/api/todos', new TodoViewSet());
698
+ app.get('/api/openapi.json', (_req, res) => {
699
+ res.json(createOpenAPISpec());
700
+ });
701
+ }
702
+ `;
703
+ }
704
+ };
705
+
706
+ //#endregion
707
+ //#region src/frameworks/strategies/express/ExpressScaffoldStrategy.ts
708
+ var ExpressScaffoldStrategy = class extends FrameworkScaffoldStrategy {
709
+ id = "express";
710
+ name = "Express";
711
+ description = "Bootstrap a Tango application hosted by Express.";
712
+ /**
713
+ * Return the file templates needed for the generated Express project.
714
+ */
715
+ getTemplates() {
716
+ return [
717
+ this.addFrameworkTemplate(new PackageJsonTemplateBuilder$1()),
718
+ this.addFrameworkTemplate(new TsConfigTemplateBuilder$1()),
719
+ this.addFrameworkTemplate(new TsConfigBuildTemplateBuilder()),
720
+ this.addTangoTemplate(new TangoConfigTemplateBuilder$1()),
721
+ this.addFrameworkTemplate(new AppSourceTemplateBuilder()),
722
+ this.addTangoTemplate(new TodoModelTemplateBuilder$1()),
723
+ this.addTangoTemplate(new ModelsBarrelTemplateBuilder$1()),
724
+ this.addTangoTemplate(new TodoSerializerTemplateBuilder$1()),
725
+ this.addTangoTemplate(new SerializersBarrelTemplateBuilder$1()),
726
+ this.addTangoTemplate(new OpenAPITemplateBuilder$1()),
727
+ this.addTangoTemplate(new ViewSetTemplateBuilder$1()),
728
+ this.addTangoTemplate(new BootstrapTemplateBuilder$1()),
729
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate("migrations/.gitkeep", "")),
730
+ this.addFrameworkTemplate(new ReadmeTemplateBuilder$1()),
731
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate(".gitignore", "node_modules\ndist\n.data\n.env\n")),
732
+ this.addTangoTemplate(new TangoRegisterTemplateBuilder())
733
+ ];
734
+ }
735
+ };
736
+
737
+ //#endregion
738
+ //#region src/frameworks/strategies/next/templates/packageJson.ts
739
+ var PackageJsonTemplateBuilder = class extends TemplateBuilder {
740
+ constructor() {
741
+ super({ name: "package.json" });
742
+ }
743
+ resolveTemplate(context) {
744
+ const deps = this.getTangoDependencyEntries(context);
745
+ const devDeps = this.getTangoDevDependencyEntries(context);
746
+ return JSON.stringify({
747
+ name: context.projectName,
748
+ private: true,
749
+ type: "module",
750
+ scripts: {
751
+ predev: "tango migrate --config ./tango.config.ts",
752
+ dev: "next dev",
753
+ prestart: "tango migrate --config ./tango.config.ts",
754
+ build: "next build",
755
+ start: "next start",
756
+ typecheck: "tsc --noEmit",
757
+ "setup:schema": "node -e \"require('node:fs').mkdirSync('./.data',{recursive:true})\" && tango migrate --config ./tango.config.ts",
758
+ "make:migrations": "tango make:migrations --config ./tango.config.ts --models ./src/lib/models/index.ts --name \"\${npm_config_name:-manual_change}\"",
759
+ prebootstrap: "node -e \"require('node:fs').mkdirSync('./.data',{recursive:true})\" && tango migrate --config ./tango.config.ts",
760
+ bootstrap: "tsx scripts/bootstrap.ts"
761
+ },
762
+ dependencies: {
763
+ ...deps,
764
+ next: "^15.1.6",
765
+ react: "^19.0.0",
766
+ "react-dom": "^19.0.0",
767
+ zod: "^4.0.0"
768
+ },
769
+ devDependencies: {
770
+ ...devDeps,
771
+ "@types/node": "^22.9.0",
772
+ "@types/react": "^19.0.6",
773
+ "@types/react-dom": "^19.0.2",
774
+ tsx: "^4.20.6",
775
+ typescript: "^5.6.3"
776
+ }
777
+ }, null, 4);
778
+ }
779
+ };
780
+
781
+ //#endregion
782
+ //#region src/frameworks/strategies/next/templates/tsconfig.ts
783
+ var TsConfigTemplateBuilder = class extends TemplateBuilder {
784
+ constructor() {
785
+ super({ name: "tsconfig.json" });
786
+ }
787
+ resolveTemplate(_context) {
788
+ return JSON.stringify({
789
+ compilerOptions: {
790
+ target: "ES2022",
791
+ lib: [
792
+ "dom",
793
+ "dom.iterable",
794
+ "es2022"
795
+ ],
796
+ strict: true,
797
+ noEmit: true,
798
+ module: "esnext",
799
+ moduleResolution: "bundler",
800
+ resolveJsonModule: true,
801
+ isolatedModules: true,
802
+ jsx: "preserve",
803
+ esModuleInterop: true,
804
+ baseUrl: ".",
805
+ paths: { "@/*": ["src/*"] }
806
+ },
807
+ include: [
808
+ "next-env.d.ts",
809
+ "**/*.ts",
810
+ "**/*.tsx",
811
+ "migrations/**/*.ts"
812
+ ],
813
+ exclude: ["node_modules"]
814
+ }, null, 4);
815
+ }
816
+ };
817
+
818
+ //#endregion
819
+ //#region src/frameworks/strategies/next/templates/tangoConfig.ts
820
+ var TangoConfigTemplateBuilder = class extends TemplateBuilder {
821
+ constructor() {
822
+ super({ name: "tango.config.ts" });
823
+ }
824
+ resolveTemplate(context) {
825
+ if (context.dialect === "sqlite") return `import { defineConfig } from '@danceroutine/tango-config';
826
+
827
+ export default defineConfig({
828
+ current: (process.env.NODE_ENV || 'development') as 'development' | 'test' | 'production',
829
+ environments: {
830
+ development: {
831
+ name: 'development',
832
+ db: {
833
+ adapter: 'sqlite',
834
+ filename: process.env.TANGO_SQLITE_FILENAME || './.data/${context.projectName}.sqlite',
835
+ maxConnections: 1,
836
+ },
837
+ migrations: { dir: './migrations', online: false },
838
+ },
839
+ test: {
840
+ name: 'test',
841
+ db: {
842
+ adapter: 'sqlite',
843
+ filename: process.env.TANGO_SQLITE_FILENAME || ':memory:',
844
+ maxConnections: 1,
845
+ },
846
+ migrations: { dir: './migrations', online: false },
847
+ },
848
+ production: {
849
+ name: 'production',
850
+ db: {
851
+ adapter: 'sqlite',
852
+ filename: process.env.TANGO_SQLITE_FILENAME || './.data/${context.projectName}.sqlite',
853
+ maxConnections: 1,
854
+ },
855
+ migrations: { dir: './migrations', online: false },
856
+ },
857
+ },
858
+ });
859
+ `;
860
+ return `import { defineConfig } from '@danceroutine/tango-config';
861
+
862
+ export default defineConfig({
863
+ current: (process.env.NODE_ENV || 'development') as 'development' | 'test' | 'production',
864
+ environments: {
865
+ development: {
866
+ name: 'development',
867
+ db: {
868
+ adapter: 'postgres',
869
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}',
870
+ maxConnections: 10,
871
+ },
872
+ migrations: { dir: './migrations', online: true },
873
+ },
874
+ test: {
875
+ name: 'test',
876
+ db: {
877
+ adapter: 'postgres',
878
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}_test',
879
+ maxConnections: 5,
880
+ },
881
+ migrations: { dir: './migrations', online: true },
882
+ },
883
+ production: {
884
+ name: 'production',
885
+ db: {
886
+ adapter: 'postgres',
887
+ url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/${context.projectName}',
888
+ maxConnections: 20,
889
+ },
890
+ migrations: { dir: './migrations', online: true },
891
+ },
892
+ },
893
+ });
894
+ `;
895
+ }
896
+ };
897
+
898
+ //#endregion
899
+ //#region src/frameworks/strategies/next/templates/models.ts
900
+ var TodoModelTemplateBuilder = class TodoModelTemplateBuilder extends TemplateBuilder {
901
+ constructor() {
902
+ super({ name: "src/lib/models/TodoModel.ts" });
903
+ }
904
+ static context(context) {
905
+ return new TodoModelTemplateBuilder().setContext(context);
906
+ }
907
+ resolveTemplate(_context) {
908
+ return `import { z } from 'zod';
909
+ import '@danceroutine/tango-orm/runtime';
910
+ import { Model, t } from '@danceroutine/tango-schema';
911
+
912
+ export const TodoReadSchema = z.object({
913
+ id: z.number(),
914
+ title: z.string().min(1),
915
+ completed: z.coerce.boolean(),
916
+ createdAt: z.string(),
917
+ updatedAt: z.string(),
918
+ });
919
+
920
+ export const TodoCreateSchema = z.object({
921
+ title: z.string().min(1),
922
+ completed: z.boolean().optional().default(false),
923
+ });
924
+
925
+ export const TodoUpdateSchema = TodoCreateSchema.partial();
926
+
927
+ export type Todo = z.output<typeof TodoReadSchema>;
928
+
929
+ export const TodoModel = Model({
930
+ namespace: 'app',
931
+ name: 'Todo',
932
+ schema: TodoReadSchema.extend({
933
+ id: t.primaryKey(z.number().int()),
934
+ title: z.string().min(1),
935
+ completed: t.default(z.coerce.boolean(), 'false'),
936
+ createdAt: t.default(z.string(), { now: true }),
937
+ updatedAt: t.default(z.string(), { now: true }),
938
+ }),
939
+ });
940
+ `;
941
+ }
942
+ };
943
+ var ModelsBarrelTemplateBuilder = class extends TemplateBuilder {
944
+ constructor() {
945
+ super({ name: "src/lib/models/index.ts" });
946
+ }
947
+ resolveTemplate(_context) {
948
+ return `export { TodoReadSchema, TodoCreateSchema, TodoUpdateSchema, TodoModel, type Todo } from './TodoModel';
949
+ `;
950
+ }
951
+ };
952
+
953
+ //#endregion
954
+ //#region src/frameworks/strategies/next/templates/serializers.ts
955
+ var TodoSerializerTemplateBuilder = class extends TemplateBuilder {
956
+ constructor() {
957
+ super({ name: "src/serializers/TodoSerializer.ts" });
958
+ }
959
+ resolveTemplate(_context) {
960
+ return `import { ModelSerializer } from '@danceroutine/tango-resources';
961
+ import { TodoCreateSchema, TodoModel, TodoReadSchema, TodoUpdateSchema, type Todo } from '@/lib/models';
962
+
963
+ export class TodoSerializer extends ModelSerializer<
964
+ Todo,
965
+ typeof TodoCreateSchema,
966
+ typeof TodoUpdateSchema,
967
+ typeof TodoReadSchema
968
+ > {
969
+ static readonly model = TodoModel;
970
+ static readonly createSchema = TodoCreateSchema;
971
+ static readonly updateSchema = TodoUpdateSchema;
972
+ static readonly outputSchema = TodoReadSchema;
973
+ }
974
+ `;
975
+ }
976
+ };
977
+ var SerializersBarrelTemplateBuilder = class extends TemplateBuilder {
978
+ constructor() {
979
+ super({ name: "src/serializers/index.ts" });
980
+ }
981
+ resolveTemplate(_context) {
982
+ return `export { TodoSerializer } from './TodoSerializer';
983
+ `;
984
+ }
985
+ };
986
+
987
+ //#endregion
988
+ //#region src/frameworks/strategies/next/templates/openapi.ts
989
+ var OpenAPITemplateBuilder = class extends TemplateBuilder {
990
+ constructor() {
991
+ super({ name: "src/lib/openapi.ts" });
992
+ }
993
+ resolveTemplate(_context) {
994
+ return `import { describeViewSet, generateOpenAPISpec, type OpenAPISpec } from '@danceroutine/tango-openapi';
995
+ import { TodoViewSet } from '@/viewsets/TodoViewSet';
996
+
997
+ export function createOpenAPISpec(): OpenAPISpec {
998
+ return generateOpenAPISpec({
999
+ title: 'Tango Next.js Todo API',
1000
+ version: '1.0.0',
1001
+ description: 'OpenAPI document generated from Tango resource instances.',
1002
+ resources: [describeViewSet({ basePath: '/api/todos', resource: new TodoViewSet() })],
1003
+ });
1004
+ }
1005
+ `;
1006
+ }
1007
+ };
1008
+
1009
+ //#endregion
1010
+ //#region src/frameworks/strategies/next/templates/viewSet.ts
1011
+ var ViewSetTemplateBuilder = class extends TemplateBuilder {
1012
+ constructor() {
1013
+ super({ name: "src/viewsets/TodoViewSet.ts" });
1014
+ }
1015
+ resolveTemplate(_context) {
1016
+ return `import { FilterSet, ModelViewSet } from '@danceroutine/tango-resources';
1017
+ import { type Todo } from '@/lib/models';
1018
+ import { TodoSerializer } from '@/serializers';
1019
+
1020
+ export class TodoViewSet extends ModelViewSet<Todo, typeof TodoSerializer> {
1021
+ constructor() {
1022
+ super({
1023
+ serializer: TodoSerializer,
1024
+ filters: FilterSet.define<Todo>({
1025
+ fields: {
1026
+ completed: true,
1027
+ },
1028
+ aliases: {
1029
+ q: { fields: ['title'], lookup: 'icontains' },
1030
+ },
1031
+ }),
1032
+ orderingFields: ['id', 'createdAt', 'updatedAt', 'title'],
1033
+ searchFields: ['title'],
1034
+ });
1035
+ }
1036
+ }
1037
+ `;
1038
+ }
1039
+ };
1040
+
1041
+ //#endregion
1042
+ //#region src/frameworks/strategies/next/templates/page.ts
1043
+ var PageTemplateBuilder = class extends TemplateBuilder {
1044
+ constructor() {
1045
+ super({ name: "src/app/page.tsx" });
1046
+ }
1047
+ resolveTemplate(_context) {
1048
+ return `import { TodoModel, TodoReadSchema } from '@/lib/models';
1049
+
1050
+ export default async function HomePage() {
1051
+ const todos = await TodoModel.objects.query().orderBy('-createdAt').limit(10).fetch(TodoReadSchema);
1052
+
1053
+ return (
1054
+ <main style={{ fontFamily: 'system-ui', margin: '2rem auto', maxWidth: '60ch' }}>
1055
+ <h1>Tango + Next.js</h1>
1056
+ <p>Showing {todos.results.length} todos through Tango's runtime-backed model manager.</p>
1057
+ <ul>
1058
+ {todos.results.map((todo) => (
1059
+ <li key={todo.id}>{todo.title}</li>
1060
+ ))}
1061
+ </ul>
1062
+ </main>
1063
+ );
1064
+ }
1065
+ `;
1066
+ }
1067
+ };
1068
+
1069
+ //#endregion
1070
+ //#region src/frameworks/strategies/next/templates/layout.ts
1071
+ var LayoutTemplateBuilder = class extends TemplateBuilder {
1072
+ constructor() {
1073
+ super({ name: "src/app/layout.tsx" });
1074
+ }
1075
+ resolveTemplate(_context) {
1076
+ return `import type { Metadata } from 'next';
1077
+
1078
+ export const metadata: Metadata = {
1079
+ title: 'Tango Todo App',
1080
+ description: 'Generated by tango new --framework next',
1081
+ };
1082
+
1083
+ export default function RootLayout({
1084
+ children,
1085
+ }: Readonly<{
1086
+ children: React.ReactNode;
1087
+ }>) {
1088
+ return (
1089
+ <html lang="en">
1090
+ <body>{children}</body>
1091
+ </html>
1092
+ );
1093
+ }
1094
+ `;
1095
+ }
1096
+ };
1097
+
1098
+ //#endregion
1099
+ //#region src/frameworks/strategies/next/templates/healthRoute.ts
1100
+ var HealthRouteTemplateBuilder = class extends TemplateBuilder {
1101
+ constructor() {
1102
+ super({ name: "src/app/api/health/route.ts" });
1103
+ }
1104
+ resolveTemplate(_context) {
1105
+ return `export async function GET(): Promise<Response> {
1106
+ return Response.json({ ok: true });
1107
+ }
1108
+ `;
1109
+ }
1110
+ };
1111
+
1112
+ //#endregion
1113
+ //#region src/frameworks/strategies/next/templates/todoRoute.ts
1114
+ var TodoRouteTemplateBuilder = class extends TemplateBuilder {
1115
+ constructor() {
1116
+ super({ name: "src/app/api/todos/[[...tango]]/route.ts" });
1117
+ }
1118
+ resolveTemplate(_context) {
1119
+ return `import { NextAdapter } from '@danceroutine/tango-adapters-next/adapter';
1120
+ import { TodoViewSet } from '@/viewsets/TodoViewSet';
1121
+
1122
+ const adapter = new NextAdapter();
1123
+ export const { GET, POST, PUT, PATCH, DELETE } = adapter.adaptViewSet(new TodoViewSet());
1124
+ `;
1125
+ }
1126
+ };
1127
+
1128
+ //#endregion
1129
+ //#region src/frameworks/strategies/next/templates/openapiRoute.ts
1130
+ var OpenAPIRouteTemplateBuilder = class extends TemplateBuilder {
1131
+ constructor() {
1132
+ super({ name: "src/app/api/openapi/route.ts" });
1133
+ }
1134
+ resolveTemplate(_context) {
1135
+ return `import { createOpenAPISpec } from '@/lib/openapi';
1136
+
1137
+ export async function GET(): Promise<Response> {
1138
+ return Response.json(createOpenAPISpec());
1139
+ }
1140
+ `;
1141
+ }
1142
+ };
1143
+
1144
+ //#endregion
1145
+ //#region src/frameworks/strategies/next/templates/bootstrap.ts
1146
+ var BootstrapTemplateBuilder = class extends TemplateBuilder {
1147
+ constructor() {
1148
+ super({ name: "scripts/bootstrap.ts" });
1149
+ }
1150
+ resolveTemplate(_context) {
1151
+ return `import { TodoModel, type Todo } from '../src/lib/models';
1152
+
1153
+ async function seedExampleData(count = 100): Promise<void> {
1154
+ const existing = await TodoModel.objects.query().count();
1155
+ if (existing >= count) {
1156
+ console.log(\`[bootstrap] Skipped; already have \${existing} todos.\`);
1157
+ return;
1158
+ }
1159
+
1160
+ const rows: Array<Partial<Todo>> = [];
1161
+ for (let index = existing; index < count; index += 1) {
1162
+ const now = new Date(Date.now() - index * 60_000).toISOString();
1163
+ rows.push({
1164
+ title: \`Seeded todo #\${index + 1}\`,
1165
+ completed: index % 3 === 0,
1166
+ createdAt: now,
1167
+ updatedAt: now,
1168
+ });
1169
+ }
1170
+
1171
+ await TodoModel.objects.bulkCreate(rows);
1172
+ console.log(\`[bootstrap] Seeded \${rows.length} todos.\`);
1173
+ }
1174
+
1175
+ const count = Number(process.env.SEED_TODOS_COUNT || '100');
1176
+ // oxlint-disable-next-line unicorn/prefer-top-level-await
1177
+ seedExampleData(Number.isFinite(count) ? count : 100).catch((error: unknown) => {
1178
+ console.error('[bootstrap] Failed:', error);
1179
+ process.exit(1);
1180
+ });
1181
+ `;
1182
+ }
1183
+ };
1184
+
1185
+ //#endregion
1186
+ //#region src/frameworks/strategies/next/templates/readme.ts
1187
+ var ReadmeTemplateBuilder = class extends TemplateBuilder {
1188
+ constructor() {
1189
+ super({ name: "README.md" });
1190
+ }
1191
+ resolveTemplate(context) {
1192
+ return `# ${context.projectName}
1193
+
1194
+ This project was scaffolded by \`tango new --framework next\`.
1195
+
1196
+ Next.js still owns pages and route handlers; Tango owns model metadata, \`Model.objects\`, serializers, migrations, querying, and resource behavior.
1197
+
1198
+ ## First-time setup
1199
+
1200
+ Generate your first migration from the scaffolded models, then start the app:
1201
+
1202
+ \`\`\`bash
1203
+ ${context.packageManager} run make:migrations --name initial
1204
+ ${context.packageManager} run dev
1205
+ \`\`\`
1206
+
1207
+ ## Scripts
1208
+
1209
+ - \`${context.packageManager} run dev\`
1210
+ - \`${context.packageManager} run make:migrations --name add_field\`
1211
+ - \`${context.packageManager} run setup:schema\`
1212
+ - \`${context.packageManager} run bootstrap\`
1213
+ - \`${context.packageManager} run typecheck\`
1214
+
1215
+ ## Useful endpoints
1216
+
1217
+ - \`GET /api/health\`
1218
+ - \`GET /api/openapi\`
1219
+ - \`GET|POST|PATCH|DELETE /api/todos...\`
1220
+
1221
+ ## Project layout
1222
+
1223
+ - \`tango.config.ts\` Tango configuration
1224
+ - \`src/lib/models/\` schemas and Tango model metadata; importing the model module enables \`Model.objects\`
1225
+ - \`src/serializers/\` serializer-backed API contracts for Tango resources
1226
+ - \`src/viewsets/\` CRUD resources backed by \`Model.objects\`
1227
+ - \`src/app/page.tsx\` server-rendered page reading through \`TodoModel.objects\`
1228
+ - \`src/app/api/todos/[[...tango]]/route.ts\` Next adapter wiring for the viewset
1229
+ - \`src/lib/openapi.ts\` OpenAPI document generation
1230
+ - \`scripts/bootstrap.ts\` seed utility for a larger demo dataset
1231
+ - \`migrations/\` checked-in Tango migrations
1232
+ `;
1233
+ }
1234
+ };
1235
+
1236
+ //#endregion
1237
+ //#region src/frameworks/strategies/next/NextScaffoldStrategy.ts
1238
+ var NextScaffoldStrategy = class extends FrameworkScaffoldStrategy {
1239
+ id = "next";
1240
+ name = "Next.js";
1241
+ description = "Bootstrap a Tango application hosted by Next.js App Router.";
1242
+ /**
1243
+ * Return the file templates needed for the generated Next.js project.
1244
+ */
1245
+ getTemplates() {
1246
+ return [
1247
+ this.addFrameworkTemplate(new PackageJsonTemplateBuilder()),
1248
+ this.addTangoTemplate(new TangoConfigTemplateBuilder()),
1249
+ this.addFrameworkTemplate(new TsConfigTemplateBuilder()),
1250
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate("next-env.d.ts", "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n")),
1251
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate("next.config.mjs", "export default {};\n")),
1252
+ this.addTangoTemplate(new TodoModelTemplateBuilder()),
1253
+ this.addTangoTemplate(new ModelsBarrelTemplateBuilder()),
1254
+ this.addTangoTemplate(new TodoSerializerTemplateBuilder()),
1255
+ this.addTangoTemplate(new SerializersBarrelTemplateBuilder()),
1256
+ this.addTangoTemplate(new OpenAPITemplateBuilder()),
1257
+ this.addTangoTemplate(new ViewSetTemplateBuilder()),
1258
+ this.addFrameworkTemplate(new LayoutTemplateBuilder()),
1259
+ this.addFrameworkTemplate(new PageTemplateBuilder()),
1260
+ this.addTangoTemplate(new HealthRouteTemplateBuilder()),
1261
+ this.addTangoTemplate(new OpenAPIRouteTemplateBuilder()),
1262
+ this.addTangoTemplate(new TodoRouteTemplateBuilder()),
1263
+ this.addTangoTemplate(new BootstrapTemplateBuilder()),
1264
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate("migrations/.gitkeep", "")),
1265
+ this.addFrameworkTemplate(new ReadmeTemplateBuilder()),
1266
+ this.addFrameworkTemplate(TemplateBuilder.createStaticTemplate(".gitignore", "node_modules\n.next\n.data\n.env\n"))
1267
+ ];
1268
+ }
1269
+ };
1270
+
1271
+ //#endregion
1272
+ //#region src/frameworks/registry/FrameworkScaffoldRegistry.ts
1273
+ var FrameworkScaffoldRegistry = class FrameworkScaffoldRegistry {
1274
+ strategies = new Map();
1275
+ /**
1276
+ * Create a registry preloaded with Tango's built-in framework scaffolds.
1277
+ */
1278
+ static createDefault() {
1279
+ const registry = new FrameworkScaffoldRegistry();
1280
+ registry.register(new ExpressScaffoldStrategy());
1281
+ registry.register(new NextScaffoldStrategy());
1282
+ return registry;
1283
+ }
1284
+ /**
1285
+ * Register a strategy under its declared framework id.
1286
+ */
1287
+ register(strategy) {
1288
+ const existing = this.strategies.get(strategy.id);
1289
+ if (existing) throw new Error(`Framework scaffold strategy '${strategy.id}' is already registered.`);
1290
+ this.strategies.set(strategy.id, strategy);
1291
+ }
1292
+ /**
1293
+ * Resolve a strategy for a known framework id.
1294
+ */
1295
+ get(id) {
1296
+ return this.strategies.get(id);
1297
+ }
1298
+ /**
1299
+ * List all registered framework strategies in registration order.
1300
+ */
1301
+ list() {
1302
+ return [...this.strategies.values()];
1303
+ }
1304
+ };
1305
+
1306
+ //#endregion
1307
+ //#region src/frameworks/scaffold/scaffoldProject.ts
1308
+ function resolveTargetDir(targetDir) {
1309
+ if (isAbsolute(targetDir)) return targetDir;
1310
+ return resolve(process.cwd(), targetDir);
1311
+ }
1312
+ async function ensureWritableTargetDir(targetDir, options) {
1313
+ const absoluteTargetDir = resolveTargetDir(targetDir);
1314
+ try {
1315
+ const targetStats = await stat(absoluteTargetDir);
1316
+ if (!targetStats.isDirectory()) throw new Error(`Target path '${absoluteTargetDir}' exists and is not a directory.`);
1317
+ if (options.mode === "new") {
1318
+ const contents = await readdir(absoluteTargetDir);
1319
+ if (contents.length > 0 && !options.force) throw new Error(`Target directory '${absoluteTargetDir}' is not empty. Pass --force to allow scaffolding into a non-empty directory.`);
1320
+ }
1321
+ } catch (error) {
1322
+ if (error.code === "ENOENT") {
1323
+ await mkdir(absoluteTargetDir, { recursive: true });
1324
+ return;
1325
+ }
1326
+ throw error;
1327
+ }
1328
+ }
1329
+ async function scaffoldProject(context, strategy, options = {}) {
1330
+ const mode = options.mode ?? "new";
1331
+ const skipExisting = options.skipExisting ?? mode === "init";
1332
+ const resolvedOptions = {
1333
+ ...options,
1334
+ mode,
1335
+ skipExisting
1336
+ };
1337
+ await ensureWritableTargetDir(context.targetDir, resolvedOptions);
1338
+ const allTemplates = strategy.getTemplates();
1339
+ const templates = allTemplates.filter((t) => t.shouldEmit(mode));
1340
+ for (const template of templates) {
1341
+ const absolutePath = resolveTargetDir(resolve(context.targetDir, template.path));
1342
+ if (skipExisting && !options.force) try {
1343
+ await stat(absolutePath);
1344
+ continue;
1345
+ } catch {}
1346
+ await mkdir(dirname(absolutePath), { recursive: true });
1347
+ await writeFile(absolutePath, template.render(context), "utf8");
1348
+ }
1349
+ }
1350
+
1351
+ //#endregion
1352
+ //#region src/frameworks/index.ts
1353
+ var frameworks_exports = {};
1354
+ __export(frameworks_exports, {
1355
+ ExpressScaffoldStrategy: () => ExpressScaffoldStrategy,
1356
+ FrameworkScaffoldRegistry: () => FrameworkScaffoldRegistry,
1357
+ FrameworkScaffoldStrategy: () => FrameworkScaffoldStrategy,
1358
+ NextScaffoldStrategy: () => NextScaffoldStrategy,
1359
+ SCAFFOLD_TEMPLATE_CATEGORY: () => SCAFFOLD_TEMPLATE_CATEGORY,
1360
+ ScaffoldTemplateDescriptor: () => ScaffoldTemplateDescriptor,
1361
+ scaffoldProject: () => scaffoldProject
1362
+ });
1363
+
1364
+ //#endregion
1365
+ export { ExpressScaffoldStrategy, FrameworkScaffoldRegistry, FrameworkScaffoldStrategy, NextScaffoldStrategy, PACKAGE_MANAGER, SCAFFOLD_DATABASE_DIALECT, SCAFFOLD_TEMPLATE_CATEGORY, SUPPORTED_FRAMEWORK, ScaffoldTemplateDescriptor, frameworks_exports, scaffoldProject };
1366
+ //# sourceMappingURL=frameworks-Bp_9BOt2.js.map