@decohere/scaffold 0.0.1-dev.18 → 0.0.1-dev.20261105.46
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 +15 -0
- package/commonjs/src/add-api.action.js +37 -0
- package/commonjs/src/add-api.command.js +39 -0
- package/commonjs/src/add.command.js +22 -0
- package/commonjs/src/add.module.js +24 -0
- package/commonjs/src/auth0-configure.action.js +28 -0
- package/commonjs/src/auth0-configure.command.js +24 -0
- package/commonjs/src/auth0-login.action.js +28 -0
- package/commonjs/src/auth0-login.command.js +23 -0
- package/commonjs/src/auth0.command.js +22 -0
- package/commonjs/src/auth0.module.js +28 -0
- package/commonjs/src/cli.js +8 -0
- package/commonjs/src/create.command.js +22 -0
- package/commonjs/src/create.module.js +24 -0
- package/commonjs/src/domain/prompt.js +62 -0
- package/commonjs/src/index.js +48 -0
- package/commonjs/src/models/compose.model.js +258 -0
- package/commonjs/src/models/config-yaml.model.js +86 -0
- package/commonjs/src/models/const.js +30 -0
- package/commonjs/src/models/dev-container.model.js +172 -0
- package/commonjs/src/models/docker-image.model.js +277 -0
- package/commonjs/src/models/env-file.model.js +48 -0
- package/commonjs/src/models/gitignore.model.js +48 -0
- package/commonjs/src/models/launch-json.model.js +70 -0
- package/commonjs/src/models/package-json.model.js +156 -0
- package/commonjs/src/models/project.model.js +132 -0
- package/commonjs/src/models/source-file.js +4 -0
- package/commonjs/src/models/tsconfig.model.js +32 -0
- package/commonjs/src/models/vitest-config.model.js +33 -0
- package/commonjs/src/models/vscode-settings.model.js +29 -0
- package/commonjs/src/program.js +64 -0
- package/commonjs/src/resolve-target-dir.js +17 -0
- package/commonjs/src/services/scaffold.service.js +60 -0
- package/commonjs/src/version.js +55 -0
- package/commonjs/src/webapi/api-name.js +93 -0
- package/commonjs/src/webapi/auth0-commands.js +250 -0
- package/commonjs/src/webapi/authorize-scopes.js +79 -0
- package/commonjs/src/webapi/cli-options.js +99 -0
- package/commonjs/src/webapi/const.js +54 -0
- package/commonjs/src/webapi/create-api.js +265 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +164 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/commonjs/src/webapi/devcontainer/host-scripts.js +50 -0
- package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/commonjs/src/webapi/features/auth.feature.js +408 -0
- package/commonjs/src/webapi/features/auth0-cli.js +472 -0
- package/commonjs/src/webapi/features/biome-format.js +47 -0
- package/commonjs/src/webapi/features/database.feature.js +433 -0
- package/commonjs/src/webapi/features/finalize.feature.js +93 -0
- package/commonjs/src/webapi/features/postman.feature.js +322 -0
- package/commonjs/src/webapi/models/api-package-json.model.js +58 -0
- package/commonjs/src/webapi/models/auth-config.model.js +24 -0
- package/commonjs/src/webapi/models/controller-source-file.model.js +190 -0
- package/commonjs/src/webapi/models/github-ci.model.js +193 -0
- package/commonjs/src/webapi/models/main-source-file.model.js +165 -0
- package/commonjs/src/webapi/models/production-dockerfile.model.js +75 -0
- package/commonjs/src/webapi/models/sample-files.model.js +58 -0
- package/commonjs/src/webapi/models/webapi-image.model.js +25 -0
- package/commonjs/src/webapi/postman/hello-openapi.js +173 -0
- package/commonjs/src/webapi/postman/local-view.js +182 -0
- package/commonjs/src/webapi/postman/openapi-to-postman.js +238 -0
- package/commonjs/src/webapi/workspace-readme.js +109 -0
- package/commonjs/src/webapi.action.js +28 -0
- package/commonjs/src/webapi.command.js +43 -0
- package/es6/src/add-api.action.js +27 -0
- package/es6/src/add-api.command.js +29 -0
- package/es6/src/add.command.js +12 -0
- package/es6/src/add.module.js +14 -0
- package/es6/src/auth0-configure.action.js +18 -0
- package/es6/src/auth0-configure.command.js +14 -0
- package/es6/src/auth0-login.action.js +18 -0
- package/es6/src/auth0-login.command.js +13 -0
- package/es6/src/auth0.command.js +12 -0
- package/es6/src/auth0.module.js +18 -0
- package/es6/src/cli.js +4 -0
- package/es6/src/create.command.js +12 -0
- package/es6/src/create.module.js +14 -0
- package/es6/src/domain/prompt.js +41 -0
- package/es6/src/index.js +9 -0
- package/es6/src/models/compose.model.js +254 -0
- package/es6/src/models/config-yaml.model.js +74 -0
- package/es6/src/models/const.js +20 -0
- package/es6/src/models/dev-container.model.js +162 -0
- package/es6/src/models/docker-image.model.js +235 -0
- package/es6/src/models/env-file.model.js +44 -0
- package/es6/src/models/gitignore.model.js +38 -0
- package/es6/src/models/launch-json.model.js +60 -0
- package/es6/src/models/package-json.model.js +141 -0
- package/es6/src/models/project.model.js +117 -0
- package/es6/src/models/source-file.js +1 -0
- package/es6/src/models/tsconfig.model.js +22 -0
- package/es6/src/models/vitest-config.model.js +23 -0
- package/es6/src/models/vscode-settings.model.js +19 -0
- package/es6/src/program.js +50 -0
- package/es6/src/resolve-target-dir.js +7 -0
- package/es6/src/services/scaffold.service.js +50 -0
- package/es6/src/version.js +34 -0
- package/es6/src/webapi/api-name.js +57 -0
- package/es6/src/webapi/auth0-commands.js +185 -0
- package/es6/src/webapi/authorize-scopes.js +55 -0
- package/es6/src/webapi/cli-options.js +34 -0
- package/es6/src/webapi/const.js +21 -0
- package/es6/src/webapi/create-api.js +245 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +154 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/es6/src/webapi/devcontainer/host-scripts.js +29 -0
- package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/es6/src/webapi/features/auth.feature.js +357 -0
- package/es6/src/webapi/features/auth0-cli.js +406 -0
- package/es6/src/webapi/features/biome-format.js +40 -0
- package/es6/src/webapi/features/database.feature.js +423 -0
- package/es6/src/webapi/features/finalize.feature.js +83 -0
- package/es6/src/webapi/features/postman.feature.js +256 -0
- package/es6/src/webapi/models/api-package-json.model.js +53 -0
- package/es6/src/webapi/models/auth-config.model.js +20 -0
- package/es6/src/webapi/models/controller-source-file.model.js +181 -0
- package/{src/webapi/models/github-ci.model.ts → es6/src/webapi/models/github-ci.model.js} +27 -29
- package/es6/src/webapi/models/main-source-file.model.js +161 -0
- package/es6/src/webapi/models/production-dockerfile.model.js +66 -0
- package/es6/src/webapi/models/sample-files.model.js +40 -0
- package/es6/src/webapi/models/webapi-image.model.js +18 -0
- package/es6/src/webapi/postman/hello-openapi.js +166 -0
- package/es6/src/webapi/postman/local-view.js +167 -0
- package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
- package/es6/src/webapi/workspace-readme.js +88 -0
- package/es6/src/webapi.action.js +18 -0
- package/es6/src/webapi.command.js +33 -0
- package/package.json +19 -9
- package/types/src/add-api.action.d.ts +13 -0
- package/types/src/add-api.command.d.ts +7 -0
- package/types/src/add.command.d.ts +6 -0
- package/types/src/add.module.d.ts +14 -0
- package/types/src/auth0-configure.action.d.ts +7 -0
- package/types/src/auth0-configure.command.d.ts +6 -0
- package/types/src/auth0-login.action.d.ts +4 -0
- package/types/src/auth0-login.command.d.ts +6 -0
- package/types/src/auth0.command.d.ts +6 -0
- package/types/src/auth0.module.d.ts +18 -0
- package/types/src/cli.d.ts +2 -0
- package/types/src/create.command.d.ts +6 -0
- package/types/src/create.module.d.ts +14 -0
- package/types/src/domain/prompt.d.ts +4 -0
- package/{src/index.ts → types/src/index.d.ts} +4 -0
- package/types/src/models/compose.model.d.ts +73 -0
- package/types/src/models/config-yaml.model.d.ts +27 -0
- package/types/src/models/const.d.ts +20 -0
- package/types/src/models/dev-container.model.d.ts +49 -0
- package/types/src/models/docker-image.model.d.ts +90 -0
- package/types/src/models/env-file.model.d.ts +16 -0
- package/types/src/models/gitignore.model.d.ts +9 -0
- package/types/src/models/launch-json.model.d.ts +7 -0
- package/types/src/models/package-json.model.d.ts +36 -0
- package/types/src/models/project.model.d.ts +60 -0
- package/{src/models/source-file.ts → types/src/models/source-file.d.ts} +4 -6
- package/types/src/models/tsconfig.model.d.ts +5 -0
- package/types/src/models/vitest-config.model.d.ts +5 -0
- package/types/src/models/vscode-settings.model.d.ts +7 -0
- package/types/src/program.d.ts +8 -0
- package/{src/resolve-target-dir.ts → types/src/resolve-target-dir.d.ts} +1 -4
- package/types/src/services/scaffold.service.d.ts +22 -0
- package/types/src/version.d.ts +6 -0
- package/types/src/webapi/api-name.d.ts +14 -0
- package/types/src/webapi/auth0-commands.d.ts +11 -0
- package/types/src/webapi/authorize-scopes.d.ts +7 -0
- package/types/src/webapi/cli-options.d.ts +6 -0
- package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +9 -21
- package/types/src/webapi/create-api.d.ts +15 -0
- package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
- package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
- package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
- package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
- package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
- package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
- package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
- package/types/src/webapi/features/auth.feature.d.ts +10 -0
- package/types/src/webapi/features/auth0-cli.d.ts +84 -0
- package/types/src/webapi/features/biome-format.d.ts +5 -0
- package/types/src/webapi/features/database.feature.d.ts +7 -0
- package/types/src/webapi/features/finalize.feature.d.ts +2 -0
- package/types/src/webapi/features/postman.feature.d.ts +16 -0
- package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
- package/types/src/webapi/models/auth-config.model.d.ts +26 -0
- package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
- package/types/src/webapi/models/github-ci.model.d.ts +9 -0
- package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
- package/types/src/webapi/models/production-dockerfile.model.d.ts +18 -0
- package/types/src/webapi/models/sample-files.model.d.ts +7 -0
- package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
- package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
- package/types/src/webapi/postman/local-view.d.ts +17 -0
- package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
- package/types/src/webapi/workspace-readme.d.ts +18 -0
- package/types/src/webapi.action.d.ts +14 -0
- package/types/src/webapi.command.d.ts +11 -0
- package/src/add-api.action.ts +0 -25
- package/src/add-api.command.ts +0 -31
- package/src/add.command.ts +0 -20
- package/src/add.module.ts +0 -11
- package/src/cli.ts +0 -4
- package/src/create.command.ts +0 -20
- package/src/create.module.ts +0 -11
- package/src/domain/prompt.ts +0 -50
- package/src/models/compose.model.ts +0 -205
- package/src/models/config-yaml.model.ts +0 -83
- package/src/models/const.ts +0 -20
- package/src/models/dev-container.model.ts +0 -136
- package/src/models/docker-image.model.ts +0 -343
- package/src/models/env-file.model.ts +0 -45
- package/src/models/gitignore.model.ts +0 -35
- package/src/models/launch-json.model.ts +0 -60
- package/src/models/package-json.model.ts +0 -169
- package/src/models/project.model.ts +0 -141
- package/src/models/tsconfig.model.ts +0 -29
- package/src/models/vitest-config.model.ts +0 -26
- package/src/models/vscode-settings.model.ts +0 -24
- package/src/program.ts +0 -63
- package/src/services/scaffold.service.ts +0 -89
- package/src/version.ts +0 -56
- package/src/webapi/api-name.ts +0 -73
- package/src/webapi/cli-options.ts +0 -56
- package/src/webapi/create-api.ts +0 -275
- package/src/webapi/devcontainer/host-scripts.ts +0 -35
- package/src/webapi/features/auth.feature.ts +0 -454
- package/src/webapi/features/auth0-cli.ts +0 -421
- package/src/webapi/features/biome-format.ts +0 -34
- package/src/webapi/features/database.feature.ts +0 -522
- package/src/webapi/features/finalize.feature.ts +0 -112
- package/src/webapi/features/postman.feature.ts +0 -287
- package/src/webapi/models/api-package-json.model.ts +0 -60
- package/src/webapi/models/auth-config.model.ts +0 -36
- package/src/webapi/models/controller-source-file.model.ts +0 -232
- package/src/webapi/models/main-source-file.model.ts +0 -208
- package/src/webapi/models/production-dockerfile.model.ts +0 -162
- package/src/webapi/models/sample-files.model.ts +0 -45
- package/src/webapi/models/webapi-image.model.ts +0 -21
- package/src/webapi/postman/hello-openapi.ts +0 -92
- package/src/webapi/postman/local-view.ts +0 -195
- package/src/webapi/postman/openapi-to-postman.ts +0 -208
- package/src/webapi/workspace-readme.ts +0 -192
- package/src/webapi.action.ts +0 -22
- package/src/webapi.command.ts +0 -41
- package/tests/auth-postman.wiring.spec.ts +0 -263
- package/tests/auth0-cli.spec.ts +0 -232
- package/tests/biome-format.spec.ts +0 -49
- package/tests/cli.integration.spec.ts +0 -658
- package/tests/compose.model.spec.ts +0 -51
- package/tests/container-image.model.spec.ts +0 -162
- package/tests/dev-container.model.spec.ts +0 -29
- package/tests/devcontainer-base-image.spec.ts +0 -32
- package/tests/hello-di-inject.scaffold.spec.ts +0 -18
- package/tests/hello-route.contract.spec.ts +0 -94
- package/tests/package-json.order.spec.ts +0 -59
- package/tests/postman-from-openapi.spec.ts +0 -197
- package/tests/seed-devcontainer-templates.ts +0 -40
- package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +0 -96
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { WELL_KNOWN_FILES } from "../../models/const.js";
|
|
2
|
+
/** Tracks named imports from a single module path, split into values and types. */ class ImportGroup {
|
|
3
|
+
_values = [];
|
|
4
|
+
_types = [];
|
|
5
|
+
add(name, typeOnly = false) {
|
|
6
|
+
if (typeOnly) {
|
|
7
|
+
// Value import already present — value wins, skip
|
|
8
|
+
if (!this._values.includes(name) && !this._types.includes(name)) {
|
|
9
|
+
this._types.push(name);
|
|
10
|
+
}
|
|
11
|
+
} else {
|
|
12
|
+
// Promote: remove from types if it was previously type-only
|
|
13
|
+
const typeIdx = this._types.indexOf(name);
|
|
14
|
+
if (typeIdx !== -1) this._types.splice(typeIdx, 1);
|
|
15
|
+
if (!this._values.includes(name)) this._values.push(name);
|
|
16
|
+
}
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
render(from) {
|
|
20
|
+
const values = [
|
|
21
|
+
...this._values
|
|
22
|
+
].sort();
|
|
23
|
+
const types = [
|
|
24
|
+
...this._types
|
|
25
|
+
].sort().map((n)=>`type ${n}`);
|
|
26
|
+
return `import { ${[
|
|
27
|
+
...values,
|
|
28
|
+
...types
|
|
29
|
+
].join(", ")} } from "${from}";`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Model for `projects/api/src/main.ts`.
|
|
34
|
+
*
|
|
35
|
+
* Starts with the minimal HTTP + YAML config setup. Features mutate the model
|
|
36
|
+
* by calling addSideEffectImport / addImport / addBuilderCall / addEntity /
|
|
37
|
+
* addRepository. files() renders whatever has been accumulated.
|
|
38
|
+
*/ export class MainSourceFile {
|
|
39
|
+
appName;
|
|
40
|
+
key = WELL_KNOWN_FILES.MAIN_TS;
|
|
41
|
+
_sideEffectImports = [];
|
|
42
|
+
_extraImports = new Map();
|
|
43
|
+
_builderCalls = [];
|
|
44
|
+
_entities = [];
|
|
45
|
+
_repositories = [];
|
|
46
|
+
_services = [];
|
|
47
|
+
_dataSourceName;
|
|
48
|
+
_typeOrmSeed;
|
|
49
|
+
constructor(appName){
|
|
50
|
+
this.appName = appName;
|
|
51
|
+
}
|
|
52
|
+
/** Adds a bare side-effect import, e.g. `@decohere/http/auth`. */ addSideEffectImport(path) {
|
|
53
|
+
if (!this._sideEffectImports.includes(path)) this._sideEffectImports.push(path);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/** Adds a named import, e.g. `import { Foo } from "bar"`. */ addImport(name, from, typeOnly = false) {
|
|
57
|
+
const group = this._extraImports.get(from) ?? new ImportGroup();
|
|
58
|
+
group.add(name, typeOnly);
|
|
59
|
+
this._extraImports.set(from, group);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Adds a builder call inserted after `.withConfiguration()` and before
|
|
64
|
+
* `.withServices()`. Pass the call without leading dot, e.g. `.withAuth()`.
|
|
65
|
+
*/ addBuilderCall(call) {
|
|
66
|
+
this._builderCalls.push(call);
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
/** Adds a TypeORM entity — import and entry in `withTypeOrm({ entities })`. */ addEntity(entity) {
|
|
70
|
+
this._entities.push(entity);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
/** Adds a TypeORM repository — import and entry in `withTypeOrm({ repositories })`. */ addRepository(repo) {
|
|
74
|
+
this._repositories.push(repo);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/** Adds a service registered in `.withServices()`. */ addService(service) {
|
|
78
|
+
this._services.push(service);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Datasource name passed to `withTypeOrm({ name })`. Must match
|
|
83
|
+
* `database.dataSources[].name` in config.yaml.
|
|
84
|
+
*/ setDataSourceName(name) {
|
|
85
|
+
this._dataSourceName = name;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Inline seed callback body for `withTypeOrm({ seed })`.
|
|
90
|
+
* Receives `dataSource` in scope. Example: `await dataSource.getRepository(User).save(...)`.
|
|
91
|
+
*/ setTypeOrmSeed(seedBody) {
|
|
92
|
+
this._typeOrmSeed = seedBody;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
files() {
|
|
96
|
+
const imports = [
|
|
97
|
+
'import "@decohere/scribe-pino";',
|
|
98
|
+
'import "@decohere/http/http";'
|
|
99
|
+
];
|
|
100
|
+
for (const path of this._sideEffectImports){
|
|
101
|
+
imports.push(`import "${path}";`);
|
|
102
|
+
}
|
|
103
|
+
imports.push('import "@decohere/config";', 'import { YamlDataSource } from "@decohere/config";', 'import { Builder } from "@decohere/http";', 'import { FastifyProvider } from "@decohere/http/providers";', 'import { scribe } from "@decohere/scribe";', 'import { SampleService } from "./services/sample.service.js";');
|
|
104
|
+
for (const e of this._entities){
|
|
105
|
+
imports.push(`import { ${e.className} } from "${e.importPath}";`);
|
|
106
|
+
}
|
|
107
|
+
for (const r of this._repositories){
|
|
108
|
+
imports.push(`import { ${r.className} } from "${r.importPath}";`);
|
|
109
|
+
}
|
|
110
|
+
for (const s of this._services){
|
|
111
|
+
imports.push(`import { ${s.className} } from "${s.importPath}";`);
|
|
112
|
+
}
|
|
113
|
+
for (const [from, group] of this._extraImports){
|
|
114
|
+
imports.push(group.render(from));
|
|
115
|
+
}
|
|
116
|
+
imports.push('import { HelloController } from "./controllers/hello.controller.js";');
|
|
117
|
+
const serviceRegs = [
|
|
118
|
+
"cb.registerSingleton(SampleService);",
|
|
119
|
+
...this._services.map((s)=>s.scope === "request" ? `cb.registerRequestScoped(${s.className});` : `cb.registerSingleton(${s.className});`)
|
|
120
|
+
].join(" ");
|
|
121
|
+
const builderCalls = [
|
|
122
|
+
`\t.withHttp(FastifyProvider)`,
|
|
123
|
+
`\t.withConfiguration(YamlDataSource)`,
|
|
124
|
+
`\t.withHealth()`,
|
|
125
|
+
`\t.withOpenApi({ info: { title: "${this.appName}", version: "1.0.0" } })`,
|
|
126
|
+
...this._builderCalls.map((c)=>`\t${c}`),
|
|
127
|
+
`\t.withServices((cb) => { ${serviceRegs} })`
|
|
128
|
+
];
|
|
129
|
+
if (this._entities.length > 0) {
|
|
130
|
+
const entities = this._entities.map((e)=>e.className).join(", ");
|
|
131
|
+
const repos = this._repositories.map((r)=>r.className).join(", ");
|
|
132
|
+
const named = this._dataSourceName != null ? `name: "${this._dataSourceName}", ` : "";
|
|
133
|
+
if (this._typeOrmSeed) {
|
|
134
|
+
builderCalls.push(`\t.withTypeOrm({ ${named}entities: [${entities}], repositories: [${repos}], seed: async (dataSource) => { ${this._typeOrmSeed} } })`);
|
|
135
|
+
} else {
|
|
136
|
+
builderCalls.push(`\t.withTypeOrm({ ${named}entities: [${entities}], repositories: [${repos}] })`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
builderCalls.push(`\t.withControllers(HelloController)`);
|
|
140
|
+
const content = [
|
|
141
|
+
...imports,
|
|
142
|
+
"",
|
|
143
|
+
`scribe.withPinoPretty().and.withPinoRoll({ file: "logs/${this.appName}.log" });`,
|
|
144
|
+
"",
|
|
145
|
+
`const app = await Builder.create({ applicationName: "${this.appName}" })`,
|
|
146
|
+
...builderCalls,
|
|
147
|
+
`\t.build();`,
|
|
148
|
+
"",
|
|
149
|
+
"await app.start({ port: Number(process.env.PORT ?? 3000) });",
|
|
150
|
+
"",
|
|
151
|
+
"export default app;",
|
|
152
|
+
""
|
|
153
|
+
].join("\n");
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
filePath: "src/main.ts",
|
|
157
|
+
content
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { WELL_KNOWN_FILES } from "../../models/const.js";
|
|
2
|
+
import { DEFAULT_WEBAPI_NAME, webapiPackagedEntry, webapiProjectDir } from "../const.js";
|
|
3
|
+
/**
|
|
4
|
+
* Production Dockerfile for each API, in the API's own folder: `projects/<name>/Dockerfile`.
|
|
5
|
+
* That is where `forge docker` and `forge package` look for a deployable, and the folder is
|
|
6
|
+
* the Docker build context.
|
|
7
|
+
*
|
|
8
|
+
* The image copies `.package/`, which `forge package` writes before Docker runs: the API's
|
|
9
|
+
* compiled code, the workspace libraries it imports, and a real (symlink-free) `node_modules`
|
|
10
|
+
* of the third-party packages it reaches. So the image build installs nothing, runs no
|
|
11
|
+
* workspace tooling, and needs no npm token.
|
|
12
|
+
*/ export class WebapiProductionDockerfile {
|
|
13
|
+
apiNames;
|
|
14
|
+
key = WELL_KNOWN_FILES.PRODUCTION_DOCKERFILE;
|
|
15
|
+
constructor(apiNames = [
|
|
16
|
+
DEFAULT_WEBAPI_NAME
|
|
17
|
+
]){
|
|
18
|
+
this.apiNames = apiNames;
|
|
19
|
+
}
|
|
20
|
+
files() {
|
|
21
|
+
const names = this.apiNames.length > 0 ? this.apiNames : [
|
|
22
|
+
DEFAULT_WEBAPI_NAME
|
|
23
|
+
];
|
|
24
|
+
return names.flatMap((name)=>[
|
|
25
|
+
{
|
|
26
|
+
filePath: `${webapiProjectDir(name)}/Dockerfile`,
|
|
27
|
+
content: renderProductionDockerfile(name)
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
filePath: `${webapiProjectDir(name)}/.dockerignore`,
|
|
31
|
+
content: dockerignoreContent()
|
|
32
|
+
}
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function renderProductionDockerfile(apiName) {
|
|
37
|
+
const projectDir = webapiProjectDir(apiName);
|
|
38
|
+
const entry = webapiPackagedEntry();
|
|
39
|
+
return `# syntax=docker/dockerfile:1
|
|
40
|
+
# Production image for ${apiName}. Docker only copies the packaged app; it installs nothing.
|
|
41
|
+
#
|
|
42
|
+
# Build the package first (release build, then .package/ in each API), then the image:
|
|
43
|
+
# pnpm run package:${apiName}
|
|
44
|
+
# docker build -t ${apiName} ${projectDir}
|
|
45
|
+
# or, with the image name and tags from the workspace:
|
|
46
|
+
# pnpm run package:${apiName} && pnpm exec forge docker --configuration release
|
|
47
|
+
#
|
|
48
|
+
# Hosts that build from a git clone (Railway, Fly) do not have .package/. Build and push the
|
|
49
|
+
# image from CI instead, or run the package step before \`docker build\`.
|
|
50
|
+
FROM node:24-bookworm-slim
|
|
51
|
+
WORKDIR /app
|
|
52
|
+
ENV NODE_ENV=production
|
|
53
|
+
|
|
54
|
+
COPY .package/ ./
|
|
55
|
+
|
|
56
|
+
EXPOSE 3000
|
|
57
|
+
CMD ["node", "${entry}"]
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
/** The build context is the project folder; only the package belongs in the image. */ function dockerignoreContent() {
|
|
61
|
+
return [
|
|
62
|
+
"*",
|
|
63
|
+
"!.package",
|
|
64
|
+
""
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const SAMPLE_CONFIG_CONTENT = `export class SampleConfig {\n\tpublic greeting: string = "";\n}\n`;
|
|
2
|
+
const SAMPLE_SERVICE_CONTENT = [
|
|
3
|
+
`import { inject, injectable } from "@decohere/crucible/decorators";`,
|
|
4
|
+
`import { ConfigurationServiceToken } from "@decohere/core/configuration";`,
|
|
5
|
+
`import type { IConfigurationService } from "@decohere/core/configuration";`,
|
|
6
|
+
`import { SampleConfig } from "../sample.config.js";`,
|
|
7
|
+
``,
|
|
8
|
+
`@injectable()`,
|
|
9
|
+
`export class SampleService {`,
|
|
10
|
+
`\tpublic constructor(`,
|
|
11
|
+
`\t\t@inject(ConfigurationServiceToken) private readonly config: IConfigurationService,`,
|
|
12
|
+
`\t) {}`,
|
|
13
|
+
``,
|
|
14
|
+
`\tpublic async getGreeting(): Promise<string> {`,
|
|
15
|
+
`\t\tconst config = await this.config.get(SampleConfig);`,
|
|
16
|
+
`\t\treturn config.greeting;`,
|
|
17
|
+
`\t}`,
|
|
18
|
+
`}`,
|
|
19
|
+
``
|
|
20
|
+
].join("\n");
|
|
21
|
+
export class SampleConfigModel {
|
|
22
|
+
files() {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
filePath: "src/sample.config.ts",
|
|
26
|
+
content: SAMPLE_CONFIG_CONTENT
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class SampleServiceModel {
|
|
32
|
+
files() {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
filePath: "src/services/sample.service.ts",
|
|
36
|
+
content: SAMPLE_SERVICE_CONTENT
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DockerfileModel } from "../../models/docker-image.model.js";
|
|
2
|
+
import { CREATE_VERSION, DEVCONTAINER_IMAGE_REPOSITORY } from "../../version.js";
|
|
3
|
+
/**
|
|
4
|
+
* Scaffolded app `.devcontainer/Dockerfile` — thin `FROM` the published base image.
|
|
5
|
+
* Optional feature layers (e.g. postgresql-client) append via `addLayer`.
|
|
6
|
+
*/ export class WebapiDevContainer extends DockerfileModel {
|
|
7
|
+
constructor(baseImage = `${DEVCONTAINER_IMAGE_REPOSITORY}:${CREATE_VERSION}`){
|
|
8
|
+
super(baseImage);
|
|
9
|
+
}
|
|
10
|
+
files() {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
filePath: ".devcontainer/Dockerfile",
|
|
14
|
+
content: this.build()
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** Minimal OpenAPI 3 document shape used for Postman conversion. */ /**
|
|
2
|
+
* Canonical Hello WebAPI OpenAPI document used to generate Postman requests.
|
|
3
|
+
* Keep paths aligned with scaffolded HelloController (`/hello`, `/hello/{id}`).
|
|
4
|
+
*/ export function buildHelloOpenApiDocument(projectName) {
|
|
5
|
+
return {
|
|
6
|
+
openapi: "3.0.3",
|
|
7
|
+
info: {
|
|
8
|
+
title: `${projectName} WebAPI`,
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "Generated from OpenAPI for Postman. Auth/env overlays are applied separately."
|
|
11
|
+
},
|
|
12
|
+
servers: [
|
|
13
|
+
{
|
|
14
|
+
url: "{{baseUrl}}"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
paths: {
|
|
18
|
+
"/hello": {
|
|
19
|
+
get: {
|
|
20
|
+
operationId: "HelloController.getAll",
|
|
21
|
+
summary: "List (anonymous)",
|
|
22
|
+
tags: [
|
|
23
|
+
"Hello"
|
|
24
|
+
],
|
|
25
|
+
parameters: [
|
|
26
|
+
{
|
|
27
|
+
name: "filter",
|
|
28
|
+
in: "query",
|
|
29
|
+
required: false,
|
|
30
|
+
schema: {
|
|
31
|
+
type: "string"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
responses: {
|
|
36
|
+
"200": {
|
|
37
|
+
description: "Successful response"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
post: {
|
|
42
|
+
operationId: "HelloController.create",
|
|
43
|
+
summary: "Create",
|
|
44
|
+
tags: [
|
|
45
|
+
"Hello"
|
|
46
|
+
],
|
|
47
|
+
requestBody: {
|
|
48
|
+
required: true,
|
|
49
|
+
content: {
|
|
50
|
+
"application/json": {
|
|
51
|
+
schema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
message: {
|
|
55
|
+
type: "string"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
required: [
|
|
59
|
+
"message"
|
|
60
|
+
],
|
|
61
|
+
example: {
|
|
62
|
+
message: "hello"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
responses: {
|
|
69
|
+
"200": {
|
|
70
|
+
description: "Successful response"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"/hello/{id}": {
|
|
76
|
+
get: {
|
|
77
|
+
operationId: "HelloController.getOne",
|
|
78
|
+
summary: "Get by id",
|
|
79
|
+
tags: [
|
|
80
|
+
"Hello"
|
|
81
|
+
],
|
|
82
|
+
parameters: [
|
|
83
|
+
{
|
|
84
|
+
name: "id",
|
|
85
|
+
in: "path",
|
|
86
|
+
required: true,
|
|
87
|
+
schema: {
|
|
88
|
+
type: "string"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
responses: {
|
|
93
|
+
"200": {
|
|
94
|
+
description: "Successful response"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
put: {
|
|
99
|
+
operationId: "HelloController.update",
|
|
100
|
+
summary: "Update",
|
|
101
|
+
tags: [
|
|
102
|
+
"Hello"
|
|
103
|
+
],
|
|
104
|
+
parameters: [
|
|
105
|
+
{
|
|
106
|
+
name: "id",
|
|
107
|
+
in: "path",
|
|
108
|
+
required: true,
|
|
109
|
+
schema: {
|
|
110
|
+
type: "string"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
requestBody: {
|
|
115
|
+
required: true,
|
|
116
|
+
content: {
|
|
117
|
+
"application/json": {
|
|
118
|
+
schema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
message: {
|
|
122
|
+
type: "string"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: [
|
|
126
|
+
"message"
|
|
127
|
+
],
|
|
128
|
+
example: {
|
|
129
|
+
message: "updated"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
responses: {
|
|
136
|
+
"200": {
|
|
137
|
+
description: "Successful response"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
delete: {
|
|
142
|
+
operationId: "HelloController.remove",
|
|
143
|
+
summary: "Delete",
|
|
144
|
+
tags: [
|
|
145
|
+
"Hello"
|
|
146
|
+
],
|
|
147
|
+
parameters: [
|
|
148
|
+
{
|
|
149
|
+
name: "id",
|
|
150
|
+
in: "path",
|
|
151
|
+
required: true,
|
|
152
|
+
schema: {
|
|
153
|
+
type: "string"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
responses: {
|
|
158
|
+
"200": {
|
|
159
|
+
description: "Successful response"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { openApiPathParamsToColon } from "./openapi-to-postman.js";
|
|
3
|
+
function yamlQuote(value) {
|
|
4
|
+
if (/[:#{}[\],&*?|<>=!%@`]/.test(value) || value.includes("\n") || value === "") {
|
|
5
|
+
return JSON.stringify(value);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/** Local View / Native Git environment (YAML). */ export function buildEnvironmentYaml(name, auth, options) {
|
|
10
|
+
const values = [
|
|
11
|
+
{
|
|
12
|
+
key: "baseUrl",
|
|
13
|
+
value: "http://localhost:3000"
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
if (auth?.auth0Domain) {
|
|
17
|
+
values.push({
|
|
18
|
+
key: "auth0Domain",
|
|
19
|
+
value: auth.auth0Domain
|
|
20
|
+
}, {
|
|
21
|
+
key: "auth0Audience",
|
|
22
|
+
value: auth.auth0Audience ?? ""
|
|
23
|
+
}, {
|
|
24
|
+
key: "auth0ClientId",
|
|
25
|
+
value: auth.auth0ClientId ?? ""
|
|
26
|
+
}, {
|
|
27
|
+
key: "authUrl",
|
|
28
|
+
value: `https://${auth.auth0Domain}/authorize?audience=${auth.auth0Audience ?? ""}`
|
|
29
|
+
}, {
|
|
30
|
+
key: "accessTokenUrl",
|
|
31
|
+
value: `https://${auth.auth0Domain}/oauth/token`
|
|
32
|
+
}, {
|
|
33
|
+
key: "callbackUrl",
|
|
34
|
+
value: options.callbacks[0]
|
|
35
|
+
}, {
|
|
36
|
+
key: "scope",
|
|
37
|
+
value: options.scopes
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const lines = [
|
|
41
|
+
`name: ${yamlQuote(name)}`,
|
|
42
|
+
"values:"
|
|
43
|
+
];
|
|
44
|
+
for (const v of values){
|
|
45
|
+
lines.push(` - key: ${v.key}`);
|
|
46
|
+
lines.push(` value: ${yamlQuote(v.value)}`);
|
|
47
|
+
lines.push(" enabled: true");
|
|
48
|
+
}
|
|
49
|
+
return `${lines.join("\n")}\n`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build Postman Local View multi-file YAML from a v2.1 collection (routes)
|
|
53
|
+
* plus hand-authored Auth0 overlay.
|
|
54
|
+
*/ export function buildLocalViewFilesFromCollection(collection, projectName, auth, options) {
|
|
55
|
+
const collectionDir = `postman/collections/${projectName} WebAPI`;
|
|
56
|
+
const files = [];
|
|
57
|
+
const authBlock = auth?.auth0Domain ? [
|
|
58
|
+
"auth:",
|
|
59
|
+
` - id: ${randomUUID()}`,
|
|
60
|
+
" type: oauth2",
|
|
61
|
+
" name: OAuth 2.0",
|
|
62
|
+
" credentials:",
|
|
63
|
+
" addTokenTo: header",
|
|
64
|
+
" grant_type: authorization_code_with_pkce",
|
|
65
|
+
` authUrl: https://${auth.auth0Domain}/authorize?audience=${auth.auth0Audience ?? ""}`,
|
|
66
|
+
` accessTokenUrl: https://${auth.auth0Domain}/oauth/token`,
|
|
67
|
+
` clientId: ${yamlQuote(auth.auth0ClientId ?? "")}`,
|
|
68
|
+
` scope: ${yamlQuote(options.scopes)}`,
|
|
69
|
+
` redirect_uri: ${options.callbacks[0]}`,
|
|
70
|
+
" useBrowser: true",
|
|
71
|
+
" challengeAlgorithm: S256",
|
|
72
|
+
" client_authentication: body",
|
|
73
|
+
' clientSecret: ""',
|
|
74
|
+
` tokenName: ${yamlQuote(`${projectName} Access Token`)}`,
|
|
75
|
+
""
|
|
76
|
+
].join("\n") : "";
|
|
77
|
+
files.push({
|
|
78
|
+
filePath: `${collectionDir}/.resources/definition.yaml`,
|
|
79
|
+
content: [
|
|
80
|
+
"$kind: collection",
|
|
81
|
+
`name: ${yamlQuote(`${projectName} WebAPI`)}`,
|
|
82
|
+
"schemaVersion: '3.0'",
|
|
83
|
+
"description: >-",
|
|
84
|
+
" Open this project root in Postman Local View (Files → Open folder).",
|
|
85
|
+
" Select the Local environment, then Authorization → Get New Access Token (PKCE).",
|
|
86
|
+
" Route requests are generated from OpenAPI; Auth0 overlay is hand-authored.",
|
|
87
|
+
authBlock
|
|
88
|
+
].filter((l)=>l !== undefined).join("\n").replace(/\n+$/, "\n")
|
|
89
|
+
});
|
|
90
|
+
let folderOrder = 1000;
|
|
91
|
+
for (const folder of collection.item ?? []){
|
|
92
|
+
if (folder.item == null) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const folderName = folder.name ?? "Requests";
|
|
96
|
+
files.push({
|
|
97
|
+
filePath: `${collectionDir}/${folderName}/.resources/definition.yaml`,
|
|
98
|
+
content: [
|
|
99
|
+
"$kind: folder",
|
|
100
|
+
`name: ${yamlQuote(folderName)}`,
|
|
101
|
+
`order: ${folderOrder}`,
|
|
102
|
+
""
|
|
103
|
+
].join("\n")
|
|
104
|
+
});
|
|
105
|
+
folderOrder += 1000;
|
|
106
|
+
let requestOrder = 1000;
|
|
107
|
+
for (const child of folder.item){
|
|
108
|
+
const yaml = requestToLocalViewYaml(child, requestOrder);
|
|
109
|
+
if (yaml == null) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const fileName = `${sanitizeFileName(child.name ?? "Request")}.request.yaml`;
|
|
113
|
+
files.push({
|
|
114
|
+
filePath: `${collectionDir}/${folderName}/${fileName}`,
|
|
115
|
+
content: yaml
|
|
116
|
+
});
|
|
117
|
+
requestOrder += 1000;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
122
|
+
function requestToLocalViewYaml(item, order) {
|
|
123
|
+
const request = item.request;
|
|
124
|
+
if (request == null) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
128
|
+
const url = requestUrlForLocalView(request);
|
|
129
|
+
const lines = [
|
|
130
|
+
"$kind: http-request",
|
|
131
|
+
`name: ${yamlQuote(item.name ?? method)}`,
|
|
132
|
+
`method: ${method}`,
|
|
133
|
+
`url: ${yamlQuote(url)}`
|
|
134
|
+
];
|
|
135
|
+
if (request.auth?.type === "noauth") {
|
|
136
|
+
lines.push("auth:", " type: noauth");
|
|
137
|
+
}
|
|
138
|
+
const contentType = request.header?.find((h)=>h.key.toLowerCase() === "content-type")?.value;
|
|
139
|
+
const rawBody = request.body?.raw?.trim();
|
|
140
|
+
if (rawBody) {
|
|
141
|
+
lines.push("headers:");
|
|
142
|
+
lines.push(` Content-Type: ${contentType ?? "application/json"}`);
|
|
143
|
+
lines.push("body:");
|
|
144
|
+
lines.push(" type: json");
|
|
145
|
+
lines.push(" content: |-");
|
|
146
|
+
for (const bodyLine of rawBody.split("\n")){
|
|
147
|
+
lines.push(` ${bodyLine}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
lines.push(`order: ${order}`, "");
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
function requestUrlForLocalView(request) {
|
|
154
|
+
const url = request.url;
|
|
155
|
+
if (typeof url === "string") {
|
|
156
|
+
return openApiPathParamsToColon(url);
|
|
157
|
+
}
|
|
158
|
+
if (url?.raw) {
|
|
159
|
+
return openApiPathParamsToColon(url.raw);
|
|
160
|
+
}
|
|
161
|
+
const host = url?.host?.[0] ?? "{{baseUrl}}";
|
|
162
|
+
const path = (url?.path ?? []).map((segment)=>openApiPathParamsToColon(String(segment))).join("/");
|
|
163
|
+
return path ? `${host}/${path}` : host;
|
|
164
|
+
}
|
|
165
|
+
function sanitizeFileName(name) {
|
|
166
|
+
return name.replace(/[^\w\s()-]+/g, "").trim() || "Request";
|
|
167
|
+
}
|