akm-cli 0.5.0 → 0.6.0-rc2
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/CHANGELOG.md +53 -5
- package/README.md +9 -9
- package/dist/cli.js +379 -1448
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/commands/curate.js +263 -0
- package/dist/{info.js → commands/info.js} +17 -11
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +14 -2
- package/dist/{installed-kits.js → commands/installed-stashes.js} +122 -50
- package/dist/commands/migration-help.js +141 -0
- package/dist/{registry-search.js → commands/registry-search.js} +68 -9
- package/dist/commands/remember.js +178 -0
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +3 -3
- package/dist/{stash-show.js → commands/show.js} +106 -81
- package/dist/{stash-add.js → commands/source-add.js} +133 -67
- package/dist/{stash-clone.js → commands/source-clone.js} +15 -13
- package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
- package/dist/{vault.js → commands/vault.js} +43 -0
- package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
- package/dist/{asset-registry.js → core/asset-registry.js} +30 -6
- package/dist/{asset-spec.js → core/asset-spec.js} +13 -6
- package/dist/{common.js → core/common.js} +147 -50
- package/dist/{config.js → core/config.js} +288 -29
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +64 -8
- package/dist/{paths.js → core/paths.js} +4 -4
- package/dist/core/write-source.js +280 -0
- package/dist/{local-search.js → indexer/db-search.js} +49 -32
- package/dist/{db.js → indexer/db.js} +210 -81
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +153 -30
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +4 -7
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +97 -55
- package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
- package/dist/{walker.js → indexer/walker.js} +1 -1
- package/dist/{lockfile.js → integrations/lockfile.js} +29 -2
- package/dist/{llm.js → llm/client.js} +12 -48
- package/dist/llm/embedder.js +127 -0
- package/dist/llm/embedders/cache.js +47 -0
- package/dist/llm/embedders/local.js +152 -0
- package/dist/llm/embedders/remote.js +121 -0
- package/dist/llm/embedders/types.js +39 -0
- package/dist/llm/metadata-enhance.js +53 -0
- package/dist/output/cli-hints.js +301 -0
- package/dist/output/context.js +95 -0
- package/dist/{renderers.js → output/renderers.js} +57 -61
- package/dist/output/shapes.js +212 -0
- package/dist/output/text.js +520 -0
- package/dist/{registry-build-index.js → registry/build-index.js} +48 -32
- package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
- package/dist/registry/factory.js +33 -0
- package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
- package/dist/registry/providers/index.js +11 -0
- package/dist/{providers → registry/providers}/skills-sh.js +60 -4
- package/dist/{providers → registry/providers}/static-index.js +126 -56
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +10 -6
- package/dist/{detect.js → setup/detect.js} +0 -27
- package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
- package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
- package/dist/{setup.js → setup/setup.js} +162 -129
- package/dist/setup/steps.js +45 -0
- package/dist/{kit-include.js → sources/include.js} +1 -1
- package/dist/sources/provider-factory.js +36 -0
- package/dist/sources/provider.js +21 -0
- package/dist/sources/providers/filesystem.js +35 -0
- package/dist/{stash-providers → sources/providers}/git.js +218 -28
- package/dist/{stash-providers → sources/providers}/index.js +4 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/sources/providers/npm.js +160 -0
- package/dist/sources/providers/provider-utils.js +173 -0
- package/dist/sources/providers/sync-from-ref.js +45 -0
- package/dist/sources/providers/tar-utils.js +154 -0
- package/dist/{stash-providers → sources/providers}/website.js +60 -20
- package/dist/{stash-resolve.js → sources/resolve.js} +13 -12
- package/dist/{wiki.js → wiki/wiki.js} +18 -17
- package/dist/{workflow-authoring.js → workflows/authoring.js} +48 -17
- package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
- package/dist/{workflow-db.js → workflows/db.js} +1 -1
- package/dist/workflows/document-cache.js +20 -0
- package/dist/workflows/parser.js +379 -0
- package/dist/workflows/renderer.js +78 -0
- package/dist/{workflow-runs.js → workflows/runs.js} +84 -30
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- package/docs/README.md +30 -0
- package/docs/migration/release-notes/0.0.13.md +4 -0
- package/docs/migration/release-notes/0.1.0.md +6 -0
- package/docs/migration/release-notes/0.2.0.md +6 -0
- package/docs/migration/release-notes/0.3.0.md +5 -0
- package/docs/migration/release-notes/0.5.0.md +6 -0
- package/docs/migration/release-notes/0.6.0.md +75 -0
- package/docs/migration/release-notes/README.md +21 -0
- package/package.json +3 -2
- package/dist/embedder.js +0 -351
- package/dist/errors.js +0 -34
- package/dist/migration-help.js +0 -110
- package/dist/registry-factory.js +0 -19
- package/dist/registry-install.js +0 -532
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -1
- package/dist/stash-providers/filesystem.js +0 -41
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-providers/provider-utils.js +0 -11
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -251
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{warn.js → core/warn.js} +0 -0
- /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
- /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
- /package/dist/{github.js → integrations/github.js} +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /package/dist/{registry-types.js → sources/types.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { buildWorkflowAction } from "../output/renderers";
|
|
2
3
|
import { registerActionBuilder, registerTypeRenderer } from "./asset-registry";
|
|
3
4
|
import { toPosix } from "./common";
|
|
4
|
-
import { buildWorkflowAction } from "./renderers";
|
|
5
5
|
const markdownSpec = {
|
|
6
6
|
isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".md",
|
|
7
7
|
toCanonicalName: (typeRoot, filePath) => {
|
|
@@ -129,8 +129,6 @@ export const ASSET_SPECS = ASSET_SPECS_INTERNAL;
|
|
|
129
129
|
export function registerAssetType(type, spec) {
|
|
130
130
|
ASSET_SPECS_INTERNAL[type] = spec;
|
|
131
131
|
TYPE_DIRS[type] = spec.stashDir;
|
|
132
|
-
ASSET_TYPES.length = 0;
|
|
133
|
-
ASSET_TYPES.push(...getAssetTypes());
|
|
134
132
|
// Auto-register renderer and action builder if provided in spec
|
|
135
133
|
if (spec.rendererName) {
|
|
136
134
|
registerTypeRenderer(type, spec.rendererName);
|
|
@@ -139,11 +137,20 @@ export function registerAssetType(type, spec) {
|
|
|
139
137
|
registerActionBuilder(type, spec.actionBuilder);
|
|
140
138
|
}
|
|
141
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Remove a previously-registered asset type.
|
|
142
|
+
*
|
|
143
|
+
* Primarily used by tests for cleanup after `registerAssetType` calls so
|
|
144
|
+
* subsequent tests see a pristine type registry. Built-in types should not
|
|
145
|
+
* normally be deregistered at runtime.
|
|
146
|
+
*/
|
|
147
|
+
export function deregisterAssetType(type) {
|
|
148
|
+
delete ASSET_SPECS_INTERNAL[type];
|
|
149
|
+
delete TYPE_DIRS[type];
|
|
150
|
+
}
|
|
142
151
|
export function getAssetTypes() {
|
|
143
152
|
return Object.keys(ASSET_SPECS_INTERNAL);
|
|
144
153
|
}
|
|
145
|
-
/** Warning: mutable array — stale if captured before `registerAssetType()` calls. Prefer `getAssetTypes()`. */
|
|
146
|
-
export const ASSET_TYPES = getAssetTypes();
|
|
147
154
|
export const TYPE_DIRS = Object.fromEntries(Object.entries(ASSET_SPECS_INTERNAL).map(([type, spec]) => [type, spec.stashDir]));
|
|
148
155
|
export function isRelevantAssetFile(assetType, fileName) {
|
|
149
156
|
return ASSET_SPECS[assetType]?.isRelevantFile(fileName) ?? false;
|
|
@@ -158,7 +165,7 @@ export function deriveCanonicalAssetNameFromStashRoot(assetType, stashRoot, file
|
|
|
158
165
|
// When the first segment matches the canonical type dir (e.g. "agents"),
|
|
159
166
|
// use it as the type root so canonical names are relative to it.
|
|
160
167
|
// Otherwise fall back to stashRoot — this preserves the full relative path
|
|
161
|
-
// as the canonical name, which is correct for installed
|
|
168
|
+
// as the canonical name, which is correct for installed stashes that live
|
|
162
169
|
// under custom directories (e.g. "tools/agents/svelte-file-editor").
|
|
163
170
|
const typeRoot = firstSegment === TYPE_DIRS[assetType] ? path.join(stashRoot, firstSegment) : stashRoot;
|
|
164
171
|
return deriveCanonicalAssetName(assetType, typeRoot, filePath);
|
|
@@ -24,21 +24,17 @@ export function isAssetType(type) {
|
|
|
24
24
|
* 2. stashDir field in config.json
|
|
25
25
|
* 3. Platform default (~/akm or ~/Documents/akm on Windows)
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* path into config.json on disk.
|
|
27
|
+
* Pure read: never writes to disk. The legacy `readOnly` option is accepted
|
|
28
|
+
* (and ignored) for one release cycle so older callers continue to compile;
|
|
29
|
+
* it can be removed in the next minor bump.
|
|
31
30
|
*
|
|
32
31
|
* Throws if no valid stash directory is found.
|
|
33
32
|
*/
|
|
34
|
-
export function resolveStashDir(
|
|
33
|
+
export function resolveStashDir(_options) {
|
|
35
34
|
// 1. Env var override (for CI, scripts, testing)
|
|
36
35
|
const envDir = process.env.AKM_STASH_DIR?.trim();
|
|
37
36
|
if (envDir) {
|
|
38
|
-
|
|
39
|
-
if (!options?.readOnly)
|
|
40
|
-
persistStashDirToConfig(resolved);
|
|
41
|
-
return resolved;
|
|
37
|
+
return validateStashDir(envDir);
|
|
42
38
|
}
|
|
43
39
|
// 2. Config file stashDir field
|
|
44
40
|
const configStashDir = readStashDirFromConfig();
|
|
@@ -50,7 +46,7 @@ export function resolveStashDir(options) {
|
|
|
50
46
|
return defaultDir;
|
|
51
47
|
}
|
|
52
48
|
throw new ConfigError(`No stash directory found. Run "akm init" to create one at ${defaultDir}, ` +
|
|
53
|
-
`or set stashDir in ${getConfigPath()}
|
|
49
|
+
`or set stashDir in ${getConfigPath()}.`, "STASH_DIR_NOT_FOUND");
|
|
54
50
|
}
|
|
55
51
|
function validateStashDir(raw) {
|
|
56
52
|
const stashDir = path.resolve(raw);
|
|
@@ -59,10 +55,10 @@ function validateStashDir(raw) {
|
|
|
59
55
|
stat = fs.statSync(stashDir);
|
|
60
56
|
}
|
|
61
57
|
catch {
|
|
62
|
-
throw new ConfigError(`Unable to read stash directory at "${stashDir}"
|
|
58
|
+
throw new ConfigError(`Unable to read stash directory at "${stashDir}".`, "STASH_DIR_UNREADABLE");
|
|
63
59
|
}
|
|
64
60
|
if (!stat.isDirectory()) {
|
|
65
|
-
throw new ConfigError(`Stash path must point to a directory: "${stashDir}"
|
|
61
|
+
throw new ConfigError(`Stash path must point to a directory: "${stashDir}".`, "STASH_DIR_NOT_A_DIRECTORY");
|
|
66
62
|
}
|
|
67
63
|
return stashDir;
|
|
68
64
|
}
|
|
@@ -92,42 +88,6 @@ function readStashDirFromConfig() {
|
|
|
92
88
|
}
|
|
93
89
|
return undefined;
|
|
94
90
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Persist stashDir to config.json if not already set, so users can
|
|
97
|
-
* transition away from relying on the AKM_STASH_DIR env var.
|
|
98
|
-
*
|
|
99
|
-
* WARNING: This function writes to disk (config.json). It is called as a side
|
|
100
|
-
* effect of `resolveStashDir()` when AKM_STASH_DIR is set and `readOnly` is
|
|
101
|
-
* not true. Callers that must not touch the filesystem should pass
|
|
102
|
-
* `{ readOnly: true }` to `resolveStashDir()`.
|
|
103
|
-
*/
|
|
104
|
-
function persistStashDirToConfig(stashDir) {
|
|
105
|
-
try {
|
|
106
|
-
const configPath = getConfigPath();
|
|
107
|
-
let raw = {};
|
|
108
|
-
try {
|
|
109
|
-
const text = fs.readFileSync(configPath, "utf8");
|
|
110
|
-
const parsed = JSON.parse(text);
|
|
111
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
112
|
-
raw = parsed;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
// No existing config or invalid — start fresh
|
|
117
|
-
}
|
|
118
|
-
if (!raw.stashDir) {
|
|
119
|
-
raw.stashDir = stashDir;
|
|
120
|
-
const dir = path.dirname(configPath);
|
|
121
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
122
|
-
const tmpPath = `${configPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
123
|
-
fs.writeFileSync(tmpPath, `${JSON.stringify(raw, null, 2)}\n`, "utf8");
|
|
124
|
-
fs.renameSync(tmpPath, configPath);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
// Non-fatal: best-effort persistence
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
91
|
export function toPosix(input) {
|
|
132
92
|
return input.replace(/\\/g, "/");
|
|
133
93
|
}
|
|
@@ -144,12 +104,39 @@ export function isWithin(candidate, root) {
|
|
|
144
104
|
const rel = path.relative(normalizedRoot, normalizedCandidate);
|
|
145
105
|
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
146
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve symlinks on `p`, walking up to the closest existing ancestor when
|
|
109
|
+
* `p` itself does not exist. This ensures that comparisons between an
|
|
110
|
+
* existing directory and a not-yet-created child path inside it are
|
|
111
|
+
* consistent even when the directory hierarchy contains symlinks (e.g.
|
|
112
|
+
* macOS /tmp → /private/tmp, or a HOME that is itself a symlink).
|
|
113
|
+
*/
|
|
147
114
|
function safeRealpath(p) {
|
|
115
|
+
const resolved = path.resolve(p);
|
|
148
116
|
try {
|
|
149
|
-
return fs.realpathSync(
|
|
117
|
+
return fs.realpathSync(resolved);
|
|
150
118
|
}
|
|
151
119
|
catch {
|
|
152
|
-
|
|
120
|
+
// Path doesn't exist — resolve symlinks on the nearest existing ancestor
|
|
121
|
+
// and reconstruct the full path from there.
|
|
122
|
+
const suffix = [];
|
|
123
|
+
let current = resolved;
|
|
124
|
+
for (;;) {
|
|
125
|
+
const parent = path.dirname(current);
|
|
126
|
+
if (parent === current) {
|
|
127
|
+
// Reached filesystem root without finding an existing entry.
|
|
128
|
+
return resolved;
|
|
129
|
+
}
|
|
130
|
+
suffix.unshift(path.basename(current));
|
|
131
|
+
current = parent;
|
|
132
|
+
try {
|
|
133
|
+
const realParent = fs.realpathSync(current);
|
|
134
|
+
return path.join(realParent, ...suffix);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// parent also doesn't exist; keep walking up
|
|
138
|
+
}
|
|
139
|
+
}
|
|
153
140
|
}
|
|
154
141
|
}
|
|
155
142
|
function normalizeFsPathForComparison(value) {
|
|
@@ -207,6 +194,116 @@ export async function fetchWithRetry(url, init, options) {
|
|
|
207
194
|
function shouldRetry(status) {
|
|
208
195
|
return status === 429 || status >= 500;
|
|
209
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Read stdin as UTF-8 text if something is piped in. Returns `undefined`
|
|
199
|
+
* when stdin is a TTY (no pipe) or when the piped content is empty.
|
|
200
|
+
*/
|
|
201
|
+
export function tryReadStdinText() {
|
|
202
|
+
if (process.stdin.isTTY)
|
|
203
|
+
return undefined;
|
|
204
|
+
const input = fs.readFileSync(0, "utf8");
|
|
205
|
+
return input.length > 0 ? input : undefined;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Default byte cap for untrusted network responses (10 MB).
|
|
209
|
+
*
|
|
210
|
+
* Applies to website scraping, registry index fetches, and any other
|
|
211
|
+
* response that is read into memory from a source the CLI does not fully
|
|
212
|
+
* control. A compromised or malicious endpoint that streams an unbounded
|
|
213
|
+
* response would otherwise exhaust RAM — this cap ensures the process
|
|
214
|
+
* aborts with a clean error instead of crashing.
|
|
215
|
+
*/
|
|
216
|
+
export const DEFAULT_RESPONSE_BYTE_CAP = 10 * 1024 * 1024;
|
|
217
|
+
/**
|
|
218
|
+
* Thrown by {@link readBodyWithByteCap} and its helpers when a response
|
|
219
|
+
* body exceeds the caller's byte cap. Callers can catch this specifically
|
|
220
|
+
* to surface a targeted error to the user.
|
|
221
|
+
*/
|
|
222
|
+
export class ResponseTooLargeError extends Error {
|
|
223
|
+
url;
|
|
224
|
+
maxBytes;
|
|
225
|
+
observedBytes;
|
|
226
|
+
constructor(url, maxBytes, observedBytes) {
|
|
227
|
+
const observed = observedBytes === null ? "unknown" : `${observedBytes} bytes`;
|
|
228
|
+
super(`Response body exceeded ${maxBytes} bytes (observed: ${observed}): ${url}`);
|
|
229
|
+
this.name = "ResponseTooLargeError";
|
|
230
|
+
this.url = url;
|
|
231
|
+
this.maxBytes = maxBytes;
|
|
232
|
+
this.observedBytes = observedBytes;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Read a Response body as a UTF-8 string with a byte-count cap.
|
|
237
|
+
*
|
|
238
|
+
* Streams the body so we abort as soon as the cap is exceeded, without
|
|
239
|
+
* buffering the full response first. If the server sent a
|
|
240
|
+
* `Content-Length` larger than the cap, we refuse before reading any
|
|
241
|
+
* bytes. `response.body` is consumed and cancelled on cap breach.
|
|
242
|
+
*
|
|
243
|
+
* `maxBytes` defaults to {@link DEFAULT_RESPONSE_BYTE_CAP} (10 MB).
|
|
244
|
+
*/
|
|
245
|
+
export async function readBodyWithByteCap(response, maxBytes = DEFAULT_RESPONSE_BYTE_CAP) {
|
|
246
|
+
const url = response.url || "(unknown URL)";
|
|
247
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
248
|
+
if (contentLengthHeader) {
|
|
249
|
+
const declared = Number(contentLengthHeader);
|
|
250
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
251
|
+
// Don't even start reading.
|
|
252
|
+
await response.body?.cancel?.().catch(() => undefined);
|
|
253
|
+
throw new ResponseTooLargeError(url, maxBytes, declared);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const body = response.body;
|
|
257
|
+
if (!body) {
|
|
258
|
+
// No streaming body available (e.g., some mock environments). Fall
|
|
259
|
+
// back to text() but still enforce the cap post-hoc.
|
|
260
|
+
const text = await response.text();
|
|
261
|
+
if (text.length > maxBytes)
|
|
262
|
+
throw new ResponseTooLargeError(url, maxBytes, text.length);
|
|
263
|
+
return text;
|
|
264
|
+
}
|
|
265
|
+
const reader = body.getReader();
|
|
266
|
+
const chunks = [];
|
|
267
|
+
let total = 0;
|
|
268
|
+
try {
|
|
269
|
+
while (true) {
|
|
270
|
+
const { done, value } = await reader.read();
|
|
271
|
+
if (done)
|
|
272
|
+
break;
|
|
273
|
+
if (!value)
|
|
274
|
+
continue;
|
|
275
|
+
total += value.byteLength;
|
|
276
|
+
if (total > maxBytes) {
|
|
277
|
+
await reader.cancel().catch(() => undefined);
|
|
278
|
+
throw new ResponseTooLargeError(url, maxBytes, total);
|
|
279
|
+
}
|
|
280
|
+
chunks.push(value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
reader.releaseLock?.();
|
|
285
|
+
}
|
|
286
|
+
if (chunks.length === 0)
|
|
287
|
+
return "";
|
|
288
|
+
if (chunks.length === 1)
|
|
289
|
+
return new TextDecoder().decode(chunks[0]);
|
|
290
|
+
const combined = new Uint8Array(total);
|
|
291
|
+
let offset = 0;
|
|
292
|
+
for (const chunk of chunks) {
|
|
293
|
+
combined.set(chunk, offset);
|
|
294
|
+
offset += chunk.byteLength;
|
|
295
|
+
}
|
|
296
|
+
return new TextDecoder().decode(combined);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Parse a Response body as JSON with a byte-count cap. A cheap wrapper
|
|
300
|
+
* around {@link readBodyWithByteCap}; prefer this for registry index
|
|
301
|
+
* fetches, GitHub API responses, and any other untrusted JSON source.
|
|
302
|
+
*/
|
|
303
|
+
export async function jsonWithByteCap(response, maxBytes = DEFAULT_RESPONSE_BYTE_CAP) {
|
|
304
|
+
const text = await readBodyWithByteCap(response, maxBytes);
|
|
305
|
+
return JSON.parse(text);
|
|
306
|
+
}
|
|
210
307
|
function parseRetryAfter(response) {
|
|
211
308
|
const header = response.headers.get("retry-after");
|
|
212
309
|
if (!header)
|