@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,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DockerCompose } from "../src/models/compose.model.js";
|
|
3
|
+
|
|
4
|
+
describe("DockerCompose SSRF/exposure hardening helpers", () => {
|
|
5
|
+
it("renders loopback-bound ports and optional profiles", () => {
|
|
6
|
+
const compose = new DockerCompose()
|
|
7
|
+
.setProjectName("webapi")
|
|
8
|
+
.addService("postgres", {
|
|
9
|
+
image: "postgres:16",
|
|
10
|
+
ports: ["127.0.0.1:5433:5432"],
|
|
11
|
+
})
|
|
12
|
+
.addService("adminer", {
|
|
13
|
+
image: "adminer",
|
|
14
|
+
profiles: ["tools"],
|
|
15
|
+
ports: ["127.0.0.1:8081:8080"],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const yaml = compose.getContent();
|
|
19
|
+
expect(yaml).toContain('ports:\n - "127.0.0.1:5433:5432"');
|
|
20
|
+
expect(yaml).toContain("profiles:\n - tools");
|
|
21
|
+
expect(yaml).toContain('ports:\n - "127.0.0.1:8081:8080"');
|
|
22
|
+
expect(yaml).not.toMatch(/ports:\n\s+- "5433:5432"/);
|
|
23
|
+
expect(yaml).not.toMatch(/ports:\n\s+- "8081:8080"/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("renders header comments and bind mounts without a named volumes section", () => {
|
|
27
|
+
const yaml = new DockerCompose()
|
|
28
|
+
.addHeaderComment("Bind mount dies with the working tree.")
|
|
29
|
+
.addService("postgres", {
|
|
30
|
+
image: "postgres:16",
|
|
31
|
+
volumes: ["./postgres-data:/var/lib/postgresql/data"],
|
|
32
|
+
})
|
|
33
|
+
.getContent();
|
|
34
|
+
|
|
35
|
+
expect(yaml.startsWith("# Bind mount dies with the working tree.")).toBe(true);
|
|
36
|
+
expect(yaml).toContain("./postgres-data:/var/lib/postgresql/data");
|
|
37
|
+
expect(yaml).not.toMatch(/^volumes:\s*$/m);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("quotes compose user so 0:0 is not parsed as a number", () => {
|
|
41
|
+
const yaml = new DockerCompose()
|
|
42
|
+
.addService("devcontainer", {
|
|
43
|
+
build: ".",
|
|
44
|
+
user: "0:0",
|
|
45
|
+
command: "sleep infinity",
|
|
46
|
+
})
|
|
47
|
+
.getContent();
|
|
48
|
+
|
|
49
|
+
expect(yaml).toContain('user: "0:0"');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DevContainerImageModel,
|
|
4
|
+
DockerfileModel,
|
|
5
|
+
DockerStep,
|
|
6
|
+
withGithubCli,
|
|
7
|
+
withPnpm,
|
|
8
|
+
withPostgresqlClient,
|
|
9
|
+
} from "../src/models/docker-image.model.js";
|
|
10
|
+
import { WebapiProductionDockerfile } from "../src/webapi/models/production-dockerfile.model.js";
|
|
11
|
+
import { WebapiDevContainer } from "../src/webapi/models/webapi-image.model.js";
|
|
12
|
+
|
|
13
|
+
describe("DockerfileModel", () => {
|
|
14
|
+
it("supports from() and entrypoint() with entrypoint rendered at the end", () => {
|
|
15
|
+
const dockerfile = new DockerfileModel("node:24").from("debian:bookworm");
|
|
16
|
+
dockerfile.entrypoint(["node", "/app/index.js"]);
|
|
17
|
+
|
|
18
|
+
const content = dockerfile.build();
|
|
19
|
+
expect(content).toContain("FROM debian:bookworm");
|
|
20
|
+
expect(content.trimEnd().endsWith('ENTRYPOINT ["node","/app/index.js"]')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("generic image model writes Dockerfile at project root", () => {
|
|
24
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
25
|
+
const files = dockerfile.files();
|
|
26
|
+
|
|
27
|
+
expect(files).toHaveLength(1);
|
|
28
|
+
expect(files[0]?.filePath).toBe("./Dockerfile");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("base model is generic and has no devcontainer defaults", () => {
|
|
32
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
33
|
+
const content = dockerfile.build();
|
|
34
|
+
|
|
35
|
+
expect(content).toContain("FROM node:24");
|
|
36
|
+
expect(content).not.toContain("ENV HISTFILE=/commandhistory/.bash_history");
|
|
37
|
+
expect(content).not.toContain("pnpm@10");
|
|
38
|
+
expect(content).not.toContain("bootloader.js");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("devcontainer subtype adds command history and vscode bootloader fix", () => {
|
|
42
|
+
const dockerfile = new DevContainerImageModel("node:24");
|
|
43
|
+
const content = dockerfile.build();
|
|
44
|
+
const files = dockerfile.files();
|
|
45
|
+
|
|
46
|
+
expect(content).toContain("ENV HISTFILE=/commandhistory/.bash_history");
|
|
47
|
+
expect(content).toContain("chown -R node /commandhistory");
|
|
48
|
+
expect(content).toContain("PROMPT_COMMAND='history -a'");
|
|
49
|
+
expect(content).toContain("bootloader.js");
|
|
50
|
+
expect(files).toHaveLength(1);
|
|
51
|
+
expect(files[0]?.filePath).toBe(".devcontainer/Dockerfile");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("applies string layers as RUN steps", () => {
|
|
55
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
56
|
+
dockerfile.addLayer("echo hello-world");
|
|
57
|
+
|
|
58
|
+
const content = dockerfile.build();
|
|
59
|
+
expect(content).toContain("RUN echo hello-world");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("executes layer writers at build and sorts keyed steps by order", () => {
|
|
63
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
64
|
+
dockerfile.addLayer((df) => {
|
|
65
|
+
df.addStep(DockerStep.run("ordered-b", "echo second", 2_001));
|
|
66
|
+
df.addStep(DockerStep.run("ordered-a", "echo first", 2_000));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const content = dockerfile.build();
|
|
70
|
+
expect(content.indexOf("RUN echo first")).toBeLessThan(content.indexOf("RUN echo second"));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("uses keyed upsert semantics when multiple layers write the same step key", () => {
|
|
74
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
75
|
+
dockerfile.addLayer((df) => {
|
|
76
|
+
df.addStep(DockerStep.run("replace-me", "echo old", 2_000));
|
|
77
|
+
});
|
|
78
|
+
dockerfile.addLayer((df) => {
|
|
79
|
+
df.addStep(DockerStep.run("replace-me", "echo new", 2_000));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const content = dockerfile.build();
|
|
83
|
+
expect(content).not.toContain("RUN echo old");
|
|
84
|
+
expect(content).toContain("RUN echo new");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("supports reusable layer writers", () => {
|
|
88
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
89
|
+
dockerfile.addLayer(withPnpm({ version: "9" }));
|
|
90
|
+
|
|
91
|
+
const content = dockerfile.build();
|
|
92
|
+
expect(content).toContain("pnpm@9");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("withPostgresqlClient escalates to root so it works on a published base image", () => {
|
|
96
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
97
|
+
dockerfile.addLayer(withPostgresqlClient());
|
|
98
|
+
|
|
99
|
+
const content = dockerfile.build();
|
|
100
|
+
expect(content).toContain("USER root");
|
|
101
|
+
expect(content).toContain("postgresql-client");
|
|
102
|
+
expect(content).toContain("apt-get install");
|
|
103
|
+
expect(content).toContain("USER node");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("withGithubCli installs gh before USER", () => {
|
|
107
|
+
const dockerfile = new DockerfileModel("node:24");
|
|
108
|
+
dockerfile.addLayer(withGithubCli());
|
|
109
|
+
|
|
110
|
+
const content = dockerfile.build();
|
|
111
|
+
expect(content).toContain("apt-get install -y --no-install-recommends gh");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("webapi production dockerfile uses conventional pnpm build/start", () => {
|
|
115
|
+
const dockerfile = new WebapiProductionDockerfile();
|
|
116
|
+
const files = dockerfile.files();
|
|
117
|
+
const docker = files.find((f) => f.filePath === "Dockerfile");
|
|
118
|
+
expect(docker?.content).toContain("pnpm run build");
|
|
119
|
+
expect(docker?.content).toContain('CMD ["pnpm", "start"]');
|
|
120
|
+
expect(docker?.content).toContain("COPY projects/api");
|
|
121
|
+
expect(files.some((f) => f.filePath === ".dockerignore")).toBe(true);
|
|
122
|
+
expect(files.some((f) => f.filePath === "Dockerfile.api")).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("webapi production dockerfile copies production-only node_modules into the run image", () => {
|
|
126
|
+
const docker = new WebapiProductionDockerfile()
|
|
127
|
+
.files()
|
|
128
|
+
.find((f) => f.filePath === "Dockerfile");
|
|
129
|
+
expect(docker?.content).toContain("AS prod-deps");
|
|
130
|
+
expect(docker?.content).toContain("pnpm install --frozen-lockfile --prod");
|
|
131
|
+
expect(docker?.content).toContain(
|
|
132
|
+
"COPY --from=prod-deps /app/node_modules ./node_modules",
|
|
133
|
+
);
|
|
134
|
+
expect(docker?.content).not.toContain("COPY --from=build /app/node_modules");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("emits Dockerfile.<name> for extra or custom-named APIs", () => {
|
|
138
|
+
const files = new WebapiProductionDockerfile(["api", "billing"]).files();
|
|
139
|
+
expect(files.some((f) => f.filePath === "Dockerfile.billing")).toBe(true);
|
|
140
|
+
const billing = files.find((f) => f.filePath === "Dockerfile.billing");
|
|
141
|
+
expect(billing?.content).toContain("COPY projects/billing");
|
|
142
|
+
expect(billing?.content).toContain('CMD ["pnpm", "run", "start:billing"]');
|
|
143
|
+
expect(billing?.content).toContain(
|
|
144
|
+
"COPY --from=prod-deps /app/node_modules ./node_modules",
|
|
145
|
+
);
|
|
146
|
+
expect(billing?.content).not.toContain("COPY --from=build /app/node_modules");
|
|
147
|
+
const root = files.find((f) => f.filePath === "Dockerfile");
|
|
148
|
+
expect(root?.content).toContain("COPY projects/api");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("webapi scaffold Dockerfile is a thin FROM the published base image", () => {
|
|
152
|
+
const dockerfile = new WebapiDevContainer();
|
|
153
|
+
const content = dockerfile.build();
|
|
154
|
+
const files = dockerfile.files();
|
|
155
|
+
|
|
156
|
+
expect(content).toMatch(/^FROM ghcr\.io\/typesmiths\/decohere\/devcontainer:/);
|
|
157
|
+
expect(content).not.toContain("pnpm@10");
|
|
158
|
+
expect(content).not.toContain("bootloader.js");
|
|
159
|
+
expect(files).toHaveLength(1);
|
|
160
|
+
expect(files[0]?.filePath).toBe(".devcontainer/Dockerfile");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DevContainer } from "../src/models/dev-container.model.js";
|
|
3
|
+
|
|
4
|
+
describe("DevContainer", () => {
|
|
5
|
+
it("renders postCreateCommand and updateContentCommand when set", () => {
|
|
6
|
+
const files = new DevContainer()
|
|
7
|
+
.setPostCreateCommand("bash .devcontainer/post-create.sh")
|
|
8
|
+
.setUpdateContentCommand("bash .devcontainer/post-create.sh")
|
|
9
|
+
.setPostStartCommand("bash .devcontainer/post-start.sh")
|
|
10
|
+
.useCompose()
|
|
11
|
+
.files();
|
|
12
|
+
|
|
13
|
+
const json = JSON.parse(files[0]!.content) as Record<string, unknown>;
|
|
14
|
+
expect(json.postCreateCommand).toBe("bash .devcontainer/post-create.sh");
|
|
15
|
+
expect(json.updateContentCommand).toBe("bash .devcontainer/post-create.sh");
|
|
16
|
+
expect(json.postStartCommand).toBe("bash .devcontainer/post-start.sh");
|
|
17
|
+
expect(json).not.toHaveProperty("initializeCommand");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("omits install lifecycle keys when unset", () => {
|
|
21
|
+
const files = new DevContainer()
|
|
22
|
+
.setPostStartCommand("bash .devcontainer/post-start.sh")
|
|
23
|
+
.files();
|
|
24
|
+
const json = JSON.parse(files[0]!.content) as Record<string, unknown>;
|
|
25
|
+
expect(json).not.toHaveProperty("postCreateCommand");
|
|
26
|
+
expect(json).not.toHaveProperty("updateContentCommand");
|
|
27
|
+
expect(json.postStartCommand).toBe("bash .devcontainer/post-start.sh");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { AppDevContainerBaseImage } from "../src/models/docker-image.model.js";
|
|
5
|
+
|
|
6
|
+
const realFs = await vi.importActual<typeof import("node:fs")>("node:fs");
|
|
7
|
+
|
|
8
|
+
const DOCKERFILE = join(
|
|
9
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
10
|
+
"../../devcontainer/Dockerfile",
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
describe("published app Dev Container base image", () => {
|
|
14
|
+
it("keeps projects/devcontainer/Dockerfile in sync with AppDevContainerBaseImage", () => {
|
|
15
|
+
const expected = new AppDevContainerBaseImage("node:24").build();
|
|
16
|
+
const onDisk = realFs.readFileSync(DOCKERFILE, "utf8");
|
|
17
|
+
const fromIdx = onDisk.indexOf("\nFROM ");
|
|
18
|
+
const body = fromIdx >= 0 ? onDisk.slice(fromIdx + 1) : onDisk;
|
|
19
|
+
expect(body).toBe(expected);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("includes writable bash history, pnpm, gh, and node user", () => {
|
|
23
|
+
const content = new AppDevContainerBaseImage("node:24").build();
|
|
24
|
+
expect(content).toContain("ENV HISTFILE=/commandhistory/.bash_history");
|
|
25
|
+
expect(content).toContain("chown -R node /commandhistory");
|
|
26
|
+
expect(content).toContain("PROMPT_COMMAND='history -a'");
|
|
27
|
+
expect(content).toContain("pnpm@10");
|
|
28
|
+
expect(content).toContain("apt-get install -y --no-install-recommends gh");
|
|
29
|
+
expect(content).toContain("USER ${USERNAME}");
|
|
30
|
+
expect(content).toContain("bootloader.js");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { ControllerSourceFile } from "../src/webapi/models/controller-source-file.model.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regression CU-869eja46u: SWC can emit design:paramtypes as Object when the
|
|
6
|
+
* service class is still undefined at decorate time. Crucible then resolves
|
|
7
|
+
* "Object" → null → getGreeting TypeError. Explicit @inject(token) is required.
|
|
8
|
+
*/
|
|
9
|
+
describe("HelloController DI inject tokens", () => {
|
|
10
|
+
it("annotates SampleService with @inject so anonymous hello cannot get a null sample", () => {
|
|
11
|
+
const content = new ControllerSourceFile().files()[0]?.content ?? "";
|
|
12
|
+
expect(content).toContain('import { inject } from "@decohere/crucible/decorators"');
|
|
13
|
+
expect(content).toContain(
|
|
14
|
+
'@inject("SampleService") private readonly sample: SampleService',
|
|
15
|
+
);
|
|
16
|
+
expect(content).toContain("await this.sample.getGreeting()");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { RouteMetadata } from "../../cortex/src/routing/anvil.types.js";
|
|
3
|
+
import {
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
Get,
|
|
7
|
+
Param,
|
|
8
|
+
Post,
|
|
9
|
+
Put,
|
|
10
|
+
Query,
|
|
11
|
+
} from "../../cortex/src/routing/index.js";
|
|
12
|
+
import { RouteRegistry } from "../../cortex/src/routing/services/route-registry.js";
|
|
13
|
+
import { POSTMAN_CALLBACKS } from "../src/webapi/features/postman.feature.js";
|
|
14
|
+
import { ControllerSourceFile } from "../src/webapi/models/controller-source-file.model.js";
|
|
15
|
+
import { MainSourceFile } from "../src/webapi/models/main-source-file.model.js";
|
|
16
|
+
import { buildHelloOpenApiDocument } from "../src/webapi/postman/hello-openapi.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Contract: scaffolded Hello routes must match Postman / OpenAPI paths.
|
|
20
|
+
* Regression: `@Post("/")` registered POST /hello/ while Postman called POST /hello.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
describe("Hello controller ↔ Postman route contract", () => {
|
|
24
|
+
it("generates @Post() without a trailing-slash path", () => {
|
|
25
|
+
const content = new ControllerSourceFile().files()[0]?.content ?? "";
|
|
26
|
+
expect(content).toMatch(/@Post\(\)/);
|
|
27
|
+
expect(content).not.toMatch(/@Post\("\/"\)/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("enables withOpenApi by default so Hello @Field bodies are validated", () => {
|
|
31
|
+
const content = new MainSourceFile("demo.api").files()[0]?.content ?? "";
|
|
32
|
+
expect(content).toContain("withOpenApi");
|
|
33
|
+
expect(content).toContain('title: "demo.api"');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("registers the same paths OpenAPI / Postman Local View call", () => {
|
|
37
|
+
@Controller({ prefix: "/hello" })
|
|
38
|
+
class HelloController {
|
|
39
|
+
@Get()
|
|
40
|
+
public getAll(@Query("filter") _filter: string) {}
|
|
41
|
+
|
|
42
|
+
@Get("/:id")
|
|
43
|
+
public getOne(@Param("id") _id: string) {}
|
|
44
|
+
|
|
45
|
+
@Post()
|
|
46
|
+
public create() {}
|
|
47
|
+
|
|
48
|
+
@Put("/:id")
|
|
49
|
+
public update(@Param("id") _id: string) {}
|
|
50
|
+
|
|
51
|
+
@Delete("/:id")
|
|
52
|
+
public remove(@Param("id") _id: string) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const registry = new RouteRegistry();
|
|
56
|
+
registry.addController(HelloController);
|
|
57
|
+
|
|
58
|
+
const registered: Record<string, RouteMetadata | undefined> = {
|
|
59
|
+
getAll: registry.getRoute("GET|/hello"),
|
|
60
|
+
getOne: registry.getRoute("GET|/hello/:id"),
|
|
61
|
+
create: registry.getRoute("POST|/hello"),
|
|
62
|
+
update: registry.getRoute("PUT|/hello/:id"),
|
|
63
|
+
remove: registry.getRoute("DELETE|/hello/:id"),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
expect(registered.getAll).toMatchObject({ method: "GET", path: "/hello" });
|
|
67
|
+
expect(registered.getOne).toMatchObject({ method: "GET", path: "/hello/:id" });
|
|
68
|
+
expect(registered.create).toMatchObject({ method: "POST", path: "/hello" });
|
|
69
|
+
expect(registered.update).toMatchObject({ method: "PUT", path: "/hello/:id" });
|
|
70
|
+
expect(registered.remove).toMatchObject({ method: "DELETE", path: "/hello/:id" });
|
|
71
|
+
|
|
72
|
+
const openapi = buildHelloOpenApiDocument("Contract");
|
|
73
|
+
expect(Object.keys(openapi.paths)).toEqual(
|
|
74
|
+
expect.arrayContaining(["/hello", "/hello/{id}"]),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const postman = {
|
|
78
|
+
list: { method: "GET", url: "{{baseUrl}}/hello" },
|
|
79
|
+
getOne: { method: "GET", url: "{{baseUrl}}/hello/:id" },
|
|
80
|
+
create: { method: "POST", url: "{{baseUrl}}/hello" },
|
|
81
|
+
update: { method: "PUT", url: "{{baseUrl}}/hello/:id" },
|
|
82
|
+
remove: { method: "DELETE", url: "{{baseUrl}}/hello/:id" },
|
|
83
|
+
};
|
|
84
|
+
expect(postman.create.url.replace("{{baseUrl}}", "")).toBe(registered.create?.path);
|
|
85
|
+
expect(postman.list.url.replace("{{baseUrl}}", "")).toBe(registered.getAll?.path);
|
|
86
|
+
expect(postman.getOne.url.replace("{{baseUrl}}", "")).toBe(registered.getOne?.path);
|
|
87
|
+
expect(postman.update.url.replace("{{baseUrl}}", "")).toBe(registered.update?.path);
|
|
88
|
+
expect(postman.remove.url.replace("{{baseUrl}}", "")).toBe(registered.remove?.path);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("keeps Postman OAuth callbacks stable (sanity for auth contract)", () => {
|
|
92
|
+
expect(POSTMAN_CALLBACKS).toContain("https://oauth.pstmn.io/v1/callback");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PackageJson, sortStringMap } from "../src/models/package-json.model.js";
|
|
3
|
+
import { ApiPackageJson } from "../src/webapi/models/api-package-json.model.js";
|
|
4
|
+
|
|
5
|
+
describe("sortStringMap CU-869ej9z6p", () => {
|
|
6
|
+
it("orders keys A→Z", () => {
|
|
7
|
+
expect(Object.keys(sortStringMap({ zebra: "1", alpha: "2", "@scope/pkg": "3" }))).toEqual([
|
|
8
|
+
"@scope/pkg",
|
|
9
|
+
"alpha",
|
|
10
|
+
"zebra",
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("PackageJson dependency order CU-869ej9z6p", () => {
|
|
16
|
+
it("renders dependencies and devDependencies sorted regardless of add order", () => {
|
|
17
|
+
const pkg = new PackageJson("demo.api", { apiNames: ["api"], workspaceName: "demo" });
|
|
18
|
+
pkg.addDependency("typeorm", "^0.3.28");
|
|
19
|
+
pkg.addDependency("@casl/ability", "^6.8.1");
|
|
20
|
+
pkg.addDependency("@decohere/codex", "0.0.1");
|
|
21
|
+
pkg.addDevDependency("vitest", "^3.2.4");
|
|
22
|
+
pkg.addDevDependency("@biomejs/biome", "^2.0.0");
|
|
23
|
+
|
|
24
|
+
const rendered = JSON.parse(pkg.files()[0]!.content) as {
|
|
25
|
+
dependencies: Record<string, string>;
|
|
26
|
+
devDependencies: Record<string, string>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
expect(Object.keys(rendered.dependencies)).toEqual(
|
|
30
|
+
[...Object.keys(rendered.dependencies)].sort((a, b) => a.localeCompare(b, "en")),
|
|
31
|
+
);
|
|
32
|
+
expect(Object.keys(rendered.devDependencies)).toEqual(
|
|
33
|
+
[...Object.keys(rendered.devDependencies)].sort((a, b) => a.localeCompare(b, "en")),
|
|
34
|
+
);
|
|
35
|
+
expect(Object.keys(rendered.dependencies).indexOf("@casl/ability")).toBeLessThan(
|
|
36
|
+
Object.keys(rendered.dependencies).indexOf("typeorm"),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("ApiPackageJson dependency order CU-869ej9z6p", () => {
|
|
42
|
+
it("renders dependencies sorted regardless of add order", () => {
|
|
43
|
+
const pkg = new ApiPackageJson("demo", "api");
|
|
44
|
+
pkg.addDependency("@decohere/codex-typeorm", "0.0.1");
|
|
45
|
+
pkg.addDependency("@casl/ability", "^6.8.1");
|
|
46
|
+
pkg.addDependency("@decohere/codex", "0.0.1");
|
|
47
|
+
|
|
48
|
+
const rendered = JSON.parse(pkg.files()[0]!.content) as {
|
|
49
|
+
dependencies: Record<string, string>;
|
|
50
|
+
};
|
|
51
|
+
expect(Object.keys(rendered.dependencies)).toEqual([
|
|
52
|
+
"@casl/ability",
|
|
53
|
+
"@decohere/codex",
|
|
54
|
+
"@decohere/codex-typeorm",
|
|
55
|
+
"@decohere/cortex",
|
|
56
|
+
"@decohere/crucible",
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildHybridPostmanCollection,
|
|
4
|
+
POSTMAN_CALLBACKS,
|
|
5
|
+
POSTMAN_SCOPES,
|
|
6
|
+
} from "../src/webapi/features/postman.feature.js";
|
|
7
|
+
import { buildHelloOpenApiDocument } from "../src/webapi/postman/hello-openapi.js";
|
|
8
|
+
import {
|
|
9
|
+
buildEnvironmentYaml,
|
|
10
|
+
buildLocalViewFilesFromCollection,
|
|
11
|
+
} from "../src/webapi/postman/local-view.js";
|
|
12
|
+
import {
|
|
13
|
+
applyPostmanAuthOverlay,
|
|
14
|
+
openApiPathParamsToColon,
|
|
15
|
+
openApiToPostmanCollection,
|
|
16
|
+
type PostmanCollection,
|
|
17
|
+
} from "../src/webapi/postman/openapi-to-postman.js";
|
|
18
|
+
|
|
19
|
+
const auth0 = {
|
|
20
|
+
auth0Domain: "example.auth0.com",
|
|
21
|
+
auth0Audience: "https://api.example.com",
|
|
22
|
+
auth0ClientId: "abc",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("Postman from OpenAPI (hybrid)", () => {
|
|
26
|
+
it("converts Hello OpenAPI into a Postman collection with :id paths", async () => {
|
|
27
|
+
const openapi = buildHelloOpenApiDocument("Demo");
|
|
28
|
+
const base = await openApiToPostmanCollection(openapi);
|
|
29
|
+
const collection = applyPostmanAuthOverlay(base, "Demo", undefined, {
|
|
30
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
31
|
+
scopes: POSTMAN_SCOPES,
|
|
32
|
+
description: "test",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(collection.info.name).toBe("Demo WebAPI");
|
|
36
|
+
const helloFolder = collection.item?.find((i) => i.name === "Hello");
|
|
37
|
+
expect(helloFolder?.item?.length).toBeGreaterThanOrEqual(5);
|
|
38
|
+
|
|
39
|
+
const urls = (helloFolder?.item ?? [])
|
|
40
|
+
.map((i) => {
|
|
41
|
+
const url = i.request?.url;
|
|
42
|
+
return typeof url === "string" ? url : url?.raw;
|
|
43
|
+
})
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
|
|
46
|
+
expect(urls).toEqual(
|
|
47
|
+
expect.arrayContaining(["{{baseUrl}}/hello", "{{baseUrl}}/hello/:id"]),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const list = helloFolder?.item?.find((i) => i.name === "List (anonymous)");
|
|
51
|
+
expect(list?.request?.auth).toEqual({ type: "noauth" });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("applies Auth0 overlay without changing route inventory", async () => {
|
|
55
|
+
const collection = await buildHybridPostmanCollection("Demo", auth0);
|
|
56
|
+
expect(collection.auth).toMatchObject({ type: "oauth2" });
|
|
57
|
+
const hello = collection.item?.find((i) => i.name === "Hello");
|
|
58
|
+
expect(hello?.item?.some((i) => i.name === "Create")).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("emits Local View YAML requests from the hybrid collection", async () => {
|
|
62
|
+
const collection = await buildHybridPostmanCollection("Demo");
|
|
63
|
+
const files = buildLocalViewFilesFromCollection(collection, "Demo", undefined, {
|
|
64
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
65
|
+
scopes: POSTMAN_SCOPES,
|
|
66
|
+
});
|
|
67
|
+
const paths = files.map((f) => f.filePath);
|
|
68
|
+
expect(paths).toContain("postman/collections/Demo WebAPI/.resources/definition.yaml");
|
|
69
|
+
expect(paths.some((p) => p.includes("List (anonymous).request.yaml"))).toBe(true);
|
|
70
|
+
expect(paths.some((p) => p.includes("Create.request.yaml"))).toBe(true);
|
|
71
|
+
|
|
72
|
+
const create = files.find((f) => f.filePath.endsWith("Create.request.yaml"));
|
|
73
|
+
expect(create?.content).toContain('url: "{{baseUrl}}/hello"');
|
|
74
|
+
expect(create?.content).toContain("method: POST");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("builds environment YAML with and without Auth0", () => {
|
|
78
|
+
const bare = buildEnvironmentYaml("Demo Local", undefined, {
|
|
79
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
80
|
+
scopes: POSTMAN_SCOPES,
|
|
81
|
+
});
|
|
82
|
+
expect(bare).toContain("name: Demo Local");
|
|
83
|
+
expect(bare).toContain("key: baseUrl");
|
|
84
|
+
expect(bare).not.toContain("auth0Domain");
|
|
85
|
+
|
|
86
|
+
const withAuth = buildEnvironmentYaml("Demo Local", auth0, {
|
|
87
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
88
|
+
scopes: POSTMAN_SCOPES,
|
|
89
|
+
});
|
|
90
|
+
expect(withAuth).toContain("key: auth0Domain");
|
|
91
|
+
expect(withAuth).toContain("value: example.auth0.com");
|
|
92
|
+
expect(withAuth).toContain("key: callbackUrl");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("emits Local View collection auth block and request edge cases", async () => {
|
|
96
|
+
const collection = await buildHybridPostmanCollection("Demo", auth0);
|
|
97
|
+
const files = buildLocalViewFilesFromCollection(collection, "Demo", auth0, {
|
|
98
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
99
|
+
scopes: POSTMAN_SCOPES,
|
|
100
|
+
});
|
|
101
|
+
const definition = files.find((f) =>
|
|
102
|
+
f.filePath.endsWith(".resources/definition.yaml"),
|
|
103
|
+
)?.content;
|
|
104
|
+
expect(definition).toContain("auth:");
|
|
105
|
+
expect(definition).toContain("type: oauth2");
|
|
106
|
+
|
|
107
|
+
const list = files.find((f) => f.filePath.endsWith("List (anonymous).request.yaml"));
|
|
108
|
+
expect(list?.content).toContain("auth:");
|
|
109
|
+
expect(list?.content).toContain("type: noauth");
|
|
110
|
+
|
|
111
|
+
const create = files.find((f) => f.filePath.endsWith("Create.request.yaml"));
|
|
112
|
+
expect(create?.content).toContain("body:");
|
|
113
|
+
expect(create?.content).toContain("type: json");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("rewrites OpenAPI path params without touching Postman variables", () => {
|
|
117
|
+
expect(openApiPathParamsToColon("{{baseUrl}}/hello/{id}")).toBe("{{baseUrl}}/hello/:id");
|
|
118
|
+
expect(openApiPathParamsToColon("/hello")).toBe("/hello");
|
|
119
|
+
expect(openApiPathParamsToColon("hello")).toBe("hello");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("uses default collection schema when converter omits info.schema", async () => {
|
|
123
|
+
const base = await openApiToPostmanCollection(buildHelloOpenApiDocument("Demo"));
|
|
124
|
+
const withoutSchema: PostmanCollection = {
|
|
125
|
+
...base,
|
|
126
|
+
info: { ...base.info, schema: undefined as unknown as string },
|
|
127
|
+
};
|
|
128
|
+
const collection = applyPostmanAuthOverlay(withoutSchema, "Demo", auth0, {
|
|
129
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
130
|
+
scopes: POSTMAN_SCOPES,
|
|
131
|
+
description: "test",
|
|
132
|
+
});
|
|
133
|
+
expect(collection.info.schema).toBe(
|
|
134
|
+
"https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("handles nested folders and non-request items in overlay rewrite", () => {
|
|
139
|
+
const collection: PostmanCollection = {
|
|
140
|
+
info: {
|
|
141
|
+
name: "stub",
|
|
142
|
+
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
|
143
|
+
},
|
|
144
|
+
item: [
|
|
145
|
+
{ name: "Docs" },
|
|
146
|
+
{
|
|
147
|
+
name: "Hello",
|
|
148
|
+
item: [
|
|
149
|
+
{
|
|
150
|
+
name: "Get one",
|
|
151
|
+
request: {
|
|
152
|
+
method: "GET",
|
|
153
|
+
url: { host: ["{{baseUrl}}"], path: ["hello", "{id}"] },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "Create",
|
|
158
|
+
request: {
|
|
159
|
+
method: "POST",
|
|
160
|
+
url: "hello",
|
|
161
|
+
header: [{ key: "Content-Type", value: "application/json" }],
|
|
162
|
+
body: { raw: '{"message":"x"}' },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
const next = applyPostmanAuthOverlay(collection, "Demo", undefined, {
|
|
170
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
171
|
+
scopes: POSTMAN_SCOPES,
|
|
172
|
+
description: "test",
|
|
173
|
+
});
|
|
174
|
+
const hello = next.item?.find((i) => i.name === "Hello");
|
|
175
|
+
const getOne = hello?.item?.find((i) => i.name === "Get one");
|
|
176
|
+
const create = hello?.item?.find((i) => i.name === "Create");
|
|
177
|
+
expect(getOne?.request?.url).toMatchObject({ raw: "{{baseUrl}}/hello/:id" });
|
|
178
|
+
expect(create?.request?.url).toMatchObject({ raw: "{{baseUrl}}/hello" });
|
|
179
|
+
expect(next.item?.find((i) => i.name === "Docs")).toEqual({ name: "Docs" });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("rejects invalid OpenAPI documents at conversion time", async () => {
|
|
183
|
+
await expect(
|
|
184
|
+
openApiToPostmanCollection({ foo: "bar" } as unknown as ReturnType<
|
|
185
|
+
typeof buildHelloOpenApiDocument
|
|
186
|
+
>),
|
|
187
|
+
).rejects.toThrow(/semantic version|conversion failed/i);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("quotes YAML values that need escaping", () => {
|
|
191
|
+
const yaml = buildEnvironmentYaml('Demo: Local "env"', auth0, {
|
|
192
|
+
callbacks: POSTMAN_CALLBACKS,
|
|
193
|
+
scopes: POSTMAN_SCOPES,
|
|
194
|
+
});
|
|
195
|
+
expect(yaml).toContain('"Demo: Local \\"env\\""');
|
|
196
|
+
});
|
|
197
|
+
});
|