spec-controller 0.1.0-alpha.4 → 0.1.0-alpha.40
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 +34 -6
- package/dist/cli-allocate/cli.d.ts +2 -0
- package/dist/cli-allocate/cli.d.ts.map +1 -0
- package/dist/cli-allocate/cli.js +57 -0
- package/dist/cli-allocate/cli.js.map +1 -0
- package/dist/cli-allocate/readHistory.d.ts +29 -0
- package/dist/cli-allocate/readHistory.d.ts.map +1 -0
- package/dist/cli-allocate/readHistory.js +136 -0
- package/dist/cli-allocate/readHistory.js.map +1 -0
- package/dist/cli-allocate/readParts.d.ts +3 -0
- package/dist/cli-allocate/readParts.d.ts.map +1 -0
- package/dist/cli-allocate/readParts.js +28 -0
- package/dist/cli-allocate/readParts.js.map +1 -0
- package/dist/cli-allocate/readTree.d.ts +16 -0
- package/dist/cli-allocate/readTree.d.ts.map +1 -0
- package/dist/cli-allocate/readTree.js +106 -0
- package/dist/cli-allocate/readTree.js.map +1 -0
- package/dist/cli-allocate/refStore.d.ts +3 -0
- package/dist/cli-allocate/refStore.d.ts.map +1 -0
- package/dist/cli-allocate/refStore.js +47 -0
- package/dist/cli-allocate/refStore.js.map +1 -0
- package/dist/cli-allocate/request.d.ts +11 -0
- package/dist/cli-allocate/request.d.ts.map +1 -0
- package/dist/cli-allocate/request.js +29 -0
- package/dist/cli-allocate/request.js.map +1 -0
- package/dist/cli-allocate/stderr.d.ts +14 -0
- package/dist/cli-allocate/stderr.d.ts.map +1 -0
- package/dist/cli-allocate/stderr.js +33 -0
- package/dist/cli-allocate/stderr.js.map +1 -0
- package/dist/cli-allocate/unreadable.d.ts +24 -0
- package/dist/cli-allocate/unreadable.d.ts.map +1 -0
- package/dist/cli-allocate/unreadable.js +43 -0
- package/dist/cli-allocate/unreadable.js.map +1 -0
- package/dist/cli-args.d.ts +73 -18
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +244 -22
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-balance/cli.d.ts +31 -13
- package/dist/cli-balance/cli.d.ts.map +1 -1
- package/dist/cli-balance/cli.js +205 -251
- package/dist/cli-balance/cli.js.map +1 -1
- package/dist/cli-balance/emit/writer.d.ts +8 -23
- package/dist/cli-balance/emit/writer.d.ts.map +1 -1
- package/dist/cli-balance/emit/writer.js +24 -32
- package/dist/cli-balance/emit/writer.js.map +1 -1
- package/dist/cli-balance/exitStatus.d.ts +3 -0
- package/dist/cli-balance/exitStatus.d.ts.map +1 -0
- package/dist/cli-balance/exitStatus.js +12 -0
- package/dist/cli-balance/exitStatus.js.map +1 -0
- package/dist/cli-balance/reRender.d.ts +22 -0
- package/dist/cli-balance/reRender.d.ts.map +1 -0
- package/dist/cli-balance/reRender.js +36 -0
- package/dist/cli-balance/reRender.js.map +1 -0
- package/dist/cli-balance/unreadableInputs.d.ts +35 -0
- package/dist/cli-balance/unreadableInputs.d.ts.map +1 -0
- package/dist/cli-balance/unreadableInputs.js +101 -0
- package/dist/cli-balance/unreadableInputs.js.map +1 -0
- package/dist/cli-registry.d.ts +28 -0
- package/dist/cli-registry.d.ts.map +1 -1
- package/dist/cli-registry.js +40 -35
- package/dist/cli-registry.js.map +1 -1
- package/dist/cli.d.ts +4 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +37 -21
- package/dist/cli.js.map +1 -1
- package/dist/host.d.ts +28 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +137 -17
- package/dist/host.js.map +1 -1
- package/dist/ingest/gherkinValidation.d.ts +3 -7
- package/dist/ingest/gherkinValidation.d.ts.map +1 -1
- package/dist/ingest/gherkinValidation.js +3 -7
- package/dist/ingest/gherkinValidation.js.map +1 -1
- package/dist/ingest/ingestQualityChecks.d.ts +25 -54
- package/dist/ingest/ingestQualityChecks.d.ts.map +1 -1
- package/dist/ingest/ingestQualityChecks.js +112 -145
- package/dist/ingest/ingestQualityChecks.js.map +1 -1
- package/dist/ingest/ingestScenarios.d.ts +11 -17
- package/dist/ingest/ingestScenarios.d.ts.map +1 -1
- package/dist/ingest/ingestScenarios.js +52 -62
- package/dist/ingest/ingestScenarios.js.map +1 -1
- package/dist/ingest/inputShapes.d.ts +47 -0
- package/dist/ingest/inputShapes.d.ts.map +1 -0
- package/dist/ingest/inputShapes.js +143 -0
- package/dist/ingest/inputShapes.js.map +1 -0
- package/dist/outputLocation.d.ts +15 -0
- package/dist/outputLocation.d.ts.map +1 -0
- package/dist/outputLocation.js +39 -0
- package/dist/outputLocation.js.map +1 -0
- package/dist/run-management/keptRun.d.ts +27 -25
- package/dist/run-management/keptRun.d.ts.map +1 -1
- package/dist/run-management/keptRun.js +20 -21
- package/dist/run-management/keptRun.js.map +1 -1
- package/dist/storedRun.d.ts +27 -0
- package/dist/storedRun.d.ts.map +1 -0
- package/dist/storedRun.js +49 -0
- package/dist/storedRun.js.map +1 -0
- package/package.json +2 -2
- package/dist/corpus/cli.d.ts +0 -34
- package/dist/corpus/cli.d.ts.map +0 -1
- package/dist/corpus/cli.js +0 -128
- package/dist/corpus/cli.js.map +0 -1
- package/dist/mutation-ratchet/index.d.ts +0 -51
- package/dist/mutation-ratchet/index.d.ts.map +0 -1
- package/dist/mutation-ratchet/index.js +0 -51
- package/dist/mutation-ratchet/index.js.map +0 -1
- package/dist/mutation-ratchet/record.d.ts +0 -178
- package/dist/mutation-ratchet/record.d.ts.map +0 -1
- package/dist/mutation-ratchet/record.js +0 -314
- package/dist/mutation-ratchet/record.js.map +0 -1
- package/dist/mutation-ratchet/report.d.ts +0 -109
- package/dist/mutation-ratchet/report.d.ts.map +0 -1
- package/dist/mutation-ratchet/report.js +0 -156
- package/dist/mutation-ratchet/report.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,15 +11,17 @@ Requires Node ≥ 22. ESM only.
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
npm install -g spec-controller
|
|
14
|
+
npm install -g spec-controller@alpha
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Or without installing anything:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
npx spec-controller balance --help
|
|
20
|
+
npx spec-controller@alpha balance --help
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
`@alpha` is not optional until `0.1.0` publishes: a bare install resolves `latest`, which npm set to `0.1.0-alpha.1` on the first publish, and that version has no `--ci-steps` and no scenario code errors.
|
|
24
|
+
|
|
23
25
|
## Your first run
|
|
24
26
|
|
|
25
27
|
Point it at your scenarios and give it nothing else:
|
|
@@ -30,6 +32,20 @@ spec-controller balance --features ./features
|
|
|
30
32
|
|
|
31
33
|
Every scenario comes back as a row whether or not any evidence exists for it, so on this first run the ones you have supplied no reports for read **missing** and the run comes back out-of-balance. That is the shape of the tool rather than a fault in your repository: the reconciliation starts from what you specified, not from what a tool happened to find, and there is no input for which the right answer is silence.
|
|
32
34
|
|
|
35
|
+
**`balance` fails your build out of the box.** An out-of-balance run exits `1`, so the first run above reds any pipeline you put it in. That is a deliberate departure from two tools it sits beside: Stryker's `thresholds.break` defaults to `null` (*"never let your build fail"*), and the Sonar scanner exits `0` whatever its quality gate says unless you set `sonar.qualitygate.wait`. Both make you opt in to gating. `balance` makes you opt out.
|
|
36
|
+
|
|
37
|
+
### Report first, gate later
|
|
38
|
+
|
|
39
|
+
To bring it into a pipeline you already have without redding it on day one, add `--exit-zero`:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
spec-controller balance --features ./features --vitest ./vitest-report.json --exit-zero
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The report is byte for byte the one the gate prints: the same rows, the same out-of-balance count, and the same headline naming the code the gate would give. Only the exit status moves. Out-of-balance and scenario code errors (`1`), and pending under `--strict` (`3`), exit `0`. Usage (`2`), unsound (`4`) and nothing to reconcile (`5`) still fail, because each means the run could not give a verdict at all, and a flag that suppresses verdicts does not suppress that.
|
|
46
|
+
|
|
47
|
+
Work the out-of-balance rows down, then delete `--exit-zero`, and the gate is on. Don't wrap the step in `continue-on-error` or `|| true` instead: those swallow the codes that mean the run could not be trusted, and they leave nothing in the command to say the gate is off.
|
|
48
|
+
|
|
33
49
|
Now give it the evidence:
|
|
34
50
|
|
|
35
51
|
```
|
|
@@ -46,7 +62,7 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
|
|
|
46
62
|
| Exit | What it says |
|
|
47
63
|
| --- | --- |
|
|
48
64
|
| `0` | balanced — every obligation has evidence, and it holds |
|
|
49
|
-
| `1` | out-of-balance — the two records disagree, and the report says where |
|
|
65
|
+
| `1` | out-of-balance — the two records disagree, and the report says where; or a scenario code is misfiled, listed under `## Scenario code errors` |
|
|
50
66
|
| `2` | usage — the invocation was wrong |
|
|
51
67
|
| `3` | balanced, but carrying Pending Items, and you asked for `--strict` |
|
|
52
68
|
| `4` | unsound — a proving suite failed to collect, so the ledger could not be trusted to disagree |
|
|
@@ -54,11 +70,23 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
|
|
|
54
70
|
|
|
55
71
|
The whole report is written before the code is set, so a failing run still tells you why.
|
|
56
72
|
|
|
73
|
+
`spec-controller allocate`, which prints the next scenario code for a feature part, reaches no verdict: it exits `0` when it answers and `2` for a usage error or an unknown part. A file or directory it cannot read, whether it reads it itself or through git, is a usage error: it is refused, naming the path and the reason, and no code is given.
|
|
74
|
+
|
|
75
|
+
`--exit-zero` moves `1` and `3` to `0` and leaves every other code alone: see *Report first, gate later*.
|
|
76
|
+
|
|
77
|
+
`balance --render-from-json <saved.json>` re-renders a saved report and exits the code its verdict names, from the same table: a saved run that failed fails again when it is read back. A report saved under `--strict` records it, so its re-render exits `3` too, and `--strict` or `--exit-zero` beside the re-render work as they do live. Any other flag beside it is refused at `2`, because a re-render reads no live input and writes only to stdout.
|
|
78
|
+
|
|
57
79
|
**Node's default is never one of these.** A run that cannot reconcile ends on an enumerated code of
|
|
58
80
|
its own rather than falling out of the process — a crash that exits `1` would be indistinguishable,
|
|
59
81
|
by status, from an honest disagreement. **An input the tool cannot read is part of `2`**: a mistyped
|
|
60
|
-
path,
|
|
61
|
-
|
|
82
|
+
path, a path it is not permitted to read, a file where a directory belongs or the reverse, an
|
|
83
|
+
unparseable report, a report that parses but is not in the shape its runner writes, a saved
|
|
84
|
+
reconciliation it cannot render, or a corpus the Gherkin parser rejects are all the invocation
|
|
85
|
+
being wrong, never a verdict about your code. The refusal names the input, the path, and what was
|
|
86
|
+
wrong with it. A well-formed report that holds nothing, such as a Vitest run that matched no test
|
|
87
|
+
file, is read, not refused. **So is an output location it cannot write**: a `--format` path that is
|
|
88
|
+
read-only, a directory, under a regular file or in a directory that does not exist, and a
|
|
89
|
+
`--store` root the run directory cannot be created under, are refused at `2`, naming the location and the operating system's reason, before any report is printed.
|
|
62
90
|
|
|
63
91
|
**The enumeration is open at the top, and that is the one thing to design your pipeline around.** A
|
|
64
92
|
state that is genuinely none of the above gets a **new** code rather than being folded into the
|
|
@@ -83,7 +111,7 @@ spec-controller balance --features ./features \
|
|
|
83
111
|
## Where to go next
|
|
84
112
|
|
|
85
113
|
- **Full flags, worked examples and CI wiring** — [`doc/how_to_use.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/how_to_use.md)
|
|
86
|
-
- **Bringing a check you already have under the reconciliation** — [`doc/
|
|
114
|
+
- **Bringing a check you already have under the reconciliation** — [`doc/adopting-fitness-checks.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/adopting-fitness-checks.md)
|
|
87
115
|
- **Why `0.x` promises nothing, and what changes at `1.0`** — [`doc/compatibility.md`](https://github.com/3f-consulting/spec-controller/blob/main/doc/compatibility.md)
|
|
88
116
|
- **Building your own tooling on the engine instead of running a command** — [`@3f-consulting/spec-controller-core`](https://www.npmjs.com/package/@3f-consulting/spec-controller-core), the same reconciliation logic as a pure library with no CLI and no I/O.
|
|
89
117
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"AAcA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAiBhD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spec-controller allocate <FFF>` — the next scenario code for a feature part, alone on stdout.
|
|
3
|
+
*/
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { nextScnCode } from "@3f-consulting/spec-controller-core";
|
|
6
|
+
import { cliRegistry, renderCommandHelp } from "../cli-registry.js";
|
|
7
|
+
import { resolveToolIdentity } from "../run-management/resolveRunInputs.js";
|
|
8
|
+
import { readHeaderCodes } from "./readParts.js";
|
|
9
|
+
import { readRequest } from "./request.js";
|
|
10
|
+
import { readOrRefuse, refusalLine, UnreadableInput } from "./unreadable.js";
|
|
11
|
+
import { historyBoundaryOf, readHistory, repositoryTop } from "./readHistory.js";
|
|
12
|
+
import { readTree, walkTree } from "./readTree.js";
|
|
13
|
+
import { alreadyHeadedNote, highestSeenLine, historyBoundaryLine, unknownPartRefusal } from "./stderr.js";
|
|
14
|
+
export function runAllocate(argv) {
|
|
15
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
16
|
+
process.stdout.write(renderCommandHelp(cliRegistry, "allocate") + "\n");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const request = readRequest(argv, cliRegistry, resolveToolIdentity().toolVersion);
|
|
20
|
+
if ("usageError" in request) {
|
|
21
|
+
process.stderr.write(request.usageError + "\n");
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
answerRequest(request);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (!(err instanceof UnreadableInput))
|
|
29
|
+
throw err;
|
|
30
|
+
process.stderr.write(refusalLine(err) + "\n");
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Read the tree, its history and its parts, and write the answer to a well-formed request, or its refusal. */
|
|
35
|
+
function answerRequest({ part, isNew }) {
|
|
36
|
+
const cwd = readOrRefuse("the working directory", "", () => process.cwd());
|
|
37
|
+
const top = repositoryTop(cwd);
|
|
38
|
+
const headerCodes = readHeaderCodes(join(top ?? cwd, "features"));
|
|
39
|
+
const answer = nextScnCode({ part, headerCodes, isNew }, sightingsFrom(cwd, part, top));
|
|
40
|
+
if (answer.kind === "unknown-part") {
|
|
41
|
+
process.stderr.write(unknownPartRefusal(answer) + "\n");
|
|
42
|
+
process.exit(2);
|
|
43
|
+
}
|
|
44
|
+
const boundaryLine = historyBoundaryLine(historyBoundaryOf(cwd), answer);
|
|
45
|
+
if (boundaryLine !== null)
|
|
46
|
+
process.stderr.write(boundaryLine + "\n");
|
|
47
|
+
process.stderr.write(highestSeenLine(answer) + "\n");
|
|
48
|
+
const note = alreadyHeadedNote(answer);
|
|
49
|
+
if (note !== null)
|
|
50
|
+
process.stderr.write(note + "\n");
|
|
51
|
+
process.stdout.write(answer.code + "\n");
|
|
52
|
+
}
|
|
53
|
+
/** The tree and its history through git, or the tree alone by a walk where there is no repository. */
|
|
54
|
+
function sightingsFrom(cwd, part, top) {
|
|
55
|
+
return top !== null ? [...readTree(top, part), ...readHistory(cwd, part)] : walkTree(cwd, part);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ScnSighting } from "@3f-consulting/spec-controller-core";
|
|
2
|
+
/** How much history the read could see: all of it, none (no repository), or back to a shallow clone's edge. */
|
|
3
|
+
export type HistoryBoundary = {
|
|
4
|
+
kind: "complete";
|
|
5
|
+
} | {
|
|
6
|
+
kind: "not-a-repository";
|
|
7
|
+
} | {
|
|
8
|
+
kind: "shallow";
|
|
9
|
+
before: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`; a refusal
|
|
13
|
+
* when the refs it starts from, or the history git walks from them, cannot be read.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readHistory(cwd: string, part: string): ScnSighting[];
|
|
16
|
+
/**
|
|
17
|
+
* The top of the repository `cwd` stands in, or null outside one. A `.git` at or above `cwd` that git
|
|
18
|
+
* will not open is a repository allocate cannot read, refused naming it, never read as no repository:
|
|
19
|
+
* that would state "no history was read" about a directory whose history exists. No git to ask at all
|
|
20
|
+
* is still no repository, a boundary said beside the answer.
|
|
21
|
+
*/
|
|
22
|
+
export declare function repositoryTop(cwd: string): string | null;
|
|
23
|
+
/** Whether `cwd` stands in a git repository. */
|
|
24
|
+
export declare function isGitRepository(cwd: string): boolean;
|
|
25
|
+
/** How far back the history read can see from `cwd`. */
|
|
26
|
+
export declare function historyBoundaryOf(cwd: string): HistoryBoundary;
|
|
27
|
+
/** The added and removed lines of `git log -p` output, each with where it stands. */
|
|
28
|
+
export declare function diffLineSightings(logOutput: string): ScnSighting[];
|
|
29
|
+
//# sourceMappingURL=readHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readHistory.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAIvE,+GAA+G;AAC/G,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAQxH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAapE;AAQD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxD;AAWD,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAQ9D;AAaD,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAQlE"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The history half of the allocator's read: the added and removed lines of every reachable commit
|
|
3
|
+
* that touched the part, so a code that was deleted is still counted and never reissued.
|
|
4
|
+
*
|
|
5
|
+
* Only diff lines count, never commit messages. History is read so that a deleted scenario's number
|
|
6
|
+
* is not reissued; a code that only ever appeared in a message was never a scenario, and a message
|
|
7
|
+
* cannot be unwritten, so one stray mention would move the answer for good. The format asks git for
|
|
8
|
+
* each commit's abbreviated hash and nothing else, so a sighting can name the commit that carried it.
|
|
9
|
+
*
|
|
10
|
+
* Git is handed the part's prefix alone, which holds no regex syntax. Which words on a line are
|
|
11
|
+
* scenario codes is core's question, answered with the shared grammar, because git's regex dialect
|
|
12
|
+
* has no `\d` and matches nothing when given one, silently.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { proveRefsReadable } from "./refStore.js";
|
|
18
|
+
import { readOrRefuse, refuse, UnreadableInput } from "./unreadable.js";
|
|
19
|
+
/** A long history's patches can be large; the default buffer would truncate them. */
|
|
20
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
21
|
+
/** The marker the format puts before each commit's hash, a byte no patch line starts with. */
|
|
22
|
+
const COMMIT_MARKER = "\u0001";
|
|
23
|
+
/**
|
|
24
|
+
* The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`; a refusal
|
|
25
|
+
* when the refs it starts from, or the history git walks from them, cannot be read.
|
|
26
|
+
*/
|
|
27
|
+
export function readHistory(cwd, part) {
|
|
28
|
+
proveRefsReadable(cwd);
|
|
29
|
+
const result = spawnSync("git", ["log", "--all", "-p", "-U0", "--no-color", "--no-ext-diff", "--format=%x01%h", "-G", `SCN-${part}-`], { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT_BYTES });
|
|
30
|
+
if (result.error !== undefined)
|
|
31
|
+
refuse("the repository's history", cwd, result.error);
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
const said = result.stderr.split("\n")[0] ?? "";
|
|
34
|
+
throw new UnreadableInput("the repository's history", cwd, `git log exited ${result.status}: ${said}`);
|
|
35
|
+
}
|
|
36
|
+
return diffLineSightings(result.stdout);
|
|
37
|
+
}
|
|
38
|
+
/** Git's answer in `cwd`, trimmed, or null when git refuses (outside a repository, or no git at all). */
|
|
39
|
+
function gitAnswer(cwd, args) {
|
|
40
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
41
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The top of the repository `cwd` stands in, or null outside one. A `.git` at or above `cwd` that git
|
|
45
|
+
* will not open is a repository allocate cannot read, refused naming it, never read as no repository:
|
|
46
|
+
* that would state "no history was read" about a directory whose history exists. No git to ask at all
|
|
47
|
+
* is still no repository, a boundary said beside the answer.
|
|
48
|
+
*/
|
|
49
|
+
export function repositoryTop(cwd) {
|
|
50
|
+
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
|
|
51
|
+
if (result.status === 0)
|
|
52
|
+
return result.stdout.trim();
|
|
53
|
+
const gitEntry = result.error === undefined ? gitEntryAtOrAbove(cwd) : null;
|
|
54
|
+
if (gitEntry === null)
|
|
55
|
+
return null;
|
|
56
|
+
const said = result.stderr.split("\n")[0] ?? "";
|
|
57
|
+
throw new UnreadableInput("the repository", gitEntry, `git rev-parse exited ${result.status}: ${said}`);
|
|
58
|
+
}
|
|
59
|
+
/** The `.git` git's own discovery would find from `dir`: the nearest at or above it, of any kind; else null. */
|
|
60
|
+
function gitEntryAtOrAbove(dir) {
|
|
61
|
+
for (let at = dir;; at = dirname(at)) {
|
|
62
|
+
const entry = join(at, ".git");
|
|
63
|
+
if (lstatSync(entry, { throwIfNoEntry: false }) !== undefined)
|
|
64
|
+
return entry;
|
|
65
|
+
if (dirname(at) === at)
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Whether `cwd` stands in a git repository. */
|
|
70
|
+
export function isGitRepository(cwd) {
|
|
71
|
+
return gitAnswer(cwd, ["rev-parse", "--git-dir"]) !== null;
|
|
72
|
+
}
|
|
73
|
+
/** How far back the history read can see from `cwd`. */
|
|
74
|
+
export function historyBoundaryOf(cwd) {
|
|
75
|
+
if (!isGitRepository(cwd))
|
|
76
|
+
return { kind: "not-a-repository" };
|
|
77
|
+
if (gitAnswer(cwd, ["rev-parse", "--is-shallow-repository"]) !== "true")
|
|
78
|
+
return { kind: "complete" };
|
|
79
|
+
const shallowFile = gitAnswer(cwd, ["rev-parse", "--git-path", "shallow"]) ?? "";
|
|
80
|
+
const shallowPath = shallowFile.startsWith("/") ? shallowFile : `${cwd}/${shallowFile}`;
|
|
81
|
+
const record = readOrRefuse("the repository's history", shallowPath, () => readFileSync(shallowPath, "utf8"));
|
|
82
|
+
const edge = record.split("\n")[0] ?? "";
|
|
83
|
+
return { kind: "shallow", before: gitAnswer(cwd, ["rev-parse", "--short", edge]) ?? edge };
|
|
84
|
+
}
|
|
85
|
+
/** The added and removed lines of `git log -p` output, each with where it stands. */
|
|
86
|
+
export function diffLineSightings(logOutput) {
|
|
87
|
+
const at = { inFileHeader: false, oldPath: "", newPath: "", oldLine: 0, newLine: 0 };
|
|
88
|
+
const sightings = [];
|
|
89
|
+
for (const line of logOutput.split("\n")) {
|
|
90
|
+
const sighting = readLine(line, at);
|
|
91
|
+
if (sighting !== undefined)
|
|
92
|
+
sightings.push(sighting);
|
|
93
|
+
}
|
|
94
|
+
return sightings;
|
|
95
|
+
}
|
|
96
|
+
/** Move the position past one line of output, and return the sighting it is, if it is one. */
|
|
97
|
+
function readLine(line, at) {
|
|
98
|
+
if (line.startsWith(COMMIT_MARKER))
|
|
99
|
+
at.commit = line.slice(COMMIT_MARKER.length);
|
|
100
|
+
else if (line.startsWith("diff --git "))
|
|
101
|
+
at.inFileHeader = true;
|
|
102
|
+
else if (at.inFileHeader)
|
|
103
|
+
readFileHeader(line, at);
|
|
104
|
+
else if (line.startsWith("-"))
|
|
105
|
+
return sightingAt(line, at.oldPath, at.oldLine++, at.commit);
|
|
106
|
+
else if (line.startsWith("+"))
|
|
107
|
+
return sightingAt(line, at.newPath, at.newLine++, at.commit);
|
|
108
|
+
else if (line.startsWith("@@ "))
|
|
109
|
+
readHunkHeader(line, at);
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
/** A file header's lines: `---`/`+++` name the two sides, and the first hunk ends the header. */
|
|
113
|
+
function readFileHeader(line, at) {
|
|
114
|
+
if (line.startsWith("--- "))
|
|
115
|
+
at.oldPath = pathOf(line);
|
|
116
|
+
else if (line.startsWith("+++ "))
|
|
117
|
+
at.newPath = pathOf(line);
|
|
118
|
+
else if (line.startsWith("@@ ")) {
|
|
119
|
+
at.inFileHeader = false;
|
|
120
|
+
readHunkHeader(line, at);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** The path a `---`/`+++` header names, without git's `a/`/`b/` prefix. */
|
|
124
|
+
function pathOf(header) {
|
|
125
|
+
return header.slice(4).replace(/^[ab]\//, "");
|
|
126
|
+
}
|
|
127
|
+
/** A hunk header, `@@ -<old>[,n] +<new>[,n] @@`, gives each side's first line. */
|
|
128
|
+
function readHunkHeader(line, at) {
|
|
129
|
+
const match = /^@@ -(\d+)(?:,\d+)? \+(\d+)/.exec(line);
|
|
130
|
+
at.oldLine = Number(match?.[1] ?? 0);
|
|
131
|
+
at.newLine = Number(match?.[2] ?? 0);
|
|
132
|
+
}
|
|
133
|
+
function sightingAt(text, path, line, commit) {
|
|
134
|
+
return commit === undefined ? { text, path, line } : { text, path, line, commit };
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=readHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readHistory.js","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readParts.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readParts.ts"],"names":[],"mappings":"AASA,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAM7D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts the corpus carries: the header codes of its feature files, read where `balance` reads its
|
|
3
|
+
* corpus by default. A part only history or a test fixture carries is not one of them.
|
|
4
|
+
*/
|
|
5
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { extractFeatureCode } from "../ingest/ingestScenarios.js";
|
|
8
|
+
import { readOrRefuse, refuse } from "./unreadable.js";
|
|
9
|
+
/** The header codes of the feature files in `featuresDir`; none when there is no such directory. */
|
|
10
|
+
export function readHeaderCodes(featuresDir) {
|
|
11
|
+
return featureFilesIn(featuresDir).flatMap((name) => {
|
|
12
|
+
const path = join(featuresDir, name);
|
|
13
|
+
const code = extractFeatureCode(readOrRefuse("a feature file", path, () => readFileSync(path, "utf8")));
|
|
14
|
+
return code === null ? [] : [code];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** The feature files `dir` lists; none when it is not there, and a refusal when it cannot be listed. */
|
|
18
|
+
function featureFilesIn(dir) {
|
|
19
|
+
try {
|
|
20
|
+
return readdirSync(dir).filter((name) => name.endsWith(".feature"));
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err.code === "ENOENT")
|
|
24
|
+
return [];
|
|
25
|
+
return refuse("the feature corpus", dir, err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=readParts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readParts.js","sourceRoot":"","sources":["../../src/cli-allocate/readParts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEvD,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,kBAAkB,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACxG,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChE,OAAO,MAAM,CAAC,oBAAoB,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ScnSighting } from "@3f-consulting/spec-controller-core";
|
|
2
|
+
/**
|
|
3
|
+
* Every line in the repository's tree carrying `SCN-<part>-`, with its path from `top`. The tree is
|
|
4
|
+
* what git lists: tracked files, and untracked files the repository does not ignore, since an author
|
|
5
|
+
* allocates because they have just written a scenario, and it is not committed yet.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readTree(top: string, part: string): ScnSighting[];
|
|
8
|
+
/**
|
|
9
|
+
* Whether a walk without git enters a directory: never a package store, never a hidden directory.
|
|
10
|
+
* An installed package carries its own codes (spec-controller's own dist carries hundreds), which
|
|
11
|
+
* would retire a consumer's numbers with a package's.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isWalkedDirectory(name: string): boolean;
|
|
14
|
+
/** Every line carrying `SCN-<part>-` in the text files under `root`, found by walking it without git. */
|
|
15
|
+
export declare function walkTree(root: string, part: string): ScnSighting[];
|
|
16
|
+
//# sourceMappingURL=readTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readTree.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AASvE;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAKjE;AAuDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,yGAAyG;AACzG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAKlE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tree half of the allocator's read: every line of every text file that mentions the part.
|
|
3
|
+
*
|
|
4
|
+
* In a repository, git says which files are the tree and allocate reads each of them itself. Git
|
|
5
|
+
* skips a file or directory it cannot read and still exits 0, so a code carried only there would be
|
|
6
|
+
* handed out again; reading the files here, and listing every directory git had to list, turns each
|
|
7
|
+
* such path into a refusal instead. Which words on a line are scenario codes is core's question,
|
|
8
|
+
* answered with the shared grammar.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { accessSync, constants, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join, relative, sep } from "node:path";
|
|
13
|
+
import { readOrRefuse, refuse, UnreadableInput } from "./unreadable.js";
|
|
14
|
+
/** Git's listing can be large on a big repository; the default buffer would truncate it. */
|
|
15
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
16
|
+
/** How far into a file git looks for a NUL before it calls the file binary and searches none of it. */
|
|
17
|
+
const BINARY_PROBE_BYTES = 8000;
|
|
18
|
+
/**
|
|
19
|
+
* Every line in the repository's tree carrying `SCN-<part>-`, with its path from `top`. The tree is
|
|
20
|
+
* what git lists: tracked files, and untracked files the repository does not ignore, since an author
|
|
21
|
+
* allocates because they have just written a scenario, and it is not committed yet.
|
|
22
|
+
*/
|
|
23
|
+
export function readTree(top, part) {
|
|
24
|
+
const listed = gitListing(top, ["ls-files", "-z", "--cached", "--others", "--exclude-standard"]);
|
|
25
|
+
const ignored = gitListing(top, ["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"]);
|
|
26
|
+
proveListable(top, "", new Set(ignored.filter(isDirectoryEntry).map((entry) => entry.slice(0, -1))));
|
|
27
|
+
return listed.filter((entry) => !isDirectoryEntry(entry)).flatMap((path) => sightingsInListed(top, path, `SCN-${part}-`));
|
|
28
|
+
}
|
|
29
|
+
/** The paths a `git ls-files -z` run lists, or the refusal when git could not list them. */
|
|
30
|
+
function gitListing(top, args) {
|
|
31
|
+
const result = spawnSync("git", args, { cwd: top, encoding: "utf8", maxBuffer: MAX_OUTPUT_BYTES });
|
|
32
|
+
if (result.error !== undefined)
|
|
33
|
+
refuse("the tree", top, result.error);
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
const said = result.stderr.split("\n")[0] ?? "";
|
|
36
|
+
throw new UnreadableInput("the tree", top, `git ls-files exited ${result.status}: ${said}`);
|
|
37
|
+
}
|
|
38
|
+
return result.stdout.split("\0").filter((entry) => entry !== "");
|
|
39
|
+
}
|
|
40
|
+
/** A listed directory rather than a file: a repository nested in the tree, or a wholly ignored one. */
|
|
41
|
+
function isDirectoryEntry(entry) {
|
|
42
|
+
return entry.endsWith("/");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every directory git had to list to answer, listed and entered here too, so one it could not list
|
|
46
|
+
* is refused rather than skipped. Ignored directories and nested repositories are not the tree.
|
|
47
|
+
*/
|
|
48
|
+
function proveListable(top, dir, ignored) {
|
|
49
|
+
const path = dir === "" ? top : join(top, dir);
|
|
50
|
+
const entries = readOrRefuse("a directory in the tree", path, () => listedAndEntered(path));
|
|
51
|
+
if (dir !== "" && entries.some((entry) => entry.name === ".git"))
|
|
52
|
+
return;
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const child = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
55
|
+
if (entry.isDirectory() && entry.name !== ".git" && !ignored.has(child))
|
|
56
|
+
proveListable(top, child, ignored);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** A directory's entries, when it may be both listed and entered. */
|
|
60
|
+
function listedAndEntered(path) {
|
|
61
|
+
accessSync(path, constants.R_OK | constants.X_OK);
|
|
62
|
+
return readdirSync(path, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
/** The sightings in one file git lists: none when it has left the working tree or is not a file. */
|
|
65
|
+
function sightingsInListed(top, path, needle) {
|
|
66
|
+
const file = join(top, path);
|
|
67
|
+
const kind = readOrRefuse("a file in the tree", file, () => lstatSync(file, { throwIfNoEntry: false }));
|
|
68
|
+
if (kind === undefined || !kind.isFile())
|
|
69
|
+
return [];
|
|
70
|
+
return linesCarrying(path, readOrRefuse("a file in the tree", file, () => readFileSync(file)), needle);
|
|
71
|
+
}
|
|
72
|
+
/** The lines of a file's content that carry `needle`; a binary file, by git's rule, carries none. */
|
|
73
|
+
function linesCarrying(path, content, needle) {
|
|
74
|
+
if (content.subarray(0, BINARY_PROBE_BYTES).includes(0))
|
|
75
|
+
return [];
|
|
76
|
+
return content
|
|
77
|
+
.toString("utf8")
|
|
78
|
+
.split("\n")
|
|
79
|
+
.flatMap((line, i) => (line.includes(needle) ? [{ path, line: i + 1, text: line }] : []));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a walk without git enters a directory: never a package store, never a hidden directory.
|
|
83
|
+
* An installed package carries its own codes (spec-controller's own dist carries hundreds), which
|
|
84
|
+
* would retire a consumer's numbers with a package's.
|
|
85
|
+
*/
|
|
86
|
+
export function isWalkedDirectory(name) {
|
|
87
|
+
return name !== "node_modules" && !name.startsWith(".");
|
|
88
|
+
}
|
|
89
|
+
/** Every line carrying `SCN-<part>-` in the text files under `root`, found by walking it without git. */
|
|
90
|
+
export function walkTree(root, part) {
|
|
91
|
+
return filesUnder(root).flatMap((file) => {
|
|
92
|
+
const content = readOrRefuse("a file in the tree", file, () => readFileSync(file));
|
|
93
|
+
return linesCarrying(relative(root, file).split(sep).join("/"), content, `SCN-${part}-`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Every file under `dir`, entering only the directories a walk enters; a refusal for one it cannot list. */
|
|
97
|
+
function filesUnder(dir) {
|
|
98
|
+
const entries = readOrRefuse("a directory in the tree", dir, () => readdirSync(dir, { withFileTypes: true }));
|
|
99
|
+
return entries.flatMap((entry) => {
|
|
100
|
+
const path = join(dir, entry.name);
|
|
101
|
+
if (entry.isDirectory())
|
|
102
|
+
return isWalkedDirectory(entry.name) ? filesUnder(path) : [];
|
|
103
|
+
return entry.isFile() ? [path] : [];
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=readTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readTree.js","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refStore.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/refStore.ts"],"names":[],"mappings":"AAiBA,0GAA0G;AAC1G,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAInD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The refs the history read starts from, proved readable before git is asked for the history.
|
|
3
|
+
*
|
|
4
|
+
* Git walks history from every ref, and a ref store it cannot read is not an error to git: with
|
|
5
|
+
* `refs/heads` unreadable, `git log --all` exits 0, writes nothing to stderr, and simply walks from
|
|
6
|
+
* fewer refs, so a deleted code only an unread branch reaches would be handed out again. Reading the
|
|
7
|
+
* store here is the only way that case is ever seen. It reads, and never interprets: whether the
|
|
8
|
+
* store is loose files, `packed-refs` or a reftable, every one of them git would read is read here.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { readOrRefuse } from "./unreadable.js";
|
|
14
|
+
/** Where each ref store lives under a git directory, whichever format the repository keeps. */
|
|
15
|
+
const REF_STORES = ["refs", "packed-refs", "reftable"];
|
|
16
|
+
/** Refuse, as history allocate cannot read, a ref store under the repository at `cwd` it may not read. */
|
|
17
|
+
export function proveRefsReadable(cwd) {
|
|
18
|
+
for (const gitDir of gitDirsOf(cwd)) {
|
|
19
|
+
for (const store of REF_STORES)
|
|
20
|
+
readEveryEntry(join(gitDir, store));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** The repository's common git directory and its own, which differ only in a linked worktree. */
|
|
24
|
+
function gitDirsOf(cwd) {
|
|
25
|
+
const result = spawnSync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir", "--git-dir"], {
|
|
26
|
+
cwd,
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
});
|
|
29
|
+
if (result.status !== 0)
|
|
30
|
+
return [];
|
|
31
|
+
return [...new Set(result.stdout.split("\n").filter((line) => line !== ""))];
|
|
32
|
+
}
|
|
33
|
+
/** Read `path` whole: a file's content, or every entry under a directory; nothing when it is not there. */
|
|
34
|
+
function readEveryEntry(path) {
|
|
35
|
+
const kind = readOrRefuse("the repository's history", path, () => lstatSync(path, { throwIfNoEntry: false }));
|
|
36
|
+
if (kind === undefined)
|
|
37
|
+
return;
|
|
38
|
+
if (kind.isDirectory()) {
|
|
39
|
+
const names = readOrRefuse("the repository's history", path, () => readdirSync(path));
|
|
40
|
+
for (const name of names)
|
|
41
|
+
readEveryEntry(join(path, name));
|
|
42
|
+
}
|
|
43
|
+
else if (kind.isFile()) {
|
|
44
|
+
readOrRefuse("the repository's history", path, () => readFileSync(path));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=refStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refStore.js","sourceRoot":"","sources":["../../src/cli-allocate/refStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,+FAA+F;AAC/F,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,CAAU,CAAC;AAEhE,0GAA0G;AAC1G,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,UAAU;YAAE,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,WAAW,CAAC,EAAE;QACxG,GAAG;QACH,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,2GAA2G;AAC3G,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,YAAY,CAAC,0BAA0B,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC9G,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,YAAY,CAAC,0BAA0B,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACtF,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC;SAAM,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACzB,YAAY,CAAC,0BAA0B,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CliRegistry } from "../cli-registry.js";
|
|
2
|
+
/** A well-formed request: the part, and whether it is asked for as new. */
|
|
3
|
+
export interface AllocateRequest {
|
|
4
|
+
readonly part: string;
|
|
5
|
+
readonly isNew: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** The request `argv` makes, or the usage error it is, worded for stderr. */
|
|
8
|
+
export declare function readRequest(argv: readonly string[], registry: CliRegistry, toolVersion: string): AllocateRequest | {
|
|
9
|
+
readonly usageError: string;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/request.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,WAAW,EACrB,WAAW,EAAE,MAAM,GAClB,eAAe,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAInD"}
|