@monster-orange/den 0.2.8 → 0.4.0
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/CHANGELOG.md +1731 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +730 -6
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +12 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +173 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +192 -0
- package/src/shared/agents/runtimes.ts +47 -0
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +257 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/sessions/client.ts +277 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +79 -0
- package/src/shared/sessions/host.ts +667 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/agents-command.ts +812 -0
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +260 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +18 -5
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +580 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/sessions-command.ts +718 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +145 -20
- package/src/ui/mcp/server.ts +27 -2
- package/src/ui/mcp/tools.ts +94 -12
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/audit.ts +35 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +539 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +37 -3
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +48 -18
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process groups that a detached `up` started (0.3.0-dev.37).
|
|
3
|
+
*
|
|
4
|
+
* A detached service is spawned as the leader of its own process group, so
|
|
5
|
+
* its pid is the group id. `up` records that pgid together with the leader's
|
|
6
|
+
* start time, and `down` signals the whole group: SIGTERM, a short grace
|
|
7
|
+
* period, then SIGKILL.
|
|
8
|
+
*
|
|
9
|
+
* A recorded pid can be reused once its process exits, so nothing is
|
|
10
|
+
* signalled until the group is shown to still be the one `up` started:
|
|
11
|
+
*
|
|
12
|
+
* - the leader (pid == pgid) is alive: its start time must equal the
|
|
13
|
+
* recorded one, and it must still lead that group. Anything else is a
|
|
14
|
+
* different process that happens to hold the pid, and it is left alone.
|
|
15
|
+
* - the leader has exited but members still carry the pgid: the group is
|
|
16
|
+
* still ours. A process group id is not handed out again while any
|
|
17
|
+
* process is in that group, so live members of it are ones the leader
|
|
18
|
+
* started.
|
|
19
|
+
* - nothing carries the pgid: the group has exited.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { execFile, execFileSync } from "child_process";
|
|
23
|
+
|
|
24
|
+
export interface RecordedProcessGroup {
|
|
25
|
+
/** The group id: the detached leader's pid. */
|
|
26
|
+
pgid: number;
|
|
27
|
+
/** The leader's start time as `ps -o lstart=` prints it. */
|
|
28
|
+
startedAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type GroupState =
|
|
32
|
+
| { state: "ours"; members: number[] }
|
|
33
|
+
| { state: "gone" }
|
|
34
|
+
| { state: "reused"; detail: string };
|
|
35
|
+
|
|
36
|
+
export type StopGroupOutcome =
|
|
37
|
+
| { outcome: "stopped"; signal: "SIGTERM" | "SIGKILL"; members: number[] }
|
|
38
|
+
| { outcome: "gone" }
|
|
39
|
+
| { outcome: "reused"; detail: string }
|
|
40
|
+
| { outcome: "survived"; members: number[] }
|
|
41
|
+
| { outcome: "error"; detail: string };
|
|
42
|
+
|
|
43
|
+
interface PsRow {
|
|
44
|
+
pid: number;
|
|
45
|
+
pgid: number;
|
|
46
|
+
zombie: boolean;
|
|
47
|
+
lstart: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PS = "ps";
|
|
51
|
+
// lstart is printed in the C locale's fixed format, whatever the user's.
|
|
52
|
+
const PS_ENV = { ...process.env, LC_ALL: "C", LANG: "C" };
|
|
53
|
+
|
|
54
|
+
function parsePs(out: string): PsRow[] {
|
|
55
|
+
const rows: PsRow[] = [];
|
|
56
|
+
for (const line of out.split("\n")) {
|
|
57
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
|
|
58
|
+
if (!m) continue;
|
|
59
|
+
rows.push({
|
|
60
|
+
pid: Number(m[1]),
|
|
61
|
+
pgid: Number(m[2]),
|
|
62
|
+
zombie: m[3].startsWith("Z"),
|
|
63
|
+
lstart: m[4].trim().replace(/\s+/g, " "),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return rows;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const PS_COLUMNS = ["-o", "pid=,pgid=,stat=,lstart="];
|
|
70
|
+
const PS_ARGS = ["-A", ...PS_COLUMNS];
|
|
71
|
+
|
|
72
|
+
function psRowsSync(args: string[]): PsRow[] | null {
|
|
73
|
+
try {
|
|
74
|
+
return parsePs(
|
|
75
|
+
execFileSync(PS, args, {
|
|
76
|
+
env: PS_ENV,
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
79
|
+
timeout: 5000,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function psRows(): Promise<PsRow[] | null> {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
execFile(
|
|
90
|
+
PS,
|
|
91
|
+
PS_ARGS,
|
|
92
|
+
{ env: PS_ENV, encoding: "utf-8", timeout: 5000 },
|
|
93
|
+
(err, stdout) => resolve(err ? null : parsePs(stdout)),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The record `up` keeps for a detached leader it just spawned, or null when
|
|
100
|
+
* the process cannot be read (it already exited, or `ps` is unavailable).
|
|
101
|
+
*/
|
|
102
|
+
export function recordProcessGroup(pid: number): RecordedProcessGroup | null {
|
|
103
|
+
const row = psRowsSync(["-p", String(pid), ...PS_COLUMNS])?.find(
|
|
104
|
+
(r) => r.pid === pid && !r.zombie,
|
|
105
|
+
);
|
|
106
|
+
if (!row || row.pgid !== pid) return null;
|
|
107
|
+
return { pgid: pid, startedAt: row.lstart };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A recorded group's state now, synchronously; null when `ps` cannot be run. */
|
|
111
|
+
export function processGroupStateSync(
|
|
112
|
+
rec: RecordedProcessGroup,
|
|
113
|
+
): GroupState | null {
|
|
114
|
+
const rows = psRowsSync(PS_ARGS);
|
|
115
|
+
return rows ? classifyGroup(rec, rows) : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Whether a recorded group is still the one `up` started (see the header). */
|
|
119
|
+
export function classifyGroup(
|
|
120
|
+
rec: RecordedProcessGroup,
|
|
121
|
+
rows: PsRow[],
|
|
122
|
+
): GroupState {
|
|
123
|
+
const live = rows.filter((r) => !r.zombie);
|
|
124
|
+
const leader = live.find((r) => r.pid === rec.pgid);
|
|
125
|
+
if (leader && (leader.lstart !== rec.startedAt || leader.pgid !== rec.pgid))
|
|
126
|
+
return {
|
|
127
|
+
state: "reused",
|
|
128
|
+
detail: `pid ${rec.pgid} now belongs to another process (started ${leader.lstart}; the recorded one started ${rec.startedAt})`,
|
|
129
|
+
};
|
|
130
|
+
const members = live.filter((r) => r.pgid === rec.pgid).map((r) => r.pid);
|
|
131
|
+
return members.length > 0 ? { state: "ours", members } : { state: "gone" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The recorded group's state now; null when `ps` cannot be run. */
|
|
135
|
+
export async function processGroupState(
|
|
136
|
+
rec: RecordedProcessGroup,
|
|
137
|
+
): Promise<GroupState | null> {
|
|
138
|
+
const rows = await psRows();
|
|
139
|
+
return rows ? classifyGroup(rec, rows) : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function signalGroup(
|
|
143
|
+
pgid: number,
|
|
144
|
+
signal: NodeJS.Signals,
|
|
145
|
+
): "sent" | "gone" | string {
|
|
146
|
+
try {
|
|
147
|
+
process.kill(-pgid, signal);
|
|
148
|
+
return "sent";
|
|
149
|
+
} catch (e) {
|
|
150
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
151
|
+
if (code === "ESRCH") return "gone";
|
|
152
|
+
return `${signal} to process group ${pgid} failed: ${code ?? (e as Error).message}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
157
|
+
|
|
158
|
+
async function waitUntilGone(
|
|
159
|
+
rec: RecordedProcessGroup,
|
|
160
|
+
ms: number,
|
|
161
|
+
): Promise<GroupState | null> {
|
|
162
|
+
const deadline = Date.now() + ms;
|
|
163
|
+
let state = await processGroupState(rec);
|
|
164
|
+
while (state?.state === "ours" && Date.now() < deadline) {
|
|
165
|
+
await sleep(100);
|
|
166
|
+
state = await processGroupState(rec);
|
|
167
|
+
}
|
|
168
|
+
return state;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Stop a recorded process group: SIGTERM, up to `graceMs` for it to exit,
|
|
173
|
+
* then SIGKILL. The group is re-verified before each signal, so a pid that
|
|
174
|
+
* was reused in between is never signalled.
|
|
175
|
+
*/
|
|
176
|
+
export async function stopProcessGroup(
|
|
177
|
+
rec: RecordedProcessGroup,
|
|
178
|
+
graceMs = 3000,
|
|
179
|
+
): Promise<StopGroupOutcome> {
|
|
180
|
+
const before = await processGroupState(rec);
|
|
181
|
+
if (!before) return { outcome: "error", detail: "could not run ps" };
|
|
182
|
+
if (before.state === "gone") return { outcome: "gone" };
|
|
183
|
+
if (before.state === "reused")
|
|
184
|
+
return { outcome: "reused", detail: before.detail };
|
|
185
|
+
|
|
186
|
+
const term = signalGroup(rec.pgid, "SIGTERM");
|
|
187
|
+
if (term === "gone") return { outcome: "gone" };
|
|
188
|
+
if (term !== "sent") return { outcome: "error", detail: term };
|
|
189
|
+
|
|
190
|
+
const afterTerm = await waitUntilGone(rec, graceMs);
|
|
191
|
+
if (!afterTerm) return { outcome: "error", detail: "could not run ps" };
|
|
192
|
+
if (afterTerm.state !== "ours")
|
|
193
|
+
return { outcome: "stopped", signal: "SIGTERM", members: before.members };
|
|
194
|
+
|
|
195
|
+
const kill = signalGroup(rec.pgid, "SIGKILL");
|
|
196
|
+
if (kill !== "sent" && kill !== "gone")
|
|
197
|
+
return { outcome: "error", detail: kill };
|
|
198
|
+
const afterKill = await waitUntilGone(rec, 2000);
|
|
199
|
+
if (afterKill?.state === "ours")
|
|
200
|
+
return { outcome: "survived", members: afterKill.members };
|
|
201
|
+
return { outcome: "stopped", signal: "SIGKILL", members: before.members };
|
|
202
|
+
}
|
|
@@ -74,6 +74,7 @@ export function spawnDaemon(
|
|
|
74
74
|
onClose: (exitCode: number) => void,
|
|
75
75
|
cwd?: string,
|
|
76
76
|
onServiceExit?: (exitCode: number) => void,
|
|
77
|
+
onSpawn?: (pid: number) => void,
|
|
77
78
|
): ReturnType<typeof spawn> {
|
|
78
79
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
79
80
|
|
|
@@ -112,6 +113,9 @@ export function spawnDaemon(
|
|
|
112
113
|
onLine(`Error: ${err.message}`);
|
|
113
114
|
onServiceExit?.(1);
|
|
114
115
|
});
|
|
116
|
+
// The service leads its own process group (detached), so its pid is the
|
|
117
|
+
// group id `down` signals later.
|
|
118
|
+
if (onSpawn && svcProc.pid) onSpawn(svcProc.pid);
|
|
115
119
|
svcProc.unref(); // den's event loop can exit independently
|
|
116
120
|
fs.closeSync(logFd); // parent closes its copy; child keeps its own fd
|
|
117
121
|
|
|
@@ -148,6 +152,7 @@ export function spawnDaemon(
|
|
|
148
152
|
* @param cwd Working directory (defaults to process.cwd())
|
|
149
153
|
* @param detached Put the child in its own process group (default false)
|
|
150
154
|
* @param logPath If provided together with detached=true, run as a daemon writing to this path
|
|
155
|
+
* @param onSpawn Daemon mode: called with the service's pid (its process group id) once spawned
|
|
151
156
|
*/
|
|
152
157
|
export function spawnStreaming(
|
|
153
158
|
command: string,
|
|
@@ -159,6 +164,7 @@ export function spawnStreaming(
|
|
|
159
164
|
detached?: boolean,
|
|
160
165
|
logPath?: string,
|
|
161
166
|
onServiceExit?: (exitCode: number) => void,
|
|
167
|
+
onSpawn?: (pid: number) => void,
|
|
162
168
|
): ReturnType<typeof spawn> {
|
|
163
169
|
// Daemon mode: service runs independently from den — survives restarts.
|
|
164
170
|
if (detached && logPath) {
|
|
@@ -170,6 +176,7 @@ export function spawnStreaming(
|
|
|
170
176
|
onClose,
|
|
171
177
|
cwd,
|
|
172
178
|
onServiceExit,
|
|
179
|
+
onSpawn,
|
|
173
180
|
);
|
|
174
181
|
}
|
|
175
182
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which environment variables are credentials, by name or by value. One
|
|
3
|
+
* set of patterns for everything that checks an environment: the test
|
|
4
|
+
* workers (tests/scrub-env.ts), the audit of `sessions start --keep-env`,
|
|
5
|
+
* and `<cli> doctor --probe` (with the text redactor's own patterns,
|
|
6
|
+
* redact.ts). They err on the side of flagging: a harmless variable called
|
|
7
|
+
* a credential costs less than a token called harmless.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here returns or logs a value.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { redactSecrets, SECRET_PATTERNS } from "./redact.js";
|
|
13
|
+
|
|
14
|
+
/** Variable names that hold credentials. */
|
|
15
|
+
const SECRET_NAME =
|
|
16
|
+
/(TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|API_?KEY|PRIVATE_KEY|ACCESS_KEY|AUTH)/i;
|
|
17
|
+
|
|
18
|
+
/** Whole families of credential variables. */
|
|
19
|
+
const SECRET_PREFIX = /^(OP_|DOPPLER_|AWS_|GH_|GITHUB_|NPM_|ANTHROPIC_)/i;
|
|
20
|
+
|
|
21
|
+
/** Values shaped like well-known tokens, whatever the variable is called. */
|
|
22
|
+
const SECRET_VALUE =
|
|
23
|
+
/(gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|ops_[A-Za-z0-9]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|dp\.(st|ct|sa|scim)\.[A-Za-z0-9]{20,}|npm_[A-Za-z0-9]{20,}|xox[abpr]-[A-Za-z0-9-]{10,})/;
|
|
24
|
+
|
|
25
|
+
/** Whether a variable looks like a credential, by name or by value. */
|
|
26
|
+
export function isCredentialEnv(
|
|
27
|
+
name: string,
|
|
28
|
+
value: string | undefined,
|
|
29
|
+
): boolean {
|
|
30
|
+
return (
|
|
31
|
+
SECRET_NAME.test(name) ||
|
|
32
|
+
SECRET_PREFIX.test(name) ||
|
|
33
|
+
(value !== undefined && SECRET_VALUE.test(value))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Credential-shaped: by name or value (isCredentialEnv), or a value the
|
|
39
|
+
* text redactor would redact.
|
|
40
|
+
*/
|
|
41
|
+
export function isCredentialShaped(
|
|
42
|
+
name: string,
|
|
43
|
+
value: string | undefined,
|
|
44
|
+
): boolean {
|
|
45
|
+
return (
|
|
46
|
+
isCredentialEnv(name, value) ||
|
|
47
|
+
(value !== undefined && value !== "" && redactSecrets(value) !== value)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every pattern isCredentialShaped uses, as plain data (source and flags,
|
|
53
|
+
* global flag dropped), for a check run in another process (the probe).
|
|
54
|
+
*/
|
|
55
|
+
export function credentialPatternData(): {
|
|
56
|
+
name: { source: string; flags: string }[];
|
|
57
|
+
value: { source: string; flags: string }[];
|
|
58
|
+
} {
|
|
59
|
+
const data = (re: RegExp) => ({
|
|
60
|
+
source: re.source,
|
|
61
|
+
flags: re.flags.replace("g", ""),
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
name: [SECRET_NAME, SECRET_PREFIX].map(data),
|
|
65
|
+
value: [SECRET_VALUE, ...SECRET_PATTERNS.map((p) => p.re)].map(data),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** An environment variable name (`--keep-env NAME`). */
|
|
70
|
+
export const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Doppler strategy — reads/writes flat keys with the `doppler` CLI.
|
|
3
|
-
* Selected with `secrets.provider: doppler
|
|
3
|
+
* Selected with `secrets.provider: doppler`.
|
|
4
4
|
*
|
|
5
5
|
* Project/config come from `secrets.doppler.{project,config}` in
|
|
6
6
|
* config/den.yml, else from the directory's `doppler setup` scope,
|