@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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/chunk-BkvOhyD0.js +12 -0
- package/dist/{generators/repository → commands}/index.d.ts +1 -1
- package/dist/commands/index.js +4 -0
- package/dist/commands/registerCodegenCommands.d.ts +5 -0
- package/dist/commands/runInitCommand.d.ts +13 -0
- package/dist/commands/runInstall.d.ts +2 -0
- package/dist/commands/runNewCommand.d.ts +16 -0
- package/dist/commands-C2Xr9uRE.js +186 -0
- package/dist/commands-C2Xr9uRE.js.map +1 -0
- package/dist/frameworks/contracts/FrameworkScaffoldStrategy.d.ts +39 -0
- package/dist/frameworks/contracts/template/ScaffoldTemplate.d.ts +22 -0
- package/dist/frameworks/contracts/template/TemplateBuilder.d.ts +40 -0
- package/dist/frameworks/contracts/template/implementation/ScaffoldTemplateDescriptor.d.ts +10 -0
- package/dist/frameworks/index.d.ts +11 -0
- package/dist/frameworks/index.js +3 -0
- package/dist/frameworks/registry/FrameworkScaffoldRegistry.d.ts +23 -0
- package/dist/frameworks/scaffold/scaffoldProject.d.ts +17 -0
- package/dist/frameworks/strategies/express/ExpressScaffoldStrategy.d.ts +14 -0
- package/dist/frameworks/strategies/express/templates/appSource.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/bootstrap.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/models.d.ts +10 -0
- package/dist/frameworks/strategies/express/templates/openapi.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/packageJson.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/readme.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/serializers.d.ts +10 -0
- package/dist/frameworks/strategies/express/templates/tangoConfig.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/tangoRegister.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/tsconfig.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/tsconfigBuild.d.ts +6 -0
- package/dist/frameworks/strategies/express/templates/viewSet.d.ts +6 -0
- package/dist/frameworks/strategies/next/NextScaffoldStrategy.d.ts +14 -0
- package/dist/frameworks/strategies/next/templates/bootstrap.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/healthRoute.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/layout.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/models.d.ts +11 -0
- package/dist/frameworks/strategies/next/templates/openapi.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/openapiRoute.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/packageJson.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/page.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/readme.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/serializers.d.ts +10 -0
- package/dist/frameworks/strategies/next/templates/tangoConfig.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/todoRoute.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/tsconfig.d.ts +6 -0
- package/dist/frameworks/strategies/next/templates/viewSet.d.ts +6 -0
- package/dist/frameworks-Bp_9BOt2.js +1366 -0
- package/dist/frameworks-Bp_9BOt2.js.map +1 -0
- package/dist/generators/index.d.ts +0 -2
- package/dist/generators/index.js +2 -2
- package/dist/generators/migration/generateMigrationFromModels.d.ts +6 -0
- package/dist/generators/model/generateModelInterface.d.ts +3 -0
- package/dist/generators/viewset/generateViewSet.d.ts +3 -0
- package/dist/{generators-CDXkQAkq.js → generators-C9UeakCq.js} +18 -55
- package/dist/generators-C9UeakCq.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -7
- package/dist/mappers/fieldType.d.ts +6 -0
- package/package.json +60 -48
- package/dist/generators/repository/generateRepository.d.ts +0 -1
- package/dist/generators-CDXkQAkq.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mappers/fieldType.js +0 -32
- 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
|