@decohere/scaffold 0.0.1-dev.126 → 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
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Runs on the host before the container is created (devcontainer initializeCommand).
|
|
3
|
-
# Creates bind-mount sources as the host user so Docker does not create them as root.
|
|
4
|
-
# Optional npm / gh / MCP setup is y/N on a TTY; no TTY skips.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
-
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
-
# shellcheck source=tty-prompt.sh
|
|
10
|
-
source "$here/tty-prompt.sh"
|
|
11
|
-
|
|
12
|
-
npm_dir="$root/.devcontainer/npm-auth"
|
|
13
|
-
gh_dir="$root/.devcontainer/gh-auth"
|
|
14
|
-
gh_env="$root/.devcontainer/gh.env"
|
|
15
|
-
cursor_dir="$root/.cursor"
|
|
16
|
-
RAG_ENV="$cursor_dir/codebase-rag.env"
|
|
17
|
-
|
|
18
|
-
run_codebase_rag_mcp() {
|
|
19
|
-
local script="$here/codebase-rag-mcp.ts"
|
|
20
|
-
CODEBASE_RAG_WORKSPACE="$root" node --experimental-strip-types "$script" "$@"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
load_rag_env() {
|
|
24
|
-
RAG_URL=""
|
|
25
|
-
RAG_TOKEN=""
|
|
26
|
-
[[ -f "$RAG_ENV" ]] || return 0
|
|
27
|
-
# shellcheck disable=SC1090
|
|
28
|
-
set -a
|
|
29
|
-
# shellcheck disable=SC1090
|
|
30
|
-
source "$RAG_ENV"
|
|
31
|
-
set +a
|
|
32
|
-
RAG_URL="${CODEBASE_RAG_MCP_URL:-}"
|
|
33
|
-
RAG_TOKEN="${CODEBASE_RAG_MCP_TOKEN:-}"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
write_rag_env() {
|
|
37
|
-
mkdir -p "$(dirname "$RAG_ENV")"
|
|
38
|
-
umask 077
|
|
39
|
-
cat >"$RAG_ENV" <<EOF
|
|
40
|
-
# Written by .devcontainer/initialize.sh. Not in git.
|
|
41
|
-
CODEBASE_RAG_MCP_URL=${RAG_URL}
|
|
42
|
-
CODEBASE_RAG_MCP_TOKEN=${RAG_TOKEN}
|
|
43
|
-
CODEBASE_RAG_MCP_ROOT=${root}
|
|
44
|
-
EOF
|
|
45
|
-
chmod 600 "$RAG_ENV"
|
|
46
|
-
if [[ -n "$RAG_URL" && -n "$RAG_TOKEN" ]]; then
|
|
47
|
-
run_codebase_rag_mcp merge || true
|
|
48
|
-
fi
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
seed_rag_from_host_mcp() {
|
|
52
|
-
local mcp="${HOME}/.cursor/mcp.json"
|
|
53
|
-
[[ -f "$mcp" ]] || return 1
|
|
54
|
-
command -v node >/dev/null 2>&1 || return 1
|
|
55
|
-
run_codebase_rag_mcp seed-print "$mcp"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
ensure_cursor_dir() {
|
|
59
|
-
mkdir -p "$cursor_dir"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
ensure_auth_dir() {
|
|
63
|
-
local dir="$1"
|
|
64
|
-
if ! mkdir -p "$dir" 2>/dev/null; then
|
|
65
|
-
echo "Cannot create $dir (permission denied)." >&2
|
|
66
|
-
echo "The scaffold container likely left .devcontainer root-owned. Fix with:" >&2
|
|
67
|
-
echo " sudo chown -R \"$USER:$USER\" \"$root\"" >&2
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
{
|
|
71
|
-
echo "*"
|
|
72
|
-
echo "!.gitignore"
|
|
73
|
-
} >"$dir/.gitignore"
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
ensure_gh_env() {
|
|
77
|
-
if [[ -d "$gh_env" ]]; then
|
|
78
|
-
echo "Refusing to start: $gh_env is a directory (left by a prior bind mount). Remove it and rebuild." >&2
|
|
79
|
-
exit 1
|
|
80
|
-
fi
|
|
81
|
-
touch "$gh_env"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
setup_npm_auth() {
|
|
85
|
-
if [[ -f "$npm_dir/.npmrc" ]]; then
|
|
86
|
-
echo "npm auth: using $npm_dir/.npmrc"
|
|
87
|
-
return 0
|
|
88
|
-
fi
|
|
89
|
-
if ! prompt_yn "Configure npm auth for this workspace?"; then
|
|
90
|
-
echo "npm auth: skipped"
|
|
91
|
-
return 0
|
|
92
|
-
fi
|
|
93
|
-
if [[ -f "${HOME}/.npmrc" ]]; then
|
|
94
|
-
cp -a "${HOME}/.npmrc" "$npm_dir/.npmrc"
|
|
95
|
-
echo "npm auth: copied ~/.npmrc → $npm_dir/.npmrc"
|
|
96
|
-
return 0
|
|
97
|
-
fi
|
|
98
|
-
echo "npm auth: no ~/.npmrc — log in inside the container if prompted"
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
setup_gh_auth() {
|
|
102
|
-
if [[ -f "$gh_dir/hosts.yml" ]]; then
|
|
103
|
-
echo "gh auth: using $gh_dir/hosts.yml"
|
|
104
|
-
return 0
|
|
105
|
-
fi
|
|
106
|
-
if ! prompt_yn "Configure GitHub CLI auth for this workspace?"; then
|
|
107
|
-
echo "gh auth: skipped"
|
|
108
|
-
return 0
|
|
109
|
-
fi
|
|
110
|
-
if [[ -f "${HOME}/.config/gh/hosts.yml" ]]; then
|
|
111
|
-
cp -a "${HOME}/.config/gh/hosts.yml" "$gh_dir/hosts.yml"
|
|
112
|
-
[[ -f "${HOME}/.config/gh/config.yml" ]] &&
|
|
113
|
-
cp -a "${HOME}/.config/gh/config.yml" "$gh_dir/config.yml"
|
|
114
|
-
echo "gh auth: copied ~/.config/gh → $gh_dir"
|
|
115
|
-
return 0
|
|
116
|
-
fi
|
|
117
|
-
echo "gh auth: no ~/.config/gh/hosts.yml — log in inside the container if prompted"
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
setup_codebase_rag_env() {
|
|
121
|
-
load_rag_env
|
|
122
|
-
if [[ -n "$RAG_URL" && -n "$RAG_TOKEN" ]]; then
|
|
123
|
-
write_rag_env
|
|
124
|
-
echo "codebase-rag MCP: using $RAG_ENV"
|
|
125
|
-
return 0
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
local seeded
|
|
129
|
-
if seeded="$(seed_rag_from_host_mcp 2>/dev/null)"; then
|
|
130
|
-
if prompt_yn "Found codebase-rag in ~/.cursor/mcp.json. Use it for this workspace?" y; then
|
|
131
|
-
RAG_URL="$(printf '%s\n' "$seeded" | sed -n '1p')"
|
|
132
|
-
RAG_TOKEN="$(printf '%s\n' "$seeded" | sed -n '2p')"
|
|
133
|
-
write_rag_env
|
|
134
|
-
echo "codebase-rag MCP: imported HTTP server from ~/.cursor/mcp.json → $RAG_ENV"
|
|
135
|
-
return 0
|
|
136
|
-
fi
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
if ! prompt_yn "Configure codebase RAG MCP (semantic search)?"; then
|
|
140
|
-
RAG_URL=""
|
|
141
|
-
RAG_TOKEN=""
|
|
142
|
-
write_rag_env
|
|
143
|
-
echo "codebase-rag MCP: skipped"
|
|
144
|
-
return 0
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
printf '%s\n' "Empty URL skips. Example: https://rag.example.com/mcp" >/dev/tty
|
|
148
|
-
printf '%s' "MCP URL: " >/dev/tty
|
|
149
|
-
read_tty RAG_URL
|
|
150
|
-
RAG_URL="$(printf '%s' "$RAG_URL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
151
|
-
if [[ -z "$RAG_URL" ]]; then
|
|
152
|
-
RAG_TOKEN=""
|
|
153
|
-
write_rag_env
|
|
154
|
-
echo "codebase-rag MCP: skipped"
|
|
155
|
-
return 0
|
|
156
|
-
fi
|
|
157
|
-
printf '%s' "Bearer token (hidden): " >/dev/tty
|
|
158
|
-
read_tty RAG_TOKEN hidden
|
|
159
|
-
write_rag_env
|
|
160
|
-
echo "codebase-rag MCP: saved $RAG_ENV"
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
ensure_auth_dir "$npm_dir"
|
|
164
|
-
ensure_auth_dir "$gh_dir"
|
|
165
|
-
ensure_gh_env
|
|
166
|
-
ensure_cursor_dir
|
|
167
|
-
setup_npm_auth
|
|
168
|
-
setup_gh_auth
|
|
169
|
-
setup_codebase_rag_env
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Runs after container create and when workspace content updates
|
|
3
|
-
# (devcontainer postCreateCommand / updateContentCommand).
|
|
4
|
-
# Installs deps when missing or when the lockfile changed; skips otherwise.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
-
cd "$root"
|
|
9
|
-
|
|
10
|
-
lockfile="pnpm-lock.yaml"
|
|
11
|
-
stamp="node_modules/.decohere-pnpm-lock.sha"
|
|
12
|
-
|
|
13
|
-
current_hash=""
|
|
14
|
-
if [[ -f "$lockfile" ]]; then
|
|
15
|
-
current_hash="$(sha256sum "$lockfile" | awk '{print $1}')"
|
|
16
|
-
elif [[ -f package.json ]]; then
|
|
17
|
-
current_hash="$(sha256sum package.json | awk '{print $1}')"
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
if [[ -d node_modules/.bin ]] && [[ -n "$current_hash" ]] && [[ -f "$stamp" ]] && [[ "$(cat "$stamp")" == "$current_hash" ]]; then
|
|
21
|
-
echo "node_modules: up to date, skipping pnpm install"
|
|
22
|
-
exit 0
|
|
23
|
-
fi
|
|
24
|
-
|
|
25
|
-
echo "node_modules: installing dependencies (first create may take a while)..."
|
|
26
|
-
if [[ -f "$lockfile" ]]; then
|
|
27
|
-
pnpm install --frozen-lockfile
|
|
28
|
-
else
|
|
29
|
-
pnpm install
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
if [[ -n "$current_hash" ]]; then
|
|
33
|
-
mkdir -p node_modules
|
|
34
|
-
printf '%s\n' "$current_hash" >"$stamp"
|
|
35
|
-
fi
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Runs on every container start (devcontainer postStartCommand).
|
|
3
|
-
# Optional npm / gh login is y/N on a TTY. Install is post-create.sh.
|
|
4
|
-
# MCP merge also runs here for later reconnects; the Compose service command
|
|
5
|
-
# merges before Cursor attaches (postStart is too late for the first snapshot).
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
-
root="$(cd "$here/.." && pwd)"
|
|
10
|
-
# shellcheck source=tty-prompt.sh
|
|
11
|
-
source "$here/tty-prompt.sh"
|
|
12
|
-
|
|
13
|
-
CODEBASE_RAG_WORKSPACE="$root" node --experimental-strip-types "$here/codebase-rag-mcp.ts" merge ||
|
|
14
|
-
echo "codebase-rag MCP: merge skipped" >&2
|
|
15
|
-
|
|
16
|
-
AUTH_REGISTRY="https://registry.npmjs.org"
|
|
17
|
-
|
|
18
|
-
ensure_npm_auth() {
|
|
19
|
-
if npm whoami --registry "$AUTH_REGISTRY" >/dev/null 2>&1; then
|
|
20
|
-
echo "npm: logged in as $(npm whoami --registry "$AUTH_REGISTRY")"
|
|
21
|
-
return 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
if prompt_yn "Log in to npm?"; then
|
|
25
|
-
echo "npm: starting login..."
|
|
26
|
-
npm login --registry "$AUTH_REGISTRY" </dev/tty >/dev/tty
|
|
27
|
-
return 0
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
echo "npm: skipped. Open a terminal and run: npm login" >&2
|
|
31
|
-
echo " (creds persist in .devcontainer/npm-auth/)" >&2
|
|
32
|
-
return 1
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
ensure_gh_auth() {
|
|
36
|
-
if ! command -v gh >/dev/null 2>&1; then
|
|
37
|
-
echo "gh: not installed in this container" >&2
|
|
38
|
-
return 1
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
if gh auth status >/dev/null 2>&1; then
|
|
42
|
-
echo "gh: $(gh auth status 2>&1 | head -n1)"
|
|
43
|
-
return 0
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
local token="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
|
47
|
-
if [[ -n "$token" ]]; then
|
|
48
|
-
echo "gh: authenticating from GH_TOKEN..."
|
|
49
|
-
printf '%s\n' "$token" | gh auth login --with-token
|
|
50
|
-
return 0
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
if prompt_yn "Log in to GitHub CLI (gh)?"; then
|
|
54
|
-
echo "gh: starting login..."
|
|
55
|
-
gh auth login </dev/tty >/dev/tty
|
|
56
|
-
return 0
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
echo "gh: skipped. Open a terminal and run: gh auth login" >&2
|
|
60
|
-
echo " (creds persist in .devcontainer/gh-auth/; or set GH_TOKEN in .devcontainer/gh.env)" >&2
|
|
61
|
-
return 1
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
ensure_npm_auth || true
|
|
65
|
-
ensure_gh_auth || true
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
can_prompt() {
|
|
2
|
-
[[ -r /dev/tty && -w /dev/tty ]]
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
read_tty() {
|
|
6
|
-
local var="$1"
|
|
7
|
-
local hidden="${2:-}"
|
|
8
|
-
local value=""
|
|
9
|
-
if [[ -n "$hidden" ]]; then
|
|
10
|
-
read -r -s value </dev/tty || true
|
|
11
|
-
printf '\n' >/dev/tty
|
|
12
|
-
else
|
|
13
|
-
read -r value </dev/tty || true
|
|
14
|
-
fi
|
|
15
|
-
printf -v "$var" '%s' "$value"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
# Returns 0 for yes. Default is n unless the second argument is y.
|
|
19
|
-
prompt_yn() {
|
|
20
|
-
local question="$1"
|
|
21
|
-
local default="${2:-n}"
|
|
22
|
-
local reply=""
|
|
23
|
-
if ! can_prompt; then
|
|
24
|
-
return 1
|
|
25
|
-
fi
|
|
26
|
-
local hint="y/N"
|
|
27
|
-
if [[ "$default" == "y" ]]; then
|
|
28
|
-
hint="Y/n"
|
|
29
|
-
fi
|
|
30
|
-
printf '%s [%s] ' "$question" "$hint" >/dev/tty
|
|
31
|
-
read_tty reply
|
|
32
|
-
reply="$(printf '%s' "$reply" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
33
|
-
if [[ -z "$reply" ]]; then
|
|
34
|
-
[[ "$default" == "y" ]]
|
|
35
|
-
return
|
|
36
|
-
fi
|
|
37
|
-
[[ "$reply" == "y" || "$reply" == "yes" ]]
|
|
38
|
-
}
|
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { ask, pick } from "../../domain/prompt.js";
|
|
2
|
-
import { WELL_KNOWN_FILES } from "../../models/const.js";
|
|
3
|
-
import { ensureLoggedIn } from "../auth0-commands.js";
|
|
4
|
-
import { AuthConfigModel } from "../models/auth-config.model.js";
|
|
5
|
-
import { ControllerSourceFile } from "../models/controller-source-file.model.js";
|
|
6
|
-
import * as auth0Cli from "./auth0-cli.js";
|
|
7
|
-
const ADMIN_ROLE = "admin";
|
|
8
|
-
const USER_ENTITY_TS = `import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
9
|
-
import type { Entity as IEntity } from "@decohere/data";
|
|
10
|
-
|
|
11
|
-
@Entity()
|
|
12
|
-
export class UserEntity implements IEntity {
|
|
13
|
-
\t@PrimaryGeneratedColumn()
|
|
14
|
-
\tpublic id!: number;
|
|
15
|
-
|
|
16
|
-
\t@Column({ unique: true })
|
|
17
|
-
\tpublic sub!: string;
|
|
18
|
-
|
|
19
|
-
\t@Column({ type: "varchar", nullable: true })
|
|
20
|
-
\tpublic email!: string | null;
|
|
21
|
-
|
|
22
|
-
\t@Column()
|
|
23
|
-
\tpublic displayName!: string;
|
|
24
|
-
|
|
25
|
-
\t/** App-owned roles. The \`admin\` role grants CASL \`manage\`/\`all\` (wildcard). */
|
|
26
|
-
\t@Column({ type: "simple-json", default: "[]" })
|
|
27
|
-
\tpublic roles!: string[];
|
|
28
|
-
}
|
|
29
|
-
`;
|
|
30
|
-
const USER_REPOSITORY_TS = `import { injectable } from "@decohere/crucible/decorators";
|
|
31
|
-
import { injectUnitOfWork } from "@decohere/data";
|
|
32
|
-
import { TypeOrmRepository, TypeOrmUnitOfWork } from "@decohere/data-typeorm";
|
|
33
|
-
import { UserEntity } from "../entities/user.entity.js";
|
|
34
|
-
|
|
35
|
-
@injectable()
|
|
36
|
-
export class UserEntityRepository extends TypeOrmRepository<UserEntity> {
|
|
37
|
-
\tpublic constructor(@injectUnitOfWork() unitOfWork: TypeOrmUnitOfWork) {
|
|
38
|
-
\t\tsuper(unitOfWork, UserEntity);
|
|
39
|
-
\t}
|
|
40
|
-
|
|
41
|
-
\tpublic findBySub(sub: string): Promise<UserEntity | null> {
|
|
42
|
-
\t\treturn this.firstOrDefault({ where: { sub } });
|
|
43
|
-
\t}
|
|
44
|
-
}
|
|
45
|
-
`;
|
|
46
|
-
const USER_SERVICE_TS = `import { injectable } from "@decohere/crucible/decorators";
|
|
47
|
-
import type { Principal } from "@decohere/http";
|
|
48
|
-
import { UserEntity } from "../entities/user.entity.js";
|
|
49
|
-
import { UserEntityRepository } from "../repositories/user.repository.js";
|
|
50
|
-
|
|
51
|
-
function claimString(principal: Principal | undefined, key: string): string | undefined {
|
|
52
|
-
\tconst value = principal?.[key];
|
|
53
|
-
\treturn typeof value === "string" && value.length > 0 ? value : undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@injectable()
|
|
57
|
-
export class UserService {
|
|
58
|
-
\tpublic constructor(private readonly users: UserEntityRepository) {}
|
|
59
|
-
|
|
60
|
-
\t/**
|
|
61
|
-
\t * Upsert a local profile keyed by JWT \`sub\`.
|
|
62
|
-
\t * Profile fields from the token fill gaps; seeded rows keep displayName/email/roles.
|
|
63
|
-
\t */
|
|
64
|
-
\tpublic async ensureFromPrincipal(principal: Principal | undefined): Promise<UserEntity> {
|
|
65
|
-
\t\tconst sub = principal?.sub;
|
|
66
|
-
\t\tif (!sub) {
|
|
67
|
-
\t\t\tthrow new Error("Authenticated request is missing principal.sub");
|
|
68
|
-
\t\t}
|
|
69
|
-
|
|
70
|
-
\t\tconst email =
|
|
71
|
-
\t\t\tclaimString(principal, "email") ?? claimString(principal, "preferred_username");
|
|
72
|
-
\t\tconst displayFromToken =
|
|
73
|
-
\t\t\tclaimString(principal, "name") ??
|
|
74
|
-
\t\t\tclaimString(principal, "nickname") ??
|
|
75
|
-
\t\t\temail;
|
|
76
|
-
|
|
77
|
-
\t\tconst existing = await this.users.findBySub(sub);
|
|
78
|
-
\t\tif (existing) {
|
|
79
|
-
\t\t\tlet dirty = false;
|
|
80
|
-
\t\t\tif (!existing.email && email) {
|
|
81
|
-
\t\t\t\texisting.email = email;
|
|
82
|
-
\t\t\t\tdirty = true;
|
|
83
|
-
\t\t\t}
|
|
84
|
-
\t\t\tif (existing.displayName === existing.sub && displayFromToken) {
|
|
85
|
-
\t\t\t\texisting.displayName = displayFromToken;
|
|
86
|
-
\t\t\t\tdirty = true;
|
|
87
|
-
\t\t\t}
|
|
88
|
-
\t\t\treturn dirty ? this.users.save(existing) : existing;
|
|
89
|
-
\t\t}
|
|
90
|
-
|
|
91
|
-
\t\tconst created = new UserEntity();
|
|
92
|
-
\t\tcreated.sub = sub;
|
|
93
|
-
\t\tcreated.email = email ?? null;
|
|
94
|
-
\t\tcreated.displayName = displayFromToken ?? sub;
|
|
95
|
-
\t\tcreated.roles = Array.isArray(principal.roles)
|
|
96
|
-
\t\t\t? principal.roles.filter((r): r is string => typeof r === "string")
|
|
97
|
-
\t\t\t: [];
|
|
98
|
-
\t\treturn this.users.save(created);
|
|
99
|
-
\t}
|
|
100
|
-
}
|
|
101
|
-
`;
|
|
102
|
-
const ABILITY_FACTORY_TS = `import type { Principal } from "@decohere/http";
|
|
103
|
-
import {
|
|
104
|
-
\tADMIN_ROLE,
|
|
105
|
-
\tcreatePrincipalFromPayload,
|
|
106
|
-
\tdefineAbilitiesFor as defaultDefineAbilitiesFor,
|
|
107
|
-
} from "@decohere/http";
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Customize CASL authorization here (product-specific rules).
|
|
111
|
-
*
|
|
112
|
-
* Default delegates to \`@decohere/http\`: \`admin\` role → \`manage\`/\`all\`; IdP scopes
|
|
113
|
-
* (\`action:Subject\`) → abilities. Replace the body when you need app rules
|
|
114
|
-
* beyond that — this file is the intentional extension point, not framework
|
|
115
|
-
* infrastructure to copy.
|
|
116
|
-
*/
|
|
117
|
-
export function defineAbilitiesFor(principal: Principal) {
|
|
118
|
-
\treturn defaultDefineAbilitiesFor(principal);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Customize JWT → Principal mapping here (IdP-specific claims).
|
|
123
|
-
*
|
|
124
|
-
* Elevates \`AUTH0_ADMIN_USER_SUB\` to the admin role so the seeded admin works
|
|
125
|
-
* before/without roles on the access token.
|
|
126
|
-
*/
|
|
127
|
-
export function mapPrincipal(payload: Record<string, unknown>): Principal {
|
|
128
|
-
\tconst principal = createPrincipalFromPayload(payload);
|
|
129
|
-
\tconst sub = principal.sub;
|
|
130
|
-
\tif (sub && sub === process.env.AUTH0_ADMIN_USER_SUB) {
|
|
131
|
-
\t\treturn {
|
|
132
|
-
\t\t\t...principal,
|
|
133
|
-
\t\t\troles: [...new Set([ADMIN_ROLE, ...(principal.roles ?? [])])],
|
|
134
|
-
\t\t};
|
|
135
|
-
\t}
|
|
136
|
-
\treturn principal;
|
|
137
|
-
}
|
|
138
|
-
`;
|
|
139
|
-
async function getOptions(workspace, cliOptions) {
|
|
140
|
-
if (cliOptions.authProvider === "auth0" && !cliOptions.issuerBaseUrl) {
|
|
141
|
-
return getAuth0Options(workspace, cliOptions);
|
|
142
|
-
}
|
|
143
|
-
if (cliOptions.authProvider != null) return {};
|
|
144
|
-
const choices = [
|
|
145
|
-
{
|
|
146
|
-
label: "none"
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
label: "auth0"
|
|
150
|
-
}
|
|
151
|
-
];
|
|
152
|
-
const selected = await pick("\n Auth provider: ", choices, (c)=>c.label);
|
|
153
|
-
const provider = selected?.label ?? "none";
|
|
154
|
-
if (provider !== "auth0") return {
|
|
155
|
-
authProvider: "none"
|
|
156
|
-
};
|
|
157
|
-
return getAuth0Options(workspace, cliOptions);
|
|
158
|
-
}
|
|
159
|
-
async function getAuth0Options(workspace, cliOptions) {
|
|
160
|
-
const active = ensureLoggedIn();
|
|
161
|
-
const issuerBaseUrl = `https://${active.name}`;
|
|
162
|
-
console.log(`\n ✓ Auth0 tenant: ${issuerBaseUrl}`);
|
|
163
|
-
process.stdout.write("\n Fetching Auth0 APIs...");
|
|
164
|
-
const userApis = auth0Cli.listApis().filter((a)=>!a.name.includes("Management API"));
|
|
165
|
-
process.stdout.write(` found ${userApis.length}.\n`);
|
|
166
|
-
const projectName = workspace.name;
|
|
167
|
-
const apiChoices = [
|
|
168
|
-
{
|
|
169
|
-
label: `<new> https://api.${projectName}`,
|
|
170
|
-
existing: null
|
|
171
|
-
},
|
|
172
|
-
...userApis.map((a)=>({
|
|
173
|
-
label: `${a.name} — ${a.identifier}`,
|
|
174
|
-
existing: a.identifier
|
|
175
|
-
}))
|
|
176
|
-
];
|
|
177
|
-
const apiSelected = await pick("\n Select Auth0 API: ", apiChoices, (c)=>c.label);
|
|
178
|
-
let audience;
|
|
179
|
-
if (!apiSelected || apiSelected.existing === null) {
|
|
180
|
-
const defaultId = `https://api.${projectName}`;
|
|
181
|
-
audience = await ask(` API Identifier [${defaultId}]: `, defaultId);
|
|
182
|
-
console.log("\n Creating Auth0 API...");
|
|
183
|
-
audience = auth0Cli.createApi(`${projectName} API`, audience).identifier;
|
|
184
|
-
console.log(` \u2713 API created: ${audience}`);
|
|
185
|
-
} else {
|
|
186
|
-
audience = apiSelected.existing;
|
|
187
|
-
console.log(` \u2713 Selected: ${audience}`);
|
|
188
|
-
try {
|
|
189
|
-
auth0Cli.updateApiScopes(audience);
|
|
190
|
-
console.log(` \u2713 API scopes ensured (${auth0Cli.HELLO_API_SCOPES.join(", ")})`);
|
|
191
|
-
} catch {
|
|
192
|
-
console.error(" ✗ Failed to update API scopes — add Hello scopes in the Auth0 dashboard.");
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const withLocalUsers = cliOptions.databaseProvider === "postgres/typeorm";
|
|
196
|
-
let adminUsername;
|
|
197
|
-
let adminPassword;
|
|
198
|
-
if (withLocalUsers) {
|
|
199
|
-
adminUsername = cliOptions.adminUsername ?? await ask("\n Admin username [admin]: ", "admin");
|
|
200
|
-
adminPassword = cliOptions.adminPassword;
|
|
201
|
-
if (adminPassword == null) {
|
|
202
|
-
adminPassword = await ask(" Admin password (leave blank to generate): ");
|
|
203
|
-
if (!adminPassword) {
|
|
204
|
-
adminPassword = auth0Cli.generateAdminPassword();
|
|
205
|
-
console.log(` ✓ Generated admin password (written to .env): ${adminPassword}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
authProvider: "auth0",
|
|
211
|
-
issuerBaseUrl,
|
|
212
|
-
audience,
|
|
213
|
-
...withLocalUsers ? {
|
|
214
|
-
adminUsername,
|
|
215
|
-
adminPassword
|
|
216
|
-
} : {}
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function adminEmail(workspaceName, username) {
|
|
220
|
-
if (username.includes("@")) return username;
|
|
221
|
-
const slug = workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
222
|
-
return `${username}@${slug}.example.com`;
|
|
223
|
-
}
|
|
224
|
-
function createAuth0AdminUser(workspace, options, authConfig) {
|
|
225
|
-
const username = options.adminUsername ?? "admin";
|
|
226
|
-
const password = options.adminPassword;
|
|
227
|
-
if (!password) return;
|
|
228
|
-
const email = adminEmail(workspace.name, username);
|
|
229
|
-
try {
|
|
230
|
-
const admin = auth0Cli.createAdminDatabaseUser({
|
|
231
|
-
email,
|
|
232
|
-
password,
|
|
233
|
-
username
|
|
234
|
-
});
|
|
235
|
-
authConfig.setAdminUser({
|
|
236
|
-
sub: admin.user_id,
|
|
237
|
-
email: admin.email,
|
|
238
|
-
password: admin.password,
|
|
239
|
-
username: admin.username
|
|
240
|
-
});
|
|
241
|
-
workspace.getModel(WELL_KNOWN_FILES.ENV_FILE)?.addEntry("AUTH0_ADMIN_USER_SUB", admin.user_id).addEntry("AUTH0_ADMIN_USER_EMAIL", admin.email).addEntry("AUTH0_ADMIN_USER_PASSWORD", admin.password).addEntry("AUTH0_ADMIN_USERNAME", admin.username);
|
|
242
|
-
console.log(` ✓ Auth0 admin user ready: ${admin.email} (role: ${ADMIN_ROLE})`);
|
|
243
|
-
console.log(` Login with EMAIL (not username): ${admin.email}`);
|
|
244
|
-
console.log(` Password: see AUTH0_ADMIN_USER_PASSWORD in .env`);
|
|
245
|
-
} catch (err) {
|
|
246
|
-
console.error(` ✗ Failed to ensure Auth0 admin user (${email}): ${err instanceof Error ? err.message : err}`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function scaffoldLocalUserProfile(workspace) {
|
|
250
|
-
for (const project of workspace.getProjects()){
|
|
251
|
-
project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addEntity({
|
|
252
|
-
className: "UserEntity",
|
|
253
|
-
importPath: "./entities/user.entity.js"
|
|
254
|
-
}).addRepository({
|
|
255
|
-
className: "UserEntityRepository",
|
|
256
|
-
importPath: "./repositories/user.repository.js"
|
|
257
|
-
}).addService({
|
|
258
|
-
className: "UserService",
|
|
259
|
-
importPath: "./services/user.service.js",
|
|
260
|
-
scope: "request"
|
|
261
|
-
}).addImport("defineAbilitiesFor", "./auth/ability.factory.js").addImport("mapPrincipal", "./auth/ability.factory.js").setTypeOrmSeed([
|
|
262
|
-
"const sub = process.env.AUTH0_ADMIN_USER_SUB;",
|
|
263
|
-
"if (!sub) return;",
|
|
264
|
-
"const repo = dataSource.getRepository(UserEntity);",
|
|
265
|
-
"const existing = await repo.findOne({ where: { sub } });",
|
|
266
|
-
"if (existing) return;",
|
|
267
|
-
"await repo.save(repo.create({",
|
|
268
|
-
" sub,",
|
|
269
|
-
" email: process.env.AUTH0_ADMIN_USER_EMAIL ?? null,",
|
|
270
|
-
' displayName: process.env.AUTH0_ADMIN_USERNAME ?? "admin",',
|
|
271
|
-
' roles: ["admin"],',
|
|
272
|
-
"}));"
|
|
273
|
-
].join(" "));
|
|
274
|
-
project.addFile({
|
|
275
|
-
files: ()=>[
|
|
276
|
-
{
|
|
277
|
-
filePath: "src/entities/user.entity.ts",
|
|
278
|
-
content: USER_ENTITY_TS
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
filePath: "src/repositories/user.repository.ts",
|
|
282
|
-
content: USER_REPOSITORY_TS
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
filePath: "src/services/user.service.ts",
|
|
286
|
-
content: USER_SERVICE_TS
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
filePath: "src/auth/ability.factory.ts",
|
|
290
|
-
content: ABILITY_FACTORY_TS
|
|
291
|
-
}
|
|
292
|
-
]
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
for (const controller of workspace.getModels(ControllerSourceFile)){
|
|
296
|
-
controller.addImport("UserService", "../services/user.service.js").addImport("inject", "@decohere/crucible/decorators").addConstructorParam("@inject(UserService) private readonly users: UserService");
|
|
297
|
-
controller.getMethod("getOne").setAsync(true).setBody([
|
|
298
|
-
"const user = await this.users.ensureFromPrincipal(this.principalProvider.currentPrincipal);",
|
|
299
|
-
"return { id, message: `Hello ${user.displayName}!`, username: user.displayName };"
|
|
300
|
-
]);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function scaffold(workspace, options) {
|
|
304
|
-
if (options.authProvider !== "auth0") return;
|
|
305
|
-
const resource = "Hello";
|
|
306
|
-
const withLocalUsers = options.databaseProvider === "postgres/typeorm";
|
|
307
|
-
for (const controller of workspace.getModels(ControllerSourceFile)){
|
|
308
|
-
controller.addDecorator(`@Authorize("read", "${resource}")`).addCortexImport("AllowAnonymous").addCortexImport("Authorize").addCortexImport("IPrincipalProviderToken").addCortexImport("IPrincipalProvider", true).addImport("inject", "@decohere/crucible/decorators").addConstructorParam(`@inject(IPrincipalProviderToken) private readonly principalProvider: IPrincipalProvider`);
|
|
309
|
-
controller.getMethod("getAll").addDecorator("@AllowAnonymous()");
|
|
310
|
-
if (!withLocalUsers) {
|
|
311
|
-
controller.getMethod("getOne").setBody([
|
|
312
|
-
"const principal = this.principalProvider.currentPrincipal;",
|
|
313
|
-
"const username =",
|
|
314
|
-
'\t(typeof principal?.name === "string" && principal.name) ||',
|
|
315
|
-
'\t(typeof principal?.nickname === "string" && principal.nickname) ||',
|
|
316
|
-
'\t(typeof principal?.email === "string" && principal.email) ||',
|
|
317
|
-
'\t(typeof principal?.preferred_username === "string" && principal.preferred_username) ||',
|
|
318
|
-
"\tprincipal?.sub ||",
|
|
319
|
-
'\t"unknown";',
|
|
320
|
-
"return { id, message: `Hello ${username}!`, username };"
|
|
321
|
-
]);
|
|
322
|
-
}
|
|
323
|
-
controller.getMethod("create").addDecorator(`@Authorize("create", "${resource}")`);
|
|
324
|
-
controller.getMethod("update").addDecorator(`@Authorize("update", "${resource}")`);
|
|
325
|
-
controller.getMethod("remove").addDecorator(`@Authorize("delete", "${resource}")`);
|
|
326
|
-
}
|
|
327
|
-
for (const project of workspace.getProjects()){
|
|
328
|
-
project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addSideEffectImport("@decohere/http/auth");
|
|
329
|
-
}
|
|
330
|
-
if (withLocalUsers) {
|
|
331
|
-
scaffoldLocalUserProfile(workspace);
|
|
332
|
-
for (const project of workspace.getProjects()){
|
|
333
|
-
project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addBuilderCall(".withAuth(defineAbilitiesFor, { principalMapper: mapPrincipal })");
|
|
334
|
-
}
|
|
335
|
-
} else {
|
|
336
|
-
for (const project of workspace.getProjects()){
|
|
337
|
-
project.getModel(WELL_KNOWN_FILES.MAIN_TS)?.addBuilderCall(".withAuth()");
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
const issuerPlaceholder = "${AUTH0_ISSUER_BASE_URL}";
|
|
341
|
-
const audPlaceholder = "${AUTH0_AUDIENCE}";
|
|
342
|
-
const configSection = `auth:\n discoveryUrl: ${issuerPlaceholder}\n audience: ${audPlaceholder}\n`;
|
|
343
|
-
workspace.getModel(WELL_KNOWN_FILES.CONFIG_YAML)?.addSection(configSection);
|
|
344
|
-
workspace.getModel(WELL_KNOWN_FILES.ENV_FILE)?.addEntry("AUTH0_ISSUER_BASE_URL", options.issuerBaseUrl ?? "").addEntry("AUTH0_AUDIENCE", options.audience ?? "");
|
|
345
|
-
const authConfig = new AuthConfigModel({
|
|
346
|
-
issuerBaseUrl: options.issuerBaseUrl ?? "",
|
|
347
|
-
audience: options.audience ?? ""
|
|
348
|
-
});
|
|
349
|
-
workspace.addModel(authConfig);
|
|
350
|
-
if (withLocalUsers && options.adminPassword) {
|
|
351
|
-
createAuth0AdminUser(workspace, options, authConfig);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
export const AuthFeature = {
|
|
355
|
-
getOptions,
|
|
356
|
-
scaffold
|
|
357
|
-
};
|