@blogic-cz/agent-tools 0.17.0 → 0.17.2
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/README.md +41 -0
- package/dist/az-tool/service.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/types.d.ts +17 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/db-tool/service.d.ts +23 -0
- package/dist/db-tool/service.d.ts.map +1 -1
- package/dist/db-tool/types.d.ts +2 -0
- package/dist/db-tool/types.d.ts.map +1 -1
- package/dist/k8s-tool/context.d.ts +26 -0
- package/dist/k8s-tool/context.d.ts.map +1 -0
- package/dist/k8s-tool/service.d.ts.map +1 -1
- package/dist/logs-tool/service.d.ts +2 -8
- package/dist/logs-tool/service.d.ts.map +1 -1
- package/dist/shared/bun.d.ts +0 -1
- package/dist/shared/bun.d.ts.map +1 -1
- package/dist/shared/exec.d.ts +8 -1
- package/dist/shared/exec.d.ts.map +1 -1
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/schemas/agent-tools.schema.json +8 -0
- package/src/az-tool/service.ts +30 -23
- package/src/config/loader.ts +2 -0
- package/src/config/types.ts +17 -0
- package/src/db-tool/service.ts +113 -1
- package/src/db-tool/types.ts +2 -0
- package/src/k8s-tool/context.ts +73 -0
- package/src/k8s-tool/service.ts +34 -62
- package/src/logs-tool/service.ts +78 -87
- package/src/shared/bun.ts +0 -7
- package/src/shared/exec.ts +58 -4
- package/src/shared/index.ts +2 -2
package/README.md
CHANGED
|
@@ -436,6 +436,47 @@ Secrets are **never** stored in the config file. The `db-tool` config references
|
|
|
436
436
|
}
|
|
437
437
|
```
|
|
438
438
|
|
|
439
|
+
#### Endpoint overrides: `hostEnvVar` and `portEnvVar`
|
|
440
|
+
|
|
441
|
+
A database environment can also take its `host` and `port` from environment variables:
|
|
442
|
+
|
|
443
|
+
| Field | Effect |
|
|
444
|
+
| ------------ | -------------------------------------------------------------------------- |
|
|
445
|
+
| `hostEnvVar` | Name of an environment variable that overrides the literal `host` when set |
|
|
446
|
+
| `portEnvVar` | Name of an environment variable that overrides the literal `port` when set |
|
|
447
|
+
|
|
448
|
+
These are **overrides with fallback**, not requirements — the opposite of `passwordEnvVar`, which fails when its variable is unset:
|
|
449
|
+
|
|
450
|
+
- variable set to a value → that value wins over the literal in the config;
|
|
451
|
+
- variable unset or empty → the literal `host` / `port` in the config is used, and no error is raised;
|
|
452
|
+
- `portEnvVar` set to something that is not an integer between 1 and 65535 → the command **fails**. It never falls back to the literal, because that would query a different database while you believe the override applied.
|
|
453
|
+
|
|
454
|
+
The variable is read from the process environment first (Bun loads `.env` files into it), then from `~/.zshrc` exports.
|
|
455
|
+
|
|
456
|
+
The typical use is a repository where every git worktree runs its own database container on its own host port, published by that worktree's `.env`:
|
|
457
|
+
|
|
458
|
+
```json5
|
|
459
|
+
{
|
|
460
|
+
database: {
|
|
461
|
+
default: {
|
|
462
|
+
environments: {
|
|
463
|
+
local: {
|
|
464
|
+
host: "127.0.0.1",
|
|
465
|
+
port: 40543, // the main checkout's database
|
|
466
|
+
user: "app",
|
|
467
|
+
database: "app",
|
|
468
|
+
portEnvVar: "APP_POSTGRES_PORT", // a worktree's .env sets this to e.g. 40643
|
|
469
|
+
hostEnvVar: "APP_POSTGRES_HOST", // optional, same fallback rule
|
|
470
|
+
prerequisites: [],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Run from the main checkout, where `APP_POSTGRES_PORT` is unset, `db-tool` connects to `127.0.0.1:40543`. Run from a worktree whose `.env` sets `APP_POSTGRES_PORT=40643`, the same config connects to `127.0.0.1:40643` — so `db-tool` and `job-tool` inspect that worktree's own database instead of the main checkout's.
|
|
479
|
+
|
|
439
480
|
Database VPN prerequisites can be set at the database profile or environment level. If an environment declares `vpn` or `prerequisites`, that environment config replaces the profile prerequisites; `prerequisites: []` explicitly disables inherited VPN setup. DB commands try the query directly first and only connect VPN prerequisites if direct access fails. Package-managed VPNs remain reusable for `idleDisconnectMs` (default 30000) after the last lease; `0` restores immediate cleanup. Preconnected or `leave-running` connections are treated as external and never stopped automatically. If runtime state is corrupt, unknown, or contains legacy artifacts, first stop all agent-tools processes using the VPN, then remove that VPN state directory under `~/.agent-tools/runtime/vpn-prerequisites`.
|
|
440
481
|
|
|
441
482
|
```json5
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/az-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAkB,MAAM,QAAQ,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGzF,OAAO,EAAE,aAAa,EAAiB,MAAM,SAAS,CAAC;;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/az-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAkB,MAAM,QAAQ,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGzF,OAAO,EAAE,aAAa,EAAiB,MAAM,SAAS,CAAC;;yBAM9B,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY,CAAC;wBACxE,CAClB,MAAM,EAAE,YAAY,KACjB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY,CAAC;;AATjG,qBAAa,SAAU,SAAQ,cAWF;IAC3B,MAAM,CAAC,QAAQ,CAAC,KAAK,yFAmNnB;CACH;AAED,eAAO,MAAM,cAAc,wFAAkB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAgB,KAAK,EAAU,MAAM,QAAQ,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAgB,KAAK,EAAU,MAAM,QAAQ,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA6NlE,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,UAAU,GAAE,MAA4B,GACvC,gBAAgB,CAmClB;AAmFD,wBAAsB,UAAU,IAAI,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAcxE;;AAED,qBAAa,aAAc,SAAQ,kBAElC;CAAG;;;;AAEJ,qBAAa,eAAgB,SAAQ,qBAAoC;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL,eAAO,MAAM,kBAAkB,oDAM9B,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,IAAI,CAC/B,gBAAgB,EAChB,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,MAAM,CAC/D,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,CAAC,GAAG,SAAS,CAkCf;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAE9F;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,OAAO,CAAC,EAAE,MAAM,GACf,gBAAgB,GAAG,SAAS,CAc9B;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,GAAG,SAAS,CAqBpB"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -77,6 +77,23 @@ export type DbEnvConfig = ProfilePrerequisites & {
|
|
|
77
77
|
password?: string;
|
|
78
78
|
/** Name of environment variable holding the password, e.g. "DB_TEST_PWD" */
|
|
79
79
|
passwordEnvVar?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Name of an environment variable that overrides `host`, e.g. "APP_POSTGRES_HOST".
|
|
82
|
+
*
|
|
83
|
+
* Override with fallback, deliberately unlike `passwordEnvVar`: an unset or empty variable is
|
|
84
|
+
* not an error, it keeps the literal `host` above. The variable usually exists only in a
|
|
85
|
+
* per-worktree environment, and the main checkout must keep working from the literal.
|
|
86
|
+
*/
|
|
87
|
+
hostEnvVar?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Name of an environment variable that overrides `port`, e.g. "APP_POSTGRES_PORT".
|
|
90
|
+
*
|
|
91
|
+
* Same override-with-fallback rule as `hostEnvVar`: unset or empty keeps the literal `port`,
|
|
92
|
+
* because only a worktree environment publishes its own port. A variable that *is* set but is
|
|
93
|
+
* not an integer in 1..65535 fails instead of falling back — a silent fallback would query the
|
|
94
|
+
* wrong database while the caller believes the override applied.
|
|
95
|
+
*/
|
|
96
|
+
portEnvVar?: string;
|
|
80
97
|
};
|
|
81
98
|
/** SQL mutation operation that can be explicitly allowed for a database environment. */
|
|
82
99
|
export declare const DbMutationOperationSchema: Schema.Literals<readonly ["insert", "update", "delete"]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,yCAAyC;AACzC,MAAM,MAAM,WAAW,GAAG;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,aAAa,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,0BAA0B,GAC1B,yBAAyB,GACzB,6BAA6B,CAAC;AAElC,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,0BAA0B,CAAC;QACpC,KAAK,CAAC,EAAE,yBAAyB,CAAC;QAClC,KAAK,CAAC,EAAE,6BAA6B,CAAC;KACvC,CAAC;IACF,uGAAuG;IACvG,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,oBAAoB,GAAG;IAC7C,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,yCAAyC;AACzC,MAAM,MAAM,WAAW,GAAG;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,aAAa,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,0BAA0B,GAC1B,yBAAyB,GACzB,6BAA6B,CAAC;AAElC,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,0BAA0B,CAAC;QACpC,KAAK,CAAC,EAAE,yBAAyB,CAAC;QAClC,KAAK,CAAC,EAAE,6BAA6B,CAAC;KACvC,CAAC;IACF,uGAAuG;IACvG,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,SAAS,GAAG,oBAAoB,GAAG;IAC7C,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,qDAAqD;AACrD,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,yBAAyB,0DAAkD,CAAC;AACzF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AACvF,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAE/F,qCAAqC;AACrC,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG;IAClD,mFAAmF;IACnF,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C,+GAA+G;IAC/G,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC,CAAC;IAClE,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAClE,OAAO,CAAC,EAAE;QACR,+DAA+D;QAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,iCAAiC;AACjC,MAAM,MAAM,UAAU,GAAG,oBAAoB,GAAG;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,yBAAyB,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9C,+BAA+B,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,sGAAsG;IACtG,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACpD,8CAA8C;IAC9C,OAAO,CAAC,EAAE;QACR,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,sFAAsF;IACtF,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC3C,CAAC"}
|
|
@@ -5,6 +5,29 @@ import { ConfigService } from "#config";
|
|
|
5
5
|
import { DbConfigService } from "./config-service";
|
|
6
6
|
import { DbSqlClient } from "./sql-client";
|
|
7
7
|
import { type DbError } from "./errors";
|
|
8
|
+
export type ResolvedPortOverride = {
|
|
9
|
+
readonly ok: true;
|
|
10
|
+
readonly port: number;
|
|
11
|
+
} | {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Applies a `portEnvVar` override to the literal `port` of a database environment.
|
|
17
|
+
*
|
|
18
|
+
* Override with fallback: no variable name, or a variable that is unset or blank, keeps
|
|
19
|
+
* `literalPort`, because the variable exists only in environments that publish their own port
|
|
20
|
+
* (a per-repository worktree) while the main checkout runs from the literal. A variable that is
|
|
21
|
+
* set but does not hold an integer in 1..65535 fails instead of falling back, since falling back
|
|
22
|
+
* would silently query the main checkout's database while the caller believes the override applied.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolvePortOverride(env: string, literalPort: number, portEnvVar: string | undefined, value: string | undefined): ResolvedPortOverride;
|
|
25
|
+
/**
|
|
26
|
+
* Applies a `hostEnvVar` override to the literal `host` of a database environment.
|
|
27
|
+
* Unset or blank keeps `literalHost`; every non-blank value is a usable host, so there is no
|
|
28
|
+
* failure case here, unlike {@link resolvePortOverride}.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveHostOverride(literalHost: string, hostEnvVar: string | undefined, value: string | undefined): string;
|
|
8
31
|
export declare function resolveDbAccessMode(env: string, host: string, hasKubectlConfig: boolean, allowedMutations?: readonly DbMutationOperation[]): Pick<DbConfig, "allowMutations" | "allowedMutations" | "host" | "needsTunnel">;
|
|
9
32
|
export declare function isFullyReadOnly(config: Pick<DbConfig, "allowMutations" | "allowedMutations" | "allowedMutationTargets">): boolean;
|
|
10
33
|
export { buildApiProbeArgs } from "#shared/k8s-probe";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/db-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAS,OAAO,EAAY,MAAM,EAAE,KAAK,EAAuB,MAAM,QAAQ,CAAC;AAEtF,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAOxC,OAAO,EAAE,eAAe,EAA4B,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAqB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAKL,KAAK,OAAO,EACb,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/db-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAS,OAAO,EAAY,MAAM,EAAE,KAAK,EAAuB,MAAM,QAAQ,CAAC;AAEtF,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAOxC,OAAO,EAAE,eAAe,EAA4B,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAqB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAKL,KAAK,OAAO,EACb,MAAM,UAAU,CAAC;AA8BlB,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,oBAAoB,CAqBtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,CAOR;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,gBAAgB,EAAE,OAAO,EACzB,gBAAgB,GAAE,SAAS,mBAAmB,EAAO,GACpD,IAAI,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,GAAG,MAAM,GAAG,aAAa,CAAC,CAUhF;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,GAAG,wBAAwB,CAAC,GACvF,OAAO,CAMT;AAGD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;;2BAK3B,CACrB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC;iCACX,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,EAChB,KAAK,CAAC,EAAE,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC;;AAZ5C,qBAAa,SAAU,SAAQ,cAcF;IAC3B,MAAM,CAAC,QAAQ,CAAC,KAAK,yHAuuBnB;CACH"}
|
package/dist/db-tool/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/db-tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEzD,YAAY,EAAE,mBAAmB,EAAE,CAAC;AACpC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,eAAe,CAAC;AAEzE,MAAM,MAAM,QAAQ,GAAG,oBAAoB,GAAG;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,sBAAsB,EAAE,wBAAwB,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/db-tool/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEzD,YAAY,EAAE,mBAAmB,EAAE,CAAC;AACpC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,eAAe,CAAC;AAEzE,MAAM,MAAM,QAAQ,GAAG,oBAAoB,GAAG;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,sBAAsB,EAAE,wBAAwB,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACpD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Kept free of I/O so the caller spawns kubectl itself instead of piping through sh, jq and head. */
|
|
2
|
+
type KubeContextEntry = {
|
|
3
|
+
readonly name?: unknown;
|
|
4
|
+
readonly context?: {
|
|
5
|
+
readonly cluster?: unknown;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
type KubeClusterEntry = {
|
|
9
|
+
readonly name?: unknown;
|
|
10
|
+
readonly cluster?: {
|
|
11
|
+
readonly server?: unknown;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
type KubeConfigView = {
|
|
15
|
+
readonly contexts?: readonly KubeContextEntry[];
|
|
16
|
+
readonly clusters?: readonly KubeClusterEntry[];
|
|
17
|
+
};
|
|
18
|
+
export declare const parseKubeConfigView: (stdout: string) => KubeConfigView | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Two strategies, in the same order the previous jq pipelines used:
|
|
21
|
+
* an exact context.cluster match first, then a cluster whose server URL contains the id.
|
|
22
|
+
* Returns the first match so a multi-context kubeconfig resolves the same way it did before.
|
|
23
|
+
*/
|
|
24
|
+
export declare const selectKubeContext: (view: KubeConfigView | undefined, clusterId: string) => string | undefined;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/k8s-tool/context.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACnD,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACjD,CAAC;AAKF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,KAAG,cAAc,GAAG,SAOrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,cAAc,GAAG,SAAS,EAChC,WAAW,MAAM,KAChB,MAAM,GAAG,SAmCX,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/k8s-tool/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/k8s-tool/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAe,MAAM,QAAQ,CAAC;AAE7D,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EACL,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,eAAe,EAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAiB,MAAM,SAAS,CAAC;;yBAe9B,CACnB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,WAAW,EAChB,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAChB,MAAM,EACN,eAAe,GAAG,eAAe,GAAG,eAAe,GAAG,wBAAwB,CAC/E;yBACoB,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAChB,aAAa,EACb,eAAe,GAAG,eAAe,GAAG,eAAe,GAAG,wBAAwB,CAC/E;yBACoB,CACnB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAChB,aAAa,EACb,eAAe,GAAG,eAAe,GAAG,eAAe,GAAG,wBAAwB,CAC/E;;AA5BL,qBAAa,UAAW,SAAQ,eA8BF;IAC5B,MAAM,CAAC,QAAQ,CAAC,KAAK,0FA2bnB;CACH;AAED,eAAO,MAAM,eAAe,yFAAmB,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
2
1
|
import { Context, Effect, Layer } from "effect";
|
|
3
2
|
import type { Environment, LogFile, ReadOptions } from "./types";
|
|
4
3
|
import { K8sService } from "#k8s/service";
|
|
@@ -6,18 +5,13 @@ import { ConfigService } from "#config/loader";
|
|
|
6
5
|
import { type LogsError } from "./errors";
|
|
7
6
|
export declare const parseLogFiles: (output: string) => LogFile[];
|
|
8
7
|
export declare const formatPrettyOutput: (output: string) => string;
|
|
9
|
-
/**
|
|
10
|
-
* Sanitize a string for safe use in shell commands by escaping single quotes
|
|
11
|
-
* and wrapping in single quotes. This prevents shell injection.
|
|
12
|
-
*/
|
|
13
|
-
export declare const sanitizeShellArg: (input: string) => string;
|
|
14
8
|
declare const LogsService_base: Context.ServiceClass<LogsService, "@agent-tools/LogsService", {
|
|
15
9
|
readonly listLogs: (env: Environment, profile?: string) => Effect.Effect<LogFile[], LogsError>;
|
|
16
10
|
readonly readLogs: (env: Environment, options: ReadOptions, profile?: string) => Effect.Effect<string, LogsError>;
|
|
17
11
|
}>;
|
|
18
12
|
export declare class LogsService extends LogsService_base {
|
|
19
|
-
static readonly layer: Layer.Layer<LogsService, never, ConfigService |
|
|
13
|
+
static readonly layer: Layer.Layer<LogsService, never, ConfigService | K8sService>;
|
|
20
14
|
}
|
|
21
|
-
export declare const LogsServiceLayer: Layer.Layer<LogsService, import("#config/loader").ConfigLoadError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
15
|
+
export declare const LogsServiceLayer: Layer.Layer<LogsService, import("#config/loader").ConfigLoadError, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
|
|
22
16
|
export {};
|
|
23
17
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/logs-tool/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/logs-tool/service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEjE,OAAO,EAAE,UAAU,EAAmB,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAqC,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAG5E,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,KAAG,OAAO,EAarD,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,KAAG,MAYnD,CAAC;;uBAiEqB,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC;uBAC3E,CACjB,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;AARzC,qBAAa,WAAY,SAAQ,gBAUF;IAC7B,MAAM,CAAC,QAAQ,CAAC,KAAK,8DA6PnB;CACH;AAED,eAAO,MAAM,gBAAgB,+IAG5B,CAAC"}
|
package/dist/shared/bun.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CommandOptions, CommandResult } from "./types";
|
|
2
2
|
export declare function expandPath(path: string): string;
|
|
3
3
|
export declare function runCommand(cmd: string[], options?: CommandOptions): Promise<CommandResult>;
|
|
4
|
-
export declare function runShellCommand(command: string, options?: CommandOptions): Promise<CommandResult>;
|
|
5
4
|
//# sourceMappingURL=bun.d.ts.map
|
package/dist/shared/bun.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/shared/bun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EAAE,EACb,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAmCxB
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/shared/bun.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EAAE,EACb,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAmCxB"}
|
package/dist/shared/exec.d.ts
CHANGED
|
@@ -13,11 +13,18 @@ export declare const collectProcessOutput: (process: ChildProcessSpawner.ChildPr
|
|
|
13
13
|
stderr: string;
|
|
14
14
|
exitCode: ChildProcessSpawner.ExitCode;
|
|
15
15
|
}, import("effect/PlatformError").PlatformError, never>;
|
|
16
|
-
export declare const execEffect: (
|
|
16
|
+
export declare const execEffect: (executable: string, args?: readonly string[], timeoutMs?: number) => Effect.Effect<{
|
|
17
17
|
stdout: string;
|
|
18
18
|
stderr: string;
|
|
19
19
|
exitCode: number;
|
|
20
20
|
}, ExecError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
21
21
|
export declare const quoteShellArg: (value: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Split a command line the way a POSIX shell would word-split it, without running one.
|
|
24
|
+
* Quotes group a value into a single argument and are removed; nothing else is interpreted,
|
|
25
|
+
* so a metacharacter reaches the child as literal text instead of shell syntax.
|
|
26
|
+
*/
|
|
27
|
+
export declare const tokenizeCommandLine: (commandLine: string) => string[];
|
|
28
|
+
export declare const renderCommandLine: (argv: readonly string[]) => string;
|
|
22
29
|
export {};
|
|
23
30
|
//# sourceMappingURL=exec.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;;;;;;;AAIhD,qBAAa,SAAU,SAAQ,cAK7B;CAAG;AAEL,eAAO,MAAM,oBAAoB,GAAI,SAAS,mBAAmB,CAAC,kBAAkB;;;;uDAUhF,CAAC;AAEL,eAAO,MAAM,UAAU,GACrB,YAAY,MAAM,EAClB,YAAW,MAA2B,KACrC,MAAM,CAAC,MAAM,CACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpD,SAAS,EACT,mBAAmB,CAAC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;;;;;;;AAIhD,qBAAa,SAAU,SAAQ,cAK7B;CAAG;AAEL,eAAO,MAAM,oBAAoB,GAAI,SAAS,mBAAmB,CAAC,kBAAkB;;;;uDAUhF,CAAC;AAEL,eAAO,MAAM,UAAU,GACrB,YAAY,MAAM,EAClB,OAAM,SAAS,MAAM,EAAO,EAC5B,YAAW,MAA2B,KACrC,MAAM,CAAC,MAAM,CACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpD,SAAS,EACT,mBAAmB,CAAC,mBAAmB,CAyCxC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,WAA0C,CAAC;AAEtF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,MAAM,EAwC/D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,MACmC,CAAC"}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export type { BaseResult, CommandOptions, CommandResult, Environment, OutputFormat, ToolResult, } from "./types";
|
|
2
2
|
export { formatAny, formatOption, formatOutput, logFormatted } from "./format";
|
|
3
|
-
export { expandPath, runCommand
|
|
3
|
+
export { expandPath, runCommand } from "./bun";
|
|
4
4
|
export { commonArgOptions, parseCommonArgs } from "./cli";
|
|
5
5
|
export { renderCauseToStderr } from "./error-renderer";
|
|
6
6
|
export { dumpCommandSchema, makeSchemaCommand, type CommandSchema } from "./schema-dump";
|
|
7
7
|
export declare const VERSION: string;
|
|
8
|
-
export { execEffect, type ExecError } from "./exec";
|
|
8
|
+
export { execEffect, renderCommandLine, tokenizeCommandLine, type ExecError } from "./exec";
|
|
9
9
|
export { createThrottle, type ThrottleError } from "./throttle";
|
|
10
10
|
export { aggregateByField, deduplicateLines, formatCountSummary, parseTextTable, stripEmptyColumns, truncateRows, } from "./transform";
|
|
11
11
|
export { transformLogOutput } from "./log-transform";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shared/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAIzF,eAAO,MAAM,OAAO,QAAc,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE5F,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -209,6 +209,14 @@
|
|
|
209
209
|
"description": "Name of the environment variable holding the database password.",
|
|
210
210
|
"type": "string"
|
|
211
211
|
},
|
|
212
|
+
"hostEnvVar": {
|
|
213
|
+
"description": "Name of an environment variable that overrides host when it is set. Unset or empty keeps the literal host.",
|
|
214
|
+
"type": "string"
|
|
215
|
+
},
|
|
216
|
+
"portEnvVar": {
|
|
217
|
+
"description": "Name of an environment variable that overrides port when it is set. Unset or empty keeps the literal port; a value that is not an integer between 1 and 65535 is an error, never a silent fallback.",
|
|
218
|
+
"type": "string"
|
|
219
|
+
},
|
|
212
220
|
"prerequisites": {
|
|
213
221
|
"description": "Ordered prerequisite references for this database environment. Declaring this or vpn overrides profile-level database prerequisites; an empty array explicitly disables inherited prerequisites.",
|
|
214
222
|
"type": "array",
|
package/src/az-tool/service.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { AzSecurityError, AzCommandError, AzTimeoutError, AzParseError } from ".
|
|
|
9
9
|
import { isCommandAllowed, isInvokeAllowed } from "./security";
|
|
10
10
|
import { transformCmdOutput } from "./transformers";
|
|
11
11
|
import { ConfigService, getToolConfig } from "#config";
|
|
12
|
+
import { renderCommandLine, tokenizeCommandLine } from "#shared/exec";
|
|
12
13
|
|
|
13
14
|
export class AzService extends Context.Service<
|
|
14
15
|
AzService,
|
|
@@ -43,10 +44,10 @@ export class AzService extends Context.Service<
|
|
|
43
44
|
|
|
44
45
|
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
45
46
|
|
|
46
|
-
const
|
|
47
|
+
const runAzCommand = (argv: readonly string[], timeoutMs: number) =>
|
|
47
48
|
Effect.scoped(
|
|
48
49
|
Effect.gen(function* () {
|
|
49
|
-
const command = ChildProcess.make("
|
|
50
|
+
const command = ChildProcess.make("az", argv, {
|
|
50
51
|
stdout: "pipe",
|
|
51
52
|
stderr: "pipe",
|
|
52
53
|
});
|
|
@@ -68,7 +69,7 @@ export class AzService extends Context.Service<
|
|
|
68
69
|
(platformError) =>
|
|
69
70
|
new AzCommandError({
|
|
70
71
|
message: `Command execution failed: ${platformError.message}`,
|
|
71
|
-
command:
|
|
72
|
+
command: renderCommandLine(["az", ...argv]),
|
|
72
73
|
exitCode: -1,
|
|
73
74
|
hint: "Check that the az CLI is installed and authenticated",
|
|
74
75
|
nextCommand: "az login",
|
|
@@ -112,17 +113,22 @@ export class AzService extends Context.Service<
|
|
|
112
113
|
firstWord as (typeof STANDALONE_AZ_COMMANDS)[number],
|
|
113
114
|
);
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
const cmdArgv = tokenizeCommandLine(cmd);
|
|
117
|
+
const scopeArgv = [
|
|
118
|
+
"--organization",
|
|
119
|
+
azConfig.organization,
|
|
120
|
+
"--project",
|
|
121
|
+
projectName,
|
|
122
|
+
] as const;
|
|
116
123
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
fullCommand = `az devops ${cmd} --organization "${azConfig.organization}" --project "${projectName}"`;
|
|
123
|
-
}
|
|
124
|
+
const argv = isStandaloneCommand
|
|
125
|
+
? cmdArgv
|
|
126
|
+
: isDirectCommand
|
|
127
|
+
? [...cmdArgv, ...scopeArgv]
|
|
128
|
+
: ["devops", ...cmdArgv, ...scopeArgv];
|
|
124
129
|
|
|
125
|
-
const
|
|
130
|
+
const fullCommand = renderCommandLine(["az", ...argv]);
|
|
131
|
+
const resultOption = yield* runAzCommand(argv, azConfig.timeoutMs ?? 60000);
|
|
126
132
|
|
|
127
133
|
if (Option.isNone(resultOption)) {
|
|
128
134
|
return yield* new AzTimeoutError({
|
|
@@ -165,7 +171,7 @@ export class AzService extends Context.Service<
|
|
|
165
171
|
});
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
const argv = ["devops", "invoke", "--area", params.area, "--resource", params.resource];
|
|
169
175
|
|
|
170
176
|
const projectName = resolveProject(params.project);
|
|
171
177
|
|
|
@@ -175,22 +181,23 @@ export class AzService extends Context.Service<
|
|
|
175
181
|
};
|
|
176
182
|
|
|
177
183
|
if (Object.keys(routeParameters).length > 0) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.
|
|
181
|
-
|
|
184
|
+
argv.push(
|
|
185
|
+
"--route-parameters",
|
|
186
|
+
...Object.entries(routeParameters).map(([k, v]) => `${k}=${v}`),
|
|
187
|
+
);
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
if (params.queryParameters) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.
|
|
188
|
-
|
|
191
|
+
argv.push(
|
|
192
|
+
"--query-parameters",
|
|
193
|
+
...Object.entries(params.queryParameters).map(([k, v]) => `${k}=${v}`),
|
|
194
|
+
);
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
|
|
197
|
+
argv.push("--organization", azConfig.organization, "--output", "json");
|
|
192
198
|
|
|
193
|
-
const
|
|
199
|
+
const fullCommand = renderCommandLine(["az", ...argv]);
|
|
200
|
+
const resultOption = yield* runAzCommand(argv, azConfig.timeoutMs ?? 60000);
|
|
194
201
|
|
|
195
202
|
if (Option.isNone(resultOption)) {
|
|
196
203
|
return yield* new AzTimeoutError({
|
package/src/config/loader.ts
CHANGED
|
@@ -105,6 +105,8 @@ const DbEnvConfigSchema = Schema.Struct({
|
|
|
105
105
|
database: Schema.String,
|
|
106
106
|
password: Schema.optionalKey(Schema.String),
|
|
107
107
|
passwordEnvVar: Schema.optionalKey(Schema.String),
|
|
108
|
+
hostEnvVar: Schema.optionalKey(Schema.String),
|
|
109
|
+
portEnvVar: Schema.optionalKey(Schema.String),
|
|
108
110
|
prerequisites: Schema.optionalKey(PrerequisitesSchema),
|
|
109
111
|
vpn: Schema.optionalKey(Schema.String),
|
|
110
112
|
});
|
package/src/config/types.ts
CHANGED
|
@@ -91,6 +91,23 @@ export type DbEnvConfig = ProfilePrerequisites & {
|
|
|
91
91
|
password?: string;
|
|
92
92
|
/** Name of environment variable holding the password, e.g. "DB_TEST_PWD" */
|
|
93
93
|
passwordEnvVar?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Name of an environment variable that overrides `host`, e.g. "APP_POSTGRES_HOST".
|
|
96
|
+
*
|
|
97
|
+
* Override with fallback, deliberately unlike `passwordEnvVar`: an unset or empty variable is
|
|
98
|
+
* not an error, it keeps the literal `host` above. The variable usually exists only in a
|
|
99
|
+
* per-worktree environment, and the main checkout must keep working from the literal.
|
|
100
|
+
*/
|
|
101
|
+
hostEnvVar?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Name of an environment variable that overrides `port`, e.g. "APP_POSTGRES_PORT".
|
|
104
|
+
*
|
|
105
|
+
* Same override-with-fallback rule as `hostEnvVar`: unset or empty keeps the literal `port`,
|
|
106
|
+
* because only a worktree environment publishes its own port. A variable that *is* set but is
|
|
107
|
+
* not an integer in 1..65535 fails instead of falling back — a silent fallback would query the
|
|
108
|
+
* wrong database while the caller believes the override applied.
|
|
109
|
+
*/
|
|
110
|
+
portEnvVar?: string;
|
|
94
111
|
};
|
|
95
112
|
|
|
96
113
|
/** SQL mutation operation that can be explicitly allowed for a database environment. */
|
package/src/db-tool/service.ts
CHANGED
|
@@ -38,6 +38,75 @@ import { transformQueryResult } from "./transformers";
|
|
|
38
38
|
|
|
39
39
|
const LOCALHOST_HOSTS = new Set(["localhost", "127.0.0.1"]);
|
|
40
40
|
|
|
41
|
+
const MIN_TCP_PORT = 1;
|
|
42
|
+
const MAX_TCP_PORT = 65535;
|
|
43
|
+
const INTEGER_ONLY = /^\d+$/;
|
|
44
|
+
|
|
45
|
+
// Bun.env is the primary source: it carries the values Bun loads from a .env file, which is where
|
|
46
|
+
// a per-worktree host/port lives. It aliases process.env, which is also the only one that exists
|
|
47
|
+
// when this module runs outside Bun (the vitest runner).
|
|
48
|
+
const readProcessEnv = (envVar: string): string | undefined =>
|
|
49
|
+
(typeof Bun === "undefined" ? process.env : Bun.env)[envVar];
|
|
50
|
+
|
|
51
|
+
export type ResolvedPortOverride =
|
|
52
|
+
| { readonly ok: true; readonly port: number }
|
|
53
|
+
| { readonly ok: false; readonly message: string };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Applies a `portEnvVar` override to the literal `port` of a database environment.
|
|
57
|
+
*
|
|
58
|
+
* Override with fallback: no variable name, or a variable that is unset or blank, keeps
|
|
59
|
+
* `literalPort`, because the variable exists only in environments that publish their own port
|
|
60
|
+
* (a per-repository worktree) while the main checkout runs from the literal. A variable that is
|
|
61
|
+
* set but does not hold an integer in 1..65535 fails instead of falling back, since falling back
|
|
62
|
+
* would silently query the main checkout's database while the caller believes the override applied.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePortOverride(
|
|
65
|
+
env: string,
|
|
66
|
+
literalPort: number,
|
|
67
|
+
portEnvVar: string | undefined,
|
|
68
|
+
value: string | undefined,
|
|
69
|
+
): ResolvedPortOverride {
|
|
70
|
+
if (!portEnvVar) {
|
|
71
|
+
return { ok: true, port: literalPort };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const trimmed = value?.trim() ?? "";
|
|
75
|
+
if (trimmed === "") {
|
|
76
|
+
return { ok: true, port: literalPort };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const parsed = Number(trimmed);
|
|
80
|
+
if (!INTEGER_ONLY.test(trimmed) || parsed < MIN_TCP_PORT || parsed > MAX_TCP_PORT) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
message:
|
|
84
|
+
`Environment variable ${portEnvVar} (override for the 'port' config field) is set to ` +
|
|
85
|
+
`"${value}" in environment ${env}, which is not an integer port between ${MIN_TCP_PORT} and ${MAX_TCP_PORT}.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { ok: true, port: parsed };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Applies a `hostEnvVar` override to the literal `host` of a database environment.
|
|
94
|
+
* Unset or blank keeps `literalHost`; every non-blank value is a usable host, so there is no
|
|
95
|
+
* failure case here, unlike {@link resolvePortOverride}.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveHostOverride(
|
|
98
|
+
literalHost: string,
|
|
99
|
+
hostEnvVar: string | undefined,
|
|
100
|
+
value: string | undefined,
|
|
101
|
+
): string {
|
|
102
|
+
if (!hostEnvVar) {
|
|
103
|
+
return literalHost;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const trimmed = value?.trim() ?? "";
|
|
107
|
+
return trimmed === "" ? literalHost : trimmed;
|
|
108
|
+
}
|
|
109
|
+
|
|
41
110
|
export function resolveDbAccessMode(
|
|
42
111
|
env: string,
|
|
43
112
|
host: string,
|
|
@@ -204,15 +273,56 @@ export class DbService extends Context.Service<
|
|
|
204
273
|
});
|
|
205
274
|
});
|
|
206
275
|
|
|
276
|
+
const lookupEnvOverride = (envVar: string, zshrcEnv: Record<string, string>) =>
|
|
277
|
+
readProcessEnv(envVar) ?? zshrcEnv[envVar];
|
|
278
|
+
|
|
207
279
|
const resolveDbConfig = Effect.fn("DbService.resolveDbConfig")(function* (
|
|
208
280
|
config: DbConfig,
|
|
209
281
|
env: string,
|
|
210
282
|
) {
|
|
211
283
|
const needsEnvResolution =
|
|
212
|
-
envTemplateRegex.test(config.user) ||
|
|
284
|
+
envTemplateRegex.test(config.user) ||
|
|
285
|
+
envTemplateRegex.test(config.database) ||
|
|
286
|
+
config.hostEnvVar !== undefined ||
|
|
287
|
+
config.portEnvVar !== undefined;
|
|
213
288
|
const zshrcEnv = needsEnvResolution ? yield* loadEnvFromZshrc() : {};
|
|
289
|
+
|
|
290
|
+
const host = resolveHostOverride(
|
|
291
|
+
config.host,
|
|
292
|
+
config.hostEnvVar,
|
|
293
|
+
config.hostEnvVar ? lookupEnvOverride(config.hostEnvVar, zshrcEnv) : undefined,
|
|
294
|
+
);
|
|
295
|
+
const portOverride = resolvePortOverride(
|
|
296
|
+
env,
|
|
297
|
+
config.port,
|
|
298
|
+
config.portEnvVar,
|
|
299
|
+
config.portEnvVar ? lookupEnvOverride(config.portEnvVar, zshrcEnv) : undefined,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (!portOverride.ok) {
|
|
303
|
+
return yield* new DbConnectionError({
|
|
304
|
+
message: portOverride.message,
|
|
305
|
+
environment: env,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// A host override can flip the tunnel decision (a forwarded localhost port vs a directly
|
|
310
|
+
// reachable host), so re-derive the access mode whenever the override changed the host.
|
|
311
|
+
const needsTunnel =
|
|
312
|
+
host === config.host
|
|
313
|
+
? config.needsTunnel
|
|
314
|
+
: resolveDbAccessMode(
|
|
315
|
+
env,
|
|
316
|
+
host,
|
|
317
|
+
dbConfig.kubectl !== undefined,
|
|
318
|
+
config.allowedMutations,
|
|
319
|
+
).needsTunnel;
|
|
320
|
+
|
|
214
321
|
return {
|
|
215
322
|
...config,
|
|
323
|
+
host,
|
|
324
|
+
port: portOverride.port,
|
|
325
|
+
needsTunnel,
|
|
216
326
|
user: yield* resolveConfigString(config.user, env, "user", zshrcEnv),
|
|
217
327
|
database: yield* resolveConfigString(config.database, env, "database", zshrcEnv),
|
|
218
328
|
};
|
|
@@ -646,6 +756,8 @@ export class DbService extends Context.Service<
|
|
|
646
756
|
database: envConfig.database,
|
|
647
757
|
password: envConfig.password,
|
|
648
758
|
passwordEnvVar: envConfig.passwordEnvVar,
|
|
759
|
+
hostEnvVar: envConfig.hostEnvVar,
|
|
760
|
+
portEnvVar: envConfig.portEnvVar,
|
|
649
761
|
...resolveEnvironmentScopedPrerequisites(dbConfig, envConfig),
|
|
650
762
|
port: envConfig.port,
|
|
651
763
|
needsTunnel: accessMode.needsTunnel,
|
package/src/db-tool/types.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Kept free of I/O so the caller spawns kubectl itself instead of piping through sh, jq and head. */
|
|
2
|
+
|
|
3
|
+
type KubeContextEntry = {
|
|
4
|
+
readonly name?: unknown;
|
|
5
|
+
readonly context?: { readonly cluster?: unknown };
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type KubeClusterEntry = {
|
|
9
|
+
readonly name?: unknown;
|
|
10
|
+
readonly cluster?: { readonly server?: unknown };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type KubeConfigView = {
|
|
14
|
+
readonly contexts?: readonly KubeContextEntry[];
|
|
15
|
+
readonly clusters?: readonly KubeClusterEntry[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const asString = (value: unknown): string | undefined =>
|
|
19
|
+
typeof value === "string" && value !== "" ? value : undefined;
|
|
20
|
+
|
|
21
|
+
export const parseKubeConfigView = (stdout: string): KubeConfigView | undefined => {
|
|
22
|
+
try {
|
|
23
|
+
const parsed: unknown = JSON.parse(stdout);
|
|
24
|
+
return typeof parsed === "object" && parsed !== null ? (parsed as KubeConfigView) : undefined;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Two strategies, in the same order the previous jq pipelines used:
|
|
32
|
+
* an exact context.cluster match first, then a cluster whose server URL contains the id.
|
|
33
|
+
* Returns the first match so a multi-context kubeconfig resolves the same way it did before.
|
|
34
|
+
*/
|
|
35
|
+
export const selectKubeContext = (
|
|
36
|
+
view: KubeConfigView | undefined,
|
|
37
|
+
clusterId: string,
|
|
38
|
+
): string | undefined => {
|
|
39
|
+
if (!view) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const contexts = view.contexts ?? [];
|
|
44
|
+
|
|
45
|
+
for (const entry of contexts) {
|
|
46
|
+
const name = asString(entry.name);
|
|
47
|
+
if (name !== undefined && asString(entry.context?.cluster) === clusterId) {
|
|
48
|
+
return name;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const clusters = view.clusters ?? [];
|
|
53
|
+
|
|
54
|
+
for (const entry of contexts) {
|
|
55
|
+
const name = asString(entry.name);
|
|
56
|
+
const clusterName = asString(entry.context?.cluster);
|
|
57
|
+
if (name === undefined || clusterName === undefined) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const matchesServer = clusters.some(
|
|
62
|
+
(cluster) =>
|
|
63
|
+
asString(cluster.name) === clusterName &&
|
|
64
|
+
(asString(cluster.cluster?.server) ?? "").includes(clusterId),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (matchesServer) {
|
|
68
|
+
return name;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return undefined;
|
|
73
|
+
};
|
package/src/k8s-tool/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
|
|
3
3
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
4
|
-
import { Context, Effect, Layer, Option, Ref
|
|
4
|
+
import { Context, Effect, Layer, Option, Ref } from "effect";
|
|
5
5
|
|
|
6
6
|
import type { CommandResult, Environment } from "./types";
|
|
7
7
|
|
|
@@ -21,6 +21,7 @@ import { normalizeProfilePrerequisites } from "#shared/prerequisites/config";
|
|
|
21
21
|
import { runWithProfilePrerequisites } from "#shared/prerequisites/runtime";
|
|
22
22
|
import { buildApiProbeArgs } from "#shared/k8s-probe";
|
|
23
23
|
import { isKubectlCommandAllowed, isSafeLogPath } from "./security";
|
|
24
|
+
import { parseKubeConfigView, selectKubeContext } from "./context";
|
|
24
25
|
|
|
25
26
|
export class K8sService extends Context.Service<
|
|
26
27
|
K8sService,
|
|
@@ -98,41 +99,31 @@ export class K8sService extends Context.Service<
|
|
|
98
99
|
);
|
|
99
100
|
});
|
|
100
101
|
|
|
101
|
-
const withKubeconfig = (command: string, kubeconfig: string | undefined) =>
|
|
102
|
-
kubeconfig ? `KUBECONFIG=${quoteShellArg(kubeconfig)} ${command}` : command;
|
|
103
102
|
const renderArg = (arg: string) =>
|
|
104
103
|
/^[A-Za-z0-9_./:=,@+-]+$/.test(arg) ? arg : quoteShellArg(arg);
|
|
105
|
-
const renderKubectlCommand = (context: string, argv: readonly string[]) =>
|
|
106
|
-
["kubectl", "--context", context, ...argv].map(renderArg).join(" ");
|
|
104
|
+
const renderKubectlCommand = (context: string | undefined, argv: readonly string[]) =>
|
|
105
|
+
["kubectl", ...(context ? ["--context", context] : []), ...argv].map(renderArg).join(" ");
|
|
107
106
|
|
|
108
107
|
// Cache context by selected profile/cluster instead of a single default profile.
|
|
109
108
|
const contextRef = yield* Ref.make<Record<string, string>>({});
|
|
110
109
|
|
|
111
110
|
// Helper that uses executor.spawn() to avoid ChildProcessSpawner requirement in return type
|
|
112
|
-
const
|
|
111
|
+
const runKubectlCapture = (
|
|
112
|
+
argv: readonly string[],
|
|
113
|
+
kubeconfig: string | undefined,
|
|
114
|
+
timeoutMs: number,
|
|
115
|
+
renderedCommand: string,
|
|
116
|
+
) =>
|
|
113
117
|
Effect.scoped(
|
|
114
118
|
Effect.gen(function* () {
|
|
115
|
-
const command = ChildProcess.make("
|
|
119
|
+
const command = ChildProcess.make("kubectl", argv, {
|
|
116
120
|
stdout: "pipe",
|
|
117
121
|
stderr: "pipe",
|
|
122
|
+
...(kubeconfig ? { env: { KUBECONFIG: kubeconfig }, extendEnv: true } : {}),
|
|
118
123
|
});
|
|
119
124
|
const process = yield* executor.spawn(command);
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
Stream.decodeText(),
|
|
123
|
-
Stream.runCollect,
|
|
124
|
-
);
|
|
125
|
-
const stdout = stdoutChunk.join("");
|
|
126
|
-
|
|
127
|
-
const stderrChunk = yield* process.stderr.pipe(
|
|
128
|
-
Stream.decodeText(),
|
|
129
|
-
Stream.runCollect,
|
|
130
|
-
);
|
|
131
|
-
const stderr = stderrChunk.join("");
|
|
132
|
-
|
|
133
|
-
const exitCode = yield* process.exitCode;
|
|
134
|
-
|
|
135
|
-
return { stdout, stderr, exitCode };
|
|
126
|
+
return yield* collectProcessOutput(process);
|
|
136
127
|
}),
|
|
137
128
|
).pipe(
|
|
138
129
|
Effect.timeoutOption(timeoutMs),
|
|
@@ -140,7 +131,7 @@ export class K8sService extends Context.Service<
|
|
|
140
131
|
const missing = missingBinaryFromSpawnFailure("kubectl", String(platformError));
|
|
141
132
|
return new K8sCommandError({
|
|
142
133
|
message: `Command execution failed: ${String(platformError)}`,
|
|
143
|
-
command:
|
|
134
|
+
command: renderedCommand,
|
|
144
135
|
exitCode: -1,
|
|
145
136
|
stderr: String(platformError),
|
|
146
137
|
...(missing ? { hint: missing.hint } : {}),
|
|
@@ -214,56 +205,37 @@ export class K8sService extends Context.Service<
|
|
|
214
205
|
return { context: cachedContext, kubeconfig };
|
|
215
206
|
}
|
|
216
207
|
|
|
217
|
-
const
|
|
218
|
-
|
|
208
|
+
const viewCommand = renderKubectlCommand(undefined, ["config", "view", "-o", "json"]);
|
|
209
|
+
const viewResultOption = yield* runKubectlCapture(
|
|
210
|
+
["config", "view", "-o", "json"],
|
|
219
211
|
kubeconfig,
|
|
212
|
+
timeoutMs,
|
|
213
|
+
viewCommand,
|
|
220
214
|
);
|
|
221
215
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (Option.isNone(contextResultOption)) {
|
|
216
|
+
if (Option.isNone(viewResultOption)) {
|
|
225
217
|
return yield* new K8sTimeoutError({
|
|
226
218
|
message: `Context resolution timed out after ${timeoutMs}ms`,
|
|
227
|
-
command:
|
|
219
|
+
command: viewCommand,
|
|
228
220
|
timeoutMs,
|
|
229
221
|
});
|
|
230
222
|
}
|
|
231
223
|
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
if (contextResult.exitCode === 0 && contextResult.stdout.trim()) {
|
|
235
|
-
const resolvedContextValue = contextResult.stdout.trim();
|
|
236
|
-
yield* Ref.update(contextRef, (contexts) => ({
|
|
237
|
-
...contexts,
|
|
238
|
-
[cacheKey]: resolvedContextValue,
|
|
239
|
-
}));
|
|
240
|
-
return { context: resolvedContextValue, kubeconfig };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const fallbackCommand = withKubeconfig(
|
|
244
|
-
`kubectl config view -o json | jq -r '.contexts[] as $ctx | .clusters[] | select(.name == $ctx.context.cluster and (.cluster.server | contains("${k8sConfig.clusterId}"))) | $ctx.name' | head -1`,
|
|
245
|
-
kubeconfig,
|
|
246
|
-
);
|
|
224
|
+
const viewResult = viewResultOption.value;
|
|
247
225
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
command: fallbackCommand,
|
|
254
|
-
timeoutMs,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const fallbackResult = fallbackResultOption.value;
|
|
226
|
+
if (viewResult.exitCode === 0) {
|
|
227
|
+
const resolvedContextValue = selectKubeContext(
|
|
228
|
+
parseKubeConfigView(viewResult.stdout),
|
|
229
|
+
k8sConfig.clusterId,
|
|
230
|
+
);
|
|
259
231
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
232
|
+
if (resolvedContextValue !== undefined) {
|
|
233
|
+
yield* Ref.update(contextRef, (contexts) => ({
|
|
234
|
+
...contexts,
|
|
235
|
+
[cacheKey]: resolvedContextValue,
|
|
236
|
+
}));
|
|
237
|
+
return { context: resolvedContextValue, kubeconfig };
|
|
238
|
+
}
|
|
267
239
|
}
|
|
268
240
|
|
|
269
241
|
return yield* new K8sContextError({
|
package/src/logs-tool/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
2
3
|
|
|
3
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
4
4
|
import { Context, Effect, Layer, Result } from "effect";
|
|
5
5
|
|
|
6
6
|
import type { Environment, LogFile, ReadOptions } from "./types";
|
|
@@ -9,7 +9,6 @@ import { K8sService, K8sServiceLayer } from "#k8s/service";
|
|
|
9
9
|
import { ConfigService, ConfigServiceLayer, getToolConfig } from "#config/loader";
|
|
10
10
|
import type { LogsConfig } from "#config/types";
|
|
11
11
|
import { LogsNotFoundError, LogsReadError, type LogsError } from "./errors";
|
|
12
|
-
import { collectProcessOutput } from "#shared/exec";
|
|
13
12
|
import { transformLogOutput } from "./transformers";
|
|
14
13
|
|
|
15
14
|
export const parseLogFiles = (output: string): LogFile[] => {
|
|
@@ -41,11 +40,36 @@ export const formatPrettyOutput = (output: string): string => {
|
|
|
41
40
|
.join("\n---\n");
|
|
42
41
|
};
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const errorText = (error: unknown): string =>
|
|
44
|
+
error instanceof Error ? error.message : String(error);
|
|
45
|
+
|
|
46
|
+
const takeLastLines = (contents: string, lines: number): string => {
|
|
47
|
+
const all = contents.split("\n");
|
|
48
|
+
const withoutTrailingBlank = all.at(-1) === "" ? all.slice(0, -1) : all;
|
|
49
|
+
return withoutTrailingBlank.slice(-lines).join("\n");
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Newest first, so callers can take the head as the latest log the way `ls -t` did. */
|
|
53
|
+
const readLocalLogDirectory = async (localDir: string): Promise<LogFile[]> => {
|
|
54
|
+
const entries = await readdir(localDir, { withFileTypes: true });
|
|
55
|
+
const logs = entries.filter((entry) => !entry.isDirectory() && entry.name.endsWith(".log"));
|
|
56
|
+
|
|
57
|
+
const described = await Promise.all(
|
|
58
|
+
logs.map(async (entry) => {
|
|
59
|
+
const stats = await stat(join(localDir, entry.name));
|
|
60
|
+
return {
|
|
61
|
+
name: entry.name,
|
|
62
|
+
size: String(stats.size),
|
|
63
|
+
date: stats.mtime.toISOString(),
|
|
64
|
+
modifiedAtMs: stats.mtimeMs,
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return described
|
|
70
|
+
.toSorted((left, right) => right.modifiedAtMs - left.modifiedAtMs)
|
|
71
|
+
.map(({ name, size, date }) => ({ name, size, date }));
|
|
72
|
+
};
|
|
49
73
|
|
|
50
74
|
const readCommandOutput = (output: unknown): string => (typeof output === "string" ? output : "");
|
|
51
75
|
const filterLogLines = (output: string, grep: string | undefined): string => {
|
|
@@ -91,45 +115,8 @@ export class LogsService extends Context.Service<
|
|
|
91
115
|
LogsService,
|
|
92
116
|
Effect.gen(function* () {
|
|
93
117
|
const k8s = yield* K8sService;
|
|
94
|
-
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
95
118
|
const config = yield* ConfigService;
|
|
96
119
|
|
|
97
|
-
const runShellCommand = (commandStr: string) =>
|
|
98
|
-
Effect.scoped(
|
|
99
|
-
Effect.gen(function* () {
|
|
100
|
-
const command = ChildProcess.make("sh", ["-c", commandStr], {
|
|
101
|
-
stdout: "pipe",
|
|
102
|
-
stderr: "pipe",
|
|
103
|
-
});
|
|
104
|
-
const process = yield* executor.spawn(command);
|
|
105
|
-
return yield* collectProcessOutput(process);
|
|
106
|
-
}),
|
|
107
|
-
).pipe(
|
|
108
|
-
Effect.catch((platformError) =>
|
|
109
|
-
Effect.succeed({
|
|
110
|
-
stdout: "",
|
|
111
|
-
stderr: String(platformError),
|
|
112
|
-
exitCode: -1,
|
|
113
|
-
}),
|
|
114
|
-
),
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const runDirectCommand = (executable: string, args: readonly string[]) =>
|
|
118
|
-
Effect.scoped(
|
|
119
|
-
Effect.gen(function* () {
|
|
120
|
-
const command = ChildProcess.make(executable, args, {
|
|
121
|
-
stdout: "pipe",
|
|
122
|
-
stderr: "pipe",
|
|
123
|
-
});
|
|
124
|
-
const process = yield* executor.spawn(command);
|
|
125
|
-
return yield* collectProcessOutput(process);
|
|
126
|
-
}),
|
|
127
|
-
).pipe(
|
|
128
|
-
Effect.catch((platformError) =>
|
|
129
|
-
Effect.succeed({ stdout: "", stderr: String(platformError), exitCode: -1 }),
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
120
|
const getLogsConfig = (profile?: string): LogsConfig | undefined =>
|
|
134
121
|
getToolConfig<LogsConfig>(config, "logs", profile);
|
|
135
122
|
|
|
@@ -137,16 +124,16 @@ export class LogsService extends Context.Service<
|
|
|
137
124
|
logsConfig: LogsConfig,
|
|
138
125
|
) {
|
|
139
126
|
const localDir = logsConfig.localDir;
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
const files = yield* Effect.tryPromise(() => readLocalLogDirectory(localDir)).pipe(
|
|
128
|
+
Effect.mapError(
|
|
129
|
+
(error) =>
|
|
130
|
+
new LogsReadError({
|
|
131
|
+
message: errorText(error) || "Failed to list local logs",
|
|
132
|
+
source: localDir,
|
|
133
|
+
}),
|
|
134
|
+
),
|
|
135
|
+
);
|
|
148
136
|
|
|
149
|
-
const files = parseLogFiles(result.stdout);
|
|
150
137
|
if (files.length === 0) {
|
|
151
138
|
return yield* new LogsNotFoundError({
|
|
152
139
|
message: "No log files found",
|
|
@@ -213,24 +200,25 @@ export class LogsService extends Context.Service<
|
|
|
213
200
|
let logFile = options.file;
|
|
214
201
|
|
|
215
202
|
if (!logFile) {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
203
|
+
const files = yield* Effect.tryPromise(() => readLocalLogDirectory(localDir)).pipe(
|
|
204
|
+
Effect.mapError(
|
|
205
|
+
(error) =>
|
|
206
|
+
new LogsReadError({
|
|
207
|
+
message: errorText(error) || "Failed to find latest log",
|
|
208
|
+
source: localDir,
|
|
209
|
+
}),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
224
212
|
|
|
225
|
-
const
|
|
226
|
-
if (!
|
|
213
|
+
const latest = files[0];
|
|
214
|
+
if (!latest) {
|
|
227
215
|
return yield* new LogsNotFoundError({
|
|
228
216
|
message: "No log files found",
|
|
229
217
|
path: localDir,
|
|
230
218
|
});
|
|
231
219
|
}
|
|
232
220
|
|
|
233
|
-
logFile =
|
|
221
|
+
logFile = latest.name;
|
|
234
222
|
}
|
|
235
223
|
|
|
236
224
|
const lexicalPath = resolveLocalLogPath(localDir, logFile);
|
|
@@ -240,20 +228,22 @@ export class LogsService extends Context.Service<
|
|
|
240
228
|
source: localDir,
|
|
241
229
|
});
|
|
242
230
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
231
|
+
const canonical = yield* Effect.tryPromise(async () => ({
|
|
232
|
+
base: await realpath(localDir),
|
|
233
|
+
path: await realpath(lexicalPath),
|
|
234
|
+
})).pipe(
|
|
235
|
+
Effect.mapError(
|
|
236
|
+
(error) =>
|
|
237
|
+
new LogsReadError({
|
|
238
|
+
message: errorText(error) || "Failed to canonicalize the log path",
|
|
239
|
+
source: lexicalPath,
|
|
240
|
+
}),
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const canonicalPath = canonical.path;
|
|
253
245
|
if (
|
|
254
|
-
|
|
255
|
-
canonicalPath === undefined ||
|
|
256
|
-
resolveLocalLogPath(canonicalBase, canonicalPath) !== canonicalPath ||
|
|
246
|
+
resolveLocalLogPath(canonical.base, canonicalPath) !== canonicalPath ||
|
|
257
247
|
!canonicalPath.endsWith(".log")
|
|
258
248
|
) {
|
|
259
249
|
return yield* new LogsReadError({
|
|
@@ -261,17 +251,18 @@ export class LogsService extends Context.Service<
|
|
|
261
251
|
source: localDir,
|
|
262
252
|
});
|
|
263
253
|
}
|
|
264
|
-
const command = `tail -${options.tail} ${sanitizeShellArg(canonicalPath)}`;
|
|
265
|
-
const result = yield* runShellCommand(command);
|
|
266
254
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
255
|
+
const contents = yield* Effect.tryPromise(() => readFile(canonicalPath, "utf8")).pipe(
|
|
256
|
+
Effect.mapError(
|
|
257
|
+
(error) =>
|
|
258
|
+
new LogsReadError({
|
|
259
|
+
message: errorText(error) || "Failed to read the log file",
|
|
260
|
+
source: canonicalPath,
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
273
264
|
|
|
274
|
-
const output = filterLogLines(
|
|
265
|
+
const output = filterLogLines(takeLastLines(contents, options.tail), options.grep).trim();
|
|
275
266
|
if (!output) {
|
|
276
267
|
return "(no matching lines)";
|
|
277
268
|
}
|
package/src/shared/bun.ts
CHANGED
|
@@ -50,10 +50,3 @@ export async function runCommand(
|
|
|
50
50
|
executionTimeMs: Date.now() - startTime,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
export function runShellCommand(
|
|
55
|
-
command: string,
|
|
56
|
-
options: CommandOptions = {},
|
|
57
|
-
): Promise<CommandResult> {
|
|
58
|
-
return runCommand(["sh", "-c", command], options);
|
|
59
|
-
}
|
package/src/shared/exec.ts
CHANGED
|
@@ -23,18 +23,21 @@ export const collectProcessOutput = (process: ChildProcessSpawner.ChildProcessHa
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
export const execEffect = (
|
|
26
|
-
|
|
26
|
+
executable: string,
|
|
27
|
+
args: readonly string[] = [],
|
|
27
28
|
timeoutMs: number = DEFAULT_TIMEOUT_MS,
|
|
28
29
|
): Effect.Effect<
|
|
29
30
|
{ stdout: string; stderr: string; exitCode: number },
|
|
30
31
|
ExecError,
|
|
31
32
|
ChildProcessSpawner.ChildProcessSpawner
|
|
32
|
-
> =>
|
|
33
|
-
|
|
33
|
+
> => {
|
|
34
|
+
const commandStr = renderCommandLine([executable, ...args]);
|
|
35
|
+
|
|
36
|
+
return Effect.scoped(
|
|
34
37
|
Effect.gen(function* () {
|
|
35
38
|
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
36
39
|
|
|
37
|
-
const command = ChildProcess.make(
|
|
40
|
+
const command = ChildProcess.make(executable, args, {
|
|
38
41
|
stdout: "pipe",
|
|
39
42
|
stderr: "pipe",
|
|
40
43
|
});
|
|
@@ -67,5 +70,56 @@ export const execEffect = (
|
|
|
67
70
|
),
|
|
68
71
|
),
|
|
69
72
|
);
|
|
73
|
+
};
|
|
70
74
|
|
|
71
75
|
export const quoteShellArg = (value: string) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Split a command line the way a POSIX shell would word-split it, without running one.
|
|
79
|
+
* Quotes group a value into a single argument and are removed; nothing else is interpreted,
|
|
80
|
+
* so a metacharacter reaches the child as literal text instead of shell syntax.
|
|
81
|
+
*/
|
|
82
|
+
export const tokenizeCommandLine = (commandLine: string): string[] => {
|
|
83
|
+
const tokens: string[] = [];
|
|
84
|
+
let current = "";
|
|
85
|
+
let started = false;
|
|
86
|
+
let quote: '"' | "'" | undefined;
|
|
87
|
+
|
|
88
|
+
for (const character of commandLine) {
|
|
89
|
+
if (quote) {
|
|
90
|
+
if (character === quote) {
|
|
91
|
+
quote = undefined;
|
|
92
|
+
} else {
|
|
93
|
+
current += character;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (character === '"' || character === "'") {
|
|
99
|
+
quote = character;
|
|
100
|
+
started = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (/\s/.test(character)) {
|
|
105
|
+
if (started) {
|
|
106
|
+
tokens.push(current);
|
|
107
|
+
current = "";
|
|
108
|
+
started = false;
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
current += character;
|
|
114
|
+
started = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (started) {
|
|
118
|
+
tokens.push(current);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return tokens;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const renderCommandLine = (argv: readonly string[]): string =>
|
|
125
|
+
argv.map((arg) => (/^[A-Za-z0-9_./:=,@+-]+$/.test(arg) ? arg : quoteShellArg(arg))).join(" ");
|
package/src/shared/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type {
|
|
|
9
9
|
|
|
10
10
|
export { formatAny, formatOption, formatOutput, logFormatted } from "./format";
|
|
11
11
|
|
|
12
|
-
export { expandPath, runCommand
|
|
12
|
+
export { expandPath, runCommand } from "./bun";
|
|
13
13
|
|
|
14
14
|
export { commonArgOptions, parseCommonArgs } from "./cli";
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ export { dumpCommandSchema, makeSchemaCommand, type CommandSchema } from "./sche
|
|
|
21
21
|
import pkg from "../../package.json" with { type: "json" };
|
|
22
22
|
export const VERSION = pkg.version;
|
|
23
23
|
|
|
24
|
-
export { execEffect, type ExecError } from "./exec";
|
|
24
|
+
export { execEffect, renderCommandLine, tokenizeCommandLine, type ExecError } from "./exec";
|
|
25
25
|
|
|
26
26
|
export { createThrottle, type ThrottleError } from "./throttle";
|
|
27
27
|
|