pi-harness-runtime 1.1.394 → 1.1.395
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/AGENTS.md +125 -3
- package/harness/glm-quota-logger.ts +17 -0
- package/harness/langchain/planner-logger.ts +15 -0
- package/harness/service-diagnostics.ts +32 -1
- package/index.ts +92 -3
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-env-format.d.ts +63 -0
- package/packages/contracts/dist/workspace-env-format.js +133 -5
- package/packages/contracts/dist/workspace-env.js +114 -6
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env-format.ts +171 -5
- package/packages/contracts/src/workspace-env.ts +120 -4
- package/packages/contracts/test/workspace-env.test.ts +129 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/continuation-controller.d.ts +47 -0
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +142 -16
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +6 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +6 -0
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
- package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-ledger.js +234 -0
- package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
- package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
- package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-plan.js +293 -0
- package/packages/jev-judge/dist/validation-plan.js.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +179 -20
- package/packages/jev-judge/src/index.ts +41 -0
- package/packages/jev-judge/src/validation-ledger.ts +290 -0
- package/packages/jev-judge/src/validation-plan.ts +406 -0
- package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
- package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
- package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
- package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
- package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/README.md +37 -0
- package/packages/logging/dist/buffer.d.ts +33 -0
- package/packages/logging/dist/buffer.d.ts.map +1 -0
- package/packages/logging/dist/buffer.js +60 -0
- package/packages/logging/dist/buffer.js.map +1 -0
- package/packages/logging/dist/config.d.ts +40 -0
- package/packages/logging/dist/config.d.ts.map +1 -0
- package/packages/logging/dist/config.js +114 -0
- package/packages/logging/dist/config.js.map +1 -0
- package/packages/logging/dist/index.d.ts +32 -0
- package/packages/logging/dist/index.d.ts.map +1 -0
- package/packages/logging/dist/index.js +31 -0
- package/packages/logging/dist/index.js.map +1 -0
- package/packages/logging/dist/redact.d.ts +43 -0
- package/packages/logging/dist/redact.d.ts.map +1 -0
- package/packages/logging/dist/redact.js +220 -0
- package/packages/logging/dist/redact.js.map +1 -0
- package/packages/logging/dist/sampler.d.ts +37 -0
- package/packages/logging/dist/sampler.d.ts.map +1 -0
- package/packages/logging/dist/sampler.js +78 -0
- package/packages/logging/dist/sampler.js.map +1 -0
- package/packages/logging/dist/shipper.d.ts +82 -0
- package/packages/logging/dist/shipper.d.ts.map +1 -0
- package/packages/logging/dist/shipper.js +238 -0
- package/packages/logging/dist/shipper.js.map +1 -0
- package/packages/logging/dist/singleton.d.ts +38 -0
- package/packages/logging/dist/singleton.d.ts.map +1 -0
- package/packages/logging/dist/singleton.js +95 -0
- package/packages/logging/dist/singleton.js.map +1 -0
- package/packages/logging/dist/spool.d.ts +39 -0
- package/packages/logging/dist/spool.d.ts.map +1 -0
- package/packages/logging/dist/spool.js +110 -0
- package/packages/logging/dist/spool.js.map +1 -0
- package/packages/logging/dist/transports/r2.d.ts +31 -0
- package/packages/logging/dist/transports/r2.d.ts.map +1 -0
- package/packages/logging/dist/transports/r2.js +60 -0
- package/packages/logging/dist/transports/r2.js.map +1 -0
- package/packages/logging/dist/transports/webhook.d.ts +22 -0
- package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
- package/packages/logging/dist/transports/webhook.js +37 -0
- package/packages/logging/dist/transports/webhook.js.map +1 -0
- package/packages/logging/dist/types.d.ts +73 -0
- package/packages/logging/dist/types.d.ts.map +1 -0
- package/packages/logging/dist/types.js +19 -0
- package/packages/logging/dist/types.js.map +1 -0
- package/packages/logging/package.json +37 -0
- package/packages/logging/src/buffer.ts +77 -0
- package/packages/logging/src/config.ts +163 -0
- package/packages/logging/src/index.ts +87 -0
- package/packages/logging/src/redact.ts +271 -0
- package/packages/logging/src/sampler.ts +97 -0
- package/packages/logging/src/shipper.ts +281 -0
- package/packages/logging/src/singleton.ts +142 -0
- package/packages/logging/src/spool.ts +139 -0
- package/packages/logging/src/transports/r2.ts +86 -0
- package/packages/logging/src/transports/webhook.ts +54 -0
- package/packages/logging/src/types.ts +85 -0
- package/packages/logging/test/buffer.test.ts +44 -0
- package/packages/logging/test/config.test.ts +97 -0
- package/packages/logging/test/integration.test.ts +135 -0
- package/packages/logging/test/redact.test.ts +109 -0
- package/packages/logging/test/sampler.test.ts +57 -0
- package/packages/logging/test/shipper.test.ts +152 -0
- package/packages/logging/test/spool.test.ts +93 -0
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/unified-webhook-server.ts +102 -0
- package/scripts/validation-run.ts +191 -0
|
@@ -135,6 +135,14 @@ export declare const MASKED_MODE_VALUE = "<set>";
|
|
|
135
135
|
* the name of an environment before its value may decide the mode.
|
|
136
136
|
*/
|
|
137
137
|
export declare function isEnvShapedKey(key: string): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
140
|
+
*
|
|
141
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
142
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
143
|
+
* of a workspace, so such a variable must never name the mode.
|
|
144
|
+
*/
|
|
145
|
+
export declare function isSessionVariableKey(key: string): boolean;
|
|
138
146
|
/**
|
|
139
147
|
* True for a key that may hold a secret.
|
|
140
148
|
*/
|
|
@@ -202,6 +210,61 @@ export interface ModeResolution {
|
|
|
202
210
|
hint: DotenvVar | null;
|
|
203
211
|
source: "process.env" | ".env file" | null;
|
|
204
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The deployment configuration of a repository.
|
|
215
|
+
*
|
|
216
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
217
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
218
|
+
* cannot prove that the workspace is a development checkout.
|
|
219
|
+
*/
|
|
220
|
+
export declare const DEPLOYMENT_MARKER_FILES: readonly ["Dockerfile", "Containerfile", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "Procfile", "fly.toml", "render.yaml", "railway.json", "vercel.json", "netlify.toml", "serverless.yml", "serverless.yaml", "app.yaml", "ecosystem.config.cjs", "ecosystem.config.js"];
|
|
221
|
+
/**
|
|
222
|
+
* The facts about the repository the workspace belongs to.
|
|
223
|
+
*
|
|
224
|
+
* They are read from the file system (and from git), never from a variable:
|
|
225
|
+
* a published package that is deployed nowhere is source code that the
|
|
226
|
+
* user runs on their own machine.
|
|
227
|
+
*/
|
|
228
|
+
export interface RepositoryFacts {
|
|
229
|
+
/** The workspace is a git checkout. */
|
|
230
|
+
isGitRepository: boolean;
|
|
231
|
+
/** The current branch ("" when it cannot be read). */
|
|
232
|
+
branch: string;
|
|
233
|
+
/** The package.json describes a package that is published to a registry. */
|
|
234
|
+
isPublishedPackage: boolean;
|
|
235
|
+
/** The repository carries a deployment configuration. */
|
|
236
|
+
hasDeploymentMarker: boolean;
|
|
237
|
+
/** A dev or build server runs in this workspace. */
|
|
238
|
+
hasRunningServer: boolean;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* A mode that the facts about the repository named.
|
|
242
|
+
*/
|
|
243
|
+
export interface RepositoryModeDetection {
|
|
244
|
+
mode: WorkspaceMode;
|
|
245
|
+
/**
|
|
246
|
+
* The phrase that names the detection.
|
|
247
|
+
*
|
|
248
|
+
* It is used as the key of the evidence ("source-only repository",
|
|
249
|
+
* "git branch develop"), so the label can name it the same way it
|
|
250
|
+
* names a variable.
|
|
251
|
+
*/
|
|
252
|
+
key: string;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
256
|
+
*
|
|
257
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
258
|
+
* variable names the environment at all. It never claims production - a
|
|
259
|
+
* checkout of a production branch is not a production environment, and the
|
|
260
|
+
* deployment marker of the repository keeps the answer honest.
|
|
261
|
+
*
|
|
262
|
+
* Two sets of facts name a development workspace:
|
|
263
|
+
* 1. a published package without a deployment configuration and without a
|
|
264
|
+
* running server: source code that the user runs on their own machine
|
|
265
|
+
* 2. a branch that names a development line of work ("develop")
|
|
266
|
+
*/
|
|
267
|
+
export declare function modeFromRepositoryFacts(facts: RepositoryFacts): RepositoryModeDetection | null;
|
|
205
268
|
/**
|
|
206
269
|
* Resolve the workspace mode from the variables that were found.
|
|
207
270
|
*
|
|
@@ -167,6 +167,49 @@ export const MASKED_MODE_VALUE = "<set>";
|
|
|
167
167
|
export function isEnvShapedKey(key) {
|
|
168
168
|
return /(^|_)(ENV|ENVIRONMENT|MODE|STAGE)$/i.test(key);
|
|
169
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* The namespaces of the desktop session variables.
|
|
172
|
+
*
|
|
173
|
+
* A session variable names the desktop session ("GNOME_SHELL_SESSION_MODE",
|
|
174
|
+
* "XDG_SESSION_TYPE"), never the environment of a workspace. Some of them
|
|
175
|
+
* end in "_MODE" or "_ENV", so without this list they look like an
|
|
176
|
+
* environment selector and decide the mode of a workspace that has no
|
|
177
|
+
* environment at all.
|
|
178
|
+
*/
|
|
179
|
+
const SESSION_VARIABLE_PREFIXES = [
|
|
180
|
+
"GNOME_",
|
|
181
|
+
"XDG_",
|
|
182
|
+
"DESKTOP_",
|
|
183
|
+
"DBUS_",
|
|
184
|
+
"GDM_",
|
|
185
|
+
"SDDM_",
|
|
186
|
+
"LIGHTDM_",
|
|
187
|
+
"GTK_",
|
|
188
|
+
"QT_",
|
|
189
|
+
"MATE_",
|
|
190
|
+
"KDE_",
|
|
191
|
+
"PLASMA_",
|
|
192
|
+
"UNITY_",
|
|
193
|
+
"XFCE_",
|
|
194
|
+
"CINNAMON_",
|
|
195
|
+
"WAYLAND_",
|
|
196
|
+
"MIR_",
|
|
197
|
+
"I3_",
|
|
198
|
+
"TERM_",
|
|
199
|
+
"SSH_",
|
|
200
|
+
"TMUX_",
|
|
201
|
+
];
|
|
202
|
+
/**
|
|
203
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
204
|
+
*
|
|
205
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
206
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
207
|
+
* of a workspace, so such a variable must never name the mode.
|
|
208
|
+
*/
|
|
209
|
+
export function isSessionVariableKey(key) {
|
|
210
|
+
const upper = key.toUpperCase();
|
|
211
|
+
return SESSION_VARIABLE_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
212
|
+
}
|
|
170
213
|
/**
|
|
171
214
|
* True for a key that may hold a secret.
|
|
172
215
|
*/
|
|
@@ -212,6 +255,9 @@ export function detectModeHint(vars, options = {}) {
|
|
|
212
255
|
const nodeEnv = vars.find((v) => v.key === "NODE_ENV");
|
|
213
256
|
if (nodeEnv)
|
|
214
257
|
return nodeEnv;
|
|
258
|
+
// A session variable is not an environment selector: it must never
|
|
259
|
+
// become the hint that names the mode.
|
|
260
|
+
const usableVars = vars.filter((v) => !isSessionVariableKey(v.key));
|
|
215
261
|
// The value match is opt-in: a caller that passes the process env
|
|
216
262
|
// variables must not let an unrelated variable that happens to hold the
|
|
217
263
|
// word "development" decide the mode.
|
|
@@ -219,7 +265,7 @@ export function detectModeHint(vars, options = {}) {
|
|
|
219
265
|
// An environment shaped key whose value names a known mode wins: an
|
|
220
266
|
// earlier "APP_MODE=staging" or an empty "APP_ENV=" of the same file
|
|
221
267
|
// must not shadow the "AMOS_ENV=development" of the same file.
|
|
222
|
-
const usable =
|
|
268
|
+
const usable = usableVars.find((v) => v.key !== "NODE_ENV" &&
|
|
223
269
|
isEnvShapedKey(v.key) &&
|
|
224
270
|
modeFromValue(v.value) !== null);
|
|
225
271
|
if (usable)
|
|
@@ -227,7 +273,7 @@ export function detectModeHint(vars, options = {}) {
|
|
|
227
273
|
}
|
|
228
274
|
// No usable key: fall back to the first environment shaped key so that
|
|
229
275
|
// the label can name it (it names no known mode).
|
|
230
|
-
return
|
|
276
|
+
return (usableVars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null);
|
|
231
277
|
}
|
|
232
278
|
/**
|
|
233
279
|
* The dotenv files of a workspace, in the order they are read.
|
|
@@ -289,12 +335,71 @@ export function collectProcessEnvVars(env = process.env) {
|
|
|
289
335
|
const value = env[key];
|
|
290
336
|
if (value === undefined)
|
|
291
337
|
continue;
|
|
338
|
+
// A session variable of the desktop is never an environment
|
|
339
|
+
// selector, so it is not even collected as a candidate.
|
|
340
|
+
if (isSessionVariableKey(key))
|
|
341
|
+
continue;
|
|
292
342
|
if (key === "NODE_ENV" || isEnvShapedKey(key)) {
|
|
293
343
|
vars.push({ key, value });
|
|
294
344
|
}
|
|
295
345
|
}
|
|
296
346
|
return vars;
|
|
297
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* The deployment configuration of a repository.
|
|
350
|
+
*
|
|
351
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
352
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
353
|
+
* cannot prove that the workspace is a development checkout.
|
|
354
|
+
*/
|
|
355
|
+
export const DEPLOYMENT_MARKER_FILES = [
|
|
356
|
+
"Dockerfile",
|
|
357
|
+
"Containerfile",
|
|
358
|
+
"docker-compose.yml",
|
|
359
|
+
"docker-compose.yaml",
|
|
360
|
+
"compose.yml",
|
|
361
|
+
"compose.yaml",
|
|
362
|
+
"Procfile",
|
|
363
|
+
"fly.toml",
|
|
364
|
+
"render.yaml",
|
|
365
|
+
"railway.json",
|
|
366
|
+
"vercel.json",
|
|
367
|
+
"netlify.toml",
|
|
368
|
+
"serverless.yml",
|
|
369
|
+
"serverless.yaml",
|
|
370
|
+
"app.yaml",
|
|
371
|
+
"ecosystem.config.cjs",
|
|
372
|
+
"ecosystem.config.js",
|
|
373
|
+
];
|
|
374
|
+
/**
|
|
375
|
+
* The branches that name a development line of work.
|
|
376
|
+
*/
|
|
377
|
+
const DEVELOPMENT_BRANCHES = new Set(["develop", "development"]);
|
|
378
|
+
/**
|
|
379
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
380
|
+
*
|
|
381
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
382
|
+
* variable names the environment at all. It never claims production - a
|
|
383
|
+
* checkout of a production branch is not a production environment, and the
|
|
384
|
+
* deployment marker of the repository keeps the answer honest.
|
|
385
|
+
*
|
|
386
|
+
* Two sets of facts name a development workspace:
|
|
387
|
+
* 1. a published package without a deployment configuration and without a
|
|
388
|
+
* running server: source code that the user runs on their own machine
|
|
389
|
+
* 2. a branch that names a development line of work ("develop")
|
|
390
|
+
*/
|
|
391
|
+
export function modeFromRepositoryFacts(facts) {
|
|
392
|
+
if (!facts.isGitRepository)
|
|
393
|
+
return null;
|
|
394
|
+
if (facts.isPublishedPackage && !facts.hasDeploymentMarker && !facts.hasRunningServer) {
|
|
395
|
+
return { mode: "development", key: "source-only repository" };
|
|
396
|
+
}
|
|
397
|
+
const branch = facts.branch.trim().toLowerCase();
|
|
398
|
+
if (DEVELOPMENT_BRANCHES.has(branch)) {
|
|
399
|
+
return { mode: "development", key: `git branch ${branch}` };
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
298
403
|
/**
|
|
299
404
|
* Resolve the workspace mode from the variables that were found.
|
|
300
405
|
*
|
|
@@ -346,6 +451,16 @@ function findModeEvidence(env) {
|
|
|
346
451
|
// A value that names a known mode wins over a value that cannot be read.
|
|
347
452
|
return candidates.find((e) => modeFromValue(e.value) !== null) ?? candidates[0];
|
|
348
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* The evidence entry that names the mode through the repository (if any).
|
|
456
|
+
*
|
|
457
|
+
* A detection is weaker than a variable: it is the shape of the checkout,
|
|
458
|
+
* not a declaration of the environment. It is therefore only used when no
|
|
459
|
+
* variable names the environment at all.
|
|
460
|
+
*/
|
|
461
|
+
function findDetectionEvidence(env) {
|
|
462
|
+
return env.evidence.find((e) => e.source === "detection");
|
|
463
|
+
}
|
|
349
464
|
/**
|
|
350
465
|
* Human readable mode label.
|
|
351
466
|
*
|
|
@@ -361,9 +476,14 @@ export function describeWorkspaceMode(env) {
|
|
|
361
476
|
const hint = findModeEvidence(env);
|
|
362
477
|
const where = hint?.file ? ` in ${hint.file}` : "";
|
|
363
478
|
if (env.mode !== "unknown") {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
479
|
+
if (hint)
|
|
480
|
+
return `${env.mode.toUpperCase()} (from ${hint.key}${where})`;
|
|
481
|
+
// The mode came from the repository, not from a variable: the label
|
|
482
|
+
// names the reason ("DEVELOPMENT (source-only repository)").
|
|
483
|
+
const detection = findDetectionEvidence(env);
|
|
484
|
+
if (detection)
|
|
485
|
+
return `${env.mode.toUpperCase()} (${detection.key})`;
|
|
486
|
+
return env.mode.toUpperCase();
|
|
367
487
|
}
|
|
368
488
|
if (hint) {
|
|
369
489
|
// The stored mode and the evidence can disagree (a stale cache row):
|
|
@@ -382,6 +502,14 @@ export function describeWorkspaceMode(env) {
|
|
|
382
502
|
}
|
|
383
503
|
return `UNKNOWN (${hint.key}=${hint.value} is not a known mode${where})`;
|
|
384
504
|
}
|
|
505
|
+
// A stale cache row can carry a detection next to an unknown mode: the
|
|
506
|
+
// detection names a mode, so the mode is shown.
|
|
507
|
+
const detection = findDetectionEvidence(env);
|
|
508
|
+
if (detection) {
|
|
509
|
+
const named = modeFromValue(detection.value);
|
|
510
|
+
if (named)
|
|
511
|
+
return `${named.toUpperCase()} (${detection.key})`;
|
|
512
|
+
}
|
|
385
513
|
return "UNKNOWN (no NODE_ENV or environment variable found)";
|
|
386
514
|
}
|
|
387
515
|
export function describeServerState(stateOrRunning, hasScript) {
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { DatabaseSync } from "node:sqlite";
|
|
15
15
|
import { execSync } from "child_process";
|
|
16
|
-
import { existsSync, readFileSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
16
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
17
17
|
import { join } from "path";
|
|
18
18
|
import { platform, totalmem, freemem } from "os";
|
|
19
|
-
import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, MASKED_MODE_VALUE, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
|
|
19
|
+
import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, DEPLOYMENT_MARKER_FILES, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, MASKED_MODE_VALUE, modeFromRepositoryFacts, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
|
|
20
20
|
import { readDotenvFiles } from "./workspace-dotenv.js";
|
|
21
21
|
const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
|
|
22
22
|
/**
|
|
@@ -520,6 +520,97 @@ function detectPackageManager(workspacePath) {
|
|
|
520
520
|
return "npm";
|
|
521
521
|
return null;
|
|
522
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* The git repository of a workspace.
|
|
525
|
+
*
|
|
526
|
+
* The root of the repository is the unit of the detection: a package
|
|
527
|
+
* published from a monorepo is described by the root, not by the folder
|
|
528
|
+
* the harness happens to run in.
|
|
529
|
+
*/
|
|
530
|
+
function detectGitFacts(workspacePath) {
|
|
531
|
+
try {
|
|
532
|
+
const out = execSync("git rev-parse --show-toplevel 2>/dev/null; git rev-parse --abbrev-ref HEAD 2>/dev/null", { cwd: workspacePath, encoding: "utf8", timeout: 1500 });
|
|
533
|
+
const lines = out.split("\n").map((line) => line.trim());
|
|
534
|
+
const root = lines[0] ?? "";
|
|
535
|
+
const branch = lines[1] ?? "";
|
|
536
|
+
// A detached HEAD reports "HEAD" - that is not a branch name.
|
|
537
|
+
return { root, branch: branch === "HEAD" ? "" : branch };
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
// No git, no repository, or a timeout: nothing is known.
|
|
541
|
+
return { root: "", branch: "" };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* True when the package is published to a registry.
|
|
546
|
+
*
|
|
547
|
+
* A package that is published names a version and declares what it ships
|
|
548
|
+
* ("files", "bin" or a "publishConfig"). A repository that only holds an
|
|
549
|
+
* application usually declares none of them - so this stays a careful
|
|
550
|
+
* signal instead of "every package.json is published".
|
|
551
|
+
*/
|
|
552
|
+
function isPublishedPackage(packageDir) {
|
|
553
|
+
const pkgJsonPath = join(packageDir, "package.json");
|
|
554
|
+
if (!existsSync(pkgJsonPath))
|
|
555
|
+
return false;
|
|
556
|
+
try {
|
|
557
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
558
|
+
if (!pkg || typeof pkg !== "object")
|
|
559
|
+
return false;
|
|
560
|
+
if (pkg.private === true)
|
|
561
|
+
return false;
|
|
562
|
+
if (typeof pkg.name !== "string" || !pkg.name)
|
|
563
|
+
return false;
|
|
564
|
+
if (typeof pkg.version !== "string" || !pkg.version)
|
|
565
|
+
return false;
|
|
566
|
+
return (Array.isArray(pkg.files) ||
|
|
567
|
+
(typeof pkg.bin === "object" && pkg.bin !== null) ||
|
|
568
|
+
(typeof pkg.publishConfig === "object" && pkg.publishConfig !== null));
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* True when the repository carries a deployment configuration.
|
|
576
|
+
*
|
|
577
|
+
* A deployment file or a deploy workflow means the repository has a
|
|
578
|
+
* runtime environment of its own - the shape of the repository can then no
|
|
579
|
+
* longer say that the workspace is a development checkout.
|
|
580
|
+
*/
|
|
581
|
+
function hasDeploymentConfiguration(repoRoot) {
|
|
582
|
+
for (const marker of DEPLOYMENT_MARKER_FILES) {
|
|
583
|
+
if (existsSync(join(repoRoot, marker)))
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
const workflowDir = join(repoRoot, ".github", "workflows");
|
|
587
|
+
if (existsSync(workflowDir)) {
|
|
588
|
+
try {
|
|
589
|
+
for (const name of readdirSync(workflowDir)) {
|
|
590
|
+
if (/deploy/i.test(name))
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// An unreadable directory proves nothing.
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Read the facts about the repository the workspace belongs to.
|
|
602
|
+
*/
|
|
603
|
+
function detectRepositoryFacts(workspacePath, hasRunningServer) {
|
|
604
|
+
const { root, branch } = detectGitFacts(workspacePath);
|
|
605
|
+
const repoRoot = root || workspacePath;
|
|
606
|
+
return {
|
|
607
|
+
isGitRepository: root !== "",
|
|
608
|
+
branch,
|
|
609
|
+
isPublishedPackage: isPublishedPackage(repoRoot),
|
|
610
|
+
hasDeploymentMarker: hasDeploymentConfiguration(repoRoot),
|
|
611
|
+
hasRunningServer,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
523
614
|
/**
|
|
524
615
|
* Determine workspace mode with proper precedence
|
|
525
616
|
* Never assume production - unknown requires investigation
|
|
@@ -531,8 +622,11 @@ function detectPackageManager(workspacePath) {
|
|
|
531
622
|
* key whose value names a known mode, then any other environment shaped
|
|
532
623
|
* key
|
|
533
624
|
* 3. an environment shaped key of the process env (the files are silent)
|
|
625
|
+
* 4. the facts about the repository (only when NO variable names the
|
|
626
|
+
* environment at all) - a published source repository without a runtime
|
|
627
|
+
* of its own is a development checkout
|
|
534
628
|
*/
|
|
535
|
-
function determineMode(processVars, dotenvVars, evidence) {
|
|
629
|
+
function determineMode(processVars, dotenvVars, evidence, repositoryFacts) {
|
|
536
630
|
const resolved = resolveWorkspaceMode(processVars, dotenvVars);
|
|
537
631
|
if (resolved.hint && resolved.source) {
|
|
538
632
|
// The value is only kept when it is provably not a secret: the
|
|
@@ -546,8 +640,21 @@ function determineMode(processVars, dotenvVars, evidence) {
|
|
|
546
640
|
timestamp: Date.now(),
|
|
547
641
|
file: resolved.hint.file,
|
|
548
642
|
});
|
|
549
|
-
|
|
550
|
-
|
|
643
|
+
// An explicit variable decides - even when it names no known mode
|
|
644
|
+
// ("staging"). The mode stays unknown and the label names the value.
|
|
645
|
+
return resolved.mode;
|
|
646
|
+
}
|
|
647
|
+
// No variable names the environment: the repository may still name it.
|
|
648
|
+
const detection = modeFromRepositoryFacts(repositoryFacts);
|
|
649
|
+
if (!detection)
|
|
650
|
+
return resolved.mode;
|
|
651
|
+
evidence.push({
|
|
652
|
+
source: "detection",
|
|
653
|
+
key: detection.key,
|
|
654
|
+
value: detection.mode,
|
|
655
|
+
timestamp: Date.now(),
|
|
656
|
+
});
|
|
657
|
+
return detection.mode;
|
|
551
658
|
}
|
|
552
659
|
/**
|
|
553
660
|
* Detect and store workspace environment
|
|
@@ -594,7 +701,8 @@ export function detectWorkspaceEnv(workspacePath) {
|
|
|
594
701
|
// workspace (see detectServerStates).
|
|
595
702
|
const { dev: devServerState, build: buildServerState } = detectServerStates(workspacePath);
|
|
596
703
|
// Determine mode - NEVER assume production
|
|
597
|
-
const
|
|
704
|
+
const repositoryFacts = detectRepositoryFacts(workspacePath, devServerState === "running" || buildServerState === "running");
|
|
705
|
+
const mode = determineMode(processVars, dotenvVars, evidence, repositoryFacts);
|
|
598
706
|
// Determine build permission
|
|
599
707
|
let buildAllowed = false;
|
|
600
708
|
if (mode === "production") {
|
|
@@ -231,6 +231,51 @@ export function isEnvShapedKey(key: string): boolean {
|
|
|
231
231
|
return /(^|_)(ENV|ENVIRONMENT|MODE|STAGE)$/i.test(key);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* The namespaces of the desktop session variables.
|
|
236
|
+
*
|
|
237
|
+
* A session variable names the desktop session ("GNOME_SHELL_SESSION_MODE",
|
|
238
|
+
* "XDG_SESSION_TYPE"), never the environment of a workspace. Some of them
|
|
239
|
+
* end in "_MODE" or "_ENV", so without this list they look like an
|
|
240
|
+
* environment selector and decide the mode of a workspace that has no
|
|
241
|
+
* environment at all.
|
|
242
|
+
*/
|
|
243
|
+
const SESSION_VARIABLE_PREFIXES = [
|
|
244
|
+
"GNOME_",
|
|
245
|
+
"XDG_",
|
|
246
|
+
"DESKTOP_",
|
|
247
|
+
"DBUS_",
|
|
248
|
+
"GDM_",
|
|
249
|
+
"SDDM_",
|
|
250
|
+
"LIGHTDM_",
|
|
251
|
+
"GTK_",
|
|
252
|
+
"QT_",
|
|
253
|
+
"MATE_",
|
|
254
|
+
"KDE_",
|
|
255
|
+
"PLASMA_",
|
|
256
|
+
"UNITY_",
|
|
257
|
+
"XFCE_",
|
|
258
|
+
"CINNAMON_",
|
|
259
|
+
"WAYLAND_",
|
|
260
|
+
"MIR_",
|
|
261
|
+
"I3_",
|
|
262
|
+
"TERM_",
|
|
263
|
+
"SSH_",
|
|
264
|
+
"TMUX_",
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
269
|
+
*
|
|
270
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
271
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
272
|
+
* of a workspace, so such a variable must never name the mode.
|
|
273
|
+
*/
|
|
274
|
+
export function isSessionVariableKey(key: string): boolean {
|
|
275
|
+
const upper = key.toUpperCase();
|
|
276
|
+
return SESSION_VARIABLE_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
277
|
+
}
|
|
278
|
+
|
|
234
279
|
/**
|
|
235
280
|
* True for a key that may hold a secret.
|
|
236
281
|
*/
|
|
@@ -282,6 +327,9 @@ export function detectModeHint(
|
|
|
282
327
|
): DotenvVar | null {
|
|
283
328
|
const nodeEnv = vars.find((v) => v.key === "NODE_ENV");
|
|
284
329
|
if (nodeEnv) return nodeEnv;
|
|
330
|
+
// A session variable is not an environment selector: it must never
|
|
331
|
+
// become the hint that names the mode.
|
|
332
|
+
const usableVars = vars.filter((v) => !isSessionVariableKey(v.key));
|
|
285
333
|
// The value match is opt-in: a caller that passes the process env
|
|
286
334
|
// variables must not let an unrelated variable that happens to hold the
|
|
287
335
|
// word "development" decide the mode.
|
|
@@ -289,7 +337,7 @@ export function detectModeHint(
|
|
|
289
337
|
// An environment shaped key whose value names a known mode wins: an
|
|
290
338
|
// earlier "APP_MODE=staging" or an empty "APP_ENV=" of the same file
|
|
291
339
|
// must not shadow the "AMOS_ENV=development" of the same file.
|
|
292
|
-
const usable =
|
|
340
|
+
const usable = usableVars.find(
|
|
293
341
|
(v) =>
|
|
294
342
|
v.key !== "NODE_ENV" &&
|
|
295
343
|
isEnvShapedKey(v.key) &&
|
|
@@ -299,7 +347,9 @@ export function detectModeHint(
|
|
|
299
347
|
}
|
|
300
348
|
// No usable key: fall back to the first environment shaped key so that
|
|
301
349
|
// the label can name it (it names no known mode).
|
|
302
|
-
return
|
|
350
|
+
return (
|
|
351
|
+
usableVars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null
|
|
352
|
+
);
|
|
303
353
|
}
|
|
304
354
|
|
|
305
355
|
/**
|
|
@@ -363,6 +413,9 @@ export function collectProcessEnvVars(env: NodeJS.ProcessEnv = process.env): Dot
|
|
|
363
413
|
for (const key of Object.keys(env)) {
|
|
364
414
|
const value = env[key];
|
|
365
415
|
if (value === undefined) continue;
|
|
416
|
+
// A session variable of the desktop is never an environment
|
|
417
|
+
// selector, so it is not even collected as a candidate.
|
|
418
|
+
if (isSessionVariableKey(key)) continue;
|
|
366
419
|
if (key === "NODE_ENV" || isEnvShapedKey(key)) {
|
|
367
420
|
vars.push({ key, value });
|
|
368
421
|
}
|
|
@@ -379,6 +432,98 @@ export interface ModeResolution {
|
|
|
379
432
|
source: "process.env" | ".env file" | null;
|
|
380
433
|
}
|
|
381
434
|
|
|
435
|
+
/**
|
|
436
|
+
* The deployment configuration of a repository.
|
|
437
|
+
*
|
|
438
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
439
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
440
|
+
* cannot prove that the workspace is a development checkout.
|
|
441
|
+
*/
|
|
442
|
+
export const DEPLOYMENT_MARKER_FILES = [
|
|
443
|
+
"Dockerfile",
|
|
444
|
+
"Containerfile",
|
|
445
|
+
"docker-compose.yml",
|
|
446
|
+
"docker-compose.yaml",
|
|
447
|
+
"compose.yml",
|
|
448
|
+
"compose.yaml",
|
|
449
|
+
"Procfile",
|
|
450
|
+
"fly.toml",
|
|
451
|
+
"render.yaml",
|
|
452
|
+
"railway.json",
|
|
453
|
+
"vercel.json",
|
|
454
|
+
"netlify.toml",
|
|
455
|
+
"serverless.yml",
|
|
456
|
+
"serverless.yaml",
|
|
457
|
+
"app.yaml",
|
|
458
|
+
"ecosystem.config.cjs",
|
|
459
|
+
"ecosystem.config.js",
|
|
460
|
+
] as const;
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The facts about the repository the workspace belongs to.
|
|
464
|
+
*
|
|
465
|
+
* They are read from the file system (and from git), never from a variable:
|
|
466
|
+
* a published package that is deployed nowhere is source code that the
|
|
467
|
+
* user runs on their own machine.
|
|
468
|
+
*/
|
|
469
|
+
export interface RepositoryFacts {
|
|
470
|
+
/** The workspace is a git checkout. */
|
|
471
|
+
isGitRepository: boolean;
|
|
472
|
+
/** The current branch ("" when it cannot be read). */
|
|
473
|
+
branch: string;
|
|
474
|
+
/** The package.json describes a package that is published to a registry. */
|
|
475
|
+
isPublishedPackage: boolean;
|
|
476
|
+
/** The repository carries a deployment configuration. */
|
|
477
|
+
hasDeploymentMarker: boolean;
|
|
478
|
+
/** A dev or build server runs in this workspace. */
|
|
479
|
+
hasRunningServer: boolean;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* A mode that the facts about the repository named.
|
|
484
|
+
*/
|
|
485
|
+
export interface RepositoryModeDetection {
|
|
486
|
+
mode: WorkspaceMode;
|
|
487
|
+
/**
|
|
488
|
+
* The phrase that names the detection.
|
|
489
|
+
*
|
|
490
|
+
* It is used as the key of the evidence ("source-only repository",
|
|
491
|
+
* "git branch develop"), so the label can name it the same way it
|
|
492
|
+
* names a variable.
|
|
493
|
+
*/
|
|
494
|
+
key: string;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The branches that name a development line of work.
|
|
499
|
+
*/
|
|
500
|
+
const DEVELOPMENT_BRANCHES = new Set(["develop", "development"]);
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
504
|
+
*
|
|
505
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
506
|
+
* variable names the environment at all. It never claims production - a
|
|
507
|
+
* checkout of a production branch is not a production environment, and the
|
|
508
|
+
* deployment marker of the repository keeps the answer honest.
|
|
509
|
+
*
|
|
510
|
+
* Two sets of facts name a development workspace:
|
|
511
|
+
* 1. a published package without a deployment configuration and without a
|
|
512
|
+
* running server: source code that the user runs on their own machine
|
|
513
|
+
* 2. a branch that names a development line of work ("develop")
|
|
514
|
+
*/
|
|
515
|
+
export function modeFromRepositoryFacts(facts: RepositoryFacts): RepositoryModeDetection | null {
|
|
516
|
+
if (!facts.isGitRepository) return null;
|
|
517
|
+
if (facts.isPublishedPackage && !facts.hasDeploymentMarker && !facts.hasRunningServer) {
|
|
518
|
+
return { mode: "development", key: "source-only repository" };
|
|
519
|
+
}
|
|
520
|
+
const branch = facts.branch.trim().toLowerCase();
|
|
521
|
+
if (DEVELOPMENT_BRANCHES.has(branch)) {
|
|
522
|
+
return { mode: "development", key: `git branch ${branch}` };
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
|
|
382
527
|
/**
|
|
383
528
|
* Resolve the workspace mode from the variables that were found.
|
|
384
529
|
*
|
|
@@ -436,6 +581,17 @@ function findModeEvidence(env: WorkspaceEnvResult): EnvEvidence | undefined {
|
|
|
436
581
|
return candidates.find((e) => modeFromValue(e.value) !== null) ?? candidates[0];
|
|
437
582
|
}
|
|
438
583
|
|
|
584
|
+
/**
|
|
585
|
+
* The evidence entry that names the mode through the repository (if any).
|
|
586
|
+
*
|
|
587
|
+
* A detection is weaker than a variable: it is the shape of the checkout,
|
|
588
|
+
* not a declaration of the environment. It is therefore only used when no
|
|
589
|
+
* variable names the environment at all.
|
|
590
|
+
*/
|
|
591
|
+
function findDetectionEvidence(env: WorkspaceEnvResult): EnvEvidence | undefined {
|
|
592
|
+
return env.evidence.find((e) => e.source === "detection");
|
|
593
|
+
}
|
|
594
|
+
|
|
439
595
|
/**
|
|
440
596
|
* Human readable mode label.
|
|
441
597
|
*
|
|
@@ -451,9 +607,12 @@ export function describeWorkspaceMode(env: WorkspaceEnvResult): string {
|
|
|
451
607
|
const hint = findModeEvidence(env);
|
|
452
608
|
const where = hint?.file ? ` in ${hint.file}` : "";
|
|
453
609
|
if (env.mode !== "unknown") {
|
|
454
|
-
return hint
|
|
455
|
-
|
|
456
|
-
|
|
610
|
+
if (hint) return `${env.mode.toUpperCase()} (from ${hint.key}${where})`;
|
|
611
|
+
// The mode came from the repository, not from a variable: the label
|
|
612
|
+
// names the reason ("DEVELOPMENT (source-only repository)").
|
|
613
|
+
const detection = findDetectionEvidence(env);
|
|
614
|
+
if (detection) return `${env.mode.toUpperCase()} (${detection.key})`;
|
|
615
|
+
return env.mode.toUpperCase();
|
|
457
616
|
}
|
|
458
617
|
if (hint) {
|
|
459
618
|
// The stored mode and the evidence can disagree (a stale cache row):
|
|
@@ -470,6 +629,13 @@ export function describeWorkspaceMode(env: WorkspaceEnvResult): string {
|
|
|
470
629
|
}
|
|
471
630
|
return `UNKNOWN (${hint.key}=${hint.value} is not a known mode${where})`;
|
|
472
631
|
}
|
|
632
|
+
// A stale cache row can carry a detection next to an unknown mode: the
|
|
633
|
+
// detection names a mode, so the mode is shown.
|
|
634
|
+
const detection = findDetectionEvidence(env);
|
|
635
|
+
if (detection) {
|
|
636
|
+
const named = modeFromValue(detection.value);
|
|
637
|
+
if (named) return `${named.toUpperCase()} (${detection.key})`;
|
|
638
|
+
}
|
|
473
639
|
return "UNKNOWN (no NODE_ENV or environment variable found)";
|
|
474
640
|
}
|
|
475
641
|
|