@decohere/scaffold 0.0.1-dev.125 → 0.0.1-dev.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -19
- package/src/add-api.action.ts +25 -0
- package/src/add-api.command.ts +31 -0
- package/src/add.command.ts +20 -0
- package/src/add.module.ts +11 -0
- package/src/cli.ts +4 -0
- package/src/create.command.ts +20 -0
- package/src/create.module.ts +11 -0
- package/src/domain/prompt.ts +50 -0
- package/{types/src/index.d.ts → src/index.ts} +0 -4
- package/src/models/compose.model.ts +205 -0
- package/src/models/config-yaml.model.ts +83 -0
- package/src/models/const.ts +20 -0
- package/src/models/dev-container.model.ts +136 -0
- package/src/models/docker-image.model.ts +343 -0
- package/src/models/env-file.model.ts +45 -0
- package/src/models/gitignore.model.ts +35 -0
- package/src/models/launch-json.model.ts +60 -0
- package/src/models/package-json.model.ts +169 -0
- package/src/models/project.model.ts +141 -0
- package/{types/src/models/source-file.d.ts → src/models/source-file.ts} +6 -4
- package/src/models/tsconfig.model.ts +29 -0
- package/src/models/vitest-config.model.ts +26 -0
- package/src/models/vscode-settings.model.ts +24 -0
- package/src/program.ts +63 -0
- package/{types/src/resolve-target-dir.d.ts → src/resolve-target-dir.ts} +4 -1
- package/src/services/scaffold.service.ts +89 -0
- package/src/version.ts +56 -0
- package/src/webapi/api-name.ts +73 -0
- package/src/webapi/cli-options.ts +56 -0
- package/{types/src/webapi/const.d.ts → src/webapi/const.ts} +21 -9
- package/src/webapi/create-api.ts +275 -0
- package/src/webapi/devcontainer/host-scripts.ts +35 -0
- package/src/webapi/features/auth.feature.ts +454 -0
- package/src/webapi/features/auth0-cli.ts +421 -0
- package/src/webapi/features/biome-format.ts +34 -0
- package/src/webapi/features/database.feature.ts +522 -0
- package/src/webapi/features/finalize.feature.ts +112 -0
- package/src/webapi/features/postman.feature.ts +287 -0
- package/src/webapi/models/api-package-json.model.ts +60 -0
- package/src/webapi/models/auth-config.model.ts +36 -0
- package/src/webapi/models/controller-source-file.model.ts +232 -0
- package/{es6/src/webapi/models/github-ci.model.js → src/webapi/models/github-ci.model.ts} +29 -27
- package/src/webapi/models/main-source-file.model.ts +208 -0
- package/src/webapi/models/production-dockerfile.model.ts +162 -0
- package/src/webapi/models/sample-files.model.ts +45 -0
- package/src/webapi/models/webapi-image.model.ts +21 -0
- package/src/webapi/postman/hello-openapi.ts +92 -0
- package/src/webapi/postman/local-view.ts +195 -0
- package/src/webapi/postman/openapi-to-postman.ts +208 -0
- package/src/webapi/workspace-readme.ts +192 -0
- package/src/webapi.action.ts +22 -0
- package/src/webapi.command.ts +41 -0
- package/tests/auth-postman.wiring.spec.ts +263 -0
- package/tests/auth0-cli.spec.ts +232 -0
- package/tests/biome-format.spec.ts +49 -0
- package/tests/cli.integration.spec.ts +658 -0
- package/tests/compose.model.spec.ts +51 -0
- package/tests/container-image.model.spec.ts +162 -0
- package/tests/dev-container.model.spec.ts +29 -0
- package/tests/devcontainer-base-image.spec.ts +32 -0
- package/tests/hello-di-inject.scaffold.spec.ts +18 -0
- package/tests/hello-route.contract.spec.ts +94 -0
- package/tests/package-json.order.spec.ts +59 -0
- package/tests/postman-from-openapi.spec.ts +197 -0
- package/tests/seed-devcontainer-templates.ts +40 -0
- package/tests/webapi/devcontainer/codebase-rag-mcp.spec.ts +96 -0
- package/LICENSE +0 -15
- package/commonjs/src/add-api.action.js +0 -37
- package/commonjs/src/add-api.command.js +0 -39
- package/commonjs/src/add.command.js +0 -22
- package/commonjs/src/add.module.js +0 -24
- package/commonjs/src/auth0-configure.action.js +0 -28
- package/commonjs/src/auth0-configure.command.js +0 -24
- package/commonjs/src/auth0-login.action.js +0 -28
- package/commonjs/src/auth0-login.command.js +0 -23
- package/commonjs/src/auth0.command.js +0 -22
- package/commonjs/src/auth0.module.js +0 -28
- package/commonjs/src/cli.js +0 -8
- package/commonjs/src/create.command.js +0 -22
- package/commonjs/src/create.module.js +0 -24
- package/commonjs/src/domain/prompt.js +0 -62
- package/commonjs/src/index.js +0 -48
- package/commonjs/src/models/compose.model.js +0 -258
- package/commonjs/src/models/config-yaml.model.js +0 -86
- package/commonjs/src/models/const.js +0 -30
- package/commonjs/src/models/dev-container.model.js +0 -172
- package/commonjs/src/models/docker-image.model.js +0 -277
- package/commonjs/src/models/env-file.model.js +0 -48
- package/commonjs/src/models/gitignore.model.js +0 -48
- package/commonjs/src/models/launch-json.model.js +0 -70
- package/commonjs/src/models/package-json.model.js +0 -156
- package/commonjs/src/models/project.model.js +0 -132
- package/commonjs/src/models/source-file.js +0 -4
- package/commonjs/src/models/tsconfig.model.js +0 -32
- package/commonjs/src/models/vitest-config.model.js +0 -33
- package/commonjs/src/models/vscode-settings.model.js +0 -29
- package/commonjs/src/program.js +0 -64
- package/commonjs/src/resolve-target-dir.js +0 -17
- package/commonjs/src/services/scaffold.service.js +0 -60
- package/commonjs/src/version.js +0 -55
- package/commonjs/src/webapi/api-name.js +0 -93
- package/commonjs/src/webapi/auth0-commands.js +0 -250
- package/commonjs/src/webapi/authorize-scopes.js +0 -79
- package/commonjs/src/webapi/cli-options.js +0 -99
- package/commonjs/src/webapi/const.js +0 -54
- package/commonjs/src/webapi/create-api.js +0 -265
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +0 -164
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
- package/commonjs/src/webapi/devcontainer/host-scripts.js +0 -50
- package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
- package/commonjs/src/webapi/devcontainer/templates/initialize.sh +0 -169
- package/commonjs/src/webapi/devcontainer/templates/post-create.sh +0 -35
- package/commonjs/src/webapi/devcontainer/templates/post-start.sh +0 -65
- package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
- package/commonjs/src/webapi/features/auth.feature.js +0 -408
- package/commonjs/src/webapi/features/auth0-cli.js +0 -472
- package/commonjs/src/webapi/features/biome-format.js +0 -47
- package/commonjs/src/webapi/features/database.feature.js +0 -433
- package/commonjs/src/webapi/features/finalize.feature.js +0 -93
- package/commonjs/src/webapi/features/postman.feature.js +0 -322
- package/commonjs/src/webapi/models/api-package-json.model.js +0 -58
- package/commonjs/src/webapi/models/auth-config.model.js +0 -24
- package/commonjs/src/webapi/models/controller-source-file.model.js +0 -190
- package/commonjs/src/webapi/models/github-ci.model.js +0 -193
- package/commonjs/src/webapi/models/main-source-file.model.js +0 -165
- package/commonjs/src/webapi/models/production-dockerfile.model.js +0 -75
- package/commonjs/src/webapi/models/sample-files.model.js +0 -58
- package/commonjs/src/webapi/models/webapi-image.model.js +0 -25
- package/commonjs/src/webapi/postman/hello-openapi.js +0 -173
- package/commonjs/src/webapi/postman/local-view.js +0 -182
- package/commonjs/src/webapi/postman/openapi-to-postman.js +0 -238
- package/commonjs/src/webapi/workspace-readme.js +0 -109
- package/commonjs/src/webapi.action.js +0 -28
- package/commonjs/src/webapi.command.js +0 -43
- package/es6/src/add-api.action.js +0 -27
- package/es6/src/add-api.command.js +0 -29
- package/es6/src/add.command.js +0 -12
- package/es6/src/add.module.js +0 -14
- package/es6/src/auth0-configure.action.js +0 -18
- package/es6/src/auth0-configure.command.js +0 -14
- package/es6/src/auth0-login.action.js +0 -18
- package/es6/src/auth0-login.command.js +0 -13
- package/es6/src/auth0.command.js +0 -12
- package/es6/src/auth0.module.js +0 -18
- package/es6/src/cli.js +0 -4
- package/es6/src/create.command.js +0 -12
- package/es6/src/create.module.js +0 -14
- package/es6/src/domain/prompt.js +0 -41
- package/es6/src/index.js +0 -9
- package/es6/src/models/compose.model.js +0 -254
- package/es6/src/models/config-yaml.model.js +0 -74
- package/es6/src/models/const.js +0 -20
- package/es6/src/models/dev-container.model.js +0 -162
- package/es6/src/models/docker-image.model.js +0 -235
- package/es6/src/models/env-file.model.js +0 -44
- package/es6/src/models/gitignore.model.js +0 -38
- package/es6/src/models/launch-json.model.js +0 -60
- package/es6/src/models/package-json.model.js +0 -141
- package/es6/src/models/project.model.js +0 -117
- package/es6/src/models/source-file.js +0 -1
- package/es6/src/models/tsconfig.model.js +0 -22
- package/es6/src/models/vitest-config.model.js +0 -23
- package/es6/src/models/vscode-settings.model.js +0 -19
- package/es6/src/program.js +0 -50
- package/es6/src/resolve-target-dir.js +0 -7
- package/es6/src/services/scaffold.service.js +0 -50
- package/es6/src/version.js +0 -34
- package/es6/src/webapi/api-name.js +0 -57
- package/es6/src/webapi/auth0-commands.js +0 -185
- package/es6/src/webapi/authorize-scopes.js +0 -55
- package/es6/src/webapi/cli-options.js +0 -34
- package/es6/src/webapi/const.js +0 -21
- package/es6/src/webapi/create-api.js +0 -245
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +0 -154
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
- package/es6/src/webapi/devcontainer/host-scripts.js +0 -29
- package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
- package/es6/src/webapi/devcontainer/templates/initialize.sh +0 -169
- package/es6/src/webapi/devcontainer/templates/post-create.sh +0 -35
- package/es6/src/webapi/devcontainer/templates/post-start.sh +0 -65
- package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
- package/es6/src/webapi/features/auth.feature.js +0 -357
- package/es6/src/webapi/features/auth0-cli.js +0 -406
- package/es6/src/webapi/features/biome-format.js +0 -40
- package/es6/src/webapi/features/database.feature.js +0 -423
- package/es6/src/webapi/features/finalize.feature.js +0 -83
- package/es6/src/webapi/features/postman.feature.js +0 -256
- package/es6/src/webapi/models/api-package-json.model.js +0 -53
- package/es6/src/webapi/models/auth-config.model.js +0 -20
- package/es6/src/webapi/models/controller-source-file.model.js +0 -181
- package/es6/src/webapi/models/main-source-file.model.js +0 -161
- package/es6/src/webapi/models/production-dockerfile.model.js +0 -66
- package/es6/src/webapi/models/sample-files.model.js +0 -40
- package/es6/src/webapi/models/webapi-image.model.js +0 -18
- package/es6/src/webapi/postman/hello-openapi.js +0 -166
- package/es6/src/webapi/postman/local-view.js +0 -167
- package/es6/src/webapi/postman/openapi-to-postman.js +0 -179
- package/es6/src/webapi/workspace-readme.js +0 -88
- package/es6/src/webapi.action.js +0 -18
- package/es6/src/webapi.command.js +0 -33
- package/types/src/add-api.action.d.ts +0 -13
- package/types/src/add-api.command.d.ts +0 -7
- package/types/src/add.command.d.ts +0 -6
- package/types/src/add.module.d.ts +0 -14
- package/types/src/auth0-configure.action.d.ts +0 -7
- package/types/src/auth0-configure.command.d.ts +0 -6
- package/types/src/auth0-login.action.d.ts +0 -4
- package/types/src/auth0-login.command.d.ts +0 -6
- package/types/src/auth0.command.d.ts +0 -6
- package/types/src/auth0.module.d.ts +0 -18
- package/types/src/cli.d.ts +0 -2
- package/types/src/create.command.d.ts +0 -6
- package/types/src/create.module.d.ts +0 -14
- package/types/src/domain/prompt.d.ts +0 -4
- package/types/src/models/compose.model.d.ts +0 -73
- package/types/src/models/config-yaml.model.d.ts +0 -27
- package/types/src/models/const.d.ts +0 -20
- package/types/src/models/dev-container.model.d.ts +0 -49
- package/types/src/models/docker-image.model.d.ts +0 -90
- package/types/src/models/env-file.model.d.ts +0 -16
- package/types/src/models/gitignore.model.d.ts +0 -9
- package/types/src/models/launch-json.model.d.ts +0 -7
- package/types/src/models/package-json.model.d.ts +0 -36
- package/types/src/models/project.model.d.ts +0 -60
- package/types/src/models/tsconfig.model.d.ts +0 -5
- package/types/src/models/vitest-config.model.d.ts +0 -5
- package/types/src/models/vscode-settings.model.d.ts +0 -7
- package/types/src/program.d.ts +0 -8
- package/types/src/services/scaffold.service.d.ts +0 -22
- package/types/src/version.d.ts +0 -6
- package/types/src/webapi/api-name.d.ts +0 -14
- package/types/src/webapi/auth0-commands.d.ts +0 -11
- package/types/src/webapi/authorize-scopes.d.ts +0 -7
- package/types/src/webapi/cli-options.d.ts +0 -6
- package/types/src/webapi/create-api.d.ts +0 -15
- package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +0 -32
- package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +0 -182
- package/types/src/webapi/devcontainer/host-scripts.d.ts +0 -7
- package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +0 -3
- package/types/src/webapi/devcontainer/templates/initialize.sh +0 -169
- package/types/src/webapi/devcontainer/templates/post-create.sh +0 -35
- package/types/src/webapi/devcontainer/templates/post-start.sh +0 -65
- package/types/src/webapi/devcontainer/templates/tty-prompt.sh +0 -38
- package/types/src/webapi/features/auth.feature.d.ts +0 -10
- package/types/src/webapi/features/auth0-cli.d.ts +0 -84
- package/types/src/webapi/features/biome-format.d.ts +0 -5
- package/types/src/webapi/features/database.feature.d.ts +0 -7
- package/types/src/webapi/features/finalize.feature.d.ts +0 -2
- package/types/src/webapi/features/postman.feature.d.ts +0 -16
- package/types/src/webapi/models/api-package-json.model.d.ts +0 -18
- package/types/src/webapi/models/auth-config.model.d.ts +0 -26
- package/types/src/webapi/models/controller-source-file.model.d.ts +0 -46
- package/types/src/webapi/models/github-ci.model.d.ts +0 -9
- package/types/src/webapi/models/main-source-file.model.d.ts +0 -54
- package/types/src/webapi/models/production-dockerfile.model.d.ts +0 -18
- package/types/src/webapi/models/sample-files.model.d.ts +0 -7
- package/types/src/webapi/models/webapi-image.model.d.ts +0 -12
- package/types/src/webapi/postman/hello-openapi.d.ts +0 -19
- package/types/src/webapi/postman/local-view.d.ts +0 -17
- package/types/src/webapi/postman/openapi-to-postman.d.ts +0 -62
- package/types/src/webapi/workspace-readme.d.ts +0 -18
- package/types/src/webapi.action.d.ts +0 -14
- package/types/src/webapi.command.d.ts +0 -11
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
type Auth0Env,
|
|
4
|
+
openApiPathParamsToColon,
|
|
5
|
+
type PostmanCollection,
|
|
6
|
+
type PostmanItem,
|
|
7
|
+
type PostmanRequest,
|
|
8
|
+
} from "./openapi-to-postman.js";
|
|
9
|
+
|
|
10
|
+
function yamlQuote(value: string): string {
|
|
11
|
+
if (/[:#{}[\],&*?|<>=!%@`]/.test(value) || value.includes("\n") || value === "") {
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Local View / Native Git environment (YAML). */
|
|
18
|
+
export function buildEnvironmentYaml(
|
|
19
|
+
name: string,
|
|
20
|
+
auth: Auth0Env | undefined,
|
|
21
|
+
options: { callbacks: readonly string[]; scopes: string },
|
|
22
|
+
): string {
|
|
23
|
+
const values: Array<{ key: string; value: string }> = [
|
|
24
|
+
{ key: "baseUrl", value: "http://localhost:3000" },
|
|
25
|
+
];
|
|
26
|
+
if (auth?.auth0Domain) {
|
|
27
|
+
values.push(
|
|
28
|
+
{ key: "auth0Domain", value: auth.auth0Domain },
|
|
29
|
+
{ key: "auth0Audience", value: auth.auth0Audience ?? "" },
|
|
30
|
+
{ key: "auth0ClientId", value: auth.auth0ClientId ?? "" },
|
|
31
|
+
{
|
|
32
|
+
key: "authUrl",
|
|
33
|
+
value: `https://${auth.auth0Domain}/authorize?audience=${auth.auth0Audience ?? ""}`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: "accessTokenUrl",
|
|
37
|
+
value: `https://${auth.auth0Domain}/oauth/token`,
|
|
38
|
+
},
|
|
39
|
+
{ key: "callbackUrl", value: options.callbacks[0] },
|
|
40
|
+
{ key: "scope", value: options.scopes },
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lines = [`name: ${yamlQuote(name)}`, "values:"];
|
|
45
|
+
for (const v of values) {
|
|
46
|
+
lines.push(` - key: ${v.key}`);
|
|
47
|
+
lines.push(` value: ${yamlQuote(v.value)}`);
|
|
48
|
+
lines.push(" enabled: true");
|
|
49
|
+
}
|
|
50
|
+
return `${lines.join("\n")}\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build Postman Local View multi-file YAML from a v2.1 collection (routes)
|
|
55
|
+
* plus hand-authored Auth0 overlay.
|
|
56
|
+
*/
|
|
57
|
+
export function buildLocalViewFilesFromCollection(
|
|
58
|
+
collection: PostmanCollection,
|
|
59
|
+
projectName: string,
|
|
60
|
+
auth: Auth0Env | undefined,
|
|
61
|
+
options: { callbacks: readonly string[]; scopes: string },
|
|
62
|
+
): Array<{ filePath: string; content: string }> {
|
|
63
|
+
const collectionDir = `postman/collections/${projectName} WebAPI`;
|
|
64
|
+
const files: Array<{ filePath: string; content: string }> = [];
|
|
65
|
+
|
|
66
|
+
const authBlock = auth?.auth0Domain
|
|
67
|
+
? [
|
|
68
|
+
"auth:",
|
|
69
|
+
` - id: ${randomUUID()}`,
|
|
70
|
+
" type: oauth2",
|
|
71
|
+
" name: OAuth 2.0",
|
|
72
|
+
" credentials:",
|
|
73
|
+
" addTokenTo: header",
|
|
74
|
+
" grant_type: authorization_code_with_pkce",
|
|
75
|
+
` authUrl: https://${auth.auth0Domain}/authorize?audience=${auth.auth0Audience ?? ""}`,
|
|
76
|
+
` accessTokenUrl: https://${auth.auth0Domain}/oauth/token`,
|
|
77
|
+
` clientId: ${yamlQuote(auth.auth0ClientId ?? "")}`,
|
|
78
|
+
` scope: ${yamlQuote(options.scopes)}`,
|
|
79
|
+
` redirect_uri: ${options.callbacks[0]}`,
|
|
80
|
+
" useBrowser: true",
|
|
81
|
+
" challengeAlgorithm: S256",
|
|
82
|
+
" client_authentication: body",
|
|
83
|
+
' clientSecret: ""',
|
|
84
|
+
` tokenName: ${yamlQuote(`${projectName} Access Token`)}`,
|
|
85
|
+
"",
|
|
86
|
+
].join("\n")
|
|
87
|
+
: "";
|
|
88
|
+
|
|
89
|
+
files.push({
|
|
90
|
+
filePath: `${collectionDir}/.resources/definition.yaml`,
|
|
91
|
+
content: [
|
|
92
|
+
"$kind: collection",
|
|
93
|
+
`name: ${yamlQuote(`${projectName} WebAPI`)}`,
|
|
94
|
+
"schemaVersion: '3.0'",
|
|
95
|
+
"description: >-",
|
|
96
|
+
" Open this project root in Postman Local View (Files → Open folder).",
|
|
97
|
+
" Select the Local environment, then Authorization → Get New Access Token (PKCE).",
|
|
98
|
+
" Route requests are generated from OpenAPI; Auth0 overlay is hand-authored.",
|
|
99
|
+
authBlock,
|
|
100
|
+
]
|
|
101
|
+
.filter((l) => l !== undefined)
|
|
102
|
+
.join("\n")
|
|
103
|
+
.replace(/\n+$/, "\n"),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
let folderOrder = 1000;
|
|
107
|
+
for (const folder of collection.item ?? []) {
|
|
108
|
+
if (folder.item == null) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const folderName = folder.name ?? "Requests";
|
|
112
|
+
files.push({
|
|
113
|
+
filePath: `${collectionDir}/${folderName}/.resources/definition.yaml`,
|
|
114
|
+
content: [
|
|
115
|
+
"$kind: folder",
|
|
116
|
+
`name: ${yamlQuote(folderName)}`,
|
|
117
|
+
`order: ${folderOrder}`,
|
|
118
|
+
"",
|
|
119
|
+
].join("\n"),
|
|
120
|
+
});
|
|
121
|
+
folderOrder += 1000;
|
|
122
|
+
|
|
123
|
+
let requestOrder = 1000;
|
|
124
|
+
for (const child of folder.item) {
|
|
125
|
+
const yaml = requestToLocalViewYaml(child, requestOrder);
|
|
126
|
+
if (yaml == null) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const fileName = `${sanitizeFileName(child.name ?? "Request")}.request.yaml`;
|
|
130
|
+
files.push({
|
|
131
|
+
filePath: `${collectionDir}/${folderName}/${fileName}`,
|
|
132
|
+
content: yaml,
|
|
133
|
+
});
|
|
134
|
+
requestOrder += 1000;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return files;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function requestToLocalViewYaml(item: PostmanItem, order: number): string | null {
|
|
142
|
+
const request = item.request;
|
|
143
|
+
if (request == null) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
147
|
+
const url = requestUrlForLocalView(request);
|
|
148
|
+
const lines = [
|
|
149
|
+
"$kind: http-request",
|
|
150
|
+
`name: ${yamlQuote(item.name ?? method)}`,
|
|
151
|
+
`method: ${method}`,
|
|
152
|
+
`url: ${yamlQuote(url)}`,
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
if (request.auth?.type === "noauth") {
|
|
156
|
+
lines.push("auth:", " type: noauth");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const contentType = request.header?.find(
|
|
160
|
+
(h) => h.key.toLowerCase() === "content-type",
|
|
161
|
+
)?.value;
|
|
162
|
+
const rawBody = request.body?.raw?.trim();
|
|
163
|
+
if (rawBody) {
|
|
164
|
+
lines.push("headers:");
|
|
165
|
+
lines.push(` Content-Type: ${contentType ?? "application/json"}`);
|
|
166
|
+
lines.push("body:");
|
|
167
|
+
lines.push(" type: json");
|
|
168
|
+
lines.push(" content: |-");
|
|
169
|
+
for (const bodyLine of rawBody.split("\n")) {
|
|
170
|
+
lines.push(` ${bodyLine}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
lines.push(`order: ${order}`, "");
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function requestUrlForLocalView(request: PostmanRequest): string {
|
|
179
|
+
const url = request.url;
|
|
180
|
+
if (typeof url === "string") {
|
|
181
|
+
return openApiPathParamsToColon(url);
|
|
182
|
+
}
|
|
183
|
+
if (url?.raw) {
|
|
184
|
+
return openApiPathParamsToColon(url.raw);
|
|
185
|
+
}
|
|
186
|
+
const host = url?.host?.[0] ?? "{{baseUrl}}";
|
|
187
|
+
const path = (url?.path ?? [])
|
|
188
|
+
.map((segment) => openApiPathParamsToColon(String(segment)))
|
|
189
|
+
.join("/");
|
|
190
|
+
return path ? `${host}/${path}` : host;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function sanitizeFileName(name: string): string {
|
|
194
|
+
return name.replace(/[^\w\s()-]+/g, "").trim() || "Request";
|
|
195
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import converter from "openapi-to-postmanv2";
|
|
2
|
+
import type { ScaffoldOpenApiDocument } from "./hello-openapi.js";
|
|
3
|
+
|
|
4
|
+
export type PostmanCollection = {
|
|
5
|
+
info: {
|
|
6
|
+
_postman_id?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
schema: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
auth?: unknown;
|
|
12
|
+
item?: PostmanItem[];
|
|
13
|
+
variable?: Array<{ key: string; value: string }>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PostmanItem = {
|
|
18
|
+
name?: string;
|
|
19
|
+
item?: PostmanItem[];
|
|
20
|
+
request?: PostmanRequest;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PostmanRequest = {
|
|
25
|
+
method?: string;
|
|
26
|
+
header?: Array<{ key: string; value: string }>;
|
|
27
|
+
body?: { mode?: string; raw?: string };
|
|
28
|
+
url?: string | { raw?: string; host?: string[]; path?: string[]; variable?: unknown[] };
|
|
29
|
+
auth?: { type: string; [key: string]: unknown };
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Auth0Env = {
|
|
34
|
+
auth0Domain?: string;
|
|
35
|
+
auth0Audience?: string;
|
|
36
|
+
auth0ClientId?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type ConvertResult = {
|
|
40
|
+
result: boolean;
|
|
41
|
+
reason?: string;
|
|
42
|
+
output?: Array<{ type: string; data: PostmanCollection }>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function convertAsync(
|
|
46
|
+
data: ScaffoldOpenApiDocument,
|
|
47
|
+
options: Record<string, unknown>,
|
|
48
|
+
): Promise<ConvertResult> {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
converter.convert(
|
|
51
|
+
{ type: "json", data },
|
|
52
|
+
options,
|
|
53
|
+
(err: Error | null, result: ConvertResult) => {
|
|
54
|
+
if (err) {
|
|
55
|
+
reject(err);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
resolve(result);
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Convert an OpenAPI document to a Postman Collection v2.1 via openapi-to-postmanv2. */
|
|
65
|
+
export async function openApiToPostmanCollection(
|
|
66
|
+
document: ScaffoldOpenApiDocument,
|
|
67
|
+
): Promise<PostmanCollection> {
|
|
68
|
+
const result = await convertAsync(document, {
|
|
69
|
+
folderStrategy: "Tags",
|
|
70
|
+
requestNameSource: "Fallback",
|
|
71
|
+
includeAuthInfoInExample: false,
|
|
72
|
+
});
|
|
73
|
+
if (!result.result || result.output?.[0]?.data == null) {
|
|
74
|
+
throw new Error(result.reason ?? "openapi-to-postmanv2 conversion failed");
|
|
75
|
+
}
|
|
76
|
+
return result.output[0].data;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Overlay Auth0 OAuth2 (PKCE) collection auth and fix Local View-friendly URLs.
|
|
81
|
+
* Route items come from OpenAPI; auth/env remain hand-authored.
|
|
82
|
+
*/
|
|
83
|
+
export function applyPostmanAuthOverlay(
|
|
84
|
+
collection: PostmanCollection,
|
|
85
|
+
projectName: string,
|
|
86
|
+
auth: Auth0Env | undefined,
|
|
87
|
+
options: {
|
|
88
|
+
callbacks: readonly string[];
|
|
89
|
+
scopes: string;
|
|
90
|
+
description: string;
|
|
91
|
+
},
|
|
92
|
+
): PostmanCollection {
|
|
93
|
+
const next: PostmanCollection = {
|
|
94
|
+
...collection,
|
|
95
|
+
info: {
|
|
96
|
+
...collection.info,
|
|
97
|
+
name: `${projectName} WebAPI`,
|
|
98
|
+
description: options.description,
|
|
99
|
+
schema:
|
|
100
|
+
collection.info.schema ??
|
|
101
|
+
"https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (auth?.auth0Domain) {
|
|
106
|
+
next.auth = {
|
|
107
|
+
type: "oauth2",
|
|
108
|
+
oauth2: [
|
|
109
|
+
{ key: "grant_type", value: "authorization_code_with_pkce", type: "string" },
|
|
110
|
+
{
|
|
111
|
+
key: "authUrl",
|
|
112
|
+
value: "https://{{auth0Domain}}/authorize?audience={{auth0Audience}}",
|
|
113
|
+
type: "string",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "accessTokenUrl",
|
|
117
|
+
value: "https://{{auth0Domain}}/oauth/token",
|
|
118
|
+
type: "string",
|
|
119
|
+
},
|
|
120
|
+
{ key: "clientId", value: "{{auth0ClientId}}", type: "string" },
|
|
121
|
+
{ key: "scope", value: options.scopes, type: "string" },
|
|
122
|
+
{ key: "redirect_uri", value: options.callbacks[0], type: "string" },
|
|
123
|
+
{ key: "useBrowser", value: "true", type: "boolean" },
|
|
124
|
+
{ key: "addTokenTo", value: "header", type: "string" },
|
|
125
|
+
{ key: "challengeAlgorithm", value: "S256", type: "string" },
|
|
126
|
+
{ key: "client_authentication", value: "body", type: "string" },
|
|
127
|
+
{ key: "clientSecret", value: "", type: "string" },
|
|
128
|
+
{ key: "tokenName", value: `${projectName} Access Token`, type: "string" },
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
next.item = rewriteCollectionItems(collection.item ?? [], {
|
|
134
|
+
markListAnonymous: true,
|
|
135
|
+
});
|
|
136
|
+
return next;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rewriteCollectionItems(
|
|
140
|
+
items: PostmanItem[],
|
|
141
|
+
opts: { markListAnonymous: boolean },
|
|
142
|
+
): PostmanItem[] {
|
|
143
|
+
return items.map((item) => {
|
|
144
|
+
if (item.item != null) {
|
|
145
|
+
return { ...item, item: rewriteCollectionItems(item.item, opts) };
|
|
146
|
+
}
|
|
147
|
+
const request = item.request;
|
|
148
|
+
if (request == null) {
|
|
149
|
+
return item;
|
|
150
|
+
}
|
|
151
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
152
|
+
const rawUrl = requestUrlRaw(request);
|
|
153
|
+
const nextRequest: PostmanRequest = {
|
|
154
|
+
...request,
|
|
155
|
+
url: toPostmanUrl(rawUrl),
|
|
156
|
+
};
|
|
157
|
+
// List Hello stays anonymous (matches scaffold AllowAnonymous on GET /hello).
|
|
158
|
+
if (
|
|
159
|
+
opts.markListAnonymous &&
|
|
160
|
+
method === "GET" &&
|
|
161
|
+
normalizePath(rawUrl) === "/hello" &&
|
|
162
|
+
!rawUrl.includes("/:")
|
|
163
|
+
) {
|
|
164
|
+
nextRequest.auth = { type: "noauth" };
|
|
165
|
+
}
|
|
166
|
+
return { ...item, request: nextRequest };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function requestUrlRaw(request: PostmanRequest): string {
|
|
171
|
+
const url = request.url;
|
|
172
|
+
if (typeof url === "string") {
|
|
173
|
+
return url;
|
|
174
|
+
}
|
|
175
|
+
if (url?.raw) {
|
|
176
|
+
return url.raw;
|
|
177
|
+
}
|
|
178
|
+
const host = url?.host?.[0] ?? "{{baseUrl}}";
|
|
179
|
+
const path = (url?.path ?? []).join("/");
|
|
180
|
+
return path ? `${host}/${path}` : host;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** OpenAPI `{id}` → Postman `:id`, without touching `{{variables}}`. */
|
|
184
|
+
export function openApiPathParamsToColon(url: string): string {
|
|
185
|
+
return url.replace(/(?<!\{)\{([A-Za-z_][\w]*)\}(?!\})/g, ":$1");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function normalizePath(rawUrl: string): string {
|
|
189
|
+
const withoutBase = rawUrl.replace(/\{\{baseUrl\}\}/g, "").split("?")[0] ?? "";
|
|
190
|
+
const withSlash = withoutBase.startsWith("/") ? withoutBase : `/${withoutBase}`;
|
|
191
|
+
return openApiPathParamsToColon(withSlash);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Prefer Postman template URL shape used by existing Local View / API uploads. */
|
|
195
|
+
function toPostmanUrl(rawUrl: string): PostmanRequest["url"] {
|
|
196
|
+
let pathPart = rawUrl.replace(/\{\{baseUrl\}\}/g, "");
|
|
197
|
+
if (!pathPart.startsWith("/")) {
|
|
198
|
+
pathPart = `/${pathPart}`;
|
|
199
|
+
}
|
|
200
|
+
// openapi-to-postman uses {id}; Local View / prior collections use :id
|
|
201
|
+
pathPart = openApiPathParamsToColon(pathPart);
|
|
202
|
+
const segments = pathPart.split("/").filter(Boolean);
|
|
203
|
+
return {
|
|
204
|
+
raw: `{{baseUrl}}${pathPart}`,
|
|
205
|
+
host: ["{{baseUrl}}"],
|
|
206
|
+
path: segments,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { WELL_KNOWN_FILES } from "../models/const.js";
|
|
4
|
+
import type { EnvFile } from "../models/env-file.model.js";
|
|
5
|
+
import type { PackageJson } from "../models/package-json.model.js";
|
|
6
|
+
import type { WorkspaceScaffold } from "../models/project.model.js";
|
|
7
|
+
import { apiNameFromProjectDir, listExistingApiNames } from "./api-name.js";
|
|
8
|
+
|
|
9
|
+
export type WorkspaceReadmeOptions = {
|
|
10
|
+
add?: boolean;
|
|
11
|
+
databaseProvider?: "none" | "postgres/typeorm";
|
|
12
|
+
authProvider?: "auth0" | "none";
|
|
13
|
+
postmanProvider?: "api" | "local" | "none";
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type WorkspaceReadmeInput = {
|
|
17
|
+
projectName: string;
|
|
18
|
+
apiNames: readonly string[];
|
|
19
|
+
hasPostgres: boolean;
|
|
20
|
+
hasAuth0: boolean;
|
|
21
|
+
hasPostman: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Pure renderer — keep feature sections short and copy-paste ready. */
|
|
25
|
+
export function renderWorkspaceReadme(input: WorkspaceReadmeInput): string {
|
|
26
|
+
const { projectName, apiNames, hasPostgres, hasAuth0, hasPostman } = input;
|
|
27
|
+
const apis = apiNames.length > 0 ? apiNames : ["api"];
|
|
28
|
+
const lines: string[] = [
|
|
29
|
+
`# ${projectName}`,
|
|
30
|
+
"",
|
|
31
|
+
"Decohere web API workspace. Scaffolded with [`@decohere/create`](https://www.npmjs.com/package/@decohere/create).",
|
|
32
|
+
"",
|
|
33
|
+
"## Get started",
|
|
34
|
+
"",
|
|
35
|
+
"1. Open this folder in VS Code / Cursor.",
|
|
36
|
+
"2. **Reopen in Container** (or rebuild the Dev Container).",
|
|
37
|
+
" Host `initialize.sh` and container `post-start.sh` ask y/N (TTY only) before npm auth or codebase RAG MCP.",
|
|
38
|
+
" Skip or decline; missing TTY skips. RAG credentials land in `.cursor/` (gitignored).",
|
|
39
|
+
"3. Install dependencies if needed:",
|
|
40
|
+
"",
|
|
41
|
+
"```bash",
|
|
42
|
+
"pnpm install",
|
|
43
|
+
"```",
|
|
44
|
+
"",
|
|
45
|
+
"## Develop",
|
|
46
|
+
"",
|
|
47
|
+
"```bash",
|
|
48
|
+
"pnpm dev # Flux HMR (first API)",
|
|
49
|
+
"pnpm start # run the compiled first API",
|
|
50
|
+
"pnpm run build # forge release build",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
for (const name of apis) {
|
|
54
|
+
lines.push(`pnpm run dev:${name} # HMR for ${name}`);
|
|
55
|
+
lines.push(`pnpm run start:${name} # production entry for ${name}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lines.push("```", "");
|
|
59
|
+
|
|
60
|
+
lines.push(
|
|
61
|
+
"## Logging",
|
|
62
|
+
"",
|
|
63
|
+
"In development, Scribe uses pino-pretty and rolling files under `logs/` (gitignored).",
|
|
64
|
+
"When `NODE_ENV=production`, the process writes structured JSON to stdout and does not rotate files.",
|
|
65
|
+
"Ship and retain those streams outside the app (platform logs, a sidecar, or a log agent) so shipping can change without rebuilding.",
|
|
66
|
+
"",
|
|
67
|
+
"Override roll path and rotation without changing app code:",
|
|
68
|
+
"`SCRIBE_LOG_FILE`, `SCRIBE_LOG_SIZE`, `SCRIBE_LOG_FREQUENCY`, `SCRIBE_LOG_KEEP`.",
|
|
69
|
+
"",
|
|
70
|
+
'Opt into in-process pretty or roll in production only with `{ when: "always" }` on `scribe.withPinoPretty` / `scribe.withPinoRoll`.',
|
|
71
|
+
"",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (hasPostgres) {
|
|
75
|
+
lines.push(
|
|
76
|
+
"## Database",
|
|
77
|
+
"",
|
|
78
|
+
"Postgres runs via Dev Container Compose:",
|
|
79
|
+
"",
|
|
80
|
+
"```bash",
|
|
81
|
+
"pnpm db:up",
|
|
82
|
+
"pnpm db:down",
|
|
83
|
+
"pnpm db:reset",
|
|
84
|
+
"```",
|
|
85
|
+
"",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lines.push(
|
|
90
|
+
"## Deploy",
|
|
91
|
+
"",
|
|
92
|
+
"PaaS hosts (`docker build` from the repo root) use the scaffolded `Dockerfile`.",
|
|
93
|
+
"The runtime image installs production dependencies only (`pnpm install --prod`);",
|
|
94
|
+
"Forge, TypeScript, Biome, and Vitest stay in the build stage.",
|
|
95
|
+
"",
|
|
96
|
+
"```bash",
|
|
97
|
+
`docker build -t ${projectName} .`,
|
|
98
|
+
"```",
|
|
99
|
+
"",
|
|
100
|
+
"Extra APIs: `docker build -f Dockerfile.<name> .`",
|
|
101
|
+
"",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
lines.push(
|
|
105
|
+
"## Add another API",
|
|
106
|
+
"",
|
|
107
|
+
"```bash",
|
|
108
|
+
"decohere add api <name>",
|
|
109
|
+
"```",
|
|
110
|
+
"",
|
|
111
|
+
"Example: `decohere add api billing`",
|
|
112
|
+
"",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (hasAuth0) {
|
|
116
|
+
lines.push(
|
|
117
|
+
"## Auth0",
|
|
118
|
+
"",
|
|
119
|
+
"JWT auth is wired from `.env` (`AUTH0_ISSUER_BASE_URL`, `AUTH0_AUDIENCE`).",
|
|
120
|
+
"Set those values (or re-run scaffold with `--auth auth0`) before calling protected routes.",
|
|
121
|
+
"",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (hasPostman) {
|
|
126
|
+
lines.push(
|
|
127
|
+
"## Postman",
|
|
128
|
+
"",
|
|
129
|
+
"Open this **project root** in Postman Local View (Files → Open folder), not the `postman/` subfolder.",
|
|
130
|
+
"Select the Local environment, then Authorization → Get New Access Token when Auth0 is enabled.",
|
|
131
|
+
"",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
lines.push(
|
|
136
|
+
"## More",
|
|
137
|
+
"",
|
|
138
|
+
"- Package docs: https://www.npmjs.com/package/@decohere/create",
|
|
139
|
+
"- After `decohere add api`, this README is refreshed so scripts and APIs stay accurate.",
|
|
140
|
+
"",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return `${lines.join("\n")}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function resolveWorkspaceReadmeInput(
|
|
147
|
+
workspace: WorkspaceScaffold,
|
|
148
|
+
options: WorkspaceReadmeOptions,
|
|
149
|
+
): WorkspaceReadmeInput {
|
|
150
|
+
const newNames = workspace
|
|
151
|
+
.getProjects()
|
|
152
|
+
.map((project) => apiNameFromProjectDir(project.dir));
|
|
153
|
+
const apiNames = options.add
|
|
154
|
+
? mergeApiNames(listExistingApiNames(workspace.targetDir), newNames)
|
|
155
|
+
: newNames;
|
|
156
|
+
|
|
157
|
+
const pkg = workspace.getModel<PackageJson>(WELL_KNOWN_FILES.PACKAGE_JSON);
|
|
158
|
+
const env = workspace.getModel<EnvFile>(WELL_KNOWN_FILES.ENV_FILE);
|
|
159
|
+
|
|
160
|
+
const hasPostgres =
|
|
161
|
+
options.databaseProvider === "postgres/typeorm" || Boolean(pkg?.hasScript("db:up"));
|
|
162
|
+
const hasAuth0 =
|
|
163
|
+
options.authProvider === "auth0" || Boolean(env?.hasEntry("AUTH0_ISSUER_BASE_URL"));
|
|
164
|
+
const hasPostman =
|
|
165
|
+
options.postmanProvider === "local" ||
|
|
166
|
+
options.postmanProvider === "api" ||
|
|
167
|
+
existsSync(join(workspace.targetDir, "postman")) ||
|
|
168
|
+
existsSync(join(workspace.targetDir, ".postman"));
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
projectName: workspace.name,
|
|
172
|
+
apiNames,
|
|
173
|
+
hasPostgres,
|
|
174
|
+
hasAuth0,
|
|
175
|
+
hasPostman,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function writeWorkspaceReadme(
|
|
180
|
+
workspace: WorkspaceScaffold,
|
|
181
|
+
options: WorkspaceReadmeOptions,
|
|
182
|
+
): void {
|
|
183
|
+
const content = renderWorkspaceReadme(resolveWorkspaceReadmeInput(workspace, options));
|
|
184
|
+
workspace.addFile({
|
|
185
|
+
files: () => [{ filePath: "README.md", content }],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mergeApiNames(existing: readonly string[], added: readonly string[]): string[] {
|
|
190
|
+
const seen = new Set(existing);
|
|
191
|
+
return [...existing, ...added.filter((name) => !seen.has(name))];
|
|
192
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { injectable } from "@decohere/crucible/decorators";
|
|
2
|
+
import { type Action, ActionResponse } from "@decohere/nexus/cli";
|
|
3
|
+
import { runScaffoldApi, toApiOptions } from "./webapi/cli-options.js";
|
|
4
|
+
|
|
5
|
+
export type WebapiOptions = {
|
|
6
|
+
directory?: string;
|
|
7
|
+
d?: string;
|
|
8
|
+
name?: string | string[];
|
|
9
|
+
database?: string;
|
|
10
|
+
auth?: string;
|
|
11
|
+
postman?: string;
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
install?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
@injectable()
|
|
17
|
+
export class WebapiAction implements Action<WebapiOptions> {
|
|
18
|
+
public async run(options: WebapiOptions): Promise<ActionResponse> {
|
|
19
|
+
await runScaffoldApi(toApiOptions(options.directory, options));
|
|
20
|
+
return ActionResponse.Success("webapi");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DecohereCommand } from "@decohere/nexus/cli";
|
|
2
|
+
import type { OptionValues } from "commander";
|
|
3
|
+
import { addWebapiFeatureOptions, collectName } from "./webapi/cli-options.js";
|
|
4
|
+
import { WebapiAction } from "./webapi.action.js";
|
|
5
|
+
|
|
6
|
+
export class WebapiCommand extends DecohereCommand {
|
|
7
|
+
public override get actions() {
|
|
8
|
+
return [WebapiAction];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public constructor(name: string) {
|
|
12
|
+
super(name);
|
|
13
|
+
this.description("Scaffold a Cortex web API workspace");
|
|
14
|
+
this.argument("[directory]", "Target directory (default: -d or current directory)");
|
|
15
|
+
this.option("-d <directory>", "Target directory (default: current directory)", ".");
|
|
16
|
+
this.option(
|
|
17
|
+
"--name <slug>",
|
|
18
|
+
"API name under projects/ (repeatable; default: api)",
|
|
19
|
+
collectName,
|
|
20
|
+
[] as string[],
|
|
21
|
+
);
|
|
22
|
+
addWebapiFeatureOptions(this);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Commander passes positional args before options when `[directory]` is declared.
|
|
27
|
+
* ModuleFactory expects `(opts, cmd)` — stash the directory on the command.
|
|
28
|
+
*/
|
|
29
|
+
public override action(fn: (...args: unknown[]) => void | Promise<void>): this {
|
|
30
|
+
return super.action((...args: unknown[]) => {
|
|
31
|
+
const cmd = args.at(-1) as DecohereCommand;
|
|
32
|
+
const first = args[0];
|
|
33
|
+
const hasPositional = typeof first === "string" || first === undefined;
|
|
34
|
+
const opts = hasPositional ? (args[1] as OptionValues) : (args[0] as OptionValues);
|
|
35
|
+
if (typeof first === "string" && first !== "") {
|
|
36
|
+
cmd.setOptionValue("directory", first);
|
|
37
|
+
}
|
|
38
|
+
return fn(opts, cmd);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|