@decohere/scaffold 0.0.1-dev.18 → 0.0.1-dev.80
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/commonjs/src/add-api.action.js +39 -0
- package/commonjs/src/add-api.command.js +41 -0
- package/commonjs/src/add.command.js +24 -0
- package/commonjs/src/add.module.js +26 -0
- package/commonjs/src/cli.js +9 -0
- package/commonjs/src/create.command.js +24 -0
- package/commonjs/src/create.module.js +26 -0
- package/commonjs/src/domain/prompt.js +64 -0
- package/commonjs/src/index.js +38 -0
- package/commonjs/src/models/compose.model.js +150 -0
- package/commonjs/src/models/config-yaml.model.js +88 -0
- package/commonjs/src/models/const.js +32 -0
- package/commonjs/src/models/dev-container.model.js +116 -0
- package/commonjs/src/models/docker-image.model.js +259 -0
- package/commonjs/src/models/env-file.model.js +50 -0
- package/commonjs/src/models/gitignore.model.js +43 -0
- package/commonjs/src/models/launch-json.model.js +74 -0
- package/commonjs/src/models/package-json.model.js +149 -0
- package/commonjs/src/models/project.model.js +134 -0
- package/commonjs/src/models/source-file.js +6 -0
- package/commonjs/src/models/tsconfig.model.js +34 -0
- package/commonjs/src/models/vitest-config.model.js +35 -0
- package/commonjs/src/models/vscode-settings.model.js +31 -0
- package/commonjs/src/program.js +57 -0
- package/commonjs/src/resolve-target-dir.js +19 -0
- package/commonjs/src/services/scaffold.service.js +62 -0
- package/commonjs/src/version.js +57 -0
- package/commonjs/src/webapi/api-name.js +95 -0
- package/commonjs/src/webapi/cli-options.js +101 -0
- package/commonjs/src/webapi/const.js +50 -0
- package/commonjs/src/webapi/create-api.js +237 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +166 -0
- package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/commonjs/src/webapi/devcontainer/host-scripts.js +52 -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 +441 -0
- package/commonjs/src/webapi/features/auth0-cli.js +435 -0
- package/commonjs/src/webapi/features/biome-format.js +49 -0
- package/commonjs/src/webapi/features/database.feature.js +433 -0
- package/commonjs/src/webapi/features/finalize.feature.js +95 -0
- package/commonjs/src/webapi/features/postman.feature.js +324 -0
- package/commonjs/src/webapi/models/api-package-json.model.js +60 -0
- package/commonjs/src/webapi/models/auth-config.model.js +26 -0
- package/commonjs/src/webapi/models/controller-source-file.model.js +193 -0
- package/commonjs/src/webapi/models/github-ci.model.js +193 -0
- package/commonjs/src/webapi/models/main-source-file.model.js +167 -0
- package/commonjs/src/webapi/models/production-dockerfile.model.js +157 -0
- package/commonjs/src/webapi/models/sample-files.model.js +60 -0
- package/commonjs/src/webapi/models/webapi-image.model.js +27 -0
- package/commonjs/src/webapi/postman/hello-openapi.js +175 -0
- package/commonjs/src/webapi/postman/local-view.js +184 -0
- package/commonjs/src/webapi/postman/openapi-to-postman.js +202 -0
- package/commonjs/src/webapi/workspace-readme.js +110 -0
- package/commonjs/src/webapi.action.js +30 -0
- package/commonjs/src/webapi.command.js +45 -0
- package/es6/src/add-api.action.js +29 -0
- package/es6/src/add-api.command.js +31 -0
- package/es6/src/add.command.js +14 -0
- package/es6/src/add.module.js +16 -0
- package/es6/src/cli.js +5 -0
- package/es6/src/create.command.js +14 -0
- package/es6/src/create.module.js +16 -0
- package/es6/src/domain/prompt.js +43 -0
- package/es6/src/index.js +8 -0
- package/es6/src/models/compose.model.js +144 -0
- package/es6/src/models/config-yaml.model.js +76 -0
- package/es6/src/models/const.js +22 -0
- package/es6/src/models/dev-container.model.js +106 -0
- package/es6/src/models/docker-image.model.js +217 -0
- package/es6/src/models/env-file.model.js +46 -0
- package/es6/src/models/gitignore.model.js +33 -0
- package/es6/src/models/launch-json.model.js +64 -0
- package/es6/src/models/package-json.model.js +137 -0
- package/es6/src/models/project.model.js +119 -0
- package/es6/src/models/source-file.js +3 -0
- package/es6/src/models/tsconfig.model.js +24 -0
- package/es6/src/models/vitest-config.model.js +25 -0
- package/es6/src/models/vscode-settings.model.js +21 -0
- package/es6/src/program.js +39 -0
- package/es6/src/resolve-target-dir.js +9 -0
- package/es6/src/services/scaffold.service.js +52 -0
- package/es6/src/version.js +36 -0
- package/es6/src/webapi/api-name.js +59 -0
- package/es6/src/webapi/cli-options.js +36 -0
- package/es6/src/webapi/const.js +20 -0
- package/es6/src/webapi/create-api.js +217 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +156 -0
- package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
- package/es6/src/webapi/devcontainer/host-scripts.js +31 -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 +390 -0
- package/es6/src/webapi/features/auth0-cli.js +375 -0
- package/es6/src/webapi/features/biome-format.js +42 -0
- package/es6/src/webapi/features/database.feature.js +423 -0
- package/es6/src/webapi/features/finalize.feature.js +85 -0
- package/es6/src/webapi/features/postman.feature.js +258 -0
- package/es6/src/webapi/models/api-package-json.model.js +55 -0
- package/es6/src/webapi/models/auth-config.model.js +22 -0
- package/es6/src/webapi/models/controller-source-file.model.js +184 -0
- package/es6/src/webapi/models/github-ci.model.js +186 -0
- package/es6/src/webapi/models/main-source-file.model.js +163 -0
- package/es6/src/webapi/models/production-dockerfile.model.js +148 -0
- package/es6/src/webapi/models/sample-files.model.js +42 -0
- package/es6/src/webapi/models/webapi-image.model.js +20 -0
- package/es6/src/webapi/postman/hello-openapi.js +168 -0
- package/es6/src/webapi/postman/local-view.js +169 -0
- package/es6/src/webapi/postman/openapi-to-postman.js +179 -0
- package/es6/src/webapi/workspace-readme.js +89 -0
- package/es6/src/webapi.action.js +20 -0
- package/es6/src/webapi.command.js +35 -0
- package/package.json +8 -8
- 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/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/types/src/models/compose.model.d.ts +64 -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 +37 -0
- package/types/src/models/docker-image.model.d.ts +84 -0
- package/types/src/models/env-file.model.d.ts +16 -0
- package/types/src/models/gitignore.model.d.ts +7 -0
- package/types/src/models/launch-json.model.d.ts +7 -0
- package/types/src/models/package-json.model.d.ts +35 -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 +3 -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/cli-options.d.ts +6 -0
- package/{src/webapi/const.ts → types/src/webapi/const.d.ts} +7 -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 +80 -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 +23 -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/github-ci.model.ts +0 -188
- 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
- /package/{src/index.ts → types/src/index.d.ts} +0 -0
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { WELL_KNOWN_FILES } from "./const.js";
|
|
2
|
-
import type { KeyedModel, SourceFile } from "./source-file.js";
|
|
3
|
-
|
|
4
|
-
export type LayerWriter = (dockerfile: DockerfileModel) => void;
|
|
5
|
-
|
|
6
|
-
export class DockerStep {
|
|
7
|
-
public constructor(
|
|
8
|
-
public readonly key: string,
|
|
9
|
-
public readonly content: string,
|
|
10
|
-
public readonly order = 0,
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
public static run(key: string, command: string, order = 0) {
|
|
14
|
-
return new DockerStep(key, `RUN ${command}`, order);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
public static raw(key: string, content: string, order = 0) {
|
|
18
|
-
return new DockerStep(key, content, order);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public static entrypoint(key: string, value: string | readonly string[], order = 0) {
|
|
22
|
-
const content =
|
|
23
|
-
typeof value === "string"
|
|
24
|
-
? `ENTRYPOINT ${value}`
|
|
25
|
-
: `ENTRYPOINT ${JSON.stringify(value)}`;
|
|
26
|
-
return new DockerStep(key, content, order);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const StepOrder = {
|
|
31
|
-
base: 1_000,
|
|
32
|
-
features: 5_000,
|
|
33
|
-
user: 9_000,
|
|
34
|
-
entrypoint: 10_000,
|
|
35
|
-
} as const;
|
|
36
|
-
|
|
37
|
-
const VSCODE_BOOTLOADER_FIX_BASH_SNIPPET = `if [[ -n "\${NODE_OPTIONS:-}" && "\${NODE_OPTIONS}" == *"bootloader.js"* ]]; then
|
|
38
|
-
_bootloader_path="$(printf '%s\\n' "$NODE_OPTIONS" | sed -n 's/.*--require[= ]\\([^[:space:]]*bootloader\\.js\\).*/\\1/p')"
|
|
39
|
-
if [[ -n "\${_bootloader_path}" && ! -f "\${_bootloader_path}" ]]; then
|
|
40
|
-
_stable_bootloader="$(find /vscode/vscode-server /home/node/.vscode-server/extensions -name "bootloader.js" -path "*/ms-vscode.js-debug/*" 2>/dev/null | head -1)"
|
|
41
|
-
if [[ -n "\${_stable_bootloader}" ]]; then
|
|
42
|
-
NODE_OPTIONS="\${NODE_OPTIONS//\${_bootloader_path}/\${_stable_bootloader}}"
|
|
43
|
-
else
|
|
44
|
-
NODE_OPTIONS="$(printf '%s\\n' "$NODE_OPTIONS" | sed -E 's#(^| )--require(=| )[[:graph:]]*bootloader\\.js( |$)# #g; s# +# #g; s#^ ##; s# $##')"
|
|
45
|
-
unset VSCODE_INSPECTOR_OPTIONS
|
|
46
|
-
fi
|
|
47
|
-
export NODE_OPTIONS
|
|
48
|
-
if [[ -z "$NODE_OPTIONS" ]]; then
|
|
49
|
-
unset NODE_OPTIONS
|
|
50
|
-
fi
|
|
51
|
-
fi
|
|
52
|
-
unset _bootloader_path _stable_bootloader
|
|
53
|
-
fi`;
|
|
54
|
-
|
|
55
|
-
/** Named volume + paths for interactive bash history in app Dev Containers. */
|
|
56
|
-
export const commandHistory = {
|
|
57
|
-
volumeName: "bashhistory",
|
|
58
|
-
dir: "/commandhistory",
|
|
59
|
-
fileName: ".bash_history",
|
|
60
|
-
histFile: "/commandhistory/.bash_history",
|
|
61
|
-
} as const;
|
|
62
|
-
|
|
63
|
-
export function withCommandHistory(options?: {
|
|
64
|
-
historyDir?: string;
|
|
65
|
-
historyFileName?: string;
|
|
66
|
-
username?: string;
|
|
67
|
-
order?: number;
|
|
68
|
-
}): LayerWriter {
|
|
69
|
-
const historyDir = options?.historyDir ?? commandHistory.dir;
|
|
70
|
-
const historyFileName = options?.historyFileName ?? commandHistory.fileName;
|
|
71
|
-
const username = options?.username ?? "node";
|
|
72
|
-
const order = options?.order ?? StepOrder.base;
|
|
73
|
-
const historyFile = `${historyDir}/${historyFileName}`;
|
|
74
|
-
|
|
75
|
-
return (dockerfile) => {
|
|
76
|
-
dockerfile
|
|
77
|
-
.addStep(
|
|
78
|
-
DockerStep.run(
|
|
79
|
-
"history-dir",
|
|
80
|
-
`mkdir -p ${historyDir} && touch ${historyFile} && chown -R ${username} ${historyDir}`,
|
|
81
|
-
order,
|
|
82
|
-
),
|
|
83
|
-
)
|
|
84
|
-
.addStep(DockerStep.raw("history-env", `ENV HISTFILE=${historyFile}`, order + 1))
|
|
85
|
-
.addStep(
|
|
86
|
-
DockerStep.raw(
|
|
87
|
-
"history-bashrc",
|
|
88
|
-
`RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=${historyFile}" \\\n\t&& echo "$SNIPPET" >> "/home/${username}/.bashrc"`,
|
|
89
|
-
order + 2,
|
|
90
|
-
),
|
|
91
|
-
);
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function withNodeUser(options?: {
|
|
96
|
-
username?: string;
|
|
97
|
-
includeUserInstruction?: boolean;
|
|
98
|
-
order?: number;
|
|
99
|
-
}): LayerWriter {
|
|
100
|
-
const username = options?.username ?? "node";
|
|
101
|
-
const includeUserInstruction = options?.includeUserInstruction ?? true;
|
|
102
|
-
const order = options?.order ?? StepOrder.features;
|
|
103
|
-
|
|
104
|
-
return (dockerfile) => {
|
|
105
|
-
dockerfile.addStep(DockerStep.raw("arg-username", `ARG USERNAME=${username}`, order));
|
|
106
|
-
|
|
107
|
-
if (includeUserInstruction) {
|
|
108
|
-
dockerfile.addStep(DockerStep.raw("user", "USER ${USERNAME}", StepOrder.user));
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function withPnpm(options?: {
|
|
114
|
-
version?: string;
|
|
115
|
-
usernameExpr?: string;
|
|
116
|
-
order?: number;
|
|
117
|
-
}): LayerWriter {
|
|
118
|
-
const version = options?.version ?? "10";
|
|
119
|
-
const usernameExpr = options?.usernameExpr ?? "${USERNAME}";
|
|
120
|
-
const order = options?.order ?? StepOrder.features + 100;
|
|
121
|
-
|
|
122
|
-
return (dockerfile) => {
|
|
123
|
-
dockerfile
|
|
124
|
-
.addStep(
|
|
125
|
-
DockerStep.run(
|
|
126
|
-
"pnpm-install",
|
|
127
|
-
`npm install --global npm@latest && npm add --global pnpm@${version}`,
|
|
128
|
-
order,
|
|
129
|
-
),
|
|
130
|
-
)
|
|
131
|
-
.addStep(
|
|
132
|
-
DockerStep.run(
|
|
133
|
-
"pnpm-config",
|
|
134
|
-
`mkdir -p /home/${usernameExpr}/.config/pnpm /home/${usernameExpr}/.pnpm-store && chown -R ${usernameExpr}:${usernameExpr} /home/${usernameExpr}/.config /home/${usernameExpr}/.pnpm-store && printf "store-dir=/home/${usernameExpr}/.pnpm-store\\n" > /home/${usernameExpr}/.config/pnpm/rc`,
|
|
135
|
-
order + 1,
|
|
136
|
-
),
|
|
137
|
-
);
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Client-only (`psql`, etc.). Escalates to root so this also works when layered on
|
|
143
|
-
* a published base image that already switched to `USER node`.
|
|
144
|
-
*/
|
|
145
|
-
export function withPostgresqlClient(options?: {
|
|
146
|
-
order?: number;
|
|
147
|
-
username?: string;
|
|
148
|
-
}): LayerWriter {
|
|
149
|
-
const order = options?.order ?? StepOrder.features + 150;
|
|
150
|
-
const username = options?.username ?? "node";
|
|
151
|
-
|
|
152
|
-
return (dockerfile) => {
|
|
153
|
-
dockerfile
|
|
154
|
-
.addStep(DockerStep.raw("postgresql-client-root", "USER root", order))
|
|
155
|
-
.addStep(
|
|
156
|
-
DockerStep.run(
|
|
157
|
-
"postgresql-client",
|
|
158
|
-
"apt-get update && apt-get install -y --no-install-recommends postgresql-client && rm -rf /var/lib/apt/lists/*",
|
|
159
|
-
order + 1,
|
|
160
|
-
),
|
|
161
|
-
)
|
|
162
|
-
.addStep(DockerStep.raw("postgresql-client-user", `USER ${username}`, order + 2));
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** GitHub CLI — install before USER switches away from root. */
|
|
167
|
-
export function withGithubCli(options?: { order?: number }): LayerWriter {
|
|
168
|
-
const order = options?.order ?? StepOrder.features + 140;
|
|
169
|
-
|
|
170
|
-
return (dockerfile) => {
|
|
171
|
-
dockerfile.addStep(
|
|
172
|
-
DockerStep.run(
|
|
173
|
-
"github-cli",
|
|
174
|
-
"apt-get update && apt-get install -y --no-install-recommends gh && rm -rf /var/lib/apt/lists/*",
|
|
175
|
-
order,
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function withWorkspaceNodeModulesOwnership(options?: {
|
|
182
|
-
workspaceDir?: string;
|
|
183
|
-
usernameExpr?: string;
|
|
184
|
-
order?: number;
|
|
185
|
-
}): LayerWriter {
|
|
186
|
-
const workspaceDir = options?.workspaceDir ?? "/workspace";
|
|
187
|
-
const usernameExpr = options?.usernameExpr ?? "${USERNAME}";
|
|
188
|
-
const order = options?.order ?? StepOrder.features + 200;
|
|
189
|
-
|
|
190
|
-
return (dockerfile) => {
|
|
191
|
-
dockerfile.addStep(
|
|
192
|
-
DockerStep.raw(
|
|
193
|
-
"workspace-node-modules",
|
|
194
|
-
`RUN mkdir -p ${workspaceDir}/node_modules && chown ${usernameExpr}:${usernameExpr} ${workspaceDir}/node_modules`,
|
|
195
|
-
order,
|
|
196
|
-
),
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function withVsCodeJsDebugBootloaderFix(options?: {
|
|
202
|
-
bashrcPath?: string;
|
|
203
|
-
order?: number;
|
|
204
|
-
}): LayerWriter {
|
|
205
|
-
const bashrcPath = options?.bashrcPath ?? "/home/${USERNAME}/.bashrc";
|
|
206
|
-
const order = options?.order ?? StepOrder.features + 300;
|
|
207
|
-
|
|
208
|
-
return (dockerfile) => {
|
|
209
|
-
dockerfile.addStep(
|
|
210
|
-
DockerStep.raw(
|
|
211
|
-
"vscode-bootloader-fix",
|
|
212
|
-
[
|
|
213
|
-
"# VS Code / Cursor js-debug Auto Attach injects --require …/bootloader.js into",
|
|
214
|
-
"# NODE_OPTIONS. On a fresh Dev Container that path often does not exist yet",
|
|
215
|
-
"# (workspaceStorage file is only created after toggling Auto Attach), which crashes",
|
|
216
|
-
"# every Node command. If the injected path is missing, prefer the stable",
|
|
217
|
-
"# bootloader from the server extension install; otherwise strip the bad require.",
|
|
218
|
-
`RUN cat <<'EOF' >> "${bashrcPath}"`,
|
|
219
|
-
VSCODE_BOOTLOADER_FIX_BASH_SNIPPET,
|
|
220
|
-
"EOF",
|
|
221
|
-
].join("\n"),
|
|
222
|
-
order,
|
|
223
|
-
),
|
|
224
|
-
);
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export class DockerfileModel implements KeyedModel {
|
|
229
|
-
public readonly key = WELL_KNOWN_FILES.DOCKERFILE;
|
|
230
|
-
private readonly _steps = new Map<string, DockerStep>();
|
|
231
|
-
private readonly _layerWriters: LayerWriter[] = [];
|
|
232
|
-
private _fromImage: string;
|
|
233
|
-
private _layerCounter = 0;
|
|
234
|
-
|
|
235
|
-
public constructor(baseImage: string) {
|
|
236
|
-
this._fromImage = baseImage;
|
|
237
|
-
this.registerBaseLayers();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
public from(baseImage: string): this {
|
|
241
|
-
this._fromImage = baseImage;
|
|
242
|
-
return this;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
public addLayer(layer: LayerWriter | string, order = StepOrder.features): this {
|
|
246
|
-
if (typeof layer === "string") {
|
|
247
|
-
const idx = ++this._layerCounter;
|
|
248
|
-
const key = `layer-run-${idx}`;
|
|
249
|
-
this._layerWriters.push((dockerfile) => {
|
|
250
|
-
dockerfile.addStep(DockerStep.run(key, layer, order + idx));
|
|
251
|
-
});
|
|
252
|
-
return this;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
this._layerWriters.push(layer);
|
|
256
|
-
return this;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public addStep(step: DockerStep): this {
|
|
260
|
-
this._steps.set(step.key, step);
|
|
261
|
-
return this;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
public removeStep(key: string): this {
|
|
265
|
-
this._steps.delete(key);
|
|
266
|
-
return this;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
public entrypoint(value: string | readonly string[], order = StepOrder.entrypoint): this {
|
|
270
|
-
this.addStep(DockerStep.entrypoint("entrypoint", value, order));
|
|
271
|
-
return this;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
public addRunStep(cmd: string): this {
|
|
275
|
-
return this.addLayer(cmd);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
public build(): string {
|
|
279
|
-
for (const layerWriter of this._layerWriters) {
|
|
280
|
-
layerWriter(this);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const lines = [`FROM ${this._fromImage}`];
|
|
284
|
-
const sortedSteps = [...this._steps.values()].sort((left, right) => {
|
|
285
|
-
if (left.order !== right.order) {
|
|
286
|
-
return left.order - right.order;
|
|
287
|
-
}
|
|
288
|
-
return left.key.localeCompare(right.key, "en");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
for (const step of sortedSteps) {
|
|
292
|
-
lines.push("", step.content);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return `${lines.join("\n")}\n`;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
protected registerBaseLayers(): void {
|
|
299
|
-
// Generic base model intentionally has no default layers.
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
public files(): SourceFile[] {
|
|
303
|
-
return [
|
|
304
|
-
{
|
|
305
|
-
filePath: "./Dockerfile",
|
|
306
|
-
content: this.build(),
|
|
307
|
-
},
|
|
308
|
-
];
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Shared layers for the published `ghcr.io/typesmiths/decohere/devcontainer` image.
|
|
314
|
-
* Checked in at `projects/devcontainer/Dockerfile` — keep in sync via the unit test.
|
|
315
|
-
*/
|
|
316
|
-
export class AppDevContainerBaseImage extends DockerfileModel {
|
|
317
|
-
protected override registerBaseLayers(): void {
|
|
318
|
-
super.registerBaseLayers();
|
|
319
|
-
this.addLayer(withCommandHistory())
|
|
320
|
-
.addLayer(withNodeUser())
|
|
321
|
-
.addLayer(withPnpm())
|
|
322
|
-
.addLayer(withGithubCli())
|
|
323
|
-
.addLayer(withWorkspaceNodeModulesOwnership())
|
|
324
|
-
.addLayer(withVsCodeJsDebugBootloaderFix());
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Generic Dev Container image helpers (history + bootloader) without webapi defaults. */
|
|
329
|
-
export class DevContainerImageModel extends DockerfileModel {
|
|
330
|
-
protected override registerBaseLayers(): void {
|
|
331
|
-
super.registerBaseLayers();
|
|
332
|
-
this.addLayer(withCommandHistory()).addLayer(withVsCodeJsDebugBootloaderFix());
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
public files(): SourceFile[] {
|
|
336
|
-
return [
|
|
337
|
-
{
|
|
338
|
-
filePath: ".devcontainer/Dockerfile",
|
|
339
|
-
content: this.build(),
|
|
340
|
-
},
|
|
341
|
-
];
|
|
342
|
-
}
|
|
343
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { WELL_KNOWN_FILES } from "./const.js";
|
|
3
|
-
import type { KeyedModel, SourceFile } from "./source-file.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Accumulates key=value pairs and renders them as a `.env` file.
|
|
7
|
-
*
|
|
8
|
-
* Register once in the workspace; features call `addEntry` during their
|
|
9
|
-
* scaffold phase to contribute environment variables. The final file is
|
|
10
|
-
* written at render time when `workspace.scaffold()` is called.
|
|
11
|
-
*/
|
|
12
|
-
export class EnvFile implements KeyedModel {
|
|
13
|
-
public readonly key = WELL_KNOWN_FILES.ENV_FILE;
|
|
14
|
-
|
|
15
|
-
private readonly _entries = new Map<string, string>();
|
|
16
|
-
|
|
17
|
-
public addEntry(key: string, value: string): this {
|
|
18
|
-
this._entries.set(key, value);
|
|
19
|
-
return this;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public hasEntry(key: string): boolean {
|
|
23
|
-
return this._entries.has(key);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public static fromFile(absolutePath: string): EnvFile {
|
|
27
|
-
const env = new EnvFile();
|
|
28
|
-
const raw = readFileSync(absolutePath, "utf8");
|
|
29
|
-
for (const line of raw.split("\n")) {
|
|
30
|
-
const trimmed = line.trim();
|
|
31
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
32
|
-
const eq = line.indexOf("=");
|
|
33
|
-
if (eq <= 0) continue;
|
|
34
|
-
env.addEntry(line.slice(0, eq).trim(), line.slice(eq + 1));
|
|
35
|
-
}
|
|
36
|
-
return env;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public files(): SourceFile[] {
|
|
40
|
-
if (this._entries.size === 0) return [];
|
|
41
|
-
const body = [...this._entries.entries()].map(([k, v]) => `${k}=${v}`).join("\n");
|
|
42
|
-
const content = `${body}\n`;
|
|
43
|
-
return [{ filePath: ".env", content }];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { WELL_KNOWN_FILES } from "./const.js";
|
|
2
|
-
import type { KeyedModel, SourceFile } from "./source-file.js";
|
|
3
|
-
|
|
4
|
-
export class Gitignore implements KeyedModel {
|
|
5
|
-
public readonly key = WELL_KNOWN_FILES.GITIGNORE;
|
|
6
|
-
private ignoreList = [
|
|
7
|
-
"node_modules",
|
|
8
|
-
"dist",
|
|
9
|
-
".pnpm-store",
|
|
10
|
-
".env",
|
|
11
|
-
".devcontainer/gh.env",
|
|
12
|
-
".cursor/codebase-rag.env",
|
|
13
|
-
".cursor/mcp.json",
|
|
14
|
-
".DS_Store",
|
|
15
|
-
"logs",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
public add(...patterns: string[]): this {
|
|
19
|
-
for (const pattern of patterns) {
|
|
20
|
-
if (!this.ignoreList.includes(pattern)) {
|
|
21
|
-
this.ignoreList.push(pattern);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public files(): SourceFile[] {
|
|
28
|
-
return [
|
|
29
|
-
{
|
|
30
|
-
filePath: ".gitignore",
|
|
31
|
-
content: `${this.ignoreList.join("\n")}\n`,
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { WELL_KNOWN_FILES } from "./const.js";
|
|
2
|
-
import type { KeyedModel, SourceFile } from "./source-file.js";
|
|
3
|
-
|
|
4
|
-
export class LaunchJson implements KeyedModel {
|
|
5
|
-
public readonly key = WELL_KNOWN_FILES.LAUNCH_JSON;
|
|
6
|
-
|
|
7
|
-
public constructor(private readonly apiNames: readonly string[] = ["api"]) {}
|
|
8
|
-
|
|
9
|
-
public files(): SourceFile[] {
|
|
10
|
-
const configurations: Record<string, unknown>[] = [
|
|
11
|
-
{
|
|
12
|
-
name: "Build",
|
|
13
|
-
type: "node",
|
|
14
|
-
request: "launch",
|
|
15
|
-
runtimeExecutable: "pnpm",
|
|
16
|
-
runtimeArgs: ["build"],
|
|
17
|
-
console: "integratedTerminal",
|
|
18
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: VS Code variable
|
|
19
|
-
cwd: "${workspaceFolder}",
|
|
20
|
-
},
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const first = this.apiNames[0] ?? "api";
|
|
24
|
-
const defaultDevArgs =
|
|
25
|
-
first === "api" && this.apiNames.length === 1 ? ["dev"] : ["run", `dev:${first}`];
|
|
26
|
-
configurations.push(serveConfig("Serve / Dev", defaultDevArgs));
|
|
27
|
-
|
|
28
|
-
for (const apiName of this.apiNames) {
|
|
29
|
-
if (apiName === first && this.apiNames.length === 1 && apiName === "api") continue;
|
|
30
|
-
configurations.push(serveConfig(`Serve / Dev (${apiName})`, ["run", `dev:${apiName}`]));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return [
|
|
34
|
-
{
|
|
35
|
-
filePath: ".vscode/launch.json",
|
|
36
|
-
content: JSON.stringify(
|
|
37
|
-
{
|
|
38
|
-
version: "0.2.0",
|
|
39
|
-
configurations,
|
|
40
|
-
},
|
|
41
|
-
null,
|
|
42
|
-
2,
|
|
43
|
-
),
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function serveConfig(name: string, runtimeArgs: string[]): Record<string, unknown> {
|
|
50
|
-
return {
|
|
51
|
-
name,
|
|
52
|
-
type: "node",
|
|
53
|
-
request: "launch",
|
|
54
|
-
runtimeExecutable: "pnpm",
|
|
55
|
-
runtimeArgs,
|
|
56
|
-
console: "integratedTerminal",
|
|
57
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: VS Code variable
|
|
58
|
-
cwd: "${workspaceFolder}",
|
|
59
|
-
};
|
|
60
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { CREATE_VERSION } from "../version.js";
|
|
3
|
-
import {
|
|
4
|
-
DEFAULT_WEBAPI_NAME,
|
|
5
|
-
webapiPackageName,
|
|
6
|
-
webapiPort,
|
|
7
|
-
webapiReleaseEntry,
|
|
8
|
-
} from "../webapi/const.js";
|
|
9
|
-
import { WELL_KNOWN_FILES } from "./const.js";
|
|
10
|
-
import type { KeyedModel, SourceFile } from "./source-file.js";
|
|
11
|
-
|
|
12
|
-
export type PackageJsonOptions = {
|
|
13
|
-
startEntry?: string;
|
|
14
|
-
/** API slugs in this workspace. Drives `start:<name>` / `dev:<name>`. */
|
|
15
|
-
apiNames?: readonly string[];
|
|
16
|
-
/** Host workspace name used in `@<workspace>/<api>` for flux `--project`. */
|
|
17
|
-
workspaceName?: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Builder for the root `package.json`.
|
|
22
|
-
*
|
|
23
|
-
* Starts with the core API dependencies. Features call `addDependency` /
|
|
24
|
-
* `addDevDependency` during their scaffold phase to inject their own deps
|
|
25
|
-
* before `files()` is called at render time.
|
|
26
|
-
*/
|
|
27
|
-
export class PackageJson implements KeyedModel {
|
|
28
|
-
public readonly key = WELL_KNOWN_FILES.PACKAGE_JSON;
|
|
29
|
-
|
|
30
|
-
private _dependencies: Record<string, string>;
|
|
31
|
-
private _devDependencies: Record<string, string>;
|
|
32
|
-
private _scripts: Record<string, string>;
|
|
33
|
-
private _version = "0.0.1";
|
|
34
|
-
private _type = "module";
|
|
35
|
-
private _rest: Record<string, unknown> = {};
|
|
36
|
-
|
|
37
|
-
public constructor(
|
|
38
|
-
private readonly name: string,
|
|
39
|
-
options?: PackageJsonOptions,
|
|
40
|
-
) {
|
|
41
|
-
const apiNames = options?.apiNames ?? [DEFAULT_WEBAPI_NAME];
|
|
42
|
-
const first = apiNames[0] ?? DEFAULT_WEBAPI_NAME;
|
|
43
|
-
const startEntry = options?.startEntry ?? webapiReleaseEntry(first);
|
|
44
|
-
const workspaceName = options?.workspaceName ?? stripApiSuffix(name);
|
|
45
|
-
const namedOrMany = apiNames.length > 1 || first !== DEFAULT_WEBAPI_NAME;
|
|
46
|
-
|
|
47
|
-
this._scripts = {
|
|
48
|
-
build: "forge build --configuration release",
|
|
49
|
-
"build:watch": "forge build --watch",
|
|
50
|
-
start: `node ${startEntry}`,
|
|
51
|
-
dev: namedOrMany ? fluxDevScript(workspaceName, first, webapiPort(0)) : "flux serve",
|
|
52
|
-
"lint:check": "biome check .",
|
|
53
|
-
typecheck: "forge typecheck",
|
|
54
|
-
test: "vitest run --passWithNoTests",
|
|
55
|
-
};
|
|
56
|
-
for (const [index, apiName] of apiNames.entries()) {
|
|
57
|
-
this._scripts[`start:${apiName}`] = `node ${webapiReleaseEntry(apiName)}`;
|
|
58
|
-
this._scripts[`dev:${apiName}`] = fluxDevScript(
|
|
59
|
-
workspaceName,
|
|
60
|
-
apiName,
|
|
61
|
-
webapiPort(index),
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
this._dependencies = {
|
|
65
|
-
"@decohere/cortex": CREATE_VERSION,
|
|
66
|
-
"@decohere/crucible": CREATE_VERSION,
|
|
67
|
-
"@decohere/nexus": CREATE_VERSION,
|
|
68
|
-
"@decohere/protocol": CREATE_VERSION,
|
|
69
|
-
"@decohere/scribe": CREATE_VERSION,
|
|
70
|
-
"@decohere/scribe-pino": CREATE_VERSION,
|
|
71
|
-
"pino-pretty": "^13.0.0",
|
|
72
|
-
"pino-roll": "^4.0.0",
|
|
73
|
-
};
|
|
74
|
-
this._devDependencies = {
|
|
75
|
-
"@decohere/forge": CREATE_VERSION,
|
|
76
|
-
"@decohere/cli": CREATE_VERSION,
|
|
77
|
-
typescript: "^6.0.3",
|
|
78
|
-
"@biomejs/biome": "^2.0.0",
|
|
79
|
-
vitest: "^3.2.4",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Load an existing root package.json so `webapi add` can merge scripts/deps. */
|
|
84
|
-
public static fromFile(filePath: string): PackageJson {
|
|
85
|
-
const raw = JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
86
|
-
const pkg = new PackageJson(typeof raw.name === "string" ? raw.name : "app", {
|
|
87
|
-
apiNames: [],
|
|
88
|
-
});
|
|
89
|
-
pkg._version = typeof raw.version === "string" ? raw.version : "0.0.1";
|
|
90
|
-
pkg._type = typeof raw.type === "string" ? raw.type : "module";
|
|
91
|
-
pkg._scripts = { ...asStringMap(raw.scripts) };
|
|
92
|
-
pkg._dependencies = { ...asStringMap(raw.dependencies) };
|
|
93
|
-
pkg._devDependencies = { ...asStringMap(raw.devDependencies) };
|
|
94
|
-
const reserved = new Set([
|
|
95
|
-
"name",
|
|
96
|
-
"version",
|
|
97
|
-
"type",
|
|
98
|
-
"scripts",
|
|
99
|
-
"dependencies",
|
|
100
|
-
"devDependencies",
|
|
101
|
-
]);
|
|
102
|
-
pkg._rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !reserved.has(key)));
|
|
103
|
-
return pkg;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
public addDependency(name: string, version: string): this {
|
|
107
|
-
this._dependencies[name] = version;
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
public addDevDependency(name: string, version: string): this {
|
|
112
|
-
this._devDependencies[name] = version;
|
|
113
|
-
return this;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public addScript(name: string, command: string): this {
|
|
117
|
-
this._scripts[name] = command;
|
|
118
|
-
return this;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public hasScript(name: string): boolean {
|
|
122
|
-
return typeof this._scripts[name] === "string";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public files(): SourceFile[] {
|
|
126
|
-
return [
|
|
127
|
-
{
|
|
128
|
-
filePath: "package.json",
|
|
129
|
-
content: JSON.stringify(
|
|
130
|
-
{
|
|
131
|
-
name: this.name,
|
|
132
|
-
version: this._version,
|
|
133
|
-
type: this._type,
|
|
134
|
-
...this._rest,
|
|
135
|
-
scripts: this._scripts,
|
|
136
|
-
dependencies: sortStringMap(this._dependencies),
|
|
137
|
-
devDependencies: sortStringMap(this._devDependencies),
|
|
138
|
-
},
|
|
139
|
-
null,
|
|
140
|
-
2,
|
|
141
|
-
),
|
|
142
|
-
},
|
|
143
|
-
];
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function stripApiSuffix(name: string): string {
|
|
148
|
-
return name.endsWith(".api") ? name.slice(0, -".api".length) : name;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function asStringMap(value: unknown): Record<string, string> {
|
|
152
|
-
if (value == null || typeof value !== "object") return {};
|
|
153
|
-
const result: Record<string, string> = {};
|
|
154
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
155
|
-
if (typeof entry === "string") result[key] = entry;
|
|
156
|
-
}
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Stable A→Z key order for package.json dependency maps. */
|
|
161
|
-
export function sortStringMap(map: Record<string, string>): Record<string, string> {
|
|
162
|
-
return Object.fromEntries(
|
|
163
|
-
Object.entries(map).sort(([left], [right]) => left.localeCompare(right, "en")),
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function fluxDevScript(workspaceName: string, apiName: string, port: number): string {
|
|
168
|
-
return `PORT=${port} flux serve --project ${webapiPackageName(workspaceName, apiName)}`;
|
|
169
|
-
}
|