@pithy-sh/core 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/asyncapi/document.d.ts +46 -0
- package/dist/asyncapi/document.d.ts.map +1 -0
- package/dist/asyncapi/document.js +115 -0
- package/dist/capability/capability.d.ts +7 -0
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/composition.d.ts.map +1 -1
- package/dist/capability/composition.js +5 -1
- package/dist/capability/settings.d.ts +10 -2
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/capability/settings.js +5 -3
- package/dist/cli/command.d.ts +364 -0
- package/dist/cli/command.d.ts.map +1 -0
- package/dist/cli/command.js +418 -0
- package/dist/cli/commandCatalog.generated.d.ts +2275 -0
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -0
- package/dist/cli/commandCatalog.generated.js +2390 -0
- package/dist/cli/commandFacts.d.ts +164 -0
- package/dist/cli/commandFacts.d.ts.map +1 -0
- package/dist/cli/commandFacts.js +215 -0
- package/dist/controlPlane/error/errors.d.ts.map +1 -1
- package/dist/controlPlane/error/errors.js +4 -3
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +11 -1
- package/dist/controlPlane/http/routes.d.ts +1 -1
- package/dist/controlPlane/http/routes.d.ts.map +1 -1
- package/dist/controlPlane/http/routes.js +34 -7
- package/dist/createBackend.d.ts +7 -0
- package/dist/createBackend.d.ts.map +1 -1
- package/dist/createBackend.js +34 -4
- package/dist/entitlement/require.js +1 -1
- package/dist/error/cause.d.ts +16 -116
- package/dist/error/cause.d.ts.map +1 -1
- package/dist/error/cause.js +19 -153
- package/dist/error/http.d.ts +40 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +80 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +26 -0
- package/dist/error/pithyError.d.ts +44 -0
- package/dist/error/pithyError.d.ts.map +1 -1
- package/dist/error/pithyError.js +56 -1
- package/dist/error/unresolved.d.ts +265 -0
- package/dist/error/unresolved.d.ts.map +1 -0
- package/dist/error/unresolved.js +599 -0
- package/dist/error/wire.d.ts +20 -0
- package/dist/error/wire.d.ts.map +1 -0
- package/dist/error/wire.js +19 -0
- package/dist/http/oidcWebhook.d.ts +3 -3
- package/dist/http/oidcWebhook.js +3 -3
- package/dist/http/routeContract.js +1 -1
- package/dist/http/routeSpec.d.ts +66 -0
- package/dist/http/routeSpec.d.ts.map +1 -0
- package/dist/http/routeSpec.js +40 -0
- package/dist/http/sameOrigin.d.ts +1 -1
- package/dist/http/sameOrigin.js +1 -1
- package/dist/http/signedWebhook.d.ts +3 -3
- package/dist/http/signedWebhook.js +3 -3
- package/dist/http/socket.d.ts +52 -0
- package/dist/http/socket.d.ts.map +1 -0
- package/dist/http/socket.js +2 -0
- package/dist/http/subprotocol.d.ts +29 -0
- package/dist/http/subprotocol.d.ts.map +1 -0
- package/dist/http/subprotocol.js +37 -0
- package/dist/http/validation.d.ts +40 -8
- package/dist/http/validation.d.ts.map +1 -1
- package/dist/http/validation.js +25 -1
- package/dist/index.js +2 -2
- package/dist/logger/record.d.ts +23 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/migrations/bookkeeping.d.ts +21 -0
- package/dist/migrations/bookkeeping.d.ts.map +1 -1
- package/dist/migrations/bookkeeping.js +32 -3
- package/dist/migrations/groups.d.ts +140 -0
- package/dist/migrations/groups.d.ts.map +1 -0
- package/dist/migrations/groups.js +191 -0
- package/dist/migrations/migrateStep.d.ts +13 -0
- package/dist/migrations/migrateStep.d.ts.map +1 -0
- package/dist/migrations/migrateStep.js +20 -0
- package/dist/migrations/owner.d.ts +7 -0
- package/dist/migrations/owner.d.ts.map +1 -1
- package/dist/migrations/owner.js +11 -7
- package/dist/migrations/retained.js +2 -2
- package/dist/migrations/runner.d.ts +76 -5
- package/dist/migrations/runner.d.ts.map +1 -1
- package/dist/migrations/runner.js +130 -19
- package/dist/naming/domains.d.ts +30 -0
- package/dist/naming/domains.d.ts.map +1 -1
- package/dist/naming/domains.js +83 -4
- package/dist/naming/environment.d.ts.map +1 -1
- package/dist/naming/environment.js +2 -1
- package/dist/naming/packageName.d.ts +17 -0
- package/dist/naming/packageName.d.ts.map +1 -0
- package/dist/naming/packageName.js +22 -0
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +2 -1
- package/dist/openapi/document.d.ts +106 -0
- package/dist/openapi/document.d.ts.map +1 -0
- package/dist/openapi/document.js +314 -0
- package/dist/openapi/routeGroups.d.ts +8 -0
- package/dist/openapi/routeGroups.d.ts.map +1 -0
- package/dist/openapi/routeGroups.js +23 -0
- package/dist/seed/devLogin.d.ts +33 -0
- package/dist/seed/devLogin.d.ts.map +1 -1
- package/dist/seed/devLogin.js +28 -1
- package/dist/text/comments.js +66 -4
- package/dist/text/literals.d.ts +43 -0
- package/dist/text/literals.d.ts.map +1 -0
- package/dist/text/literals.js +293 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.d.ts.map +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflow/dispatchRoute.d.ts.map +1 -1
- package/dist/workflow/dispatchRoute.js +12 -6
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +2 -3
- package/dist/workflow/hostEnv.d.ts +1 -0
- package/dist/workflow/hostEnv.d.ts.map +1 -1
- package/dist/workflow/hostEnv.js +4 -2
- package/dist/workflow/loopback.d.ts.map +1 -1
- package/dist/workflow/loopback.js +2 -1
- package/package.json +3 -3
- package/src/asyncapi/document.ts +164 -0
- package/src/capability/capability.ts +7 -0
- package/src/capability/composition.ts +2 -1
- package/src/capability/settings.ts +19 -2
- package/src/cli/command.ts +865 -0
- package/src/cli/commandCatalog.generated.ts +2533 -0
- package/src/cli/commandFacts.ts +219 -0
- package/src/controlPlane/error/errors.ts +10 -3
- package/src/controlPlane/http/guard.ts +14 -2
- package/src/controlPlane/http/routes.ts +57 -9
- package/src/createBackend.ts +35 -2
- package/src/entitlement/require.ts +1 -1
- package/src/error/cause.ts +18 -181
- package/src/error/payload.ts +59 -0
- package/src/error/pithyError.ts +76 -0
- package/src/error/unresolved.ts +853 -0
- package/src/error/wire.ts +40 -0
- package/src/http/oidcWebhook.ts +3 -3
- package/src/http/routeContract.ts +1 -1
- package/src/http/routeSpec.ts +81 -0
- package/src/http/sameOrigin.ts +1 -1
- package/src/http/signedWebhook.ts +3 -3
- package/src/http/socket.ts +55 -0
- package/src/http/subprotocol.ts +47 -0
- package/src/http/validation.ts +50 -8
- package/src/migrations/bookkeeping.ts +39 -2
- package/src/migrations/groups.ts +295 -0
- package/src/migrations/migrateStep.ts +19 -0
- package/src/migrations/owner.ts +10 -6
- package/src/migrations/retained.ts +2 -2
- package/src/migrations/runner.ts +214 -22
- package/src/naming/domains.ts +97 -16
- package/src/naming/environment.ts +2 -1
- package/src/naming/packageName.ts +20 -0
- package/src/naming/provisionScope.ts +2 -1
- package/src/openapi/document.ts +412 -0
- package/src/openapi/routeGroups.ts +25 -0
- package/src/seed/devLogin.ts +31 -0
- package/src/text/comments.ts +56 -4
- package/src/text/literals.ts +294 -0
- package/src/version.generated.ts +1 -1
- package/src/workflow/dispatchRoute.ts +13 -6
- package/src/workflow/faults.ts +5 -3
- package/src/workflow/hostEnv.ts +9 -1
- package/src/workflow/loopback.ts +2 -1
package/src/error/cause.ts
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* This module holds only what does not — the facts about the shapes a runtime hands over, and the filter
|
|
12
12
|
* that decides whether one of its strings is safe to put in front of an adopter.
|
|
13
13
|
*
|
|
14
|
+
* The one exception is an import that would not resolve, and it has its own module: `unresolved.ts`
|
|
15
|
+
* decides the kind and the remedy for all three surfaces, because three surfaces deciding it kept
|
|
16
|
+
* disagreeing (#709).
|
|
17
|
+
*
|
|
14
18
|
* **The filter is here for the same reason the facts are, and the argument took two issues to finish.**
|
|
15
|
-
* #223 moved `rootCause
|
|
19
|
+
* #223 moved `rootCause` and `prop` in and deliberately left `safeReason` beside
|
|
16
20
|
* the three refusals, on the reading that what a surface may say is that surface's business. That is true
|
|
17
21
|
* of the sentences and false of the filter: whether a string carries a path, a stack frame or half a
|
|
18
22
|
* parser's ANSI box is a property of the string, and the answer cannot differ between the CLI, a
|
|
@@ -26,11 +30,12 @@
|
|
|
26
30
|
/**
|
|
27
31
|
* Read a property off an unknown throwable without widening anything to `any`.
|
|
28
32
|
*
|
|
29
|
-
* Deliberately duck-typed.
|
|
30
|
-
*
|
|
31
|
-
* `instanceof Error
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* Deliberately duck-typed. A cause is `unknown`, and an `instanceof Error` gate is a claim about a
|
|
34
|
+
* runtime's classes that the runtime can break. Bun did: on 1.3 its `ResolveMessage` and `BuildMessage`
|
|
35
|
+
* were **not** `instanceof Error`, so a gate passed a whole suite under vitest, which runs on Node, and
|
|
36
|
+
* silently dropped the parser's own sentence on the runtime that ships. That is #207. Bun 1.4 made both
|
|
37
|
+
* real `Error`s (measured on 1.4.2), and nothing here had to change, which is the point: every reader of
|
|
38
|
+
* a cause in this kit goes through this function and reads fields, never a class.
|
|
34
39
|
*
|
|
35
40
|
* Non-enumerable properties are read too — `AggregateError.errors` is one, and {@link rootCause}
|
|
36
41
|
* depends on it.
|
|
@@ -49,7 +54,7 @@ export function prop(cause: unknown, key: string): unknown {
|
|
|
49
54
|
* and only by running on Bun with a failure of the *realistic* shape.
|
|
50
55
|
*
|
|
51
56
|
* **And the realistic shape is the wrapped one.** A stray brace cascades — one missing `}` in a
|
|
52
|
-
* `pithy.config.ts`
|
|
57
|
+
* `pithy.config.ts` produces four diagnostics on Bun 1.4.2 — so a syntax error arrives wrapped far more
|
|
53
58
|
* often than bare. Classified as-is, the wrapper matches nothing: not a resolution failure, not a parse
|
|
54
59
|
* error, just something with a message. The refusal then says *the config threw while loading, run the
|
|
55
60
|
* file directly* while holding the line and column in its hand. #207 fixed the bare case and left this
|
|
@@ -73,33 +78,6 @@ export function rootCause(cause: unknown): unknown {
|
|
|
73
78
|
return current;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
/** Bun's wrapper announces itself: `4 errors building "<absolute path>"`, and nothing else says that. */
|
|
77
|
-
const BUILD_FAILURE = /^\d+ errors? building "/;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The wrapper **after the diagnostics are gone** — a build that failed, and that is all that is left.
|
|
81
|
-
*
|
|
82
|
-
* {@link rootCause} handles the first `import()`. It cannot handle the second: Bun caches a failed module
|
|
83
|
-
* and re-throws an `AggregateError` with **no `errors` at all**, just the count and the path. There is
|
|
84
|
-
* nothing to unwrap, so a classifier that only unwraps reads a syntax error as "it threw" from the second
|
|
85
|
-
* caller onward — which is what `pithy doctor` shows an adopter, because resolving the project's account
|
|
86
|
-
* loads the root config before the report does (#206, #223).
|
|
87
|
-
*
|
|
88
|
-
* Nothing else Bun throws degrades this way: a `ResolveMessage`, a bare `BuildMessage` and a module's own
|
|
89
|
-
* `Error` are byte-identical on every import. Only the wrapper forgets.
|
|
90
|
-
*
|
|
91
|
-
* So the wrapper is recognized by its own message. **What it proves is narrow and worth stating:** the
|
|
92
|
-
* module reached the builder and the builder produced diagnostics. It did not fail to resolve — Bun
|
|
93
|
-
* throws the first `ResolveMessage` bare, wrapping nothing, even for a file with several bad imports —
|
|
94
|
-
* and it did not run, so it cannot have thrown. It failed to parse or to build. A caller may say that
|
|
95
|
-
* much and no more: the message itself carries an absolute path and a count, neither of which is the
|
|
96
|
-
* adopter's business, so it is a *predicate* here rather than a string anyone is tempted to print.
|
|
97
|
-
*/
|
|
98
|
-
export function isBuildFailureWrapper(cause: unknown): boolean {
|
|
99
|
-
const message = prop(cause, "message");
|
|
100
|
-
return typeof message === "string" && BUILD_FAILURE.test(message);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
81
|
/** Escape sequences a runtime colors its diagnostics with. They are formatting, and they never travel. */
|
|
104
82
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping the control characters is the point.
|
|
105
83
|
const ANSI = /\u001b\[[0-9;]*m/g;
|
|
@@ -139,19 +117,9 @@ export function causeMessage(cause: unknown): string | undefined {
|
|
|
139
117
|
* developer one frame of digging, and a mistake puts a path, a source line or a stack frame in a field
|
|
140
118
|
* that is rendered.
|
|
141
119
|
*
|
|
142
|
-
* **Provenance first, then content.** Bun's build-failure wrapper is refused outright, before a single
|
|
143
|
-
* content test runs. The content tests exist for a diagnostic that *might* be safe; the wrapper never is,
|
|
144
|
-
* because its whole message is a count nobody asked for and a path that must not travel. Leaving it to
|
|
145
|
-
* the content tests is what leaked: `2 errors building "app/config:12:5.ts"` has no leading slash, sailed
|
|
146
|
-
* through the absolute-path check, and produced *The config does not parse: 2 errors building
|
|
147
|
-
* "app/config:12:5.ts". Line 12, column 5.* — a path the adopter never wrote, and a position fabricated
|
|
148
|
-
* out of a file name (#223). That suppression was written three times because the filter was. It is
|
|
149
|
-
* written once here, and {@link failurePosition} refuses the same shape for the same reason.
|
|
150
|
-
*
|
|
151
120
|
* A trailing period comes off: every caller puts this inside a sentence of its own.
|
|
152
121
|
*/
|
|
153
122
|
export function safeReason(cause: unknown): string | undefined {
|
|
154
|
-
if (isBuildFailureWrapper(cause)) return undefined;
|
|
155
123
|
const text = (causeMessage(cause) ?? "").trim();
|
|
156
124
|
if (text.length === 0 || text.length > REASON_LIMIT) return undefined;
|
|
157
125
|
if (text.includes("\n")) return undefined;
|
|
@@ -176,13 +144,14 @@ export interface FailurePosition {
|
|
|
176
144
|
* path they are appended to. Whether a surface prints them is that surface's policy: two of the three
|
|
177
145
|
* classifiers do, and the capability loader deliberately does not.
|
|
178
146
|
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
147
|
+
* `isOwn`, when given, is asked about the file a structured position names, and a position in a file it
|
|
148
|
+
* disowns is no position at all: Bun's line and column in a file the config imports, quoted as the config's,
|
|
149
|
+
* send the adopter to a place in their config that is fine (#709).
|
|
182
150
|
*/
|
|
183
|
-
export function failurePosition(cause: unknown): FailurePosition | undefined {
|
|
184
|
-
if (isBuildFailureWrapper(cause)) return undefined;
|
|
151
|
+
export function failurePosition(cause: unknown, isOwn?: (file: string) => boolean): FailurePosition | undefined {
|
|
185
152
|
const position = prop(cause, "position");
|
|
153
|
+
const file = prop(position, "file");
|
|
154
|
+
if (isOwn !== undefined && typeof file === "string" && !isOwn(file)) return undefined;
|
|
186
155
|
const line = prop(position, "line");
|
|
187
156
|
const column = prop(position, "column");
|
|
188
157
|
if (typeof line === "number" && line > 0 && typeof column === "number") return { line, column };
|
|
@@ -190,135 +159,3 @@ export function failurePosition(cause: unknown): FailurePosition | undefined {
|
|
|
190
159
|
if (!match?.[1] || !match[2]) return undefined;
|
|
191
160
|
return { line: Number(match[1]), column: Number(match[2]) };
|
|
192
161
|
}
|
|
193
|
-
|
|
194
|
-
/** What kind of thing failed to resolve. Three causes that wore one sentence until #489. */
|
|
195
|
-
export type UnresolvedKind =
|
|
196
|
-
/** A bare specifier for a package that is not installed. Installing dependencies is the remedy. */
|
|
197
|
-
| "package"
|
|
198
|
-
/** An installed package asked for a subpath it does not provide. Installing again changes nothing. */
|
|
199
|
-
| "package-subpath"
|
|
200
|
-
/** A relative import of a file in the project's own tree. Installing again changes nothing. */
|
|
201
|
-
| "local-file";
|
|
202
|
-
|
|
203
|
-
/** Whether a path sits inside an installed package. */
|
|
204
|
-
function insideNodeModules(path: string): boolean {
|
|
205
|
-
return path.split(/[\\/]/).includes("node_modules");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Which of the three unresolved-import causes this was, or `undefined` when it cannot be told.
|
|
210
|
-
*
|
|
211
|
-
* **The discriminator was already in the message and was being thrown away.** `unresolvedSpecifier`
|
|
212
|
-
* matches `Cannot find (?:package|module)` — and `package` versus `module` is exactly the fact that
|
|
213
|
-
* separates a missing dependency from the other two. It sat in a non-capturing group, so every cause
|
|
214
|
-
* arrived wearing one sentence, and for two of the three that sentence advised an install which is
|
|
215
|
-
* *guaranteed* to change nothing. That is #489's class exactly: guidance a caller cannot follow, worst
|
|
216
|
-
* for the caller it is written for, which has no human to shrug and try something else.
|
|
217
|
-
*
|
|
218
|
-
* Measured across Node 24.13.0 and Bun 1.3.14, because the two runtimes hand back different halves and
|
|
219
|
-
* each hands back exactly one usable one:
|
|
220
|
-
*
|
|
221
|
-
* | cause | Node says | Bun's `specifier` |
|
|
222
|
-
* |---|---|---|
|
|
223
|
-
* | package absent | `Cannot find package 'pkg'`, no `url` | `pkg` |
|
|
224
|
-
* | subpath missing | `Cannot find module '/abs/node_modules/pkg/src/nope.js'`, `url` under node_modules | `pkg/src/nope` |
|
|
225
|
-
* | adopter's file | `Cannot find module '/abs/apps/board/src/x'`, `url` outside node_modules | `./src/x` |
|
|
226
|
-
*
|
|
227
|
-
* Bun gives the **specifier as written**, so a leading `.` settles it. Node gives the **already-resolved
|
|
228
|
-
* path**, where being under `node_modules` settles it. Neither read works on both runtimes; the pair
|
|
229
|
-
* does, and it is the same predicate — *is this inside node_modules* — applied to whichever fact the
|
|
230
|
-
* runtime supplied.
|
|
231
|
-
*/
|
|
232
|
-
export function unresolvedKind(cause: unknown): UnresolvedKind | undefined {
|
|
233
|
-
if (/Cannot find package /.test(causeMessage(cause) ?? "")) return "package";
|
|
234
|
-
|
|
235
|
-
const specifier = unresolvedSpecifier(cause);
|
|
236
|
-
if (specifier === undefined) return undefined;
|
|
237
|
-
|
|
238
|
-
// Bun's field is the specifier as written: relative means the adopter's own file.
|
|
239
|
-
if (/^(?:\.|\/|[A-Za-z]:[\\/])/.test(specifier)) {
|
|
240
|
-
return insideNodeModules(specifier) ? "package-subpath" : "local-file";
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Node states an already-resolved absolute path; a bare specifier reaching here is a package subpath.
|
|
244
|
-
const url = prop(cause, "url");
|
|
245
|
-
if (typeof url === "string" && url.length > 0) {
|
|
246
|
-
return insideNodeModules(url) ? "package-subpath" : "local-file";
|
|
247
|
-
}
|
|
248
|
-
return "package-subpath";
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* The specifier that did not resolve.
|
|
253
|
-
*
|
|
254
|
-
* A recorded fact about two runtimes: Bun's `ResolveMessage` carries it as a **field**, and Node states it
|
|
255
|
-
* in prose. The field is preferred wherever there is one, because the prose around it names the referrer's
|
|
256
|
-
* absolute path — the specifier is the adopter's own import, the referrer is our frame.
|
|
257
|
-
*
|
|
258
|
-
* Whether a surface names it, and what it advises when it does, is that surface's policy and stays there.
|
|
259
|
-
*/
|
|
260
|
-
export function unresolvedSpecifier(cause: unknown): string | undefined {
|
|
261
|
-
const field = prop(cause, "specifier");
|
|
262
|
-
if (typeof field === "string" && field.length > 0) return field;
|
|
263
|
-
const match = /Cannot find (?:package|module) ['"]([^'"]+)['"]/.exec(causeMessage(cause) ?? "");
|
|
264
|
-
return match?.[1];
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** A package name as npm allows one: an optional `@scope/`, and never a store's dot-directory. */
|
|
268
|
-
const PACKAGE_NAME = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* The file the failing import was written in, as the runtime named it. **Module-private on purpose**: it
|
|
272
|
-
* is an absolute path, and {@link importingPackage} exists to make sure only its package half leaves here.
|
|
273
|
-
*/
|
|
274
|
-
function importerPath(cause: unknown): string | undefined {
|
|
275
|
-
const field = prop(cause, "referrer");
|
|
276
|
-
if (typeof field === "string" && field.length > 0) return field;
|
|
277
|
-
const message = causeMessage(cause) ?? "";
|
|
278
|
-
// Node ends its sentence with the path, so it is taken to the end of the line — a directory may
|
|
279
|
-
// contain a space. Bun quotes it when it states one in prose, and always carries the field as well.
|
|
280
|
-
return (/ imported from (.+)$/.exec(message) ?? /\bfrom ['"]([^'"]+)['"]/.exec(message))?.[1];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* The package whose own source contained the import that did not resolve — **never a path**.
|
|
285
|
-
*
|
|
286
|
-
* {@link unresolvedSpecifier} answers *what* did not resolve. This answers *where the import lives*, and
|
|
287
|
-
* the two are different facts whenever the failing import is not the adopter's own. #480 is that case
|
|
288
|
-
* verbatim: `pithy add email` on a fresh project refused with
|
|
289
|
-
*
|
|
290
|
-
* > Nothing resolves "@pithy-sh/core/src/capability/capability".
|
|
291
|
-
*
|
|
292
|
-
* while `@pithy-sh/core` resolved from the Worker at every step of the run, before and after. The
|
|
293
|
-
* specifier was honest — the runtime really could not resolve it — but the import belonged to
|
|
294
|
-
* `@pithy-sh/email`, which had just been installed and was the package genuinely not linked. So the
|
|
295
|
-
* refusal named the one dependency that was definitely fine, and an adopter following it ran `bun
|
|
296
|
-
* install` against a tree that already had core in it. The importer is the half that would have pointed
|
|
297
|
-
* at `email`, and both runtimes hand it over: **Bun's `ResolveMessage` carries `referrer` as a field**
|
|
298
|
-
* (measured on 1.3.14) and Node states it in prose as `imported from <path>`.
|
|
299
|
-
*
|
|
300
|
-
* **A package name, or nothing.** The referrer is an absolute path — our frame, and the reason
|
|
301
|
-
* {@link unresolvedSpecifier} drops the prose around the specifier — so what travels is the one part of
|
|
302
|
-
* it that is not a path: the package directory it sits under. A referrer outside `node_modules` is the
|
|
303
|
-
* adopter's own file, and there is nothing to say about it that the refusal does not already say by
|
|
304
|
-
* naming the config, so it answers `undefined` rather than a path. The path itself is never returned
|
|
305
|
-
* from this module, which is what makes leaking it impossible rather than merely discouraged.
|
|
306
|
-
*
|
|
307
|
-
* The **last** `node_modules` in the path, because every store layout nests: pnpm's
|
|
308
|
-
* `node_modules/.pnpm/<id>/node_modules/<pkg>` and Bun's isolated linker's
|
|
309
|
-
* `node_modules/.bun/<pkg>@<version>/node_modules/<pkg>` (both measured) put the real owner last, and so
|
|
310
|
-
* does an ordinary nested install. The name is then checked against what npm allows, so a store's own
|
|
311
|
-
* bookkeeping directory (`.bun`, `.pnpm`, `hono@4.6.3`) can only ever answer nothing.
|
|
312
|
-
*
|
|
313
|
-
* Whether a surface names it, and what it advises when it does, is that surface's policy and stays there.
|
|
314
|
-
*/
|
|
315
|
-
export function importingPackage(cause: unknown): string | undefined {
|
|
316
|
-
const referrer = importerPath(cause);
|
|
317
|
-
if (referrer === undefined) return undefined;
|
|
318
|
-
const segments = referrer.split(/[\\/]/);
|
|
319
|
-
const at = segments.lastIndexOf("node_modules");
|
|
320
|
-
const head = at === -1 ? undefined : segments[at + 1];
|
|
321
|
-
if (head === undefined) return undefined;
|
|
322
|
-
const name = head.startsWith("@") ? `${head}/${segments[at + 2] ?? ""}` : head;
|
|
323
|
-
return PACKAGE_NAME.test(name) ? name : undefined;
|
|
324
|
-
}
|
package/src/error/payload.ts
CHANGED
|
@@ -85,6 +85,54 @@ const InvalidInputPublic = z
|
|
|
85
85
|
})
|
|
86
86
|
.describe("Input failed validation at a boundary (400).");
|
|
87
87
|
|
|
88
|
+
const InputRequiredPublic = z
|
|
89
|
+
.object({
|
|
90
|
+
code: z
|
|
91
|
+
.literal("input/required")
|
|
92
|
+
.describe(
|
|
93
|
+
"A run that may not prompt reached a question with no answer. `PITHY_NONINTERACTIVE=1`, `--no-input` and `--json` each forbid a prompt; this is what one turns into, so an agent is told what to pass rather than left waiting on a question nobody will answer.",
|
|
94
|
+
),
|
|
95
|
+
status: z.literal(400).describe("Bad Request — the call left out something it has to say."),
|
|
96
|
+
flag: z
|
|
97
|
+
.string()
|
|
98
|
+
.nullable()
|
|
99
|
+
.describe(
|
|
100
|
+
"The flag that answers the question, as it is typed (`--worker`). `null` when no flag can: only a person, or a document piped to stdin, answers it.",
|
|
101
|
+
),
|
|
102
|
+
choices: z
|
|
103
|
+
.array(z.string().describe("One value the flag takes."))
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Every value the flag takes, when the set is known. Absent when it is open, such as a name."),
|
|
106
|
+
...publicFields,
|
|
107
|
+
})
|
|
108
|
+
.describe("A run that may not prompt needed an answer it was not given (400).");
|
|
109
|
+
|
|
110
|
+
const PreconditionFailedPublic = z
|
|
111
|
+
.object({
|
|
112
|
+
code: z
|
|
113
|
+
.literal("core/precondition_failed")
|
|
114
|
+
.describe(
|
|
115
|
+
"The call is well formed, and the state it needs is not there yet: no credentials, an environment that is not provisioned, a resource nothing has created. Distinct from `validation/invalid_input` because the remedy is a different step run first, not a different call.",
|
|
116
|
+
),
|
|
117
|
+
status: z.literal(412).describe("Precondition Failed — the request is valid, and what it depends on is missing."),
|
|
118
|
+
...publicFields,
|
|
119
|
+
})
|
|
120
|
+
.describe("The state this request depends on is not there yet (412).");
|
|
121
|
+
|
|
122
|
+
const PersonRequiredPublic = z
|
|
123
|
+
.object({
|
|
124
|
+
code: z
|
|
125
|
+
.literal("core/person_required")
|
|
126
|
+
.describe(
|
|
127
|
+
"The step is one only a person may take: a typed confirmation phrase, or a command that is manual by design. Distinct from `input/required` because no flag value an automated caller could choose is the right answer — the right move is to stop and ask.",
|
|
128
|
+
),
|
|
129
|
+
status: z
|
|
130
|
+
.literal(403)
|
|
131
|
+
.describe("Forbidden — the caller may not take this step for a person, however the call is phrased."),
|
|
132
|
+
...publicFields,
|
|
133
|
+
})
|
|
134
|
+
.describe("A step only a person may take (403).");
|
|
135
|
+
|
|
88
136
|
const InvalidTokenPublic = z
|
|
89
137
|
.object({
|
|
90
138
|
code: z.literal("auth/invalid_token").describe("The bearer/session credential is missing, expired, or invalid."),
|
|
@@ -1555,6 +1603,9 @@ const TestersNotConfiguredPublic = z
|
|
|
1555
1603
|
export const KitPublicErrorPayload = z
|
|
1556
1604
|
.discriminatedUnion("code", [
|
|
1557
1605
|
InvalidInputPublic,
|
|
1606
|
+
InputRequiredPublic,
|
|
1607
|
+
PreconditionFailedPublic,
|
|
1608
|
+
PersonRequiredPublic,
|
|
1558
1609
|
InvalidTokenPublic,
|
|
1559
1610
|
ForbiddenPublic,
|
|
1560
1611
|
AuthProviderUnavailablePublic,
|
|
@@ -1690,6 +1741,11 @@ export const KitPublicErrorPayload = z
|
|
|
1690
1741
|
// The full members: each public member plus the operator-only `action` and `detail`. Built as explicit consts
|
|
1691
1742
|
// (not mapped) so the `code`-literal discriminated-union types survive for `Extract` narrowing.
|
|
1692
1743
|
const InvalidInput = InvalidInputPublic.extend(internalFields).describe(InvalidInputPublic.description ?? "");
|
|
1744
|
+
const InputRequired = InputRequiredPublic.extend(internalFields).describe(InputRequiredPublic.description ?? "");
|
|
1745
|
+
const PreconditionFailed = PreconditionFailedPublic.extend(internalFields).describe(
|
|
1746
|
+
PreconditionFailedPublic.description ?? "",
|
|
1747
|
+
);
|
|
1748
|
+
const PersonRequired = PersonRequiredPublic.extend(internalFields).describe(PersonRequiredPublic.description ?? "");
|
|
1693
1749
|
const InvalidToken = InvalidTokenPublic.extend(internalFields).describe(InvalidTokenPublic.description ?? "");
|
|
1694
1750
|
const Forbidden = ForbiddenPublic.extend(internalFields).describe(ForbiddenPublic.description ?? "");
|
|
1695
1751
|
const AuthProviderUnavailable = AuthProviderUnavailablePublic.extend(internalFields).describe(
|
|
@@ -2053,6 +2109,9 @@ const SupportReplyFailed = SupportReplyFailedPublic.extend(internalFields).descr
|
|
|
2053
2109
|
export const KitErrorPayload = z
|
|
2054
2110
|
.discriminatedUnion("code", [
|
|
2055
2111
|
InvalidInput,
|
|
2112
|
+
InputRequired,
|
|
2113
|
+
PreconditionFailed,
|
|
2114
|
+
PersonRequired,
|
|
2056
2115
|
InvalidToken,
|
|
2057
2116
|
Forbidden,
|
|
2058
2117
|
AuthProviderUnavailable,
|
package/src/error/pithyError.ts
CHANGED
|
@@ -61,6 +61,82 @@ export class ValidationError extends PithyError {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* A run that may not prompt reached a question nobody gave it the answer to (#697).
|
|
66
|
+
*
|
|
67
|
+
* `flag` is the flag that answers it, or `null` when no flag can — a person, or a document on stdin, is
|
|
68
|
+
* the only answer. `choices` is every value the flag takes when that set is known, and is left off the
|
|
69
|
+
* payload rather than written empty when it is not: an empty list would claim the flag takes nothing.
|
|
70
|
+
*/
|
|
71
|
+
export class InputRequiredError extends PithyError {
|
|
72
|
+
declare readonly payload: Extract<ErrorPayload, { code: "input/required" }>;
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
args: ErrorArgs & { flag?: string | null; choices?: readonly string[] } = {},
|
|
76
|
+
options?: { cause?: unknown },
|
|
77
|
+
) {
|
|
78
|
+
super(
|
|
79
|
+
{
|
|
80
|
+
code: "input/required",
|
|
81
|
+
status: 400,
|
|
82
|
+
message: args.message ?? "An answer is required, and this run may not ask for it.",
|
|
83
|
+
action: args.action,
|
|
84
|
+
detail: args.detail,
|
|
85
|
+
params: args.params,
|
|
86
|
+
flag: args.flag ?? null,
|
|
87
|
+
...(args.choices === undefined ? {} : { choices: [...args.choices] }),
|
|
88
|
+
},
|
|
89
|
+
options,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The call is well formed, and what it depends on is not there yet (#697): no credentials, an environment
|
|
96
|
+
* nothing has provisioned. A separate class from {@link ValidationError} because the remedy is a step to
|
|
97
|
+
* run first, not a different call — the CLI exits 3 for it and 2 for a validation failure.
|
|
98
|
+
*/
|
|
99
|
+
export class PreconditionFailedError extends PithyError {
|
|
100
|
+
declare readonly payload: Extract<ErrorPayload, { code: "core/precondition_failed" }>;
|
|
101
|
+
|
|
102
|
+
constructor(args: ErrorArgs = {}, options?: { cause?: unknown }) {
|
|
103
|
+
super(
|
|
104
|
+
{
|
|
105
|
+
code: "core/precondition_failed",
|
|
106
|
+
status: 412,
|
|
107
|
+
message: args.message ?? "Something this needs is not set up yet.",
|
|
108
|
+
action: args.action,
|
|
109
|
+
detail: args.detail,
|
|
110
|
+
params: args.params,
|
|
111
|
+
},
|
|
112
|
+
options,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A step only a person may take (#697): a typed confirmation phrase, or a command that is manual by
|
|
119
|
+
* design. Not {@link InputRequiredError}, which names a flag an automated caller can pass — here no value
|
|
120
|
+
* a caller could choose is the right answer, and the CLI exits 5 so an agent stops and asks.
|
|
121
|
+
*/
|
|
122
|
+
export class PersonRequiredError extends PithyError {
|
|
123
|
+
declare readonly payload: Extract<ErrorPayload, { code: "core/person_required" }>;
|
|
124
|
+
|
|
125
|
+
constructor(args: ErrorArgs = {}, options?: { cause?: unknown }) {
|
|
126
|
+
super(
|
|
127
|
+
{
|
|
128
|
+
code: "core/person_required",
|
|
129
|
+
status: 403,
|
|
130
|
+
message: args.message ?? "Only a person may take this step.",
|
|
131
|
+
action: args.action,
|
|
132
|
+
detail: args.detail,
|
|
133
|
+
params: args.params,
|
|
134
|
+
},
|
|
135
|
+
options,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
64
140
|
export class UnauthorizedError extends PithyError {
|
|
65
141
|
declare readonly payload: Extract<ErrorPayload, { code: "auth/invalid_token" }>;
|
|
66
142
|
|