@davmash/bygone 0.8.14 → 0.9.2
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 +40 -0
- package/README.md +2 -2
- package/cli/commandSpec.js +12 -2
- package/cli/completions.js +1 -1
- package/cli/present.js +89 -7
- package/cli/tour.js +13 -2
- package/cli/tourFile.js +9 -0
- package/cli/tourHistory.js +432 -0
- package/completions/_bygone +2 -1
- package/completions/bygone +1 -1
- package/completions/bygone.fish +1 -0
- package/docs/change-tour-format.md +199 -7
- package/docs/explore.md +21 -1
- package/docs/generating-change-tours.md +70 -17
- package/docs/present.md +134 -5
- package/docs/product-surface.md +64 -8
- package/docs/releasing.md +25 -29
- package/examples/bygone-history.bygone +52 -10
- package/examples/navigation-lab.bygone.yaml +420 -0
- package/media/diff.worker.js +3 -3
- package/media/webview.css +1 -1
- package/media/webview.js +546 -178
- package/out/changeTour.js +1391 -469
- package/out/gitComparison.js +354 -0
- package/out/standalone-main.js +13989 -4749
- package/out/tourCoverage.js +1434 -461
- package/package.json +2 -2
- package/schemas/change-tour-source.schema.json +222 -3
- package/skills/pr-tour-guide/SKILL.md +111 -11
- package/standalone/index.html +6 -1
- package/web/index.html +114 -73
- package/web/presenter.css +658 -230
- package/web/web-host.js +374 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.9.2
|
|
6
|
+
|
|
7
|
+
- Documented the stable v3 tour contract, compatibility policy, and evidence-linked review notes
|
|
8
|
+
- Updated Electron and vulnerable packaging dependencies within their existing major versions
|
|
9
|
+
- Shared the expanded commit navigator across tours, History, and Compare, with explicit draft submission and loaded-panel badges
|
|
10
|
+
- Kept History's commit axis and expanded panels stable across files, with markers for commits that changed the current file
|
|
11
|
+
- Preserved navigator tabs and comparison drafts across mode switches
|
|
12
|
+
- Replaced tour mode and comparison endpoint drop-downs with direct mode buttons and multi-select revision comparison
|
|
13
|
+
- Added two-or-more-panel Compare views ordered from oldest to newest with adjacent diffs
|
|
14
|
+
- Unified Present navigation into one Tour, Files, or Commits navigator
|
|
15
|
+
- Made tours a continuous reading flow through title, chapters, scene overviews, and steps, with a collapsible outline and scene context above each step
|
|
16
|
+
- Added shared workspace tour discovery, editable skill-based v3 authoring prompts, and same-repository transitions into History
|
|
17
|
+
- Clarified tour mode restoration and directory overview scope while preserving first-visit History context
|
|
18
|
+
|
|
19
|
+
macOS desktop downloads are unsigned and not notarized. If Gatekeeper reports
|
|
20
|
+
the app as damaged, run `xattr -cr /Applications/Bygone.app`, then reopen it.
|
|
21
|
+
|
|
22
|
+
## 0.9.1
|
|
23
|
+
|
|
24
|
+
- Restored authored Final walkthroughs and legacy Final deep links for version 2 deconstructed tours
|
|
25
|
+
- Reused the full revision rail and toolbar in tour History mode while preserving the selected file on first entry
|
|
26
|
+
- Expanded Files and Tour details by default and kept Scene context available alongside the active step
|
|
27
|
+
|
|
28
|
+
## 0.9.0
|
|
29
|
+
|
|
30
|
+
- Added independently authored Historical and Deconstructed tours with stable per-mode reading positions
|
|
31
|
+
- Added optional scene-level directory overviews with safe repository-relative scoping and authored comparison endpoints
|
|
32
|
+
- Added evidence-linked review notes for concepts, boundaries, tradeoffs, and open questions
|
|
33
|
+
- Introduced source and manifest format v3 for the new tour metadata while preserving v1 and v2 compatibility
|
|
34
|
+
|
|
35
|
+
## 0.8.16
|
|
36
|
+
|
|
37
|
+
- Clarified authored-tour errors when a document requires a newer format version
|
|
38
|
+
- Made tag-triggered npm release checks fetch repository history needed by checked-in tour fixtures
|
|
39
|
+
|
|
40
|
+
## 0.8.15
|
|
41
|
+
|
|
42
|
+
- Fixed v2 deconstructed-tour coverage so authored Final-mode walkthrough anchors determine coverage while Explanation assignment completeness remains a separate metric
|
|
43
|
+
- Added distinct Final walkthrough coverage and Explanation assignment indicators to authored tours in the Present sidebar
|
|
44
|
+
|
|
5
45
|
## 0.8.14
|
|
6
46
|
|
|
7
47
|
- Reorganized desktop menus around familiar File, Edit, Git, Present, Navigate, View, and Window responsibilities while exposing file, directory, revision, branch, history, tour, and blank-comparison workflows
|
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Change tours can layer an authored, code-connected narrative over the complete g
|
|
|
123
123
|
|
|
124
124
|
Packaged macOS builds register `.bygone` with the desktop app, so Finder can open a presentation directly. Windows and Linux builds currently require opening the file through Bygone or the `bygone` command; they do not install an operating-system file association.
|
|
125
125
|
|
|
126
|
-
Agents can validate anchors and structure with `bygone tour validate <file.bygone> --json`, compile a
|
|
126
|
+
Agents can validate anchors and structure with `bygone tour validate <file.bygone> --json`, compile a repository-bound manifest with `bygone tour compile`, and retrieve the authoring contract with `bygone tour schema`. See [Generating change tours with an LLM](./docs/generating-change-tours.md).
|
|
127
127
|
|
|
128
128
|
Install the agent skill:
|
|
129
129
|
|
|
@@ -210,7 +210,7 @@ Publishing is intentionally explicit:
|
|
|
210
210
|
npm run release:publish
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
Run `release:publish` after committing the version and release changes. It aborts unless the worktree is clean and the current branch is `main`, pushes `main`, waits for that exact commit's GitHub Release Check workflow, rebuilds and installs all local artifacts,
|
|
213
|
+
Run `release:publish` after committing the version and release changes. It aborts unless the worktree is clean and the current branch is `main`, pushes `main`, waits for that exact commit's GitHub Release Check workflow, rebuilds and installs all local artifacts, creates a GitHub release for desktop artifacts, and updates the Homebrew tap. The release tag triggers the protected GitHub Actions Release workflow, which publishes the npm package through trusted publishing after approval. The tap checkout defaults to the sibling `homebrew-bygone` directory; set `BYGONE_HOMEBREW_TAP` to override it. Until Marketplace trusted-publisher policies are publicly configurable, upload the packaged VSIX from the Visual Studio Marketplace publisher page.
|
|
214
214
|
|
|
215
215
|
Reinstall the current built artifacts without rebuilding:
|
|
216
216
|
|
package/cli/commandSpec.js
CHANGED
|
@@ -10,6 +10,7 @@ const CLI_SPEC = Object.freeze({
|
|
|
10
10
|
'bygone --diff <file1> <file2> <file3> [...]',
|
|
11
11
|
'bygone --history <path>',
|
|
12
12
|
'bygone --git-diff <ref1> <ref2> [<ref3>...]',
|
|
13
|
+
'bygone --read-only <desktop-arguments...>',
|
|
13
14
|
'bygone review [<head>] [--base <base>]',
|
|
14
15
|
'bygone -C <directory> <command-or-path> [...]',
|
|
15
16
|
'bygone present [<head>] [--base <base>] [--tour <file.bygone>]',
|
|
@@ -107,6 +108,13 @@ const CLI_SPEC = Object.freeze({
|
|
|
107
108
|
description: 'Use an authored .bygone presentation',
|
|
108
109
|
argument: 'path'
|
|
109
110
|
},
|
|
111
|
+
{
|
|
112
|
+
id: 'readOnly',
|
|
113
|
+
kind: 'global',
|
|
114
|
+
tokens: ['--read-only'],
|
|
115
|
+
description: 'Open live Explore sources without allowing edits',
|
|
116
|
+
argument: 'none'
|
|
117
|
+
},
|
|
110
118
|
{
|
|
111
119
|
id: 'directory',
|
|
112
120
|
kind: 'global',
|
|
@@ -144,13 +152,15 @@ const CLI_SPEC = Object.freeze({
|
|
|
144
152
|
'Two positional paths auto-select file diff or directory compare.',
|
|
145
153
|
'Three or more positional paths auto-select multi-panel file diff or multi-directory compare.',
|
|
146
154
|
'`--git-diff` accepts branches, tags, SHAs, HEAD~1, stash@{0}, INDEX, and WORKTREE.',
|
|
155
|
+
'In `--git-diff`, committed refs and INDEX are read-only; WORKTREE (also WORKDIR or WORKINGTREE) is live and writable.',
|
|
156
|
+
'`--read-only` prevents editing every live file or WORKTREE panel in the launched Explore session.',
|
|
147
157
|
'`review` compares merge-base(BASE,HEAD) with HEAD and detects the default base when omitted.',
|
|
148
158
|
'`present` turns the same range into an app-hosted, ordered change tour.',
|
|
149
159
|
'`-C <directory>` resolves relative paths and Git refs from that directory without changing the shell working directory.',
|
|
150
160
|
'`tour validate` resolves every authored anchor; add `--json` for agent-readable output.',
|
|
151
|
-
'`tour compile` writes a
|
|
161
|
+
'`tour compile` writes a repository-bound manifest to stdout or `--output`; `tour schema` prints its source schema.',
|
|
152
162
|
'`tour context` emits compact, structured Git evidence for an LLM without invoking a model.',
|
|
153
|
-
'`tour coverage` reports
|
|
163
|
+
'`tour coverage` reports authored walkthrough anchor coverage and author-declared explanation depth; for v2 tours it reads Final mode and reports deconstructed Explanation assignment separately, while `--minimum-coverage` gates the walkthrough metric.',
|
|
154
164
|
'`--branch-diff` is retained as an alias for `review`.',
|
|
155
165
|
'`completion` prints a completion script for Zsh, Bash, or Fish.',
|
|
156
166
|
'In the standalone app, drop 1 file for history, 2 files/directories for compare, or 3+ matching paths for multi-panel compare.'
|
package/cli/completions.js
CHANGED
|
@@ -75,7 +75,7 @@ ${legacyOptions}
|
|
|
75
75
|
${historyOptions}
|
|
76
76
|
)
|
|
77
77
|
shells=('zsh:Z shell' 'bash:Bash' 'fish:Fish shell')
|
|
78
|
-
tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a
|
|
78
|
+
tour_actions=('context:Build structured change evidence' 'coverage:Measure authored tour coverage' 'validate:Validate source and anchors' 'compile:Compile a repository-bound manifest' 'schema:Print the source schema')
|
|
79
79
|
|
|
80
80
|
if (( CURRENT == 2 )); then
|
|
81
81
|
_describe 'bygone command' root_items
|
package/cli/present.js
CHANGED
|
@@ -3,9 +3,11 @@ const { createServer } = require('http');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
5
|
const { buildChangeTourManifest, parseChangeTourStory } = require('../out/changeTour.js');
|
|
6
|
+
const { buildTourAuthoringCoverage, buildTourCoverageReport } = require('../out/tourCoverage.js');
|
|
6
7
|
const { buildTourWindowTitle } = require('../out/windowTitle.js');
|
|
7
8
|
const { tokenMatches } = require('./commandSpec.js');
|
|
8
|
-
const { loadTourSource } = require('./tourFile.js');
|
|
9
|
+
const { loadTourSource, parseTourSourceText, buildManifestForTourSource } = require('./tourFile.js');
|
|
10
|
+
const { createTourHistory } = require('./tourHistory.js');
|
|
9
11
|
|
|
10
12
|
const MIME_TYPES = new Map([
|
|
11
13
|
['.css', 'text/css; charset=utf-8'],
|
|
@@ -19,20 +21,24 @@ const MIME_TYPES = new Map([
|
|
|
19
21
|
|
|
20
22
|
async function startPresentation(args, cwd, packageRoot, options = {}) {
|
|
21
23
|
const { headRef, baseRef, tourPath, explicitHeadRef } = parsePresentArgs(args);
|
|
22
|
-
const story = process.env.BYGONE_TOUR_STORY
|
|
24
|
+
const story = !options.ignoreEnvironment && process.env.BYGONE_TOUR_STORY
|
|
23
25
|
? parseChangeTourStory(JSON.parse(readFileSync(path.resolve(cwd, process.env.BYGONE_TOUR_STORY), 'utf8')))
|
|
24
26
|
: undefined;
|
|
25
27
|
const source = tourPath ? loadTourSource(cwd, tourPath).source : undefined;
|
|
26
|
-
const
|
|
28
|
+
const builtManifest = buildChangeTourManifest(cwd, {
|
|
27
29
|
headRef: explicitHeadRef || source?.range?.head || headRef,
|
|
28
30
|
baseRef: baseRef || source?.range?.base,
|
|
29
|
-
title: process.env.BYGONE_TOUR_TITLE,
|
|
30
|
-
sourceUrl: process.env.BYGONE_TOUR_SOURCE_URL,
|
|
31
|
+
title: options.ignoreEnvironment ? undefined : process.env.BYGONE_TOUR_TITLE,
|
|
32
|
+
sourceUrl: options.ignoreEnvironment ? undefined : process.env.BYGONE_TOUR_SOURCE_URL,
|
|
31
33
|
story,
|
|
32
34
|
source
|
|
33
35
|
});
|
|
36
|
+
const manifest = source
|
|
37
|
+
? { ...builtManifest, authoringCoverage: buildTourAuthoringCoverage(buildTourCoverageReport(cwd, source)) }
|
|
38
|
+
: builtManifest;
|
|
39
|
+
const history = createTourHistory(manifest);
|
|
34
40
|
const serializedManifest = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
35
|
-
const outputPath = process.env.BYGONE_TOUR_OUTPUT;
|
|
41
|
+
const outputPath = options.ignoreEnvironment ? undefined : process.env.BYGONE_TOUR_OUTPUT;
|
|
36
42
|
if (outputPath) {
|
|
37
43
|
const resolvedOutput = path.resolve(cwd, outputPath);
|
|
38
44
|
mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
|
@@ -43,9 +49,75 @@ async function startPresentation(args, cwd, packageRoot, options = {}) {
|
|
|
43
49
|
const tourWindowTitle = buildTourWindowTitle(manifest, 'Bygone');
|
|
44
50
|
const presenterIndexPath = path.join(packageRoot, 'web', 'index.html');
|
|
45
51
|
let presenterIndexTemplate;
|
|
52
|
+
// Uploaded documents remain immutable, scoped to this existing repository,
|
|
53
|
+
// and addressed independently so opening one cannot mutate another tab.
|
|
54
|
+
const uploadedTours = new Map();
|
|
46
55
|
|
|
47
56
|
const server = createServer((request, response) => {
|
|
48
57
|
const requestUrl = new URL(request.url || '/', 'http://127.0.0.1');
|
|
58
|
+
if (requestUrl.pathname === '/tour/open') {
|
|
59
|
+
if (request.method !== 'POST') return respondJson(response, 405, { error: 'Method not allowed' });
|
|
60
|
+
if (!isSameOriginLoopbackRequest(request)) return respondJson(response, 403, { error: 'Forbidden' });
|
|
61
|
+
if (uploadedTours.size >= 16) return respondJson(response, 409, { error: 'This presentation has reached its open-document limit. Start a new presentation to open more documents.' });
|
|
62
|
+
let body = '';
|
|
63
|
+
request.setEncoding('utf8');
|
|
64
|
+
request.on('data', (chunk) => {
|
|
65
|
+
body += chunk;
|
|
66
|
+
if (Buffer.byteLength(body, 'utf8') > 2 * 1024 * 1024) { respondJson(response, 413, { error: 'Request too large' }); request.destroy(); }
|
|
67
|
+
});
|
|
68
|
+
request.on('end', () => {
|
|
69
|
+
if (response.writableEnded) return;
|
|
70
|
+
try {
|
|
71
|
+
const uploadedSource = parseTourSourceText(JSON.parse(body).source);
|
|
72
|
+
const loaded = buildManifestForTourSource(cwd, uploadedSource);
|
|
73
|
+
const id = String(uploadedTours.size + 1);
|
|
74
|
+
uploadedTours.set(id, { manifest: loaded, history: createTourHistory(loaded) });
|
|
75
|
+
respondJson(response, 200, { manifestUrl: `/loaded/${id}/tour.json`, repository: loaded.repository?.root || cwd, range: loaded.range });
|
|
76
|
+
} catch (error) { respondJson(response, 400, { error: `Invalid tour for this repository: ${error.message}` }); }
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const loadedMatch = requestUrl.pathname.match(/^\/loaded\/(\d+)\/(tour\.json|history\/(?:list|diff|compare|compare-many|revisions))$/);
|
|
81
|
+
const loadedTour = loadedMatch ? uploadedTours.get(loadedMatch[1]) : null;
|
|
82
|
+
if (loadedMatch && !loadedTour) return respondJson(response, 404, { error: 'Loaded document not found' });
|
|
83
|
+
if (loadedTour && loadedMatch[2] === 'tour.json') return respondJson(response, 200, loadedTour.manifest);
|
|
84
|
+
const requestHistory = loadedTour ? loadedTour.history : history;
|
|
85
|
+
if (loadedMatch) requestUrl.pathname = `/${loadedMatch[2]}`;
|
|
86
|
+
if (requestUrl.pathname === '/history/list' || requestUrl.pathname === '/history/diff'
|
|
87
|
+
|| requestUrl.pathname === '/history/compare' || requestUrl.pathname === '/history/compare-many'
|
|
88
|
+
|| requestUrl.pathname === '/history/revisions') {
|
|
89
|
+
if (request.method !== 'POST') return respondJson(response, 405, { error: 'Method not allowed' });
|
|
90
|
+
if (!isSameOriginLoopbackRequest(request)) return respondJson(response, 403, { error: 'Forbidden' });
|
|
91
|
+
if (!requestHistory) return respondJson(response, 404, { error: 'History requires a version 2 tour.' });
|
|
92
|
+
let body = '';
|
|
93
|
+
request.setEncoding('utf8');
|
|
94
|
+
request.on('data', chunk => {
|
|
95
|
+
body += chunk;
|
|
96
|
+
if (body.length > 8192) {
|
|
97
|
+
respondJson(response, 413, { error: 'Request too large' });
|
|
98
|
+
request.destroy();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
request.on('end', () => {
|
|
102
|
+
if (response.writableEnded) return;
|
|
103
|
+
try {
|
|
104
|
+
const input = JSON.parse(body);
|
|
105
|
+
const result = requestUrl.pathname === '/history/list'
|
|
106
|
+
? requestHistory.list(input)
|
|
107
|
+
: requestUrl.pathname === '/history/diff'
|
|
108
|
+
? requestHistory.diff(input)
|
|
109
|
+
: requestUrl.pathname === '/history/compare'
|
|
110
|
+
? requestHistory.compare(input)
|
|
111
|
+
: requestUrl.pathname === '/history/compare-many'
|
|
112
|
+
? requestHistory.compareMany(input)
|
|
113
|
+
: requestHistory.revisions(input);
|
|
114
|
+
respondJson(response, 200, result);
|
|
115
|
+
} catch {
|
|
116
|
+
respondJson(response, 400, { error: 'Could not load history for this file and revision.' });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
49
121
|
if (requestUrl.pathname === '/narration/claim') {
|
|
50
122
|
if (request.method !== 'POST') {
|
|
51
123
|
respond(response, 405, 'Method not allowed');
|
|
@@ -94,7 +166,7 @@ async function startPresentation(args, cwd, packageRoot, options = {}) {
|
|
|
94
166
|
});
|
|
95
167
|
createReadStream(targetPath).on('error', () => respond(response, 500, 'Read failed')).pipe(response);
|
|
96
168
|
});
|
|
97
|
-
const requestedPort = readPort(process.env.BYGONE_TOUR_PORT);
|
|
169
|
+
const requestedPort = readPort(options.ignoreEnvironment ? undefined : process.env.BYGONE_TOUR_PORT);
|
|
98
170
|
await new Promise((resolve, reject) => {
|
|
99
171
|
server.once('error', reject);
|
|
100
172
|
server.listen(requestedPort, '127.0.0.1', resolve);
|
|
@@ -194,6 +266,16 @@ function respond(response, statusCode, message) {
|
|
|
194
266
|
response.end(message);
|
|
195
267
|
}
|
|
196
268
|
|
|
269
|
+
function respondJson(response, statusCode, body) {
|
|
270
|
+
if (!response.headersSent) {
|
|
271
|
+
response.writeHead(statusCode, {
|
|
272
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
273
|
+
'Cache-Control': 'no-store'
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
response.end(JSON.stringify(body));
|
|
277
|
+
}
|
|
278
|
+
|
|
197
279
|
function isSameOriginLoopbackRequest(request) {
|
|
198
280
|
const origin = request.headers.origin;
|
|
199
281
|
const host = request.headers.host;
|
package/cli/tour.js
CHANGED
|
@@ -27,7 +27,8 @@ function runTourCommand(args, cwd, packageRoot, output = process.stdout) {
|
|
|
27
27
|
const report = buildTourCoverageReport(cwd, source);
|
|
28
28
|
output.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : renderCoverageReport(report));
|
|
29
29
|
if (options.minimumCoverage !== undefined && report.totals.coveragePercent < options.minimumCoverage) {
|
|
30
|
-
|
|
30
|
+
const label = report.version === 2 ? 'Final walkthrough coverage' : 'Tour coverage';
|
|
31
|
+
throw new Error(`${label} ${report.totals.coveragePercent}% is below the required ${options.minimumCoverage}%.`);
|
|
31
32
|
}
|
|
32
33
|
return { action: 'coverage', sourcePath: resolvedPath, report };
|
|
33
34
|
}
|
|
@@ -98,10 +99,20 @@ function parseTourArgs(args) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
function renderCoverageReport(report) {
|
|
102
|
+
const coverageLabel = report.version === 2 ? 'Final walkthrough coverage' : 'Tour coverage';
|
|
101
103
|
const lines = [
|
|
102
|
-
|
|
104
|
+
`${coverageLabel}: ${report.totals.coveredUnits}/${report.totals.includedUnits} hunks (${report.totals.coveragePercent}%)`,
|
|
103
105
|
`Depth: ${report.depth.mentioned} mentioned · ${report.depth.explained} explained · ${report.depth.contextualized} contextualized`
|
|
104
106
|
];
|
|
107
|
+
if (report.version === 2) {
|
|
108
|
+
for (const assignment of report.explanationAssignments) {
|
|
109
|
+
const suffix = report.explanationAssignments.length > 1 ? ` (${assignment.sceneId})` : '';
|
|
110
|
+
lines.push(
|
|
111
|
+
`Explanation assignment${suffix}: ${assignment.completeUnits}/${assignment.totalUnits} hunks `
|
|
112
|
+
+ `(${assignment.assignmentPercent}%; ${assignment.assignedUnits} assigned, ${assignment.excludedUnits} excluded)`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
105
116
|
if (report.totals.excludedUnits > 0) lines.push(`Excluded: ${report.totals.excludedUnits}/${report.totals.originalUnits} hunks`);
|
|
106
117
|
for (const file of report.files.filter((entry) => entry.uncoveredHunks.length > 0)) {
|
|
107
118
|
lines.push(`${file.path}: ${file.uncoveredHunks.length} uncovered (${file.uncoveredHunks.join(', ')})`);
|
package/cli/tourFile.js
CHANGED
|
@@ -48,6 +48,14 @@ function readTourSourceDocument(sourcePath, options = {}) {
|
|
|
48
48
|
throw new Error(`Could not decode Bygone source ${sourceLabel} as UTF-8: ${errorMessage(error)}`, { cause: error });
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
return parseTourSourceText(text, sourceLabel, { maxBytes });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseTourSourceText(text, sourceLabel = 'uploaded tour', options = {}) {
|
|
55
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_TOUR_SOURCE_BYTES;
|
|
56
|
+
if (typeof text !== 'string' || Buffer.byteLength(text, 'utf8') > maxBytes || text.includes('\0')) {
|
|
57
|
+
throw new Error('Invalid tour text or source size limit exceeded.');
|
|
58
|
+
}
|
|
51
59
|
let documents;
|
|
52
60
|
try {
|
|
53
61
|
documents = loadYamlDocuments(text);
|
|
@@ -84,5 +92,6 @@ module.exports = {
|
|
|
84
92
|
DEFAULT_MAX_TOUR_SOURCE_BYTES,
|
|
85
93
|
buildManifestForTourSource,
|
|
86
94
|
loadTourSource,
|
|
95
|
+
parseTourSourceText,
|
|
87
96
|
readTourSourceDocument
|
|
88
97
|
};
|