@cpajs/cpa 0.0.1-alpha-3 → 0.1.0-rc.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.
Files changed (62) hide show
  1. package/README.md +108 -12
  2. package/dist/client/resources/oauth.d.ts +6 -2
  3. package/dist/client/resources/oauth.d.ts.map +1 -1
  4. package/dist/client/resources/oauth.js +28 -8
  5. package/dist/client/resources/oauth.js.map +1 -1
  6. package/dist/client/transport.d.ts.map +1 -1
  7. package/dist/client/transport.js +7 -3
  8. package/dist/client/transport.js.map +1 -1
  9. package/dist/server/args.js +1 -1
  10. package/dist/server/args.js.map +1 -1
  11. package/dist/server/binary.d.ts +19 -0
  12. package/dist/server/binary.d.ts.map +1 -1
  13. package/dist/server/binary.js +35 -1
  14. package/dist/server/binary.js.map +1 -1
  15. package/dist/server/config.d.ts +46 -7
  16. package/dist/server/config.d.ts.map +1 -1
  17. package/dist/server/config.js +148 -22
  18. package/dist/server/config.js.map +1 -1
  19. package/dist/server/errors.d.ts +4 -2
  20. package/dist/server/errors.d.ts.map +1 -1
  21. package/dist/server/errors.js +1 -1
  22. package/dist/server/errors.js.map +1 -1
  23. package/dist/server/index.d.ts +18 -1
  24. package/dist/server/index.d.ts.map +1 -1
  25. package/dist/server/index.js +383 -79
  26. package/dist/server/index.js.map +1 -1
  27. package/dist/server/internal.d.ts +19 -0
  28. package/dist/server/internal.d.ts.map +1 -0
  29. package/dist/server/internal.js +10 -0
  30. package/dist/server/internal.js.map +1 -0
  31. package/dist/server/management.d.ts +29 -0
  32. package/dist/server/management.d.ts.map +1 -0
  33. package/dist/server/management.js +64 -0
  34. package/dist/server/management.js.map +1 -0
  35. package/dist/server/process.d.ts +12 -1
  36. package/dist/server/process.d.ts.map +1 -1
  37. package/dist/server/process.js +55 -13
  38. package/dist/server/process.js.map +1 -1
  39. package/dist/server/readiness.d.ts +16 -2
  40. package/dist/server/readiness.d.ts.map +1 -1
  41. package/dist/server/readiness.js +32 -2
  42. package/dist/server/readiness.js.map +1 -1
  43. package/dist/server/targets.d.ts +4 -0
  44. package/dist/server/targets.d.ts.map +1 -1
  45. package/dist/server/targets.js +6 -0
  46. package/dist/server/targets.js.map +1 -1
  47. package/dist/server/types.d.ts +88 -14
  48. package/dist/server/types.d.ts.map +1 -1
  49. package/package.json +31 -24
  50. package/src/client/resources/oauth.ts +28 -13
  51. package/src/client/transport.ts +7 -3
  52. package/src/server/args.ts +1 -1
  53. package/src/server/binary.ts +46 -1
  54. package/src/server/config.ts +169 -22
  55. package/src/server/errors.ts +4 -2
  56. package/src/server/index.ts +459 -94
  57. package/src/server/internal.ts +20 -0
  58. package/src/server/management.ts +89 -0
  59. package/src/server/process.ts +60 -13
  60. package/src/server/readiness.ts +38 -3
  61. package/src/server/targets.ts +8 -0
  62. package/src/server/types.ts +90 -14
@@ -15,6 +15,22 @@ export interface HomeOptions {
15
15
  /** Keep using the JWT's address instead of discovering cluster nodes (`-home-disable-cluster-discovery`). */
16
16
  disableClusterDiscovery?: boolean;
17
17
  }
18
+ export interface ManagementOptions {
19
+ /** Management API key. Empty keys are rejected. */
20
+ key: string;
21
+ /**
22
+ * Write `management.allow-remote`, which lets non-loopback clients use the
23
+ * management API. Default `false`; ignored by `viaEnvironment`, which always
24
+ * enables remote access upstream.
25
+ */
26
+ allowRemote?: boolean;
27
+ /**
28
+ * Pass the key to CPA through `MANAGEMENT_PASSWORD` instead of writing a
29
+ * bcrypt hash into the managed config. Upstream treats that variable as
30
+ * enabling remote management and compares the key in plaintext.
31
+ */
32
+ viaEnvironment?: boolean;
33
+ }
18
34
  interface CommonServerOptions {
19
35
  /** Use this executable instead of the bundled platform package. */
20
36
  binaryPath?: string;
@@ -23,10 +39,15 @@ interface CommonServerOptions {
23
39
  * directory. When omitted, a temporary directory is created and removed on stop.
24
40
  */
25
41
  stateDir?: string;
42
+ /** Management API key and how it reaches CPA. */
43
+ management?: ManagementOptions;
26
44
  /**
27
- * Management key passed to CPA through the `MANAGEMENT_PASSWORD` environment
28
- * variable, so it never touches disk. Note that upstream treats this variable
29
- * as allowing remote management access regardless of `management.allow-remote`.
45
+ * Deprecated alias for `management: { key }`. It uses the safe default
46
+ * (bcrypt hash in the config, remote management disabled) and will be removed
47
+ * in a future release. Use `management.viaEnvironment` for the old
48
+ * `MANAGEMENT_PASSWORD` behaviour.
49
+ *
50
+ * @deprecated Use `management` instead.
30
51
  */
31
52
  managementKey?: string;
32
53
  /**
@@ -34,6 +55,22 @@ interface CommonServerOptions {
34
55
  * (`-local-model`). Explicit `models.*` config sources still win.
35
56
  */
36
57
  localModel?: boolean;
58
+ /**
59
+ * Start CPA through `cpa-launcher` so it cannot outlive this process, even
60
+ * when this process is killed with SIGKILL or crashes. Default `true`.
61
+ *
62
+ * - Linux: the launcher sets PR_SET_PDEATHSIG and execs CPA (no extra process).
63
+ * - macOS / BSD: the launcher stays as CPA's parent and stops it when this
64
+ * process dies; `pid` is then the launcher's pid, and signals sent to it are
65
+ * forwarded to CPA.
66
+ * - Windows: not needed; Node already kills child processes with their parent.
67
+ *
68
+ * The launcher ships next to the CPA binary in every platform package. With a
69
+ * custom `binaryPath` it is looked up next to that binary and then in an
70
+ * installed platform package; if none is found CPA is started directly and a
71
+ * notice is logged through `onLog`.
72
+ */
73
+ launcher?: boolean;
37
74
  /** Run as a node of a CPA Home control plane. Home mode disables the local management API. */
38
75
  home?: HomeOptions;
39
76
  /**
@@ -52,23 +89,39 @@ interface CommonServerOptions {
52
89
  onLog?: (log: LogLine) => void;
53
90
  /** Additional strings to redact from logs and errors. */
54
91
  redact?: readonly string[];
55
- /** Default grace period for `stop()` before the process is killed. Default 10 000 ms. */
92
+ /** Default grace period for `stop()` before the process is killed. Ignored on Windows, where `stop()` terminates forcefully. Default 10 000 ms. */
56
93
  stopTimeoutMs?: number;
57
94
  }
58
95
  export interface StartWithConfigOptions extends CommonServerOptions {
59
96
  /**
60
- * Native CPA configuration. It is written to `<stateDir>/config.yaml` on
61
- * every start, replacing whatever CPA persisted there during the last run.
97
+ * Native CPA configuration.
98
+ *
99
+ * Written as YAML to `<stateDir>/config.yaml` only when that file does not
100
+ * exist. On later starts the existing file is reused, so changes made through
101
+ * the management API (or by CPA itself) survive a `stop()` / `startServer()`
102
+ * cycle. Set `overwriteConfig: true` to replace it with this object.
62
103
  */
63
104
  config?: CpaConfig;
64
105
  configPath?: never;
65
- /** Bind host. Overrides `config.server.host`. Default `127.0.0.1`. */
106
+ /**
107
+ * Replace `<stateDir>/config.yaml` even when it already exists. Default
108
+ * `false`. When the existing file is kept, `config`, `host` and `port` are not
109
+ * applied and a message is emitted through `onLog`.
110
+ */
111
+ overwriteConfig?: boolean;
112
+ /** Bind host, when cpajs writes the managed file. Default `config.server.host`, else `127.0.0.1`. */
66
113
  host?: string;
67
- /** Bind port. Overrides `config.server.port`. Default `0`, meaning a free port is picked. */
114
+ /** Bind port, when cpajs writes the managed file. Default `config.server.port`, else `0` (a free port is picked). */
68
115
  port?: number;
69
116
  }
70
117
  export interface StartWithConfigPathOptions extends CommonServerOptions {
71
- /** Existing CPA config file, handed to CPA as-is. CPA may rewrite it (e.g. to hash `secret-key`). */
118
+ /**
119
+ * Existing CPA config file, handed to CPA as-is. CPA may rewrite it on
120
+ * startup: it hashes a plaintext `management.secret-key`, removes legacy
121
+ * keys that conflict with their v8 equivalents, and normalizes the layout.
122
+ * The file must set a fixed positive port; a missing or non-positive port
123
+ * makes CPA bind a random port it does not report.
124
+ */
72
125
  configPath: string;
73
126
  config?: never;
74
127
  host?: never;
@@ -80,9 +133,18 @@ export interface ExitResult {
80
133
  signal: NodeJS.Signals | null;
81
134
  }
82
135
  export interface StopOptions {
83
- /** Grace period after the termination signal before the process is killed. */
136
+ /** Grace period after the termination signal before the process is killed. Ignored on Windows, where the process is terminated forcefully right away. */
84
137
  timeoutMs?: number;
85
138
  }
139
+ export interface RestartOptions {
140
+ /**
141
+ * Grace period after the termination signal before the old process is killed.
142
+ * Defaults to `stopTimeoutMs`.
143
+ */
144
+ timeoutMs?: number;
145
+ /** Cancels the restart. The server is left stopped, like any other failed restart. */
146
+ signal?: AbortSignal;
147
+ }
86
148
  export interface CpaServer {
87
149
  /** e.g. `http://127.0.0.1:8317`. Use as the client `baseUrl`. */
88
150
  readonly origin: string;
@@ -94,15 +156,27 @@ export interface CpaServer {
94
156
  readonly binaryPath: string;
95
157
  readonly configPath: string;
96
158
  readonly stateDir: string;
97
- /** The management key given in the options, if any. */
98
- readonly managementKey: string | undefined;
99
159
  readonly process: ChildProcess;
100
160
  /** Recent output lines (redacted), oldest first. */
101
161
  readonly recentLogs: readonly LogLine[];
102
- /** Settles when the process exits, whether or not `stop()` was called. */
162
+ /**
163
+ * Settles when the process exits for good: on `stop()` or an unexpected exit.
164
+ * A restart stops one process and starts another, so it does not settle this.
165
+ */
103
166
  readonly exited: Promise<ExitResult>;
104
- /** Terminates CPA gracefully, killing it after the grace period. Idempotent. */
167
+ /**
168
+ * Stops CPA. On POSIX it sends SIGTERM and then SIGKILL after the grace
169
+ * period. On Windows Node cannot deliver a graceful signal, so the process is
170
+ * terminated forcefully (there is no grace period and CPA is not asked to
171
+ * flush state or update auth files). Idempotent.
172
+ */
105
173
  stop(options?: StopOptions): Promise<ExitResult>;
174
+ /**
175
+ * Restarts CPA with the original options, reusing `origin`, `baseUrl`, `host`,
176
+ * `port`, `stateDir` and the existing config file. Concurrent calls share one
177
+ * operation. A failed or cancelled restart leaves the server stopped.
178
+ */
179
+ restart(options?: RestartOptions): Promise<void>;
106
180
  }
107
181
  export {};
108
182
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAE3D,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,6GAA6G;IAC7G,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,UAAU,mBAAmB;IAC3B,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,4GAA4G;IAC5G,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnD,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/B,yDAAyD;IACzD,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6FAA6F;IAC7F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA2B,SAAQ,mBAAmB;IACrE,qGAAqG;IACrG,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,KAAK,CAAC;CACd;AAED,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG,0BAA0B,CAAC;AAErF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,gFAAgF;IAChF,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cpajs/cpa",
3
- "version": "0.0.1-alpha-3",
3
+ "version": "0.1.0-rc.2",
4
4
  "description": "Run the official CLIProxyAPI from JavaScript and talk to its v8 management API with a typed client",
5
5
  "license": "MIT",
6
6
  "author": "akarachen",
@@ -35,7 +35,11 @@
35
35
  "scripts": {
36
36
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
37
37
  "typecheck": "tsc -p tsconfig.json",
38
+ "lint": "biome lint .",
39
+ "format": "biome format --write .",
40
+ "format:check": "biome format .",
38
41
  "test": "vitest run",
42
+ "test:coverage": "vitest run --coverage",
39
43
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
40
44
  "cpa:source": "node scripts/fetch-source.ts",
41
45
  "cpa:build": "node scripts/build-binary.ts",
@@ -46,10 +50,13 @@
46
50
  "test:install": "node scripts/install-test.ts"
47
51
  },
48
52
  "dependencies": {
53
+ "bcryptjs": "^3.0.3",
49
54
  "yaml": "^2.9.1"
50
55
  },
51
56
  "devDependencies": {
57
+ "@biomejs/biome": "^2.6.3",
52
58
  "@types/node": "^24.0.0",
59
+ "@vitest/coverage-v8": "^5.0.5",
53
60
  "typescript": "^7.0.2",
54
61
  "vitest": "^5.0.5"
55
62
  },
@@ -58,28 +65,28 @@
58
65
  "access": "public"
59
66
  },
60
67
  "optionalDependencies": {
61
- "@cpajs/darwin-x64": "0.0.1-alpha-3",
62
- "@cpajs/darwin-arm64": "0.0.1-alpha-3",
63
- "@cpajs/win32-x64": "0.0.1-alpha-3",
64
- "@cpajs/win32-arm64": "0.0.1-alpha-3",
65
- "@cpajs/win32-ia32": "0.0.1-alpha-3",
66
- "@cpajs/linux-x64-gnu": "0.0.1-alpha-3",
67
- "@cpajs/linux-arm64-gnu": "0.0.1-alpha-3",
68
- "@cpajs/linux-x64-musl": "0.0.1-alpha-3",
69
- "@cpajs/linux-arm64-musl": "0.0.1-alpha-3",
70
- "@cpajs/linux-arm": "0.0.1-alpha-3",
71
- "@cpajs/linux-ia32": "0.0.1-alpha-3",
72
- "@cpajs/linux-riscv64": "0.0.1-alpha-3",
73
- "@cpajs/linux-ppc64": "0.0.1-alpha-3",
74
- "@cpajs/linux-s390x": "0.0.1-alpha-3",
75
- "@cpajs/linux-loong64": "0.0.1-alpha-3",
76
- "@cpajs/linux-mips": "0.0.1-alpha-3",
77
- "@cpajs/linux-mipsel": "0.0.1-alpha-3",
78
- "@cpajs/freebsd-x64": "0.0.1-alpha-3",
79
- "@cpajs/freebsd-arm64": "0.0.1-alpha-3",
80
- "@cpajs/freebsd-ia32": "0.0.1-alpha-3",
81
- "@cpajs/openbsd-x64": "0.0.1-alpha-3",
82
- "@cpajs/openbsd-arm64": "0.0.1-alpha-3",
83
- "@cpajs/netbsd-x64": "0.0.1-alpha-3"
68
+ "@cpajs/darwin-x64": "0.1.0-rc.2",
69
+ "@cpajs/darwin-arm64": "0.1.0-rc.2",
70
+ "@cpajs/win32-x64": "0.1.0-rc.2",
71
+ "@cpajs/win32-arm64": "0.1.0-rc.2",
72
+ "@cpajs/win32-ia32": "0.1.0-rc.2",
73
+ "@cpajs/linux-x64-gnu": "0.1.0-rc.2",
74
+ "@cpajs/linux-arm64-gnu": "0.1.0-rc.2",
75
+ "@cpajs/linux-x64-musl": "0.1.0-rc.2",
76
+ "@cpajs/linux-arm64-musl": "0.1.0-rc.2",
77
+ "@cpajs/linux-arm": "0.1.0-rc.2",
78
+ "@cpajs/linux-ia32": "0.1.0-rc.2",
79
+ "@cpajs/linux-riscv64": "0.1.0-rc.2",
80
+ "@cpajs/linux-ppc64": "0.1.0-rc.2",
81
+ "@cpajs/linux-s390x": "0.1.0-rc.2",
82
+ "@cpajs/linux-loong64": "0.1.0-rc.2",
83
+ "@cpajs/linux-mips": "0.1.0-rc.2",
84
+ "@cpajs/linux-mipsel": "0.1.0-rc.2",
85
+ "@cpajs/freebsd-x64": "0.1.0-rc.2",
86
+ "@cpajs/freebsd-arm64": "0.1.0-rc.2",
87
+ "@cpajs/freebsd-ia32": "0.1.0-rc.2",
88
+ "@cpajs/openbsd-x64": "0.1.0-rc.2",
89
+ "@cpajs/openbsd-arm64": "0.1.0-rc.2",
90
+ "@cpajs/netbsd-x64": "0.1.0-rc.2"
84
91
  }
85
92
  }
@@ -9,7 +9,8 @@ import type {
9
9
  VertexImportResponse,
10
10
  } from '../types/oauth.ts';
11
11
  import type { SIWCStatusResponse } from '../types/credentials.ts';
12
- import type { RequestOptions, Transport } from '../transport.ts';
12
+ import { CpaAbortError } from '../errors.ts';
13
+ import { managementUrl, type RequestOptions, type Transport } from '../transport.ts';
13
14
  import type { CredentialContent, CredentialSelector } from './credentials.ts';
14
15
 
15
16
  export interface WaitForOAuthOptions extends RequestOptions {
@@ -38,23 +39,20 @@ export class OAuthResource {
38
39
  return (await this.#t.request<OAuthStatusResponse>({ ...options, method: 'GET', path: '/oauth/status', query: { state } })).data;
39
40
  }
40
41
 
41
- /** Polls {@link status} until the session leaves `wait`. Abort with `signal`. */
42
+ /**
43
+ * Polls {@link status} until the session leaves `wait`. Abort with `signal`
44
+ * (for an overall limit use `AbortSignal.timeout()`); cancellation always
45
+ * rejects with `CpaAbortError`, whether it happens during a request or between polls.
46
+ */
42
47
  async waitForCompletion(state: string, options: WaitForOAuthOptions = {}): Promise<Exclude<OAuthStatusResponse, { status: 'wait' }>> {
43
48
  const { intervalMs = 2_000, ...requestOptions } = options;
49
+ const url = managementUrl(this.#t.baseUrl, '/oauth/status');
50
+ url.searchParams.set('state', state);
51
+ const target = { method: 'GET', url: url.toString() };
44
52
  while (true) {
45
53
  const result = await this.status(state, requestOptions);
46
54
  if (result.status !== 'wait') return result;
47
- await new Promise<void>((resolve, reject) => {
48
- const timer = setTimeout(resolve, intervalMs);
49
- requestOptions.signal?.addEventListener(
50
- 'abort',
51
- () => {
52
- clearTimeout(timer);
53
- reject(requestOptions.signal!.reason);
54
- },
55
- { once: true },
56
- );
57
- });
55
+ await abortableDelay(intervalMs, requestOptions.signal, target);
58
56
  }
59
57
  }
60
58
 
@@ -103,3 +101,20 @@ export class OAuthResource {
103
101
  return (await this.#t.request<SIWCStatusResponse>({ ...options, method: 'GET', path: '/oauth/siwc/status', query: { ...selector } })).data;
104
102
  }
105
103
  }
104
+
105
+ /** Waits `ms`, rejecting with `CpaAbortError` if `signal` aborts; never leaves a listener behind. */
106
+ function abortableDelay(ms: number, signal: AbortSignal | undefined, target: { method: string; url: string }): Promise<void> {
107
+ const aborted = () => new CpaAbortError(`${target.method} ${target.url} was aborted`, target, { cause: signal!.reason });
108
+ if (signal?.aborted) return Promise.reject(aborted());
109
+ return new Promise<void>((resolve, reject) => {
110
+ const onAbort = (): void => {
111
+ clearTimeout(timer);
112
+ reject(aborted());
113
+ };
114
+ const timer = setTimeout(() => {
115
+ signal?.removeEventListener('abort', onAbort);
116
+ resolve();
117
+ }, ms);
118
+ signal?.addEventListener('abort', onAbort, { once: true });
119
+ });
120
+ }
@@ -163,6 +163,8 @@ export class Transport {
163
163
  if (body !== undefined) init.body = body;
164
164
 
165
165
  const responseType = req.responseType ?? 'json';
166
+ // Set once a Response is handed to the caller, who then owns reading its body.
167
+ let handedOff = false;
166
168
  try {
167
169
  let response: Response;
168
170
  try {
@@ -175,6 +177,7 @@ export class Transport {
175
177
  throw new CpaHttpError(target, response, await guard(response.text()));
176
178
  }
177
179
  if (responseType === 'response') {
180
+ handedOff = true;
178
181
  return { data: response as T, status: response.status, headers: response.headers };
179
182
  }
180
183
  if (responseType === 'blob') {
@@ -194,9 +197,10 @@ export class Transport {
194
197
  }
195
198
  throw classify(error);
196
199
  } finally {
197
- // For `response`, the caller reads the body; the timeout covers the headers only,
198
- // while the caller's own signal keeps working through the request's signal.
199
- if (responseType === 'response') clearTimeout(timer);
200
+ // A handed-off Response is read by the caller: the timeout covered the headers
201
+ // only, and the caller's signal must keep cancelling the body, so its listener
202
+ // stays until that signal aborts. Every other outcome releases everything.
203
+ if (handedOff) clearTimeout(timer);
200
204
  else release();
201
205
  }
202
206
  }
@@ -26,7 +26,7 @@ const MANAGED_FLAGS: Record<string, string> = {
26
26
  'local-model': 'localModel',
27
27
  'home-jwt': 'home.jwt',
28
28
  'home-disable-cluster-discovery': 'home.disableClusterDiscovery',
29
- password: 'managementKey',
29
+ password: 'management.key',
30
30
  };
31
31
 
32
32
  function flagName(arg: string): string | undefined {
@@ -2,7 +2,7 @@ import { accessSync, chmodSync, constants, existsSync, readdirSync, statSync } f
2
2
  import { createRequire } from 'node:module';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { CpaBinaryError } from './errors.ts';
5
- import { TARGETS, UNSUPPORTED_PLATFORMS, binaryName, packageName, type Target } from './targets.ts';
5
+ import { LAUNCHER_NAME, TARGETS, UNSUPPORTED_PLATFORMS, binaryName, packageName, type Target } from './targets.ts';
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8
 
@@ -126,3 +126,48 @@ export function resolveBinary(options: { binaryPath?: string; platform?: HostPla
126
126
  'make sure your lockfile was not created on another platform without it, or pass binaryPath.',
127
127
  );
128
128
  }
129
+
130
+ /** How the launcher keeps CPA from outliving this process. */
131
+ export type LauncherMode = 'exec' | 'supervise';
132
+
133
+ export interface ResolvedLauncher {
134
+ path: string;
135
+ /**
136
+ * `exec` (Linux): the launcher sets PR_SET_PDEATHSIG and becomes CPA.
137
+ * `supervise` (macOS, BSD): the launcher stays as CPA's parent and stops it
138
+ * when its stdin pipe from this process closes.
139
+ */
140
+ mode: LauncherMode;
141
+ }
142
+
143
+ function isExecutable(path: string): boolean {
144
+ try {
145
+ if (!statSync(path).isFile()) return false;
146
+ accessSync(path, constants.X_OK);
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Finds `cpa-launcher` for a CPA binary: next to the binary first (platform
155
+ * packages and local builds keep both in one directory), then in an installed
156
+ * platform package. Returns undefined on Windows, where Node already places
157
+ * child processes in a job object that is closed (and its processes killed)
158
+ * when this process exits.
159
+ */
160
+ export function resolveLauncher(cpaPath: string, platform: HostPlatform = currentPlatform()): ResolvedLauncher | undefined {
161
+ if (platform.os === 'win32') return undefined;
162
+ const mode: LauncherMode = platform.os === 'linux' ? 'exec' : 'supervise';
163
+ const candidates = [join(dirname(cpaPath), LAUNCHER_NAME)];
164
+ for (const target of candidateTargets(platform)) {
165
+ try {
166
+ candidates.push(join(dirname(require.resolve(`${packageName(target)}/package.json`)), 'bin', LAUNCHER_NAME));
167
+ } catch {
168
+ // Package not installed.
169
+ }
170
+ }
171
+ const path = candidates.find(isExecutable);
172
+ return path === undefined ? undefined : { path, mode };
173
+ }