@hraness/sponge 0.2.6 → 0.3.1
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 -9
- package/bin/sponge.js +8 -7
- package/dist/contracts/index.js +24 -6
- package/dist/contracts/validate.js +106 -29
- package/dist/migrations/008.d.ts +3 -0
- package/dist/migrations/008.js +5 -0
- package/dist/migrations/009.d.ts +3 -0
- package/dist/migrations/009.js +8 -0
- package/dist/migrations/010.d.ts +3 -0
- package/dist/migrations/010.js +11 -0
- package/dist/migrations/index.d.ts +15 -0
- package/dist/migrations/index.js +39 -0
- package/dist/src/acquisition/capture.js +87 -28
- package/dist/src/acquisition/cleanup.d.ts +0 -4
- package/dist/src/acquisition/cleanup.js +3 -5
- package/dist/src/acquisition/errors.js +17 -2
- package/dist/src/acquisition/files.d.ts +0 -1
- package/dist/src/acquisition/files.js +14 -11
- package/dist/src/acquisition/index.d.ts +2 -3
- package/dist/src/acquisition/index.js +1 -2
- package/dist/src/acquisition/lock.js +1 -1
- package/dist/src/acquisition/normalize.js +57 -18
- package/dist/src/acquisition/pins.js +22 -4
- package/dist/src/acquisition/process.js +67 -29
- package/dist/src/acquisition/runtime.d.ts +5 -7
- package/dist/src/acquisition/runtime.js +129 -68
- package/dist/src/acquisition/verify-helper.js +10 -3
- package/dist/src/annotations/quote.js +40 -12
- package/dist/src/annotations/store.d.ts +0 -1
- package/dist/src/annotations/store.js +66 -14
- package/dist/src/app/article.d.ts +2 -1
- package/dist/src/app/article.js +66 -70
- package/dist/src/app/bundle-files.d.ts +0 -4
- package/dist/src/app/bundle-files.js +11 -14
- package/dist/src/app/markdown.js +18 -6
- package/dist/src/app/source.d.ts +0 -4
- package/dist/src/app/source.js +18 -14
- package/dist/src/cli/arguments.d.ts +2 -8
- package/dist/src/cli/arguments.js +67 -80
- package/dist/src/cli/commands.d.ts +42 -0
- package/dist/src/cli/commands.js +795 -0
- package/dist/src/cli/config.js +12 -9
- package/dist/src/cli/digest.d.ts +1 -1
- package/dist/src/cli/digest.js +38 -16
- package/dist/src/cli/import.js +25 -4
- package/dist/src/cli/indexing.d.ts +2 -0
- package/dist/src/cli/indexing.js +26 -9
- package/dist/src/cli/main.js +47 -386
- package/dist/src/cli/portability.d.ts +0 -2
- package/dist/src/cli/portability.js +13 -8
- package/dist/src/cli/queue.js +12 -8
- package/dist/src/cli/runtimes.d.ts +1 -3
- package/dist/src/cli/runtimes.js +13 -6
- package/dist/src/cli/terminal.js +1 -1
- package/dist/src/cli/worker.d.ts +7 -0
- package/dist/src/cli/worker.js +54 -15
- package/dist/src/derivations/kinds.d.ts +34 -0
- package/dist/src/derivations/kinds.js +14 -0
- package/dist/src/derivations/types.d.ts +48 -0
- package/dist/src/derivations/types.js +1 -0
- package/dist/src/digest/derivation.d.ts +25 -0
- package/dist/src/digest/derivation.js +124 -0
- package/dist/src/digest/editorial.d.ts +9 -46
- package/dist/src/digest/editorial.js +57 -49
- package/dist/src/digest/endpoint.js +63 -27
- package/dist/src/digest/pipeline.d.ts +49 -28
- package/dist/src/digest/pipeline.js +369 -102
- package/dist/src/digest/schema.d.ts +11 -25
- package/dist/src/digest/schema.js +86 -18
- package/dist/src/errors.d.ts +46 -0
- package/dist/src/errors.js +117 -0
- package/dist/src/identity/query-policy.d.ts +0 -5
- package/dist/src/identity/query-policy.js +61 -9
- package/dist/src/identity/uri.d.ts +6 -5
- package/dist/src/identity/uri.js +37 -18
- package/dist/src/identity/visibility.d.ts +11 -1
- package/dist/src/identity/visibility.js +81 -17
- package/dist/src/importers/apply.js +104 -36
- package/dist/src/importers/custody.js +27 -9
- package/dist/src/importers/inspect.js +125 -38
- package/dist/src/importers/jungle-digest.d.ts +3 -6
- package/dist/src/importers/jungle-digest.js +196 -69
- package/dist/src/importers/jungle-store.d.ts +19 -6
- package/dist/src/importers/jungle-store.js +124 -28
- package/dist/src/importers/jungle.d.ts +14 -14
- package/dist/src/importers/jungle.js +142 -31
- package/dist/src/importers/manifest.js +57 -21
- package/dist/src/importers/verify-helper.js +18 -4
- package/dist/src/indexing/image.js +29 -10
- package/dist/src/indexing/index.d.ts +37 -6
- package/dist/src/indexing/index.js +360 -124
- package/dist/src/indexing/jobs.js +156 -47
- package/dist/src/indexing/prune-run.d.ts +2 -0
- package/dist/src/indexing/prune-run.js +69 -26
- package/dist/src/indexing/prune.d.ts +12 -3
- package/dist/src/indexing/prune.js +85 -35
- package/dist/src/indexing/runner.d.ts +2 -1
- package/dist/src/indexing/runner.js +83 -40
- package/dist/src/indexing/upgrade.d.ts +9 -0
- package/dist/src/indexing/upgrade.js +79 -0
- package/dist/src/inference/asr/bridge.js +6 -2
- package/dist/src/inference/asr/protocol.js +109 -19
- package/dist/src/inference/asr/runtime.js +58 -21
- package/dist/src/inference/asr/windows.js +9 -5
- package/dist/src/inference/embedding/index.d.ts +2 -6
- package/dist/src/inference/embedding/index.js +89 -29
- package/dist/src/ingest/attempt.d.ts +29 -0
- package/dist/src/ingest/attempt.js +55 -0
- package/dist/src/media/capture-helper.js +7 -2
- package/dist/src/media/capture.d.ts +2 -1
- package/dist/src/media/capture.js +124 -43
- package/dist/src/media/handler.d.ts +4 -2
- package/dist/src/media/handler.js +88 -70
- package/dist/src/media/transcribe.js +63 -21
- package/dist/src/metadata/article.js +49 -17
- package/dist/src/models/artifacts.d.ts +10 -8
- package/dist/src/models/artifacts.js +237 -106
- package/dist/src/models/catalog.d.ts +2 -5
- package/dist/src/models/catalog.js +94 -25
- package/dist/src/models/download.js +55 -15
- package/dist/src/models/runtime.d.ts +0 -2
- package/dist/src/models/runtime.js +69 -33
- package/dist/src/pdf/capture-helper.js +3 -1
- package/dist/src/pdf/capture.d.ts +2 -1
- package/dist/src/pdf/capture.js +95 -27
- package/dist/src/pdf/errors.js +12 -1
- package/dist/src/pdf/handler.d.ts +2 -1
- package/dist/src/pdf/handler.js +70 -71
- package/dist/src/pdf/index.d.ts +1 -3
- package/dist/src/pdf/index.js +0 -2
- package/dist/src/pdf/input.js +12 -7
- package/dist/src/pdf/normalize.js +80 -21
- package/dist/src/pdf/source-identity.js +3 -2
- package/dist/src/pdf/source.js +12 -8
- package/dist/src/pdf/verify-helper.js +15 -3
- package/dist/src/portability/backup.d.ts +2 -9
- package/dist/src/portability/backup.js +106 -22
- package/dist/src/portability/database.js +51 -17
- package/dist/src/portability/export.js +35 -9
- package/dist/src/portability/files.d.ts +1 -4
- package/dist/src/portability/files.js +127 -67
- package/dist/src/portability/gc.js +65 -22
- package/dist/src/publish/component.js +1 -1
- package/dist/src/publish/feed.d.ts +2 -2
- package/dist/src/publish/feed.js +75 -29
- package/dist/src/publish/identity.d.ts +5 -1
- package/dist/src/publish/identity.js +31 -11
- package/dist/src/publish/index.d.ts +2 -3
- package/dist/src/publish/index.js +34 -21
- package/dist/src/publish/projection.d.ts +2 -9
- package/dist/src/publish/projection.js +21 -14
- package/dist/src/search/extension.js +3 -1
- package/dist/src/search/hybrid.d.ts +3 -8
- package/dist/src/search/hybrid.js +45 -11
- package/dist/src/search/lexical.js +18 -4
- package/dist/src/search/query.js +56 -25
- package/dist/src/search/vector.js +37 -10
- package/dist/src/service/launch-agent.d.ts +5 -3
- package/dist/src/service/launch-agent.js +272 -83
- package/dist/src/service/lifetime.js +47 -14
- package/dist/src/service/loop.d.ts +3 -3
- package/dist/src/service/loop.js +47 -19
- package/dist/src/store/annotations.d.ts +32 -0
- package/dist/src/store/annotations.js +44 -0
- package/dist/src/store/backups.d.ts +18 -0
- package/dist/src/store/backups.js +110 -0
- package/dist/src/store/blobs.d.ts +24 -0
- package/dist/src/store/blobs.js +139 -0
- package/dist/src/store/changes.d.ts +5 -12
- package/dist/src/store/changes.js +108 -56
- package/dist/src/store/context.d.ts +15 -0
- package/dist/src/store/context.js +19 -0
- package/dist/src/store/derivations.d.ts +42 -0
- package/dist/src/store/derivations.js +104 -0
- package/dist/src/store/files.d.ts +0 -2
- package/dist/src/store/files.js +19 -24
- package/dist/src/store/ids.d.ts +30 -0
- package/dist/src/store/ids.js +34 -0
- package/dist/src/store/imports.d.ts +26 -0
- package/dist/src/store/imports.js +57 -0
- package/dist/src/store/index.d.ts +3 -186
- package/dist/src/store/index.js +2 -809
- package/dist/src/store/lease.d.ts +20 -0
- package/dist/src/store/lease.js +62 -0
- package/dist/src/store/library.d.ts +75 -0
- package/dist/src/store/library.js +174 -0
- package/dist/src/store/open.d.ts +7 -0
- package/dist/src/store/open.js +183 -0
- package/dist/src/store/queue.d.ts +65 -0
- package/dist/src/store/queue.js +327 -0
- package/dist/src/store/revisions.d.ts +12 -0
- package/dist/src/store/revisions.js +189 -0
- package/dist/src/store/search-index.d.ts +63 -0
- package/dist/src/store/search-index.js +190 -0
- package/dist/src/store/sync-ready.d.ts +6 -0
- package/dist/src/store/sync-ready.js +121 -0
- package/dist/src/store/types.d.ts +1 -5
- package/dist/src/store/types.js +1 -8
- package/dist/src/util/canonical.d.ts +2 -0
- package/dist/src/util/canonical.js +17 -0
- package/dist/src/util/exact.d.ts +2 -0
- package/dist/src/util/exact.js +9 -0
- package/dist/src/util/fsync.d.ts +4 -0
- package/dist/src/util/fsync.js +22 -0
- package/dist/src/util/hash.d.ts +4 -0
- package/dist/src/util/hash.js +8 -0
- package/dist/src/worker/basic.d.ts +0 -1
- package/dist/src/worker/basic.js +23 -7
- package/dist/src/worker/custody.d.ts +4 -1
- package/dist/src/worker/custody.js +111 -38
- package/dist/src/worker/engine.d.ts +2 -1
- package/dist/src/worker/engine.js +42 -20
- package/dist/src/worker/identity.js +13 -5
- package/dist/src/worker/journal.d.ts +1 -1
- package/dist/src/worker/journal.js +54 -24
- package/dist/src/worker/lock.d.ts +18 -0
- package/dist/src/worker/lock.js +196 -0
- package/dist/src/worker/runtime-gc.d.ts +4 -3
- package/dist/src/worker/runtime-gc.js +68 -41
- package/dist/src/worker/supervisor-child.js +19 -6
- package/dist/src/worker/supervisor.js +41 -8
- package/native/runtime-manifest.json +4 -4
- package/package.json +10 -2
- package/dist/src/digest/store.d.ts +0 -57
- package/dist/src/digest/store.js +0 -79
- package/dist/src/store/foundation.d.ts +0 -7
- package/dist/src/store/foundation.js +0 -7
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { articleMetadata, articleMetadataWarnings } from '../metadata/article.js';
|
|
2
2
|
import { limits } from '../../contracts/index.js';
|
|
3
3
|
import { validateCapture, validateAsset } from '../../contracts/validate.js';
|
|
4
|
-
import { readConfined
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { readConfined } from './files.js';
|
|
5
|
+
import { sha256 as hash } from '../util/hash.js';
|
|
6
|
+
import { SpongeError } from '../errors.js';
|
|
7
|
+
export function object(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
throw new SpongeError('integrity');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
7
12
|
export function metadataFromMarkdown(markdown, observedAt) {
|
|
8
13
|
if (!markdown.startsWith('---\n'))
|
|
9
14
|
return [];
|
|
10
15
|
const end = markdown.indexOf('\n---', 4);
|
|
11
16
|
if (end < 0 || Buffer.byteLength(markdown.slice(0, end)) > limits.metadataBytes)
|
|
12
|
-
throw
|
|
17
|
+
throw new SpongeError('limit-exceeded');
|
|
13
18
|
const claims = [];
|
|
14
19
|
for (const line of markdown.slice(4, end).split('\n')) {
|
|
15
20
|
const match = /^(title|author|description|published|canonical_url|language): (".*")$/.exec(line);
|
|
@@ -24,39 +29,73 @@ export function metadataFromMarkdown(markdown, observedAt) {
|
|
|
24
29
|
}
|
|
25
30
|
export async function normalizeBundle(input) {
|
|
26
31
|
const verified = object(input.inspection), inspection = object(verified.inspection), document = object(inspection.document), summary = object(input.summary);
|
|
27
|
-
if (verified.ok !== true ||
|
|
28
|
-
|
|
32
|
+
if (verified.ok !== true ||
|
|
33
|
+
inspection.schemaVersion !== 4 ||
|
|
34
|
+
document.integrity !== 'verified' ||
|
|
35
|
+
!Array.isArray(inspection.assets) ||
|
|
36
|
+
inspection.assets.some((a) => object(a).integrity !== 'verified'))
|
|
37
|
+
throw new SpongeError('integrity');
|
|
29
38
|
const manifestBytes = await readConfined(input.bundleRoot, 'capture.json', limits.metadataBytes), manifest = object(JSON.parse(manifestBytes.toString()));
|
|
30
|
-
if (manifest.schemaVersion !== 4 ||
|
|
31
|
-
|
|
39
|
+
if (manifest.schemaVersion !== 4 ||
|
|
40
|
+
manifest.sourceUrl !== input.sourceUrl ||
|
|
41
|
+
inspection.sourceUrl !== input.sourceUrl)
|
|
42
|
+
throw new SpongeError('integrity');
|
|
32
43
|
const documentRecord = object(manifest.document);
|
|
33
|
-
const markdown = {
|
|
44
|
+
const markdown = {
|
|
45
|
+
relativePath: String(documentRecord.path),
|
|
46
|
+
sha256: String(documentRecord.sha256),
|
|
47
|
+
bytes: Number(documentRecord.bytes),
|
|
48
|
+
mediaType: 'text/markdown',
|
|
49
|
+
};
|
|
34
50
|
validateAsset(markdown);
|
|
35
51
|
const bytes = await readConfined(input.bundleRoot, markdown.relativePath, limits.markdownBytes);
|
|
36
52
|
if (bytes.length !== markdown.bytes || hash(bytes) !== markdown.sha256 || document.sha256 !== markdown.sha256)
|
|
37
|
-
throw
|
|
38
|
-
if (!Array.isArray(manifest.assets) ||
|
|
39
|
-
|
|
53
|
+
throw new SpongeError('integrity');
|
|
54
|
+
if (!Array.isArray(manifest.assets) ||
|
|
55
|
+
manifest.assets.length > limits.imagesPerItem ||
|
|
56
|
+
manifest.assets.length !== inspection.assets.length)
|
|
57
|
+
throw new SpongeError('limit-exceeded');
|
|
40
58
|
const assets = [];
|
|
41
59
|
for (const raw of manifest.assets) {
|
|
42
60
|
const item = object(raw);
|
|
43
|
-
const asset = {
|
|
61
|
+
const asset = {
|
|
62
|
+
relativePath: String(item.path),
|
|
63
|
+
sha256: String(item.sha256),
|
|
64
|
+
bytes: Number(item.bytes),
|
|
65
|
+
mediaType: String(item.mimeType),
|
|
66
|
+
};
|
|
44
67
|
validateAsset(asset);
|
|
45
68
|
if (!asset.mediaType.startsWith('image/') || asset.bytes > limits.imageBytes)
|
|
46
|
-
throw
|
|
47
|
-
const checked = inspection.assets.find(a => object(a).path === asset.relativePath);
|
|
69
|
+
throw new SpongeError('limit-exceeded');
|
|
70
|
+
const checked = inspection.assets.find((a) => object(a).path === asset.relativePath);
|
|
48
71
|
if (!checked || object(checked).expectedSha256 !== asset.sha256 || object(checked).expectedBytes !== asset.bytes)
|
|
49
|
-
throw
|
|
72
|
+
throw new SpongeError('integrity');
|
|
50
73
|
assets.push(asset);
|
|
51
74
|
}
|
|
52
75
|
const images = object(object(manifest.artifacts).images);
|
|
53
76
|
if (!['captured', 'partial', 'not-requested'].includes(String(images.status)))
|
|
54
|
-
throw
|
|
77
|
+
throw new SpongeError('integrity');
|
|
55
78
|
const text = manifest.status === 'complete' && summary.status === 'complete' ? 'complete' : 'partial';
|
|
56
79
|
const imageStatus = images.status === 'partial' ? 'partial' : images.status === 'not-requested' ? 'not-requested' : 'complete';
|
|
57
80
|
const metadataWarnings = manifest.metadata === undefined ? [] : articleMetadataWarnings(manifest.metadata);
|
|
58
81
|
const partial = text === 'partial' || imageStatus === 'partial' || metadataWarnings.length > 0;
|
|
59
|
-
const capture = {
|
|
82
|
+
const capture = {
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
requestId: input.requestId,
|
|
85
|
+
kind: 'article',
|
|
86
|
+
status: partial ? 'partial' : 'complete',
|
|
87
|
+
sourceUrl: input.sourceUrl,
|
|
88
|
+
markdown,
|
|
89
|
+
assets,
|
|
90
|
+
metadataWarnings,
|
|
91
|
+
metadata: manifest.metadata === undefined
|
|
92
|
+
? metadataFromMarkdown(bytes.toString('utf8'), String(manifest.capturedAt))
|
|
93
|
+
: articleMetadata(manifest.metadata, String(manifest.capturedAt)),
|
|
94
|
+
warnings: partial ? ['partial'] : [],
|
|
95
|
+
extractor: { name: 'wordcell', version: input.wordcellVersion },
|
|
96
|
+
manifestSha256: hash(manifestBytes),
|
|
97
|
+
completeness: { text, images: imageStatus },
|
|
98
|
+
};
|
|
60
99
|
validateCapture(capture);
|
|
61
100
|
return capture;
|
|
62
101
|
}
|
|
@@ -2,8 +2,26 @@ import { captureLockfile } from './lock.js';
|
|
|
2
2
|
/** Immutable released media/PDF capability pins; installed qualification is recorded separately. */
|
|
3
3
|
export const defaultCapturePins = {
|
|
4
4
|
id: 'capture-ghostget-0.18.102-bun-1.3.14-darwin-arm64',
|
|
5
|
-
ghostget: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
ghostget: {
|
|
6
|
+
version: '0.18.102',
|
|
7
|
+
url: 'https://github.com/hraness/ghostget/releases/download/v0.18.102/hraness-ghostget-0.18.102.tgz',
|
|
8
|
+
bytes: 12493537,
|
|
9
|
+
sha256: '68edcad50b72f77b2ed73e9affba3b49e6b683622cdec9e2fabdffa90e3a0bdb',
|
|
10
|
+
},
|
|
11
|
+
wordcell: {
|
|
12
|
+
version: '0.27.0',
|
|
13
|
+
url: 'https://github.com/hraness/wordcell/releases/download/v0.27.0/hraness-wordcell-0.27.0.tgz',
|
|
14
|
+
bytes: 1465897,
|
|
15
|
+
sha256: 'df47868147cda46d741895d3cd1ddcbec14a563b341aa09052545be7696c9262',
|
|
16
|
+
},
|
|
17
|
+
bun: {
|
|
18
|
+
version: '1.3.14',
|
|
19
|
+
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-darwin-aarch64.zip',
|
|
20
|
+
bytes: 23586433,
|
|
21
|
+
sha256: 'd8b96221828ad6f97ac7ac0ab7e95872341af763001e8803e8267652c2652620',
|
|
22
|
+
executableSha256: 'e0c90ec15d33363e6b70713d56bc3b2c7585c17f40a0fe0f8fd9305901d4e233',
|
|
23
|
+
archiveExecutable: 'bun-darwin-aarch64/bun',
|
|
24
|
+
},
|
|
25
|
+
lockfile: captureLockfile,
|
|
26
|
+
lockSha256: '456370c26a7286bab70307595745f4be382a684a11223e848dbb0032761d59d0',
|
|
9
27
|
};
|
|
@@ -3,34 +3,56 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
export async function runOwned(executable, args, options) {
|
|
4
4
|
if (options.signal?.aborted)
|
|
5
5
|
return { code: null, stdout: '', stopped: 'cancelled', cleanup: 'settled' };
|
|
6
|
-
return new Promise(resolve => {
|
|
7
|
-
const child = spawn(executable, [...args], {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const child = spawn(executable, [...args], {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
env: options.env,
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
detached: process.platform !== 'win32',
|
|
12
|
+
});
|
|
8
13
|
const stdout = [];
|
|
9
14
|
let bytes = 0, stopped, escalation, finished = false;
|
|
10
|
-
const exists = () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
const exists = () => {
|
|
16
|
+
if (!child.pid)
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
process.kill(process.platform === 'win32' ? child.pid : -child.pid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
return e.code !== 'ESRCH';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const send = (signal) => {
|
|
27
|
+
if (!child.pid)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
process.kill(process.platform === 'win32' ? child.pid : -child.pid, signal);
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
};
|
|
34
|
+
const stop = (reason) => {
|
|
35
|
+
stopped ??= reason;
|
|
36
|
+
if (escalation)
|
|
37
|
+
return;
|
|
38
|
+
send('SIGTERM');
|
|
39
|
+
escalation = setTimeout(() => send('SIGKILL'), options.graceMs ?? 5000);
|
|
40
|
+
};
|
|
25
41
|
const abort = () => stop('cancelled');
|
|
26
42
|
options.signal?.addEventListener('abort', abort, { once: true });
|
|
27
43
|
const timer = setTimeout(() => stop('unavailable'), options.deadlineMs);
|
|
28
|
-
child.stdout.on('data', (data) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
child.stdout.on('data', (data) => {
|
|
45
|
+
bytes += data.length;
|
|
46
|
+
if (bytes > options.maxOutputBytes)
|
|
47
|
+
stop('limit-exceeded');
|
|
48
|
+
else
|
|
49
|
+
stdout.push(data);
|
|
50
|
+
});
|
|
51
|
+
child.stderr.on('data', (data) => {
|
|
52
|
+
bytes += data.length;
|
|
53
|
+
if (bytes > options.maxOutputBytes)
|
|
54
|
+
stop('limit-exceeded');
|
|
55
|
+
});
|
|
34
56
|
const finish = async (code) => {
|
|
35
57
|
if (finished)
|
|
36
58
|
return;
|
|
@@ -43,21 +65,37 @@ export async function runOwned(executable, args, options) {
|
|
|
43
65
|
send('SIGTERM');
|
|
44
66
|
const end = Date.now() + 500;
|
|
45
67
|
while (exists() && Date.now() < end)
|
|
46
|
-
await new Promise(r => setTimeout(r, 10));
|
|
68
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
47
69
|
if (exists())
|
|
48
70
|
send('SIGKILL');
|
|
49
71
|
}
|
|
50
72
|
const end = Date.now() + 1500;
|
|
51
73
|
while (exists() && Date.now() < end)
|
|
52
|
-
await new Promise(r => setTimeout(r, 10));
|
|
53
|
-
resolve({
|
|
74
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
75
|
+
resolve({
|
|
76
|
+
code,
|
|
77
|
+
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
78
|
+
stopped,
|
|
79
|
+
cleanup: exists() ? 'pending' : 'settled',
|
|
80
|
+
});
|
|
54
81
|
};
|
|
55
|
-
child.once('error', () => {
|
|
56
|
-
|
|
82
|
+
child.once('error', () => {
|
|
83
|
+
stopped ??= 'unavailable';
|
|
84
|
+
void finish(null);
|
|
85
|
+
});
|
|
86
|
+
child.once('close', (code) => void finish(code));
|
|
57
87
|
if (options.signal?.aborted)
|
|
58
88
|
abort();
|
|
59
89
|
});
|
|
60
90
|
}
|
|
61
91
|
export function isolatedEnvironment(home, tmp, stateHome) {
|
|
62
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
HRANESS_NO_UPDATE: '1',
|
|
94
|
+
HOME: home,
|
|
95
|
+
TMPDIR: tmp,
|
|
96
|
+
GHOSTGET_STATE_HOME: stateHome,
|
|
97
|
+
PATH: '/usr/bin:/bin:/usr/sbin:/sbin',
|
|
98
|
+
LANG: 'en_US.UTF-8',
|
|
99
|
+
NO_COLOR: '1',
|
|
100
|
+
};
|
|
63
101
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export interface DownloadPin {
|
|
1
|
+
interface DownloadPin {
|
|
3
2
|
url: string;
|
|
4
3
|
sha256: string;
|
|
5
4
|
bytes: number;
|
|
@@ -37,12 +36,11 @@ export interface SetupPlan {
|
|
|
37
36
|
requiredFreeBytes: number;
|
|
38
37
|
capabilities: string[];
|
|
39
38
|
}
|
|
40
|
-
export declare class CaptureSetupError extends Error {
|
|
41
|
-
readonly code: ErrorCode;
|
|
42
|
-
readonly recoveryRoot?: string | undefined;
|
|
43
|
-
constructor(code: ErrorCode, recoveryRoot?: string | undefined);
|
|
44
|
-
}
|
|
45
39
|
export declare function captureSetupPlan(pins: CapturePins): SetupPlan;
|
|
40
|
+
/** Full inventory walks performed by this process (observability for tests). */
|
|
41
|
+
export declare const runtimeVerification: {
|
|
42
|
+
fullWalks: number;
|
|
43
|
+
};
|
|
46
44
|
declare function provisionRuntime(options: {
|
|
47
45
|
runtimeRoot: string;
|
|
48
46
|
pins: CapturePins;
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, writeFile, rename, rm, chmod, statfs, lstat, readdir, readlink, realpath } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, mkdtemp, writeFile, rename, rm, chmod, statfs, lstat, readdir, readlink, realpath, } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
import { runOwned, isolatedEnvironment } from './process.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
recoveryRoot;
|
|
8
|
-
constructor(code, recoveryRoot) {
|
|
9
|
-
super(code);
|
|
10
|
-
this.code = code;
|
|
11
|
-
this.recoveryRoot = recoveryRoot;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
4
|
+
import { privateRoot, readConfined, physicalBytes } from './files.js';
|
|
5
|
+
import { sha256 as hash } from '../util/hash.js';
|
|
6
|
+
import { SpongeError } from '../errors.js';
|
|
14
7
|
// Retained inventory excludes the isolated Bun cache and temporary Bun zip.
|
|
15
8
|
const budget = 1024 ** 3;
|
|
16
9
|
const stagingBudget = 2 * budget;
|
|
@@ -18,35 +11,53 @@ const stagingBudget = 2 * budget;
|
|
|
18
11
|
const installDeadlineMs = 30 * 60 * 1000;
|
|
19
12
|
function checkPins(pins) {
|
|
20
13
|
if (!/^[a-z0-9][a-z0-9.-]{0,99}$/.test(pins.id) || hash(pins.lockfile) !== pins.lockSha256)
|
|
21
|
-
throw new
|
|
14
|
+
throw new SpongeError('invalid-input');
|
|
22
15
|
for (const pin of [pins.bun, pins.ghostget, pins.wordcell]) {
|
|
23
16
|
let url;
|
|
24
17
|
try {
|
|
25
18
|
url = new URL(pin.url);
|
|
26
19
|
}
|
|
27
20
|
catch {
|
|
28
|
-
throw new
|
|
21
|
+
throw new SpongeError('invalid-input');
|
|
29
22
|
}
|
|
30
|
-
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(pin.version) ||
|
|
31
|
-
|
|
23
|
+
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(pin.version) ||
|
|
24
|
+
!/^[a-f0-9]{64}$/.test(pin.sha256) ||
|
|
25
|
+
!Number.isSafeInteger(pin.bytes) ||
|
|
26
|
+
pin.bytes < 1 ||
|
|
27
|
+
pin.bytes > budget ||
|
|
28
|
+
url.protocol !== 'https:' ||
|
|
29
|
+
url.username ||
|
|
30
|
+
url.password)
|
|
31
|
+
throw new SpongeError('invalid-input');
|
|
32
32
|
}
|
|
33
|
-
if (!/^[a-z0-9][a-z0-9.-]*\/bun$/.test(pins.bun.archiveExecutable) ||
|
|
34
|
-
|
|
33
|
+
if (!/^[a-z0-9][a-z0-9.-]*\/bun$/.test(pins.bun.archiveExecutable) ||
|
|
34
|
+
!/^[a-f0-9]{64}$/.test(pins.bun.executableSha256))
|
|
35
|
+
throw new SpongeError('invalid-input');
|
|
35
36
|
}
|
|
36
37
|
export function captureSetupPlan(pins) {
|
|
37
38
|
checkPins(pins);
|
|
38
39
|
const artifacts = [pins.bun, pins.ghostget, pins.wordcell], downloadBytes = artifacts.reduce((n, a) => n + a.bytes, 0);
|
|
39
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
artifacts,
|
|
42
|
+
downloadBytes,
|
|
43
|
+
installationBudgetBytes: budget,
|
|
44
|
+
stagingBudgetBytes: stagingBudget,
|
|
45
|
+
installationBudgetKind: 'monitored',
|
|
46
|
+
installationDeadlineMs: installDeadlineMs,
|
|
47
|
+
dependencyDownloads: 'frozen-lock',
|
|
48
|
+
requiredFreeBytes: 2 * 1024 ** 3 + budget + stagingBudget + downloadBytes,
|
|
49
|
+
capabilities: ['article-http', 'selected-ghostget-account', 'selected-attached-browser', 'verified-offline-images'],
|
|
50
|
+
};
|
|
40
51
|
}
|
|
41
52
|
async function download(pin, path, signal) {
|
|
42
53
|
const boundedSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(120000)]) : AbortSignal.timeout(120000);
|
|
43
54
|
const response = await fetch(pin.url, { signal: boundedSignal });
|
|
44
55
|
if (!response.ok || !response.body || new URL(response.url).protocol !== 'https:')
|
|
45
|
-
throw new
|
|
56
|
+
throw new SpongeError('unavailable');
|
|
46
57
|
const declared = response.headers.get('content-length');
|
|
47
58
|
if (declared !== null && Number(declared) !== pin.bytes) {
|
|
48
59
|
await response.body.cancel();
|
|
49
|
-
throw new
|
|
60
|
+
throw new SpongeError('integrity');
|
|
50
61
|
}
|
|
51
62
|
// Primary release artifacts are small, fixed byte records. Never write an unchecked chunk.
|
|
52
63
|
const chunks = [];
|
|
@@ -54,16 +65,17 @@ async function download(pin, path, signal) {
|
|
|
54
65
|
for await (const chunk of response.body) {
|
|
55
66
|
bytes += chunk.length;
|
|
56
67
|
if (bytes > pin.bytes)
|
|
57
|
-
throw new
|
|
68
|
+
throw new SpongeError('integrity');
|
|
58
69
|
chunks.push(chunk);
|
|
59
70
|
}
|
|
60
71
|
const data = Buffer.concat(chunks);
|
|
61
72
|
if (bytes !== pin.bytes || hash(data) !== pin.sha256)
|
|
62
|
-
throw new
|
|
73
|
+
throw new SpongeError('integrity');
|
|
63
74
|
await writeFile(path, data, { mode: 0o600, flag: 'wx' });
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
/** Hashes every retained byte when `full`; always records a stat line per entry (ctime cannot be set from user space). */
|
|
77
|
+
async function inventory(root, full = true) {
|
|
78
|
+
const entries = [], stats = [];
|
|
67
79
|
let total = 0, count = 0;
|
|
68
80
|
async function visit(dir) {
|
|
69
81
|
for (const name of (await readdir(dir)).sort()) {
|
|
@@ -71,44 +83,66 @@ async function inventory(root) {
|
|
|
71
83
|
if (rel === 'runtime-receipt.json')
|
|
72
84
|
continue;
|
|
73
85
|
if (++count > 60000)
|
|
74
|
-
throw new
|
|
75
|
-
const stat = await lstat(path);
|
|
76
|
-
|
|
86
|
+
throw new SpongeError('limit-exceeded');
|
|
87
|
+
const stat = await lstat(path, { bigint: true });
|
|
88
|
+
stats.push(statLine(rel, stat));
|
|
89
|
+
total += Number(stat.size);
|
|
77
90
|
if (total > budget)
|
|
78
|
-
throw new
|
|
91
|
+
throw new SpongeError('limit-exceeded');
|
|
79
92
|
if (stat.isDirectory())
|
|
80
93
|
await visit(path);
|
|
94
|
+
else if (!full)
|
|
95
|
+
continue;
|
|
81
96
|
else if (stat.isSymbolicLink()) {
|
|
82
97
|
const target = await realpath(path);
|
|
83
98
|
if (!target.startsWith(root + '/'))
|
|
84
|
-
throw new
|
|
99
|
+
throw new SpongeError('integrity');
|
|
85
100
|
entries.push(`${rel}\0link\0${await readlink(path)}`);
|
|
86
101
|
}
|
|
87
102
|
else if (stat.isFile())
|
|
88
|
-
entries.push(`${rel}\0${hash(await readConfined(root, rel, budget - total + stat.size))}`);
|
|
103
|
+
entries.push(`${rel}\0${hash(await readConfined(root, rel, budget - total + Number(stat.size)))}`);
|
|
89
104
|
else
|
|
90
|
-
throw new
|
|
105
|
+
throw new SpongeError('integrity');
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
await visit(root);
|
|
94
|
-
return hash(entries.join('\n'));
|
|
109
|
+
return { sha256: hash(entries.join('\n')), stats };
|
|
95
110
|
}
|
|
111
|
+
const statLine = (rel, s) => `${rel}\0${s.dev}\0${s.ino}\0${s.mode}\0${s.uid}\0${s.size}\0${s.mtimeNs}\0${s.ctimeNs}`;
|
|
112
|
+
// One full verify per process and runtime; later uses compare only the stat receipt and fall back to a full verify on any difference.
|
|
113
|
+
const verified = new Map();
|
|
114
|
+
/** Full inventory walks performed by this process (observability for tests). */
|
|
115
|
+
export const runtimeVerification = { fullWalks: 0 };
|
|
96
116
|
async function inspectRuntime(root, pins) {
|
|
97
117
|
checkPins(pins);
|
|
98
118
|
await privateRoot(root);
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
const key = JSON.stringify([root, pins.id, pins.lockSha256, pins.bun.archiveExecutable, pins.bun.executableSha256]), bun = join(root, pins.bun.archiveExecutable), ghostget = join(root, 'node_modules/@hraness/ghostget/src/cli.ts');
|
|
120
|
+
// Stat receipt of the root, its receipt file and every inventoried entry. Each stat precedes any read of its entry, so a later change always shows.
|
|
121
|
+
const head = [
|
|
122
|
+
statLine('.', await lstat(root, { bigint: true })),
|
|
123
|
+
statLine('runtime-receipt.json', await lstat(join(root, 'runtime-receipt.json'), { bigint: true })),
|
|
124
|
+
];
|
|
125
|
+
const cached = verified.get(key);
|
|
126
|
+
if (cached !== undefined && cached === hash([...head, ...(await inventory(root, false)).stats].join('\n')))
|
|
127
|
+
return { root, bun, ghostget, pins };
|
|
128
|
+
verified.delete(key);
|
|
129
|
+
runtimeVerification.fullWalks++;
|
|
130
|
+
const receipt = JSON.parse((await readConfined(root, 'runtime-receipt.json', 4096)).toString()), walked = await inventory(root);
|
|
131
|
+
if (receipt.schemaVersion !== 1 ||
|
|
132
|
+
receipt.pinId !== pins.id ||
|
|
133
|
+
receipt.lockSha256 !== pins.lockSha256 ||
|
|
134
|
+
receipt.inventorySha256 !== walked.sha256)
|
|
135
|
+
throw new SpongeError('integrity');
|
|
103
136
|
if (hash(await readConfined(root, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
|
|
104
|
-
throw new
|
|
137
|
+
throw new SpongeError('integrity');
|
|
138
|
+
verified.set(key, hash([...head, ...walked.stats].join('\n')));
|
|
105
139
|
return { root, bun, ghostget, pins };
|
|
106
140
|
}
|
|
107
141
|
async function provisionRuntime(options) {
|
|
108
142
|
const { pins, signal } = options, plan = captureSetupPlan(pins);
|
|
109
143
|
options.onPlan?.(plan);
|
|
110
144
|
if (process.platform !== 'darwin' || process.arch !== 'arm64')
|
|
111
|
-
throw new
|
|
145
|
+
throw new SpongeError('unsupported');
|
|
112
146
|
await mkdir(options.runtimeRoot, { recursive: true, mode: 0o700 });
|
|
113
147
|
const base = await privateRoot(await realpath(options.runtimeRoot));
|
|
114
148
|
const destination = join(base, pins.id);
|
|
@@ -122,7 +156,7 @@ async function provisionRuntime(options) {
|
|
|
122
156
|
}
|
|
123
157
|
const disk = await statfs(base);
|
|
124
158
|
if (disk.bavail * disk.bsize < plan.requiredFreeBytes)
|
|
125
|
-
throw new
|
|
159
|
+
throw new SpongeError('limit-exceeded');
|
|
126
160
|
const stage = await mkdtemp(join(base, '.install-'));
|
|
127
161
|
await chmod(stage, 0o700);
|
|
128
162
|
const home = join(stage, 'home');
|
|
@@ -133,65 +167,92 @@ async function provisionRuntime(options) {
|
|
|
133
167
|
if (signal?.aborted)
|
|
134
168
|
abort();
|
|
135
169
|
let cleanup = 'settled', checking = false, limitExceeded = false;
|
|
136
|
-
const monitor = setInterval(async () => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
const monitor = setInterval(async () => {
|
|
171
|
+
if (checking)
|
|
172
|
+
return;
|
|
173
|
+
checking = true;
|
|
174
|
+
try {
|
|
175
|
+
if ((await physicalBytes(stage)) > stagingBudget) {
|
|
176
|
+
limitExceeded = true;
|
|
177
|
+
controller.abort();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
140
181
|
controller.abort();
|
|
141
182
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
finally {
|
|
147
|
-
checking = false;
|
|
148
|
-
} }, 100);
|
|
183
|
+
finally {
|
|
184
|
+
checking = false;
|
|
185
|
+
}
|
|
186
|
+
}, 100);
|
|
149
187
|
try {
|
|
150
188
|
await download(pins.bun, join(stage, 'bun.zip'), controller.signal);
|
|
151
|
-
const unpack = await runOwned('/usr/bin/unzip', ['-q', join(stage, 'bun.zip'), '-d', stage], {
|
|
189
|
+
const unpack = await runOwned('/usr/bin/unzip', ['-q', join(stage, 'bun.zip'), '-d', stage], {
|
|
190
|
+
cwd: stage,
|
|
191
|
+
env: isolatedEnvironment(home, home, home),
|
|
192
|
+
signal: controller.signal,
|
|
193
|
+
deadlineMs: 30000,
|
|
194
|
+
maxOutputBytes: 65536,
|
|
195
|
+
});
|
|
152
196
|
cleanup = unpack.cleanup;
|
|
153
197
|
if (unpack.code !== 0 || unpack.stopped || cleanup !== 'settled')
|
|
154
|
-
throw new
|
|
198
|
+
throw new SpongeError(limitExceeded ? 'limit-exceeded' : (unpack.stopped ?? 'unavailable'));
|
|
155
199
|
const bun = join(stage, pins.bun.archiveExecutable);
|
|
156
200
|
if (hash(await readConfined(stage, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
|
|
157
|
-
throw new
|
|
201
|
+
throw new SpongeError('integrity');
|
|
158
202
|
await chmod(bun, 0o700);
|
|
159
203
|
await download(pins.ghostget, join(stage, `hraness-ghostget-${pins.ghostget.version}.tgz`), controller.signal);
|
|
160
204
|
await download(pins.wordcell, join(stage, `hraness-wordcell-${pins.wordcell.version}.tgz`), controller.signal);
|
|
161
|
-
await writeFile(join(stage, 'package.json'), JSON.stringify({
|
|
205
|
+
await writeFile(join(stage, 'package.json'), JSON.stringify({
|
|
206
|
+
private: true,
|
|
207
|
+
dependencies: { '@hraness/ghostget': `./hraness-ghostget-${pins.ghostget.version}.tgz` },
|
|
208
|
+
}), { mode: 0o600 });
|
|
162
209
|
await writeFile(join(stage, 'bun.lock'), pins.lockfile, { mode: 0o600 });
|
|
163
|
-
const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], {
|
|
210
|
+
const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], {
|
|
211
|
+
cwd: stage,
|
|
212
|
+
env: isolatedEnvironment(home, home, home),
|
|
213
|
+
signal: controller.signal,
|
|
214
|
+
deadlineMs: installDeadlineMs,
|
|
215
|
+
maxOutputBytes: 65536,
|
|
216
|
+
});
|
|
164
217
|
cleanup = install.cleanup;
|
|
165
218
|
if (install.code !== 0 || install.stopped || cleanup !== 'settled')
|
|
166
|
-
throw new
|
|
219
|
+
throw new SpongeError(limitExceeded ? 'limit-exceeded' : (install.stopped ?? 'unavailable'));
|
|
167
220
|
await rm(home, { recursive: true, force: true });
|
|
168
221
|
await rm(join(stage, 'bun.zip'));
|
|
169
|
-
for (const [name, pin] of [
|
|
222
|
+
for (const [name, pin] of [
|
|
223
|
+
['ghostget', pins.ghostget],
|
|
224
|
+
['wordcell', pins.wordcell],
|
|
225
|
+
]) {
|
|
170
226
|
const pkg = JSON.parse((await readConfined(stage, `node_modules/@hraness/${name}/package.json`, 65536)).toString());
|
|
171
227
|
if (pkg.version !== pin.version)
|
|
172
|
-
throw new
|
|
228
|
+
throw new SpongeError('integrity');
|
|
173
229
|
}
|
|
174
|
-
await writeFile(join(stage, 'runtime-receipt.json'), JSON.stringify({
|
|
230
|
+
await writeFile(join(stage, 'runtime-receipt.json'), JSON.stringify({
|
|
231
|
+
schemaVersion: 1,
|
|
232
|
+
pinId: pins.id,
|
|
233
|
+
lockSha256: pins.lockSha256,
|
|
234
|
+
inventorySha256: (await inventory(stage)).sha256,
|
|
235
|
+
}), { mode: 0o600 });
|
|
175
236
|
if (controller.signal.aborted)
|
|
176
|
-
throw new
|
|
237
|
+
throw new SpongeError('cancelled');
|
|
177
238
|
await rename(stage, destination);
|
|
178
239
|
return await inspectCaptureRuntime(destination, pins);
|
|
179
240
|
}
|
|
180
241
|
finally {
|
|
181
242
|
clearInterval(monitor);
|
|
182
243
|
while (checking)
|
|
183
|
-
await new Promise(r => setTimeout(r, 5));
|
|
244
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
184
245
|
signal?.removeEventListener('abort', abort);
|
|
185
246
|
if (cleanup === 'settled') {
|
|
186
247
|
try {
|
|
187
248
|
await rm(stage, { recursive: true, force: true });
|
|
188
249
|
}
|
|
189
250
|
catch {
|
|
190
|
-
throw new
|
|
251
|
+
throw new SpongeError('cleanup-required', { detail: { stage } });
|
|
191
252
|
}
|
|
192
253
|
}
|
|
193
254
|
else
|
|
194
|
-
throw new
|
|
255
|
+
throw new SpongeError('cleanup-required', { detail: { stage } });
|
|
195
256
|
}
|
|
196
257
|
}
|
|
197
258
|
export async function inspectCaptureRuntime(root, pins) {
|
|
@@ -199,9 +260,9 @@ export async function inspectCaptureRuntime(root, pins) {
|
|
|
199
260
|
return await inspectRuntime(root, pins);
|
|
200
261
|
}
|
|
201
262
|
catch (error) {
|
|
202
|
-
if (error instanceof
|
|
263
|
+
if (error instanceof SpongeError)
|
|
203
264
|
throw error;
|
|
204
|
-
throw new
|
|
265
|
+
throw new SpongeError('runtime-missing');
|
|
205
266
|
}
|
|
206
267
|
}
|
|
207
268
|
export async function provisionCapture(options) {
|
|
@@ -209,10 +270,10 @@ export async function provisionCapture(options) {
|
|
|
209
270
|
return await provisionRuntime(options);
|
|
210
271
|
}
|
|
211
272
|
catch (error) {
|
|
212
|
-
if (error instanceof
|
|
273
|
+
if (error instanceof SpongeError)
|
|
213
274
|
throw error;
|
|
214
275
|
if (options.signal?.aborted)
|
|
215
|
-
throw new
|
|
216
|
-
throw new
|
|
276
|
+
throw new SpongeError('cancelled');
|
|
277
|
+
throw new SpongeError('unavailable');
|
|
217
278
|
}
|
|
218
279
|
}
|