@vibe-agent-toolkit/utils 0.2.0-rc.3 → 0.2.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -13
- package/dist/crawl-timing.d.ts +43 -66
- package/dist/crawl-timing.d.ts.map +1 -1
- package/dist/crawl-timing.js +35 -79
- package/dist/crawl-timing.js.map +1 -1
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/git.d.ts +11 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +11 -4
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +19 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -41
- package/dist/index.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/testing.d.ts +73 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +93 -3
- package/dist/testing.js.map +1 -1
- package/dist/timing-dump.d.ts +22 -5
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +89 -23
- package/dist/timing-dump.js.map +1 -1
- package/eslint/index.cjs +9 -0
- package/eslint/index.d.cts +14 -7
- package/eslint/rules/no-process-exit-in-phase.cjs +117 -0
- package/package.json +5 -7
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -139
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -115
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Macro loader + expander.
|
|
3
|
-
*
|
|
4
|
-
* Loads the bundled `macros.yaml` once at module init and exposes
|
|
5
|
-
* `expandMacro(name, overrides?)` which deep-merges the named macro with any
|
|
6
|
-
* adopter overrides. The merge is "adopter wins": objects merge recursively,
|
|
7
|
-
* arrays and primitives are replaced wholesale (no element-wise array merge).
|
|
8
|
-
*
|
|
9
|
-
* The shipped macro file is at `link-auth/macros.yaml`, copied into the dist
|
|
10
|
-
* tree by `packages/dev-tools/src/copy-yaml-assets.ts` during build so the
|
|
11
|
-
* runtime `fs.readFileSync(new URL('./macros.yaml', import.meta.url))`
|
|
12
|
-
* resolves in both source-mode (vitest) and built-mode (dist).
|
|
13
|
-
*
|
|
14
|
-
* Per design issue #113 §5 (macros are config, not a privileged code path).
|
|
15
|
-
*/
|
|
16
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { parse as parseYaml } from 'yaml';
|
|
19
|
-
let macrosCache;
|
|
20
|
-
/**
|
|
21
|
-
* Load macros lazily on first use rather than at module init.
|
|
22
|
-
*
|
|
23
|
-
* Why lazy: vitest tests elsewhere in the repo mock `node:fs` (replacing
|
|
24
|
-
* `readFileSync` with `vi.fn()` that returns `undefined`). If we eagerly read
|
|
25
|
-
* at module load, any unrelated test that imports `@vibe-agent-toolkit/utils`
|
|
26
|
-
* and mocks fs crashes inside `parseYaml(undefined)` — module-init code runs
|
|
27
|
-
* unconditionally, before the mock-setup intent reaches our file.
|
|
28
|
-
*
|
|
29
|
-
* Lazy load makes module import side-effect-free; only callers of
|
|
30
|
-
* `expandMacro` pay the fs cost. `macrosPath` is also computed here (not at
|
|
31
|
-
* module top level) so that merely importing this module leaves no `new URL()`
|
|
32
|
-
* reference for bundler static analysis to trip over.
|
|
33
|
-
*/
|
|
34
|
-
function getMacros() {
|
|
35
|
-
if (macrosCache !== undefined)
|
|
36
|
-
return macrosCache;
|
|
37
|
-
// Path is derived from `import.meta.url`, not user input — points at the
|
|
38
|
-
// shipped macros.yaml asset next to this module in both src and dist trees.
|
|
39
|
-
const macrosPath = fileURLToPath(new URL('./macros.yaml', import.meta.url));
|
|
40
|
-
// Not a content read: `macros.yaml` is an asset THIS PACKAGE authors, commits
|
|
41
|
-
// and publishes beside this module, so the encoding was chosen at the write
|
|
42
|
-
// rather than discovered at the read. Left on the raw reader deliberately —
|
|
43
|
-
// the lazy-load note above documents that tests elsewhere mock
|
|
44
|
-
// `readFileSync`, and routing this through another module's import of it is
|
|
45
|
-
// gratuitous risk for a file whose bytes we control.
|
|
46
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename, local/no-raw-text-decode -- our own published asset; writer is this package
|
|
47
|
-
const macrosFileContent = readFileSync(macrosPath, 'utf8');
|
|
48
|
-
const parsed = parseYaml(macrosFileContent);
|
|
49
|
-
if (!isPlainObject(parsed)) {
|
|
50
|
-
throw new Error('macros.yaml: expected top-level object mapping macro name → provider config.');
|
|
51
|
-
}
|
|
52
|
-
macrosCache = freezeMacros(parsed);
|
|
53
|
-
return macrosCache;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Thrown when a `use: <name>` references a macro not in the shipped set.
|
|
57
|
-
* Message lists the available macros so a typo surfaces clearly.
|
|
58
|
-
*/
|
|
59
|
-
export class UnknownMacroError extends Error {
|
|
60
|
-
constructor(name, available) {
|
|
61
|
-
super(`Unknown macro "${name}". Available: ${available.join(', ')}.`);
|
|
62
|
-
this.name = 'UnknownMacroError';
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Look up a macro by name and deep-merge optional adopter overrides on top.
|
|
67
|
-
*
|
|
68
|
-
* Merge semantics:
|
|
69
|
-
* - Plain objects merge recursively (sibling keys preserved).
|
|
70
|
-
* - Arrays are replaced wholesale (override's array wins; no concat).
|
|
71
|
-
* - Primitives are replaced.
|
|
72
|
-
* - `undefined` in an override is treated as "not provided" (base wins).
|
|
73
|
-
*
|
|
74
|
-
* The returned object and all nested plain objects use null prototypes so
|
|
75
|
-
* `__proto__` / `constructor` keys can never poison consumers.
|
|
76
|
-
*
|
|
77
|
-
* @throws {UnknownMacroError} if `name` is not in the shipped macro set
|
|
78
|
-
*/
|
|
79
|
-
export function expandMacro(name, overrides) {
|
|
80
|
-
const macros = getMacros();
|
|
81
|
-
const base = macros[name];
|
|
82
|
-
if (base === undefined) {
|
|
83
|
-
throw new UnknownMacroError(name, Object.keys(macros));
|
|
84
|
-
}
|
|
85
|
-
if (overrides === undefined) {
|
|
86
|
-
return cloneWithNullProto(base);
|
|
87
|
-
}
|
|
88
|
-
const merged = deepMerge(base, overrides);
|
|
89
|
-
// Top-level result is guaranteed object here because base is.
|
|
90
|
-
return merged;
|
|
91
|
-
}
|
|
92
|
-
function deepMerge(base, override) {
|
|
93
|
-
if (override === undefined)
|
|
94
|
-
return base;
|
|
95
|
-
if (!isPlainObject(base) || !isPlainObject(override))
|
|
96
|
-
return cloneValue(override);
|
|
97
|
-
const result = Object.create(null);
|
|
98
|
-
for (const [key, value] of Object.entries(base)) {
|
|
99
|
-
result[key] = cloneValue(value);
|
|
100
|
-
}
|
|
101
|
-
for (const [key, value] of Object.entries(override)) {
|
|
102
|
-
const existing = Object.hasOwn(result, key) ? result[key] : undefined;
|
|
103
|
-
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
104
|
-
result[key] = deepMerge(existing, value);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
result[key] = cloneValue(value);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
function cloneValue(value) {
|
|
113
|
-
if (Array.isArray(value))
|
|
114
|
-
return value.map(cloneValue);
|
|
115
|
-
if (isPlainObject(value))
|
|
116
|
-
return cloneWithNullProto(value);
|
|
117
|
-
return value;
|
|
118
|
-
}
|
|
119
|
-
function cloneWithNullProto(obj) {
|
|
120
|
-
const cloned = Object.create(null);
|
|
121
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
122
|
-
cloned[key] = cloneValue(value);
|
|
123
|
-
}
|
|
124
|
-
return cloned;
|
|
125
|
-
}
|
|
126
|
-
function freezeMacros(parsed) {
|
|
127
|
-
const frozen = Object.create(null);
|
|
128
|
-
for (const [name, value] of Object.entries(parsed)) {
|
|
129
|
-
if (!isPlainObject(value)) {
|
|
130
|
-
throw new Error(`macros.yaml: macro "${name}" is not an object.`);
|
|
131
|
-
}
|
|
132
|
-
frozen[name] = cloneWithNullProto(value);
|
|
133
|
-
}
|
|
134
|
-
return frozen;
|
|
135
|
-
}
|
|
136
|
-
function isPlainObject(v) {
|
|
137
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=expand-macro.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"expand-macro.js","sourceRoot":"","sources":["../../src/link-auth/expand-macro.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# linkAuth provider macros — shipped defaults referenced by `use: <name>` in
|
|
2
|
-
# adopter `vibe-agent-toolkit.config.yaml` files.
|
|
3
|
-
#
|
|
4
|
-
# Each entry is a full provider config in the §4 vocabulary (match, rewrite,
|
|
5
|
-
# auth, token, check). Adopters reference a macro by name and may deep-merge
|
|
6
|
-
# overrides on top — see design issue #113 §5.
|
|
7
|
-
#
|
|
8
|
-
# Adding a new host is intended to be a config-only PR: append an entry here
|
|
9
|
-
# and add unit tests. Only `vocabulary` changes (e.g. a new transform) require
|
|
10
|
-
# engine code.
|
|
11
|
-
|
|
12
|
-
github:
|
|
13
|
-
match:
|
|
14
|
-
host: github.com
|
|
15
|
-
rewrite:
|
|
16
|
-
- when: '^https://github\.com/(?<owner>[^/]+)/(?<repo>[^/]+)/(?:blob|tree)/(?<ref>[^/]+)/(?<path>.+)$'
|
|
17
|
-
to: 'https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${ref}'
|
|
18
|
-
auth:
|
|
19
|
-
headers:
|
|
20
|
-
Authorization: 'Bearer ${token}'
|
|
21
|
-
Accept: application/vnd.github+json
|
|
22
|
-
token:
|
|
23
|
-
- command: gh auth token
|
|
24
|
-
- env: GITHUB_TOKEN
|
|
25
|
-
check:
|
|
26
|
-
method: GET
|
|
27
|
-
aliveStatus: [200]
|
|
28
|
-
notFoundMeaning: ambiguous
|
|
29
|
-
|
|
30
|
-
sharepoint:
|
|
31
|
-
match:
|
|
32
|
-
host: '*.sharepoint.com'
|
|
33
|
-
excludeHost:
|
|
34
|
-
- '*-my.sharepoint.com'
|
|
35
|
-
rewrite:
|
|
36
|
-
- when: '^(?<u>https://.+)$'
|
|
37
|
-
vars:
|
|
38
|
-
shareId: 'u!${base64url(u)}'
|
|
39
|
-
to: 'https://graph.microsoft.com/v1.0/shares/${shareId}/driveItem'
|
|
40
|
-
auth:
|
|
41
|
-
headers:
|
|
42
|
-
Authorization: 'Bearer ${token}'
|
|
43
|
-
# SharePoint has no zero-config token source (design §5.1 note): a SharePoint-
|
|
44
|
-
# scoped token requires an Entra app registration; the stock `az` token does
|
|
45
|
-
# not work. Adopter must supply their own token command.
|
|
46
|
-
token: []
|
|
47
|
-
check:
|
|
48
|
-
method: GET
|
|
49
|
-
aliveStatus: [200]
|
|
50
|
-
notFoundMeaning: dead
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token resolution — ordered, first-non-empty-wins.
|
|
3
|
-
*
|
|
4
|
-
* Iterates a provider's `token` source list and returns the first source that
|
|
5
|
-
* yields a non-empty value. Two source shapes:
|
|
6
|
-
* - `{ env: "NAME" }` — read from process env (no trimming)
|
|
7
|
-
* - `{ command: argv }` — run a command, return trimmed stdout
|
|
8
|
-
* - `{ command: "gh auth token" }` — convenience: whitespace-tokenized into
|
|
9
|
-
* argv. **Not** passed through a shell — operators (`|`, `&&`, `$(...)`)
|
|
10
|
-
* become literal argv elements, per the design's §6.1 sharp-edge note.
|
|
11
|
-
*
|
|
12
|
-
* Command sources can be disabled at runtime with `VAT_LINKAUTH_ALLOW_COMMAND=0`
|
|
13
|
-
* (or by passing `allowCommand: false` in deps). Useful in security-sensitive
|
|
14
|
-
* environments where arbitrary command execution is undesirable.
|
|
15
|
-
*
|
|
16
|
-
* Returns `undefined` if every source fails or yields an empty/whitespace
|
|
17
|
-
* value — the caller's `resolveAuthenticatedUrl` translates that to the
|
|
18
|
-
* `unverified` outcome (surfaced as `LINK_AUTH_UNVERIFIED` by the validator).
|
|
19
|
-
*
|
|
20
|
-
* Per design issue #113 §4 (token vocabulary) and §6.1 (command execution,
|
|
21
|
-
* `safeExecSync`-backed, `shell: false`).
|
|
22
|
-
*/
|
|
23
|
-
export type TokenSource = {
|
|
24
|
-
readonly env: string;
|
|
25
|
-
} | {
|
|
26
|
-
readonly command: string | readonly string[];
|
|
27
|
-
};
|
|
28
|
-
export interface TokenResolutionDeps {
|
|
29
|
-
/**
|
|
30
|
-
* Environment lookup map. Defaults to `process.env`. Injectable for tests so
|
|
31
|
-
* unit tests don't depend on ambient environment state.
|
|
32
|
-
*/
|
|
33
|
-
readonly env: Record<string, string | undefined>;
|
|
34
|
-
/**
|
|
35
|
-
* Command runner. Defaults to `safeExecResult`-wrapped invocation. Injectable
|
|
36
|
-
* for tests. Receives argv; returns `success` + `stdout`. Should NOT throw
|
|
37
|
-
* for normal exec failures (return `success: false` instead). Throws are
|
|
38
|
-
* propagated by `resolveToken` — they indicate operator-level bugs.
|
|
39
|
-
*/
|
|
40
|
-
readonly runCommand: (argv: readonly string[]) => {
|
|
41
|
-
success: boolean;
|
|
42
|
-
stdout: string;
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* Whether `{ command: ... }` sources are allowed. Defaults to reading
|
|
46
|
-
* `VAT_LINKAUTH_ALLOW_COMMAND` from the resolved `env` map (not ambient
|
|
47
|
-
* `process.env`), so a caller supplying a curated `deps.env` can control the
|
|
48
|
-
* flag without touching real process state. Set to `false` (or set
|
|
49
|
-
* `VAT_LINKAUTH_ALLOW_COMMAND=0` in the env) to skip all command sources and
|
|
50
|
-
* rely solely on env-var sources — useful in locked-down CI or security reviews.
|
|
51
|
-
*/
|
|
52
|
-
readonly allowCommand: boolean;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Return a copy of the given env with all `GIT_*` keys removed. Case-insensitive
|
|
56
|
-
* on the key so Windows env vars (which are case-insensitive at the OS level,
|
|
57
|
-
* though `process.env` preserves original case) can't sneak through as e.g.
|
|
58
|
-
* `Git_Dir`.
|
|
59
|
-
*
|
|
60
|
-
* Exported for unit testing and for callers assembling their own `runCommand`
|
|
61
|
-
* who want the exact same scrub `defaultRunCommand` applies.
|
|
62
|
-
*
|
|
63
|
-
* Rationale: `vat resources validate` is often invoked from git pre-commit
|
|
64
|
-
* hooks, which pre-set `GIT_DIR` / `GIT_WORK_TREE` / `GIT_INDEX_FILE`. These
|
|
65
|
-
* poison any nested tool that shells out to git, notably `gh auth token`.
|
|
66
|
-
*/
|
|
67
|
-
export declare function scrubGitEnv(source: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
68
|
-
/**
|
|
69
|
-
* Default `runCommand` implementation — exported so callers that want to
|
|
70
|
-
* memoize per-validate-run can wrap it without duplicating the spawn logic.
|
|
71
|
-
* Forwards to `safeExecResult` (no shell, argv-based), with `GIT_*` vars
|
|
72
|
-
* stripped from the child env — see {@link scrubGitEnv}.
|
|
73
|
-
*/
|
|
74
|
-
export declare const defaultRunCommand: TokenResolutionDeps['runCommand'];
|
|
75
|
-
/**
|
|
76
|
-
* Resolve a token from an ordered list of sources.
|
|
77
|
-
*
|
|
78
|
-
* @returns the first non-empty value, or `undefined` if every source failed.
|
|
79
|
-
* @throws whatever the injected `runCommand` throws (operator-level bug; not
|
|
80
|
-
* swallowed). Standard `safeExecResult` does not throw under normal use.
|
|
81
|
-
*/
|
|
82
|
-
export declare function resolveToken(sources: readonly TokenSource[], deps?: Partial<TokenResolutionDeps>): string | undefined;
|
|
83
|
-
//# sourceMappingURL=resolve-token.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-token.d.ts","sourceRoot":"","sources":["../../src/link-auth/resolve-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAEtG,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvF;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAIxE;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,mBAAmB,CAAC,YAAY,CAqB/D,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAClC,MAAM,GAAG,SAAS,CAWpB"}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token resolution — ordered, first-non-empty-wins.
|
|
3
|
-
*
|
|
4
|
-
* Iterates a provider's `token` source list and returns the first source that
|
|
5
|
-
* yields a non-empty value. Two source shapes:
|
|
6
|
-
* - `{ env: "NAME" }` — read from process env (no trimming)
|
|
7
|
-
* - `{ command: argv }` — run a command, return trimmed stdout
|
|
8
|
-
* - `{ command: "gh auth token" }` — convenience: whitespace-tokenized into
|
|
9
|
-
* argv. **Not** passed through a shell — operators (`|`, `&&`, `$(...)`)
|
|
10
|
-
* become literal argv elements, per the design's §6.1 sharp-edge note.
|
|
11
|
-
*
|
|
12
|
-
* Command sources can be disabled at runtime with `VAT_LINKAUTH_ALLOW_COMMAND=0`
|
|
13
|
-
* (or by passing `allowCommand: false` in deps). Useful in security-sensitive
|
|
14
|
-
* environments where arbitrary command execution is undesirable.
|
|
15
|
-
*
|
|
16
|
-
* Returns `undefined` if every source fails or yields an empty/whitespace
|
|
17
|
-
* value — the caller's `resolveAuthenticatedUrl` translates that to the
|
|
18
|
-
* `unverified` outcome (surfaced as `LINK_AUTH_UNVERIFIED` by the validator).
|
|
19
|
-
*
|
|
20
|
-
* Per design issue #113 §4 (token vocabulary) and §6.1 (command execution,
|
|
21
|
-
* `safeExecSync`-backed, `shell: false`).
|
|
22
|
-
*/
|
|
23
|
-
import { safeExecResult } from '../safe-exec.js';
|
|
24
|
-
/**
|
|
25
|
-
* Return a copy of the given env with all `GIT_*` keys removed. Case-insensitive
|
|
26
|
-
* on the key so Windows env vars (which are case-insensitive at the OS level,
|
|
27
|
-
* though `process.env` preserves original case) can't sneak through as e.g.
|
|
28
|
-
* `Git_Dir`.
|
|
29
|
-
*
|
|
30
|
-
* Exported for unit testing and for callers assembling their own `runCommand`
|
|
31
|
-
* who want the exact same scrub `defaultRunCommand` applies.
|
|
32
|
-
*
|
|
33
|
-
* Rationale: `vat resources validate` is often invoked from git pre-commit
|
|
34
|
-
* hooks, which pre-set `GIT_DIR` / `GIT_WORK_TREE` / `GIT_INDEX_FILE`. These
|
|
35
|
-
* poison any nested tool that shells out to git, notably `gh auth token`.
|
|
36
|
-
*/
|
|
37
|
-
export function scrubGitEnv(source) {
|
|
38
|
-
return Object.fromEntries(Object.entries(source).filter(([k]) => !k.toUpperCase().startsWith('GIT_')));
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Default `runCommand` implementation — exported so callers that want to
|
|
42
|
-
* memoize per-validate-run can wrap it without duplicating the spawn logic.
|
|
43
|
-
* Forwards to `safeExecResult` (no shell, argv-based), with `GIT_*` vars
|
|
44
|
-
* stripped from the child env — see {@link scrubGitEnv}.
|
|
45
|
-
*/
|
|
46
|
-
export const defaultRunCommand = (argv) => {
|
|
47
|
-
if (argv.length === 0)
|
|
48
|
-
return { success: false, stdout: '' };
|
|
49
|
-
const [bin, ...args] = argv;
|
|
50
|
-
if (bin === undefined)
|
|
51
|
-
return { success: false, stdout: '' };
|
|
52
|
-
const result = safeExecResult(bin, [...args], {
|
|
53
|
-
encoding: 'utf8',
|
|
54
|
-
env: scrubGitEnv(process.env),
|
|
55
|
-
// The operator chose this argv; VAT neither composed it nor knows whether it
|
|
56
|
-
// means a path or the ambient repository, so the usual "say which you mean"
|
|
57
|
-
// refusal has nobody to ask. `git credential fill` and friends are ordinary
|
|
58
|
-
// things to configure here. The env is already handled above, and more
|
|
59
|
-
// strictly than runGit would: every GIT_* key, case-insensitively.
|
|
60
|
-
allowGit: true,
|
|
61
|
-
});
|
|
62
|
-
// Not a file read: these bytes are the stdout of the credential helper spawned
|
|
63
|
-
// three lines up, and its encoding is that program's contract, not a property
|
|
64
|
-
// of any file. `safeExecResult` was already asked for `encoding: 'utf8'`, so
|
|
65
|
-
// this branch only runs when it handed back a Buffer anyway.
|
|
66
|
-
// eslint-disable-next-line local/no-raw-text-decode -- subprocess stdout; producer is the credential helper spawned above
|
|
67
|
-
const stdout = typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf8');
|
|
68
|
-
return { success: result.success, stdout };
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Resolve a token from an ordered list of sources.
|
|
72
|
-
*
|
|
73
|
-
* @returns the first non-empty value, or `undefined` if every source failed.
|
|
74
|
-
* @throws whatever the injected `runCommand` throws (operator-level bug; not
|
|
75
|
-
* swallowed). Standard `safeExecResult` does not throw under normal use.
|
|
76
|
-
*/
|
|
77
|
-
export function resolveToken(sources, deps) {
|
|
78
|
-
const env = deps?.env ?? process.env;
|
|
79
|
-
const runCommand = deps?.runCommand ?? defaultRunCommand;
|
|
80
|
-
const allowCommand = deps?.allowCommand ?? (env['VAT_LINKAUTH_ALLOW_COMMAND'] !== '0');
|
|
81
|
-
for (const source of sources) {
|
|
82
|
-
if (!allowCommand && 'command' in source)
|
|
83
|
-
continue;
|
|
84
|
-
const value = tryResolveSource(source, env, runCommand);
|
|
85
|
-
if (value !== undefined && value.length > 0)
|
|
86
|
-
return value;
|
|
87
|
-
}
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
function tryResolveSource(source, env, runCommand) {
|
|
91
|
-
if ('env' in source) {
|
|
92
|
-
// Object.hasOwn defends against env names like "__proto__" returning
|
|
93
|
-
// Object.prototype via the prototype chain.
|
|
94
|
-
if (!Object.hasOwn(env, source.env))
|
|
95
|
-
return undefined;
|
|
96
|
-
return env[source.env];
|
|
97
|
-
}
|
|
98
|
-
const argv = toArgv(source.command);
|
|
99
|
-
if (argv.length === 0)
|
|
100
|
-
return undefined;
|
|
101
|
-
const result = runCommand(argv);
|
|
102
|
-
if (!result.success)
|
|
103
|
-
return undefined;
|
|
104
|
-
const trimmed = result.stdout.trim();
|
|
105
|
-
return trimmed.length === 0 ? undefined : trimmed;
|
|
106
|
-
}
|
|
107
|
-
function toArgv(command) {
|
|
108
|
-
if (Array.isArray(command))
|
|
109
|
-
return command;
|
|
110
|
-
// Whitespace-tokenize the string form. Empty segments (from multiple spaces)
|
|
111
|
-
// are filtered. Shell operators ('|', '&&', etc.) become literal argv, NOT
|
|
112
|
-
// pipes — see design §6.1.
|
|
113
|
-
return command.split(/\s+/).filter((s) => s.length > 0);
|
|
114
|
-
}
|
|
115
|
-
//# sourceMappingURL=resolve-token.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-token.js","sourceRoot":"","sources":["../../src/link-auth/resolve-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public API for the linkAuth pure engine.
|
|
3
|
-
*
|
|
4
|
-
* `resolveAuthenticatedUrl(url, config)` is the single entry point per design
|
|
5
|
-
* §6: select the first provider whose `match.host` claims the URL, run its
|
|
6
|
-
* rewrite pipeline, resolve a token, build the auth headers, and return
|
|
7
|
-
* everything the caller needs to issue an authenticated fetch.
|
|
8
|
-
*
|
|
9
|
-
* Three outcomes:
|
|
10
|
-
* - `{ fetchUrl, headers }` — ready to fetch (provider claimed,
|
|
11
|
-
* rewrite matched, token resolved)
|
|
12
|
-
* - `{ outcome: 'unsupported' }` — no provider claims the host, OR
|
|
13
|
-
* host matched but no rewrite did
|
|
14
|
-
* (per §4: "the provider does not
|
|
15
|
-
* claim the URL for rewriting")
|
|
16
|
-
* - `{ outcome: 'unverified', reason }` — claimed and rewrote, but no token
|
|
17
|
-
* source resolved a non-empty value
|
|
18
|
-
*
|
|
19
|
-
* Per design issue #113 §6.
|
|
20
|
-
*/
|
|
21
|
-
import { type TokenResolutionDeps, type TokenSource } from './resolve-token.js';
|
|
22
|
-
import { type RewriteRule } from './rewrite.js';
|
|
23
|
-
import { type ProviderMatch } from './select-provider.js';
|
|
24
|
-
export interface ProviderAuth {
|
|
25
|
-
readonly headers: Record<string, string>;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Optional content-fetch header overrides (design issue #113 §6.2).
|
|
29
|
-
*
|
|
30
|
-
* Health-check and content retrieval often need different `Accept` (or other)
|
|
31
|
-
* headers. The canonical example: GitHub's `application/vnd.github+json`
|
|
32
|
-
* returns 200 for any size but omits bytes >1 MiB, while
|
|
33
|
-
* `application/vnd.github.raw` streams the bytes inline. The provider declares
|
|
34
|
-
* `auth.headers` for health-check and an optional `fetch.headers` for content
|
|
35
|
-
* retrieval. Both are templated against the same context (URL captures + token).
|
|
36
|
-
*/
|
|
37
|
-
export interface ProviderFetch {
|
|
38
|
-
readonly headers: Record<string, string>;
|
|
39
|
-
}
|
|
40
|
-
export interface ProviderCheck {
|
|
41
|
-
readonly method: 'GET' | 'HEAD';
|
|
42
|
-
readonly aliveStatus: readonly number[];
|
|
43
|
-
readonly notFoundMeaning: 'ambiguous' | 'dead';
|
|
44
|
-
}
|
|
45
|
-
export interface Provider {
|
|
46
|
-
readonly match: ProviderMatch;
|
|
47
|
-
readonly rewrite: readonly RewriteRule[];
|
|
48
|
-
readonly auth: ProviderAuth;
|
|
49
|
-
/**
|
|
50
|
-
* Optional — present when a provider needs different headers for content
|
|
51
|
-
* retrieval than for health-check. Absent for hosts where one header set
|
|
52
|
-
* does both jobs.
|
|
53
|
-
*/
|
|
54
|
-
readonly fetch?: ProviderFetch;
|
|
55
|
-
readonly token: readonly TokenSource[];
|
|
56
|
-
readonly check: ProviderCheck;
|
|
57
|
-
}
|
|
58
|
-
export interface LinkAuthConfig {
|
|
59
|
-
readonly providers: readonly Provider[];
|
|
60
|
-
/**
|
|
61
|
-
* Optional content-cache config (consumed by the slice-3 content-fetch
|
|
62
|
-
* primitive, not by the engine itself). The engine stays stateless; this
|
|
63
|
-
* field rides along on the config object so the primitive doesn't need a
|
|
64
|
-
* second source of truth.
|
|
65
|
-
*/
|
|
66
|
-
readonly cache?: {
|
|
67
|
-
readonly ttlMinutes?: number;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
export type ResolveOutcome = {
|
|
71
|
-
readonly fetchUrl: string;
|
|
72
|
-
readonly headers: Record<string, string>;
|
|
73
|
-
/**
|
|
74
|
-
* Expanded fetch-mode headers, only present when the provider declared
|
|
75
|
-
* a `fetch` block. Templated against the same context as `headers`
|
|
76
|
-
* (URL captures + resolved token), so callers do not need to re-resolve
|
|
77
|
-
* the token to send these. Per §6.2 — content-fetch consumers send
|
|
78
|
-
* these instead of (or merged over) `headers` for the request body.
|
|
79
|
-
*/
|
|
80
|
-
readonly fetchHeaders?: Record<string, string>;
|
|
81
|
-
/**
|
|
82
|
-
* The matched provider's `check` block, passed through so the post-fetch
|
|
83
|
-
* classifier (in `packages/resources`) can route status codes to outcomes
|
|
84
|
-
* without re-running `selectProvider`. Reading this from the engine —
|
|
85
|
-
* rather than asking the validator to re-derive it — keeps the
|
|
86
|
-
* provider-match decision in exactly one place.
|
|
87
|
-
*/
|
|
88
|
-
readonly check: ProviderCheck;
|
|
89
|
-
} | {
|
|
90
|
-
readonly outcome: 'unsupported';
|
|
91
|
-
} | {
|
|
92
|
-
readonly outcome: 'unverified';
|
|
93
|
-
readonly reason: string;
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* Resolve an authenticated fetch plan for `url` against the configured providers.
|
|
97
|
-
*
|
|
98
|
-
* @param deps - Optional dependency injection for token resolution (`env` map
|
|
99
|
-
* + `runCommand`). Production callers omit this; tests supply mocks.
|
|
100
|
-
*/
|
|
101
|
-
export declare function resolveAuthenticatedUrl(url: string, config: LinkAuthConfig, deps?: Partial<TokenResolutionDeps>): ResolveOutcome;
|
|
102
|
-
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/link-auth/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,KAAK,aAAa,EAAkB,MAAM,sBAAsB,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,WAAW,GAAG,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH;AAED,MAAM,MAAM,cAAc,GACtB;IACE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC/B,GACD;IAAE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GACnC;IAAE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAClC,cAAc,CAqChB"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public API for the linkAuth pure engine.
|
|
3
|
-
*
|
|
4
|
-
* `resolveAuthenticatedUrl(url, config)` is the single entry point per design
|
|
5
|
-
* §6: select the first provider whose `match.host` claims the URL, run its
|
|
6
|
-
* rewrite pipeline, resolve a token, build the auth headers, and return
|
|
7
|
-
* everything the caller needs to issue an authenticated fetch.
|
|
8
|
-
*
|
|
9
|
-
* Three outcomes:
|
|
10
|
-
* - `{ fetchUrl, headers }` — ready to fetch (provider claimed,
|
|
11
|
-
* rewrite matched, token resolved)
|
|
12
|
-
* - `{ outcome: 'unsupported' }` — no provider claims the host, OR
|
|
13
|
-
* host matched but no rewrite did
|
|
14
|
-
* (per §4: "the provider does not
|
|
15
|
-
* claim the URL for rewriting")
|
|
16
|
-
* - `{ outcome: 'unverified', reason }` — claimed and rewrote, but no token
|
|
17
|
-
* source resolved a non-empty value
|
|
18
|
-
*
|
|
19
|
-
* Per design issue #113 §6.
|
|
20
|
-
*/
|
|
21
|
-
import { buildHeaders } from './build-headers.js';
|
|
22
|
-
import { resolveToken, } from './resolve-token.js';
|
|
23
|
-
import { rewriteUrl } from './rewrite.js';
|
|
24
|
-
import { selectProvider } from './select-provider.js';
|
|
25
|
-
/**
|
|
26
|
-
* Resolve an authenticated fetch plan for `url` against the configured providers.
|
|
27
|
-
*
|
|
28
|
-
* @param deps - Optional dependency injection for token resolution (`env` map
|
|
29
|
-
* + `runCommand`). Production callers omit this; tests supply mocks.
|
|
30
|
-
*/
|
|
31
|
-
export function resolveAuthenticatedUrl(url, config, deps) {
|
|
32
|
-
const provider = selectProvider(url, config.providers);
|
|
33
|
-
if (provider === undefined)
|
|
34
|
-
return { outcome: 'unsupported' };
|
|
35
|
-
const rewrite = rewriteUrl(url, provider.rewrite);
|
|
36
|
-
if (!rewrite.matched)
|
|
37
|
-
return { outcome: 'unsupported' };
|
|
38
|
-
const token = resolveToken(provider.token, deps);
|
|
39
|
-
// eslint-disable-next-line security/detect-possible-timing-attacks -- compare to undefined sentinel, not secret content
|
|
40
|
-
if (token === undefined) {
|
|
41
|
-
return {
|
|
42
|
-
outcome: 'unverified',
|
|
43
|
-
reason: 'No token source resolved a non-empty value — configure `token` or log in.',
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
// Headers see captures + vars + the resolved token. The resolved token wins
|
|
47
|
-
// over any regex capture named "token" (later in Object.assign wins), so
|
|
48
|
-
// URL-derived data never leaks into Authorization values.
|
|
49
|
-
const headerContext = Object.create(null);
|
|
50
|
-
Object.assign(headerContext, rewrite.context);
|
|
51
|
-
headerContext['token'] = token;
|
|
52
|
-
const headers = buildHeaders(provider.auth.headers, headerContext);
|
|
53
|
-
// Expand fetch.headers against the same context so the resolved token wins
|
|
54
|
-
// over any URL-captured "token" group here too — the precedence discipline
|
|
55
|
-
// applies to both header sets, not just auth.headers.
|
|
56
|
-
const fetchHeaders = provider.fetch === undefined
|
|
57
|
-
? undefined
|
|
58
|
-
: buildHeaders(provider.fetch.headers, headerContext);
|
|
59
|
-
return {
|
|
60
|
-
fetchUrl: rewrite.rewrittenUrl,
|
|
61
|
-
headers,
|
|
62
|
-
...(fetchHeaders === undefined ? {} : { fetchHeaders }),
|
|
63
|
-
check: provider.check,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=resolve.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/link-auth/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EACL,YAAY,GAGb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAoB,UAAU,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA6E1E;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAAW,EACX,MAAsB,EACtB,IAAmC;IAEnC,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACvD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAE9D,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAExD,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjD,wHAAwH;IACxH,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,2EAA2E;SACpF,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,yEAAyE;IACzE,0DAA0D;IAC1D,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA2B,CAAC;IACpE,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,aAAa,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;IAE/B,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACnE,2EAA2E;IAC3E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAM,YAAY,GAChB,QAAQ,CAAC,KAAK,KAAK,SAAS;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC1D,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,YAAY;QAC9B,OAAO;QACP,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;QACvD,KAAK,EAAE,QAAQ,CAAC,KAAK;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rewrite pipeline: ordered when → vars → to.
|
|
3
|
-
*
|
|
4
|
-
* Each provider declares one or more rewrite rules. For an input URL the
|
|
5
|
-
* pipeline strips `?query` and `#fragment`, finds the first rule whose
|
|
6
|
-
* `when` regex matches, computes `vars` (templates over captures), and
|
|
7
|
-
* renders the `to` template against captures + vars merged. The merged
|
|
8
|
-
* context flows out so downstream header templates can interpolate the
|
|
9
|
-
* same variables.
|
|
10
|
-
*
|
|
11
|
-
* Per design issue #113 §4 (vocabulary) and §5.2 (fragment/query
|
|
12
|
-
* stripping must precede a greedy `(?<path>.+)` capture).
|
|
13
|
-
*/
|
|
14
|
-
export interface RewriteRule {
|
|
15
|
-
readonly when: string;
|
|
16
|
-
readonly vars?: Record<string, string>;
|
|
17
|
-
readonly to: string;
|
|
18
|
-
}
|
|
19
|
-
export type RewriteOutcome = {
|
|
20
|
-
readonly matched: true;
|
|
21
|
-
readonly rewrittenUrl: string;
|
|
22
|
-
readonly context: Record<string, string>;
|
|
23
|
-
} | {
|
|
24
|
-
readonly matched: false;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Thrown when a rule's `when` field is not a compilable regex.
|
|
28
|
-
*/
|
|
29
|
-
export declare class InvalidRewriteRuleError extends Error {
|
|
30
|
-
constructor(pattern: string, cause: unknown);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Thrown when a `vars` key collides with a named capture in `when`.
|
|
34
|
-
* Adopter must rename one — vars cannot shadow captures (the design's
|
|
35
|
-
* intent is captures + vars in a single namespace).
|
|
36
|
-
*/
|
|
37
|
-
export declare class VarCaptureCollisionError extends Error {
|
|
38
|
-
constructor(name: string);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Apply an ordered list of rewrite rules to a URL.
|
|
42
|
-
*
|
|
43
|
-
* @returns `{ matched: true, rewrittenUrl, context }` for the first matching
|
|
44
|
-
* rule, or `{ matched: false }` if no rule claims the URL.
|
|
45
|
-
* @throws {InvalidRewriteRuleError} if a `when` field does not compile
|
|
46
|
-
* @throws {VarCaptureCollisionError} if a vars name shadows a capture name
|
|
47
|
-
* @throws {TemplateMissingVarError} from a template referencing an unknown name
|
|
48
|
-
* @throws {TemplateSyntaxError} from a malformed template expression
|
|
49
|
-
* @throws {UnknownTransformError} from a template calling an unknown transform
|
|
50
|
-
*/
|
|
51
|
-
export declare function rewriteUrl(url: string, rules: readonly RewriteRule[]): RewriteOutcome;
|
|
52
|
-
//# sourceMappingURL=rewrite.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../../src/link-auth/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GACtB;IACE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,GACD;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC;AAEhC;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAK5C;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,IAAI,EAAE,MAAM;CAMzB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,cAAc,CAerF"}
|