@pithy-sh/core 0.9.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 +17 -0
- package/dist/naming/domains.d.ts.map +1 -1
- package/dist/naming/domains.js +62 -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/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 +75 -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/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
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { causeMessage, prop, rootCause } from "./cause.js";
|
|
4
|
+
import { packageOf } from "../naming/packageName.js";
|
|
5
|
+
//#region src/error/unresolved.ts
|
|
6
|
+
/**
|
|
7
|
+
* The {@link InstalledProbe} a surface on Node or Bun passes: `pkg` on the `node_modules` chain above
|
|
8
|
+
* `referrer`, **walked from where `referrer` really is**.
|
|
9
|
+
*
|
|
10
|
+
* Both runtimes resolve from the real path of the importing file, and Bun's isolated linker puts every
|
|
11
|
+
* package behind a symlink into its store, with that package's own dependencies beside the real
|
|
12
|
+
* directory and not beside the link. Walked from the link, the chain is the root's — a different copy,
|
|
13
|
+
* or none — so a dependency the resolver could see was reported absent, and the adopter told to install
|
|
14
|
+
* what was installed. A `referrer` that will not resolve is walked as given.
|
|
15
|
+
*
|
|
16
|
+
* Core has no filesystem, so the two calls are the caller's. The walk is here, once.
|
|
17
|
+
*/
|
|
18
|
+
function nodeModulesProbe(fs) {
|
|
19
|
+
return (pkg, referrer) => installedAt(fs, pkg, referrer) !== void 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The {@link EntryProbe} a surface on Node or Bun passes: the same walk as {@link nodeModulesProbe}, then
|
|
23
|
+
* the package's own `package.json`, and `exists` for the paths beside it.
|
|
24
|
+
*/
|
|
25
|
+
function packageEntryProbe(fs) {
|
|
26
|
+
return (pkg, from) => {
|
|
27
|
+
const dir = installedAt(fs, pkg, from);
|
|
28
|
+
if (dir === void 0) return void 0;
|
|
29
|
+
let manifest;
|
|
30
|
+
try {
|
|
31
|
+
manifest = JSON.parse(fs.read(`${dir}/package.json`));
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
manifest,
|
|
37
|
+
has: (path) => fs.exists(path === "" ? dir : `${dir}/${path}`)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The entry an installed `pkg` declares, through the probe: `undefined` when there is no probe or no package. */
|
|
42
|
+
function declaredBy(options, pkg, from) {
|
|
43
|
+
const installed = options.entry?.(pkg, from);
|
|
44
|
+
return installed === void 0 ? void 0 : declaredEntry(installed.manifest);
|
|
45
|
+
}
|
|
46
|
+
/** The directory `pkg` is installed in on the chain above `referrer`, walked from where it really is. */
|
|
47
|
+
function installedAt(fs, pkg, referrer) {
|
|
48
|
+
let real = referrer;
|
|
49
|
+
try {
|
|
50
|
+
real = fs.realpath(referrer);
|
|
51
|
+
} catch {}
|
|
52
|
+
let dir = parentOf(real);
|
|
53
|
+
for (;;) {
|
|
54
|
+
const at = `${dir.replace(/[\\/]+$/, "")}/node_modules/${pkg}`;
|
|
55
|
+
if (fs.exists(`${at}/package.json`)) return at;
|
|
56
|
+
const up = parentOf(dir);
|
|
57
|
+
if (up === dir) return void 0;
|
|
58
|
+
dir = up;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** The conditions an ESM import of a package matches, on Node and on Vite's runner. */
|
|
62
|
+
const IMPORT_CONDITIONS = /* @__PURE__ */ new Set([
|
|
63
|
+
"import",
|
|
64
|
+
"node",
|
|
65
|
+
"module-sync",
|
|
66
|
+
"default"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* The entry a manifest declares for an `import` of the package's bare name, or `null` for none. `exports`
|
|
70
|
+
* wins when present — its `"."`, or the whole map when no key is a subpath — and a target is the first
|
|
71
|
+
* one whose condition an import matches. With no `exports`, it is `main`.
|
|
72
|
+
*/
|
|
73
|
+
function declaredEntry(manifest) {
|
|
74
|
+
const exports = prop(manifest, "exports");
|
|
75
|
+
if (exports !== void 0 && exports !== null) return importTarget(subpathMap(exports)["."]);
|
|
76
|
+
const main = prop(manifest, "main");
|
|
77
|
+
return typeof main === "string" && main.length > 0 ? inside(main) : null;
|
|
78
|
+
}
|
|
79
|
+
/** An `exports` field as its subpath map: a target or a map of conditions alone is the package's `"."`. */
|
|
80
|
+
function subpathMap(exports) {
|
|
81
|
+
return typeof exports === "object" && exports !== null && !Array.isArray(exports) && Object.keys(exports).some((key) => key.startsWith(".")) ? exports : { ".": exports };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* What an import of `subpath` (`"."`, `"./src/capability"`) takes from a manifest's `exports`, as Node and
|
|
85
|
+
* Vite resolve it: an exact key first, then the pattern with the longest prefix, each target followed
|
|
86
|
+
* through the conditions an import matches. `null` when the map does not export it — no key, no pattern,
|
|
87
|
+
* or a `null` target — and `undefined` when the manifest has no `exports`, so the subpath is a file path.
|
|
88
|
+
*/
|
|
89
|
+
function exportedTarget(manifest, subpath) {
|
|
90
|
+
const exports = prop(manifest, "exports");
|
|
91
|
+
if (exports === void 0 || exports === null) return void 0;
|
|
92
|
+
const map = subpathMap(exports);
|
|
93
|
+
if (Object.hasOwn(map, subpath) && !subpath.includes("*")) {
|
|
94
|
+
const target = importTarget(map[subpath]);
|
|
95
|
+
return target === null ? null : { target };
|
|
96
|
+
}
|
|
97
|
+
let best;
|
|
98
|
+
for (const key of Object.keys(map)) {
|
|
99
|
+
const at = key.indexOf("*");
|
|
100
|
+
if (at === -1 || key.indexOf("*", at + 1) !== -1) continue;
|
|
101
|
+
const prefix = key.slice(0, at);
|
|
102
|
+
const suffix = key.slice(at + 1);
|
|
103
|
+
if (!subpath.startsWith(prefix) || subpath === prefix) continue;
|
|
104
|
+
if (!subpath.endsWith(suffix) || subpath.length < key.length) continue;
|
|
105
|
+
if (best !== void 0) {
|
|
106
|
+
const held = best.key.indexOf("*");
|
|
107
|
+
if (at < held || at === held && key.length <= best.key.length) continue;
|
|
108
|
+
}
|
|
109
|
+
best = {
|
|
110
|
+
key,
|
|
111
|
+
star: subpath.slice(prefix.length, subpath.length - suffix.length)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (best === void 0) return null;
|
|
115
|
+
const pattern = importTarget(map[best.key]);
|
|
116
|
+
if (pattern === null) return null;
|
|
117
|
+
return {
|
|
118
|
+
target: pattern.replaceAll("*", best.star),
|
|
119
|
+
under: patternBase(pattern)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The import that reaches `file` — a path inside the package, the one Node reports — through `exports`:
|
|
124
|
+
* the key that maps to it, or the pattern whose `*` it fills. Only an answer that resolves back to `file`
|
|
125
|
+
* through {@link exportedTarget} counts, so a pattern a longer one shadows never names the import.
|
|
126
|
+
*/
|
|
127
|
+
function exportedSubpath(manifest, file) {
|
|
128
|
+
const exports = prop(manifest, "exports");
|
|
129
|
+
if (exports === void 0 || exports === null) return void 0;
|
|
130
|
+
for (const [key, value] of Object.entries(subpathMap(exports))) {
|
|
131
|
+
const target = importTarget(value);
|
|
132
|
+
if (target === null) continue;
|
|
133
|
+
let subpath;
|
|
134
|
+
const at = target.indexOf("*");
|
|
135
|
+
if (!key.includes("*")) subpath = target === file ? key : void 0;
|
|
136
|
+
else if (at !== -1) {
|
|
137
|
+
const prefix = target.slice(0, at);
|
|
138
|
+
const suffix = target.slice(at + 1).split("*")[0] ?? "";
|
|
139
|
+
if (file.startsWith(prefix) && file.endsWith(suffix) && file.length > prefix.length + suffix.length) subpath = key.replace("*", file.slice(prefix.length, file.length - suffix.length));
|
|
140
|
+
}
|
|
141
|
+
const back = subpath === void 0 ? void 0 : exportedTarget(manifest, subpath);
|
|
142
|
+
if (subpath !== void 0 && back?.target === file) return {
|
|
143
|
+
...back,
|
|
144
|
+
subpath
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** The directory inside the package a pattern target's `*` expands under: `dist/*.js` → `dist`. */
|
|
149
|
+
function patternBase(pattern) {
|
|
150
|
+
const head = pattern.slice(0, pattern.indexOf("*"));
|
|
151
|
+
return head.slice(0, Math.max(head.lastIndexOf("/"), 0));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* **A target the manifest declares and the disk does not have: the package unbuilt, or the import mistyped.**
|
|
155
|
+
*
|
|
156
|
+
* A key of its own is the package's word that the file exists, so its absence is the package's — unbuilt,
|
|
157
|
+
* or a broken install. A pattern says nothing of the sort: `./src/*` matches `./src/capabilty` as readily as
|
|
158
|
+
* `./src/capability`. What tells them apart is the directory the pattern expands under. With `dist` absent
|
|
159
|
+
* the package was never built, and every import of it fails the same way whatever was typed; with `dist`
|
|
160
|
+
* there, the build ran and this file is not in it, so the import names a path the package does not have.
|
|
161
|
+
* A file deleted from a built `dist` reads as the second, and its remedy — check the version you have —
|
|
162
|
+
* still leads to a reinstall.
|
|
163
|
+
*/
|
|
164
|
+
function exportedKind(found, installed) {
|
|
165
|
+
if (found.under === void 0) return "package-entry";
|
|
166
|
+
return installed.has(found.under) ? "package-subpath" : "package-entry";
|
|
167
|
+
}
|
|
168
|
+
/** One `exports` target, followed through conditions and fallbacks to the path an import takes. */
|
|
169
|
+
function importTarget(target) {
|
|
170
|
+
if (typeof target === "string") return inside(target);
|
|
171
|
+
if (Array.isArray(target)) {
|
|
172
|
+
for (const option of target) {
|
|
173
|
+
const found = importTarget(option);
|
|
174
|
+
if (found !== null) return found;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (typeof target !== "object" || target === null) return null;
|
|
179
|
+
for (const [condition, value] of Object.entries(target)) {
|
|
180
|
+
if (!IMPORT_CONDITIONS.has(condition)) continue;
|
|
181
|
+
const found = importTarget(value);
|
|
182
|
+
if (found !== null) return found;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/** A manifest's path, as a path inside the package: no `./`, no trailing slash. */
|
|
187
|
+
function inside(path) {
|
|
188
|
+
return path.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
189
|
+
}
|
|
190
|
+
/** Every code a runtime throws for a module it could not find, or would not load as a module. */
|
|
191
|
+
const CODES = /* @__PURE__ */ new Set([
|
|
192
|
+
"ERR_MODULE_NOT_FOUND",
|
|
193
|
+
"MODULE_NOT_FOUND",
|
|
194
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
195
|
+
"ERR_PACKAGE_IMPORT_NOT_DEFINED",
|
|
196
|
+
"ERR_UNSUPPORTED_DIR_IMPORT",
|
|
197
|
+
"ERR_UNKNOWN_BUILTIN_MODULE",
|
|
198
|
+
"ERR_UNSUPPORTED_ESM_URL_SCHEME",
|
|
199
|
+
"ERR_RESOLVE_PACKAGE_ENTRY_FAIL"
|
|
200
|
+
]);
|
|
201
|
+
/** The same, said in prose by something that sets no code: Vite's module runner, and older loaders. */
|
|
202
|
+
const SENTENCE = /Cannot find (?:package|module) |Failed to resolve (?:import|module)|Failed to load url |No such built-in module: |is not defined by "exports"|No "exports" main defined in |Package import specifier |is not exported under the conditions |Failed to resolve entry for package /;
|
|
203
|
+
/**
|
|
204
|
+
* Classify an import that would not resolve, or answer `undefined` when `cause` is not one.
|
|
205
|
+
*
|
|
206
|
+
* Read duck-typed, for `prop`'s reason in `cause.ts`, and through Bun's `AggregateError` wrapper, for
|
|
207
|
+
* `rootCause`'s.
|
|
208
|
+
*/
|
|
209
|
+
function unresolvedImport(wrapped, options = {}) {
|
|
210
|
+
const cause = rootCause(wrapped);
|
|
211
|
+
if (!isUnresolved(cause)) return void 0;
|
|
212
|
+
const referrer = importerPath(cause);
|
|
213
|
+
const owner = ownerOf(referrer, options.loader);
|
|
214
|
+
const found = locate(cause, referrer, options);
|
|
215
|
+
const installHelps = found.kind === "package" || found.kind === "undetermined" || found.kind === "unnamed";
|
|
216
|
+
return {
|
|
217
|
+
...found,
|
|
218
|
+
...owner === void 0 ? {} : { owner },
|
|
219
|
+
installHelps,
|
|
220
|
+
remedy: remedy(found, owner)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The package whose own source holds the import that did not resolve — **never a path**.
|
|
225
|
+
*
|
|
226
|
+
* #480 is why: `pithy add email` refused with *Nothing resolves "@pithy-sh/core/src/capability/capability"*
|
|
227
|
+
* while core resolved throughout, because the import was `@pithy-sh/email`'s and email was the package
|
|
228
|
+
* not linked. The importer is the half that points there. The **last** `node_modules` in the path wins,
|
|
229
|
+
* because every store nests (pnpm's `.pnpm/<id>/node_modules/<pkg>`, Bun's `.bun/<pkg>@<v>/node_modules/<pkg>`),
|
|
230
|
+
* and the name is held to what npm allows, so a store's bookkeeping directory answers nothing.
|
|
231
|
+
*/
|
|
232
|
+
function importingPackage(cause) {
|
|
233
|
+
return ownerOf(importerPath(cause), void 0);
|
|
234
|
+
}
|
|
235
|
+
function isUnresolved(cause) {
|
|
236
|
+
if (typeof cause !== "object" || cause === null) return false;
|
|
237
|
+
const code = prop(cause, "code");
|
|
238
|
+
if (typeof code === "string" && CODES.has(code)) return true;
|
|
239
|
+
if (prop(cause, "name") === "ResolveMessage") return true;
|
|
240
|
+
return SENTENCE.test(causeMessage(cause) ?? "");
|
|
241
|
+
}
|
|
242
|
+
/** A specifier that opens with a URL scheme: `node:`, `bun:`, `data:`. A Windows drive letter is one letter. */
|
|
243
|
+
const URL_SCHEME = /^[a-z][a-z0-9+.-]+:/i;
|
|
244
|
+
/** An absolute path: POSIX, a Windows drive, or a UNC share. */
|
|
245
|
+
const ABSOLUTE = /^(?:\/|\\\\|[A-Za-z]:[\\/])/;
|
|
246
|
+
function locate(cause, referrer, options) {
|
|
247
|
+
const code = prop(cause, "code");
|
|
248
|
+
const message = causeMessage(cause) ?? "";
|
|
249
|
+
if (code === "ERR_UNSUPPORTED_ESM_URL_SCHEME") {
|
|
250
|
+
const scheme = /Received protocol '([a-z][a-z0-9+.-]*:)'/i.exec(message)?.[1];
|
|
251
|
+
return scheme === void 0 ? { kind: "builtin" } : {
|
|
252
|
+
kind: "builtin",
|
|
253
|
+
shown: scheme
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (code === "ERR_PACKAGE_PATH_NOT_EXPORTED" || /is not defined by "exports"|No "exports" main defined in |is not exported under the conditions /.test(message)) return unexported(cause, message);
|
|
257
|
+
const unloadable = /Failed to resolve entry for package "([^"]+)"/.exec(message)?.[1];
|
|
258
|
+
if (unloadable !== void 0) {
|
|
259
|
+
const from = referrer ?? (options.relativeTo === void 0 ? void 0 : `${options.relativeTo}/package.json`);
|
|
260
|
+
return {
|
|
261
|
+
kind: (from === void 0 ? void 0 : declaredBy(options, unloadable, from)) === null ? "package-subpath" : "package-entry",
|
|
262
|
+
package: unloadable,
|
|
263
|
+
shown: unloadable
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const specifier = writtenSpecifier(cause, message);
|
|
267
|
+
if (specifier === void 0) return { kind: "unnamed" };
|
|
268
|
+
if (code === "ERR_UNKNOWN_BUILTIN_MODULE") return {
|
|
269
|
+
kind: "builtin",
|
|
270
|
+
shown: specifier
|
|
271
|
+
};
|
|
272
|
+
if (specifier.startsWith("#")) return {
|
|
273
|
+
kind: "imports-map",
|
|
274
|
+
shown: specifier
|
|
275
|
+
};
|
|
276
|
+
if (specifier.startsWith("file:")) return atPath(urlPath(specifier), referrer, options);
|
|
277
|
+
if (URL_SCHEME.test(specifier)) return {
|
|
278
|
+
kind: "builtin",
|
|
279
|
+
shown: specifier
|
|
280
|
+
};
|
|
281
|
+
const url = prop(cause, "url");
|
|
282
|
+
if (typeof url === "string" && url.startsWith("file:")) return atPath(urlPath(url), referrer, options);
|
|
283
|
+
if (specifier.startsWith(".")) {
|
|
284
|
+
if (referrer === void 0) return {
|
|
285
|
+
kind: "local-file",
|
|
286
|
+
shown: specifier
|
|
287
|
+
};
|
|
288
|
+
return atPath(joinPath(parentOf(referrer), specifier), referrer, options, specifier);
|
|
289
|
+
}
|
|
290
|
+
if (ABSOLUTE.test(specifier)) {
|
|
291
|
+
const entry = /^Cannot find package '/.test(message) ? packageAt(specifier) : void 0;
|
|
292
|
+
if (entry !== void 0) return {
|
|
293
|
+
kind: declaredBy(options, entry.name, `${entry.dir}/package.json`) === null ? "package-subpath" : "package-entry",
|
|
294
|
+
package: entry.name,
|
|
295
|
+
shown: entry.name
|
|
296
|
+
};
|
|
297
|
+
return atPath(specifier, referrer, options);
|
|
298
|
+
}
|
|
299
|
+
const pkg = packageOf(specifier);
|
|
300
|
+
const bare = pkg === specifier;
|
|
301
|
+
if (options.installed === void 0 || referrer === void 0) return bare ? {
|
|
302
|
+
kind: "package",
|
|
303
|
+
package: pkg,
|
|
304
|
+
shown: pkg
|
|
305
|
+
} : {
|
|
306
|
+
kind: "undetermined",
|
|
307
|
+
package: pkg,
|
|
308
|
+
shown: specifier
|
|
309
|
+
};
|
|
310
|
+
if (!options.installed(pkg, referrer)) return {
|
|
311
|
+
kind: "package",
|
|
312
|
+
package: pkg,
|
|
313
|
+
shown: pkg
|
|
314
|
+
};
|
|
315
|
+
const installed = options.entry?.(pkg, referrer);
|
|
316
|
+
if (bare) return typeof (installed === void 0 ? void 0 : declaredEntry(installed.manifest)) === "string" ? {
|
|
317
|
+
kind: "package-entry",
|
|
318
|
+
package: pkg,
|
|
319
|
+
shown: pkg
|
|
320
|
+
} : {
|
|
321
|
+
kind: "package-subpath",
|
|
322
|
+
package: pkg,
|
|
323
|
+
shown: specifier
|
|
324
|
+
};
|
|
325
|
+
const exported = installed === void 0 ? void 0 : exportedTarget(installed.manifest, `./${specifier.slice(pkg.length + 1)}`);
|
|
326
|
+
return installed !== void 0 && exported !== void 0 && exported !== null ? {
|
|
327
|
+
kind: exportedKind(exported, installed),
|
|
328
|
+
package: pkg,
|
|
329
|
+
shown: specifier
|
|
330
|
+
} : {
|
|
331
|
+
kind: "package-subpath",
|
|
332
|
+
package: pkg,
|
|
333
|
+
shown: specifier
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* An installed package that does not export the path. The loader's field is the import as written where
|
|
338
|
+
* there is one; Node's sentence names the subpath and the package's `package.json`, from which the
|
|
339
|
+
* package — never the path — is taken. An `exports` map with no `"."` is the bare name unexported, and
|
|
340
|
+
* Node's sentence for it names only the `package.json`.
|
|
341
|
+
*/
|
|
342
|
+
function unexported(cause, message) {
|
|
343
|
+
const field = prop(cause, "specifier");
|
|
344
|
+
if (typeof field === "string" && field.length > 0 && !ABSOLUTE.test(field) && !field.startsWith(".")) return {
|
|
345
|
+
kind: "package-subpath",
|
|
346
|
+
package: packageOf(field),
|
|
347
|
+
shown: field
|
|
348
|
+
};
|
|
349
|
+
const main = /No "exports" main defined in (.+?)[\\/]package\.json/.exec(message)?.[1];
|
|
350
|
+
const entry = main === void 0 ? void 0 : packageAt(`${main}/package.json`);
|
|
351
|
+
if (entry !== void 0) return {
|
|
352
|
+
kind: "package-subpath",
|
|
353
|
+
package: entry.name,
|
|
354
|
+
shown: entry.name
|
|
355
|
+
};
|
|
356
|
+
const match = /Package subpath '([^']+)' is not defined by "exports" in (.+?)[\\/]package\.json/.exec(message) ?? /"([^"]+)" is not exported under the conditions \[[^\]]*\] from package (.+?) \(see exports field/.exec(message);
|
|
357
|
+
const at = match?.[2] === void 0 ? void 0 : packageAt(`${match[2]}/package.json`);
|
|
358
|
+
if (match?.[1] === void 0 || at === void 0) return { kind: "package-subpath" };
|
|
359
|
+
const subpath = match[1].replace(/^\.\/?/, "");
|
|
360
|
+
return {
|
|
361
|
+
kind: "package-subpath",
|
|
362
|
+
package: at.name,
|
|
363
|
+
shown: subpath === "" ? at.name : `${at.name}/${subpath}`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* A path that did not resolve, from either runtime: inside an installed package, or in the adopter's tree.
|
|
368
|
+
*
|
|
369
|
+
* Inside a package, it is that package's own missing file when the import was written in the same package,
|
|
370
|
+
* and a path the package does not have when anything else asked for it. In the adopter's tree, it is shown
|
|
371
|
+
* relative to the project, or as the relative import it was written as, or not at all.
|
|
372
|
+
*/
|
|
373
|
+
function atPath(path, referrer, options, written) {
|
|
374
|
+
const inPackage = packageAt(path);
|
|
375
|
+
if (inPackage !== void 0) {
|
|
376
|
+
const shown = inPackage.rest === "" ? inPackage.name : `${inPackage.name}/${inPackage.rest}`;
|
|
377
|
+
if (referrer !== void 0 && packageAt(referrer)?.name === inPackage.name) return {
|
|
378
|
+
kind: "package-file",
|
|
379
|
+
package: inPackage.name,
|
|
380
|
+
shown
|
|
381
|
+
};
|
|
382
|
+
const installed = options.entry?.(inPackage.name, `${inPackage.dir}/package.json`);
|
|
383
|
+
const declared = installed === void 0 ? void 0 : declaredEntry(installed.manifest);
|
|
384
|
+
if (typeof declared === "string" && isEntry(inPackage.rest, declared)) return {
|
|
385
|
+
kind: "package-entry",
|
|
386
|
+
package: inPackage.name,
|
|
387
|
+
shown: inPackage.name
|
|
388
|
+
};
|
|
389
|
+
const written = installed === void 0 ? void 0 : exportedSubpath(installed.manifest, inPackage.rest);
|
|
390
|
+
if (installed !== void 0 && written !== void 0) {
|
|
391
|
+
const subpath = written.subpath.replace(/^\.\/?/, "");
|
|
392
|
+
const as = subpath === "" ? inPackage.name : `${inPackage.name}/${subpath}`;
|
|
393
|
+
return {
|
|
394
|
+
kind: exportedKind(written, installed),
|
|
395
|
+
package: inPackage.name,
|
|
396
|
+
shown: as
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
kind: "package-subpath",
|
|
401
|
+
package: inPackage.name,
|
|
402
|
+
shown
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const { relativeTo } = options;
|
|
406
|
+
const shown = (relativeTo === void 0 ? void 0 : within(path, relativeTo)) ?? written;
|
|
407
|
+
return shown === void 0 ? { kind: "local-file" } : {
|
|
408
|
+
kind: "local-file",
|
|
409
|
+
shown
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/** A package name as npm allows one: an optional `@scope/`, and never a store's dot-directory. */
|
|
413
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
|
|
414
|
+
/**
|
|
415
|
+
* The package a path sits in, by the last `node_modules` in it: its name, its directory, and the path
|
|
416
|
+
* inside it. The directory is for reading its manifest and never leaves this module.
|
|
417
|
+
*/
|
|
418
|
+
function packageAt(path) {
|
|
419
|
+
const segments = path.split(/[\\/]/);
|
|
420
|
+
const at = segments.lastIndexOf("node_modules");
|
|
421
|
+
if (at === -1) return void 0;
|
|
422
|
+
const tail = segments.slice(at + 1);
|
|
423
|
+
const name = packageOf(tail.join("/"));
|
|
424
|
+
if (!PACKAGE_NAME.test(name)) return void 0;
|
|
425
|
+
const depth = name.split("/").length;
|
|
426
|
+
return {
|
|
427
|
+
name,
|
|
428
|
+
dir: segments.slice(0, at + 1 + depth).join("/"),
|
|
429
|
+
rest: tail.slice(depth).join("/")
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Whether a path inside a package is the entry it declares, or that entry with the `.js` or `/index.js` a
|
|
434
|
+
* `main` is tried with. Exact, so a deep import beneath a directory `main` is never read as the entry.
|
|
435
|
+
*/
|
|
436
|
+
function isEntry(rest, declared) {
|
|
437
|
+
return rest === declared || rest === `${declared}.js` || rest === `${declared}/index.js`;
|
|
438
|
+
}
|
|
439
|
+
/** The importing package, unless it is the loader's own frame. */
|
|
440
|
+
function ownerOf(referrer, loader) {
|
|
441
|
+
const name = referrer === void 0 ? void 0 : packageAt(referrer)?.name;
|
|
442
|
+
return name === loader ? void 0 : name;
|
|
443
|
+
}
|
|
444
|
+
/** `path` as `./…` beneath `root`, or `undefined` when it is not beneath it. Separators are shown as `/`. */
|
|
445
|
+
function within(path, root) {
|
|
446
|
+
const target = path.replaceAll("\\", "/");
|
|
447
|
+
const base = root.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
448
|
+
if (target === base) return ".";
|
|
449
|
+
return target.startsWith(`${base}/`) ? `./${target.slice(base.length + 1)}` : void 0;
|
|
450
|
+
}
|
|
451
|
+
/** The directory a path is in. A root is its own parent, which is what ends a walk. */
|
|
452
|
+
function parentOf(path) {
|
|
453
|
+
const trimmed = path.replace(/[\\/]+$/, "");
|
|
454
|
+
const at = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
455
|
+
if (at < 0) return path;
|
|
456
|
+
if (at === 0) return path.slice(0, 1);
|
|
457
|
+
const head = trimmed.slice(0, at);
|
|
458
|
+
return /^[A-Za-z]:$/.test(head) ? `${head}${trimmed[at]}` : head;
|
|
459
|
+
}
|
|
460
|
+
/** A relative import, followed from the directory it was written in. `..` past the root stays at the root. */
|
|
461
|
+
function joinPath(base, relative) {
|
|
462
|
+
const separator = base.includes("/") ? "/" : "\\";
|
|
463
|
+
const parts = base.replace(/[\\/]+$/, "").split(/[\\/]/);
|
|
464
|
+
for (const part of relative.split(/[\\/]/)) {
|
|
465
|
+
if (part === "" || part === ".") continue;
|
|
466
|
+
if (part === "..") {
|
|
467
|
+
if (parts.length > 1) parts.pop();
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
parts.push(part);
|
|
471
|
+
}
|
|
472
|
+
return parts.join(separator) || separator;
|
|
473
|
+
}
|
|
474
|
+
/** The path a `file:` URL names, or the URL with its scheme taken off when it will not parse as one. */
|
|
475
|
+
function urlPath(specifier) {
|
|
476
|
+
try {
|
|
477
|
+
const path = decodeURIComponent(new URL(specifier).pathname);
|
|
478
|
+
return /^\/[A-Za-z]:\//.test(path) ? path.slice(1) : path;
|
|
479
|
+
} catch {
|
|
480
|
+
return specifier.replace(/^file:\/*/, "/");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The import that did not resolve, as the runtime named it. Bun's field where there is one; otherwise the
|
|
485
|
+
* one sentence each loader states it in. The prose around it names the referrer's absolute path, which
|
|
486
|
+
* is why only the quoted part is taken.
|
|
487
|
+
*/
|
|
488
|
+
function writtenSpecifier(cause, message) {
|
|
489
|
+
const field = prop(cause, "specifier");
|
|
490
|
+
if (typeof field === "string" && field.length > 0) return field;
|
|
491
|
+
return (/Cannot find (?:package|module) ['"]([^'"]+)['"]/.exec(message) ?? /^Failed to load url (\S+) \(resolved id: /.exec(message) ?? /^No such built-in module: (\S+)$/.exec(message) ?? /^Package import specifier "([^"]+)" is not defined/.exec(message) ?? /^Directory import '([^']+)' is not supported/.exec(message))?.[1];
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* The file the failing import was written in, as the runtime named it. **Module-private on purpose**: it
|
|
495
|
+
* is an absolute path, and only its package half ever leaves here.
|
|
496
|
+
*
|
|
497
|
+
* Node ends its sentence with the path, so it is taken to the end — a directory may contain a space.
|
|
498
|
+
* Vite's module runner quotes it, and its own `ERR_LOAD_URL` sentence names it before `. Does the file
|
|
499
|
+
* exist?` (Vite 8.3.0, measured).
|
|
500
|
+
*/
|
|
501
|
+
function importerPath(cause) {
|
|
502
|
+
const field = prop(cause, "referrer");
|
|
503
|
+
if (typeof field === "string" && field.length > 0) return field;
|
|
504
|
+
const message = causeMessage(cause) ?? "";
|
|
505
|
+
return (/ imported from '(.+)'$/.exec(message) ?? / imported from (.+)$/.exec(message) ?? /^Failed to load url \S+ \(resolved id: [^)]*\) in (.+)\. Does the file exist\?$/.exec(message))?.[1];
|
|
506
|
+
}
|
|
507
|
+
/** Every remedy that cannot be helped by an install says so, in these words. */
|
|
508
|
+
const NO_INSTALL = "installing dependencies will not help.";
|
|
509
|
+
/** The one sentence for a classified import. Built only from fields that cannot carry a path. */
|
|
510
|
+
function remedy(found, owner) {
|
|
511
|
+
const { kind, shown, package: pkg } = found;
|
|
512
|
+
switch (kind) {
|
|
513
|
+
case "package": {
|
|
514
|
+
const name = pkg ?? shown ?? "a package";
|
|
515
|
+
return owner === void 0 ? `"${name}" is not installed. Install the project's dependencies (bun install), or correct that import.` : `${owner} imports "${name}", which is not installed. Install the project's dependencies (bun install). If it is still missing, ${owner} does not declare it — report that to its maintainer.`;
|
|
516
|
+
}
|
|
517
|
+
case "package-subpath":
|
|
518
|
+
if (shown === void 0 || pkg === void 0) return `An import asks an installed package for a path it does not provide. Check the version you have, or correct that import — ${NO_INSTALL}`;
|
|
519
|
+
if (shown === pkg && (owner === void 0 || owner === pkg)) return `The installed ${pkg} has no entry to import by its bare name. Import one of its paths, or check the version of ${pkg} you have — ${NO_INSTALL}`;
|
|
520
|
+
return owner === void 0 || owner === pkg ? `"${shown}" is not something the installed ${pkg} provides. Check the version of ${pkg} you have, or correct that import — ${NO_INSTALL}` : `${owner} imports "${shown}", which the installed ${pkg} does not provide. Bring ${owner} and ${pkg} to matching versions, or report this to its maintainer — ${NO_INSTALL}`;
|
|
521
|
+
case "package-entry": {
|
|
522
|
+
const fix = `so it is unbuilt or its install is broken. Rebuild ${pkg}, or remove it and install it again.`;
|
|
523
|
+
const deep = shown !== void 0 && shown !== pkg;
|
|
524
|
+
const missing = deep ? `exports "${shown}" from a file that is not there, ${fix}` : `names an entry that is not there, ${fix}`;
|
|
525
|
+
if (owner === void 0 || owner === pkg) return `The installed ${pkg} ${missing}`;
|
|
526
|
+
return deep ? `${owner} imports "${shown}", and the installed ${pkg} exports it from a file that is not there, ${fix}` : `${owner} imports "${pkg}", and the installed ${pkg} ${missing}`;
|
|
527
|
+
}
|
|
528
|
+
case "package-file": return `${pkg} is missing its own file "${shown}". Report this to its maintainer — ${NO_INSTALL}`;
|
|
529
|
+
case "builtin":
|
|
530
|
+
if (shown === void 0) return `An import names a module this runtime does not provide. Correct that import — ${NO_INSTALL}`;
|
|
531
|
+
return owner === void 0 ? `"${shown}" is not a module this runtime provides. Correct that import — ${NO_INSTALL}` : `${owner} imports "${shown}", which this runtime does not provide. Run it on a runtime that has that module, or report this to its maintainer — ${NO_INSTALL}`;
|
|
532
|
+
case "local-file": return shown === void 0 ? `An import names a file that is not there. Create that file or correct the import — ${NO_INSTALL}` : `Nothing at "${shown}". Create that file or correct the import — ${NO_INSTALL}`;
|
|
533
|
+
case "imports-map": return owner === void 0 ? `"${shown}" is not in the project's imports map. Add it to the imports field of package.json, or correct the import — ${NO_INSTALL}` : `${owner} imports "${shown}", which its own imports map does not provide. Report this to its maintainer — ${NO_INSTALL}`;
|
|
534
|
+
case "undetermined": return owner === void 0 ? `Nothing resolves "${shown}". Install the project's dependencies (bun install), or correct that import.` : `${owner} imports "${shown}", and nothing resolves it. Install the project's dependencies (bun install), or bring ${owner} and ${pkg} to matching versions.`;
|
|
535
|
+
case "unnamed": return "An import does not resolve. Check the imports, then install the project's dependencies (bun install).";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/** What Node says of code whose syntax contradicts the module type its package declares. */
|
|
539
|
+
const MODULE_TYPE = / is not defined in ES module scope|Cannot use import statement outside a module|Unexpected token 'export'|Cannot use 'import\.meta' outside a module/;
|
|
540
|
+
/**
|
|
541
|
+
* **A file inside an installed package would not load, so the package is named and never the config.**
|
|
542
|
+
*
|
|
543
|
+
* A `"type"` its code contradicts — `module` over `module.exports`, `commonjs` over `import` — throws from
|
|
544
|
+
* inside that package, on Node and through Vite's runner alike, as a `ReferenceError` or a `SyntaxError`.
|
|
545
|
+
* Unclassified, the first read as *the config threw while loading* and the second as *the config does not
|
|
546
|
+
* parse*: the adopter told to fix a file that is fine. A package's file that does not parse at all is the
|
|
547
|
+
* same fault in the same place, and is named the same way.
|
|
548
|
+
*
|
|
549
|
+
* **The file is the one that failed to load, and only that one.** Node heads a compile error with the file
|
|
550
|
+
* and line it could not compile, and Bun 1.4.2 hands over a `BuildMessage` with no stack at all, the file
|
|
551
|
+
* in its `position`. Otherwise the error's first stack frame must be a module's own top level, run as it
|
|
552
|
+
* is imported — `at file:///…:L:C`, or Vite's `at eval (…)`, with no function of its own. A first frame
|
|
553
|
+
* inside a function is the config calling into a package, and a later frame is a caller: so a package's
|
|
554
|
+
* deliberate throw, which the config provoked — json5's `parse` throws a `SyntaxError` on bad input — is
|
|
555
|
+
* never laid at the package's door. Only errors a module throws while it is loaded are read: a
|
|
556
|
+
* `SyntaxError` or a `BuildMessage`, and a `ReferenceError` for a CommonJS global in a module's scope.
|
|
557
|
+
*
|
|
558
|
+
* `undefined` when the cause is not one, or was thrown in the adopter's own file — that stays theirs.
|
|
559
|
+
*/
|
|
560
|
+
function packageLoadFailure(wrapped, options = {}) {
|
|
561
|
+
const cause = rootCause(wrapped);
|
|
562
|
+
const typeMismatch = MODULE_TYPE.test(causeMessage(cause) ?? "");
|
|
563
|
+
const name = prop(cause, "name");
|
|
564
|
+
if (!typeMismatch && name !== "SyntaxError" && name !== "BuildMessage") return void 0;
|
|
565
|
+
const owner = ownerOf(failedToLoad(cause), options.loader);
|
|
566
|
+
if (owner === void 0) return void 0;
|
|
567
|
+
const check = `Check the version of ${owner} you have, or report this to its maintainer — ${NO_INSTALL}`;
|
|
568
|
+
return {
|
|
569
|
+
owner,
|
|
570
|
+
remedy: typeMismatch ? `The installed ${owner} does not load as the module type its package.json declares. ${check}` : `The installed ${owner} does not parse. ${check}`
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The file that failed as it loaded, or none: the file and line Node heads a compile error with, the file a
|
|
575
|
+
* stackless `BuildMessage` names in its `position` (Bun 1.4.2), or a first frame at a module's top level.
|
|
576
|
+
* Never a frame inside a function, which is code running after its module loaded. **Module-private**, like
|
|
577
|
+
* {@link importerPath}: it is an absolute path.
|
|
578
|
+
*/
|
|
579
|
+
function failedToLoad(cause) {
|
|
580
|
+
const stack = prop(cause, "stack");
|
|
581
|
+
if (typeof stack !== "string") {
|
|
582
|
+
const file = prop(prop(cause, "position"), "file");
|
|
583
|
+
return typeof file === "string" ? locationPath(file) : void 0;
|
|
584
|
+
}
|
|
585
|
+
const lines = stack.split("\n");
|
|
586
|
+
const head = /^(.+):\d+$/.exec(lines[0] ?? "")?.[1];
|
|
587
|
+
if (head !== void 0 && (head.startsWith("file:") || ABSOLUTE.test(head))) return locationPath(head);
|
|
588
|
+
const frame = lines.find((line) => /^\s+at /.test(line));
|
|
589
|
+
if (frame === void 0) return void 0;
|
|
590
|
+
const at = /^\s+at eval \((.+?):\d+(?::\d+)?\)$/.exec(frame)?.[1] ?? /^\s+at ([^()]+?):\d+(?::\d+)?$/.exec(frame)?.[1];
|
|
591
|
+
return at === void 0 ? void 0 : locationPath(at);
|
|
592
|
+
}
|
|
593
|
+
/** A stack location as a path: a `file:` URL converted, an absolute path kept, anything else — `node:` — none. */
|
|
594
|
+
function locationPath(location) {
|
|
595
|
+
if (location.startsWith("file:")) return urlPath(location);
|
|
596
|
+
return ABSOLUTE.test(location) ? location : void 0;
|
|
597
|
+
}
|
|
598
|
+
//#endregion
|
|
599
|
+
export { declaredEntry, exportedTarget, importingPackage, nodeModulesProbe, packageEntryProbe, packageLoadFailure, unresolvedImport };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* The wire body of every error a route returns, whatever its status: `pithyErrorHandler` renders
|
|
6
|
+
* `{ error: clientError(payload) }`, and a socket's error frame carries the same `error` object. The payload's own schema is a union over every kit code, which
|
|
7
|
+
* would put several hundred variants into each operation, so the contract describes the envelope and
|
|
8
|
+
* names the code list's home instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ErrorBody: z.ZodObject<{
|
|
11
|
+
error: z.ZodObject<{
|
|
12
|
+
code: z.ZodString;
|
|
13
|
+
status: z.ZodNumber;
|
|
14
|
+
message: z.ZodString;
|
|
15
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
16
|
+
issues: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type ErrorBody = z.infer<typeof ErrorBody>;
|
|
20
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/error/wire.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,eAAO,MAAM,SAAS;;;;;;;;iBAwB0B,CAAC;AACjD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/error/wire.ts
|
|
5
|
+
/**
|
|
6
|
+
* The wire body of every error a route returns, whatever its status: `pithyErrorHandler` renders
|
|
7
|
+
* `{ error: clientError(payload) }`, and a socket's error frame carries the same `error` object. The payload's own schema is a union over every kit code, which
|
|
8
|
+
* would put several hundred variants into each operation, so the contract describes the envelope and
|
|
9
|
+
* names the code list's home instead.
|
|
10
|
+
*/
|
|
11
|
+
const ErrorBody = z.object({ error: z.object({
|
|
12
|
+
code: z.string().describe("Namespaced `domain/reason`, e.g. `auth/invalid_token`. Stable: a client switches on it. The full list is `pithy schema --json`'s `errorCodes`."),
|
|
13
|
+
status: z.number().int().describe("The HTTP status, repeated in the body."),
|
|
14
|
+
message: z.string().describe("English, for a person. A client that translates renders its own wording for `code` instead."),
|
|
15
|
+
params: z.record(z.string(), z.unknown()).optional().describe("Values a translating client interpolates into its own wording for `code`."),
|
|
16
|
+
issues: z.array(z.unknown()).optional().describe("For `validation/invalid_input`: one entry per field that failed, with its path.")
|
|
17
|
+
}).describe("The public error payload. Never carries the operator's remedy or internal detail.") }).describe("The body of every error response.");
|
|
18
|
+
//#endregion
|
|
19
|
+
export { ErrorBody };
|
|
@@ -295,8 +295,8 @@ export interface OidcWebhookGuardOptions extends OidcWebhookScheme {
|
|
|
295
295
|
*/
|
|
296
296
|
export declare function verifyOidcToken(token: string, options: VerifyOidcTokenOptions): Promise<OidcClaims>;
|
|
297
297
|
/**
|
|
298
|
-
* The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}),
|
|
299
|
-
*
|
|
298
|
+
* The OIDC gate, as a route wears it: `app.post(path, requireOidcWebhook({…}), validate("json", Body),
|
|
299
|
+
* handler)`.
|
|
300
300
|
*
|
|
301
301
|
* ## Why the header is read before anything else
|
|
302
302
|
*
|
|
@@ -332,7 +332,7 @@ export declare function verifyOidcToken(token: string, options: VerifyOidcTokenO
|
|
|
332
332
|
* null-check.
|
|
333
333
|
*
|
|
334
334
|
* The body is never read here — unlike the HMAC gate, whose proof covers the bytes. An OIDC token proves who
|
|
335
|
-
* sent the request and says nothing about what they sent, so the route's `
|
|
335
|
+
* sent the request and says nothing about what they sent, so the route's `validate` is the only thing that
|
|
336
336
|
* touches the body, and it sees an untouched stream.
|
|
337
337
|
*/
|
|
338
338
|
export declare function requireOidcWebhook(options: OidcWebhookGuardOptions): MiddlewareHandler<PithyHonoEnv>;
|