spec-controller 0.1.0-alpha.37 → 0.1.0-alpha.38

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 CHANGED
@@ -70,7 +70,7 @@ A scenario tagged `@unit` names an obligation — a unit test must exist and pas
70
70
 
71
71
  The whole report is written before the code is set, so a failing run still tells you why.
72
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.
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
74
 
75
75
  `--exit-zero` moves `1` and `3` to `0` and leaves every other code alone: see *Report first, gate later*.
76
76
 
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"AAaA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyBhD"}
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"}
@@ -7,6 +7,7 @@ import { cliRegistry, renderCommandHelp } from "../cli-registry.js";
7
7
  import { resolveToolIdentity } from "../run-management/resolveRunInputs.js";
8
8
  import { readHeaderCodes } from "./readParts.js";
9
9
  import { readRequest } from "./request.js";
10
+ import { readOrRefuse, refusalLine, UnreadableInput } from "./unreadable.js";
10
11
  import { historyBoundaryOf, readHistory, repositoryTop } from "./readHistory.js";
11
12
  import { readTree, walkTree } from "./readTree.js";
12
13
  import { alreadyHeadedNote, highestSeenLine, historyBoundaryLine, unknownPartRefusal } from "./stderr.js";
@@ -20,11 +21,22 @@ export function runAllocate(argv) {
20
21
  process.stderr.write(request.usageError + "\n");
21
22
  process.exit(2);
22
23
  }
23
- const { part, isNew } = request;
24
- const cwd = process.cwd();
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());
25
37
  const top = repositoryTop(cwd);
26
38
  const headerCodes = readHeaderCodes(join(top ?? cwd, "features"));
27
- const answer = nextScnCode({ part, headerCodes, isNew }, sightingsFrom(cwd, part, top !== null));
39
+ const answer = nextScnCode({ part, headerCodes, isNew }, sightingsFrom(cwd, part, top));
28
40
  if (answer.kind === "unknown-part") {
29
41
  process.stderr.write(unknownPartRefusal(answer) + "\n");
30
42
  process.exit(2);
@@ -39,7 +51,7 @@ export function runAllocate(argv) {
39
51
  process.stdout.write(answer.code + "\n");
40
52
  }
41
53
  /** The tree and its history through git, or the tree alone by a walk where there is no repository. */
42
- function sightingsFrom(cwd, part, inRepository) {
43
- return inRepository ? [...readTree(cwd, part), ...readHistory(cwd, part)] : walkTree(cwd, part);
54
+ function sightingsFrom(cwd, part, top) {
55
+ return top !== null ? [...readTree(top, part), ...readHistory(cwd, part)] : walkTree(cwd, part);
44
56
  }
45
57
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli-allocate/cli.ts"],"names":[],"mappings":"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"}
@@ -8,9 +8,17 @@ export type HistoryBoundary = {
8
8
  kind: "shallow";
9
9
  before: string;
10
10
  };
11
- /** The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`. */
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
+ */
12
15
  export declare function readHistory(cwd: string, part: string): ScnSighting[];
13
- /** The top of the repository `cwd` stands in, or null outside one. */
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
+ */
14
22
  export declare function repositoryTop(cwd: string): string | null;
15
23
  /** Whether `cwd` stands in a git repository. */
16
24
  export declare function isGitRepository(cwd: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"readHistory.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAEvE,+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,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAOpE;AAQD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED,gDAAgD;AAChD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAM9D;AAaD,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAQlE"}
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"}
@@ -7,17 +7,32 @@
7
7
  * cannot be unwritten, so one stray mention would move the answer for good. The format asks git for
8
8
  * each commit's abbreviated hash and nothing else, so a sighting can name the commit that carried it.
9
9
  *
10
- * As with the tree read, git is handed a fixed string only.
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.
11
13
  */
12
14
  import { spawnSync } from "node:child_process";
13
- import { readFileSync } from "node:fs";
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";
14
19
  /** A long history's patches can be large; the default buffer would truncate them. */
15
20
  const MAX_OUTPUT_BYTES = 1024 * 1024 * 1024;
16
21
  /** The marker the format puts before each commit's hash, a byte no patch line starts with. */
17
22
  const COMMIT_MARKER = "\u0001";
18
- /** The added and removed lines of every reachable commit whose patch touched `SCN-<part>-`. */
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
+ */
19
27
  export function readHistory(cwd, part) {
28
+ proveRefsReadable(cwd);
20
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
+ }
21
36
  return diffLineSightings(result.stdout);
22
37
  }
23
38
  /** Git's answer in `cwd`, trimmed, or null when git refuses (outside a repository, or no git at all). */
@@ -25,9 +40,31 @@ function gitAnswer(cwd, args) {
25
40
  const result = spawnSync("git", args, { cwd, encoding: "utf8" });
26
41
  return result.status === 0 ? result.stdout.trim() : null;
27
42
  }
28
- /** The top of the repository `cwd` stands in, or null outside one. */
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
+ */
29
49
  export function repositoryTop(cwd) {
30
- return gitAnswer(cwd, ["rev-parse", "--show-toplevel"]);
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
+ }
31
68
  }
32
69
  /** Whether `cwd` stands in a git repository. */
33
70
  export function isGitRepository(cwd) {
@@ -40,7 +77,9 @@ export function historyBoundaryOf(cwd) {
40
77
  if (gitAnswer(cwd, ["rev-parse", "--is-shallow-repository"]) !== "true")
41
78
  return { kind: "complete" };
42
79
  const shallowFile = gitAnswer(cwd, ["rev-parse", "--git-path", "shallow"]) ?? "";
43
- const edge = readFileSync(shallowFile.startsWith("/") ? shallowFile : `${cwd}/${shallowFile}`, "utf8").split("\n")[0] ?? "";
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] ?? "";
44
83
  return { kind: "shallow", before: gitAnswer(cwd, ["rev-parse", "--short", edge]) ?? edge };
45
84
  }
46
85
  /** The added and removed lines of `git log -p` output, each with where it stands. */
@@ -1 +1 @@
1
- {"version":3,"file":"readHistory.js","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readHistory.js","sourceRoot":"","sources":["../../src/cli-allocate/readHistory.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"readParts.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readParts.ts"],"names":[],"mappings":"AAQA,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D"}
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"}
@@ -5,13 +5,16 @@
5
5
  import { readdirSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { extractFeatureCode } from "../ingest/ingestScenarios.js";
8
+ import { readOrRefuse, refuse } from "./unreadable.js";
8
9
  /** The header codes of the feature files in `featuresDir`; none when there is no such directory. */
9
10
  export function readHeaderCodes(featuresDir) {
10
11
  return featureFilesIn(featuresDir).flatMap((name) => {
11
- const code = extractFeatureCode(readFileSync(join(featuresDir, name), "utf8"));
12
+ const path = join(featuresDir, name);
13
+ const code = extractFeatureCode(readOrRefuse("a feature file", path, () => readFileSync(path, "utf8")));
12
14
  return code === null ? [] : [code];
13
15
  });
14
16
  }
17
+ /** The feature files `dir` lists; none when it is not there, and a refusal when it cannot be listed. */
15
18
  function featureFilesIn(dir) {
16
19
  try {
17
20
  return readdirSync(dir).filter((name) => name.endsWith(".feature"));
@@ -19,7 +22,7 @@ function featureFilesIn(dir) {
19
22
  catch (err) {
20
23
  if (err.code === "ENOENT")
21
24
  return [];
22
- throw err;
25
+ return refuse("the feature corpus", dir, err);
23
26
  }
24
27
  }
25
28
  //# sourceMappingURL=readParts.js.map
@@ -1 +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;AAElE,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,kBAAkB,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/E,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,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,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
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"}
@@ -1,11 +1,10 @@
1
1
  import type { ScnSighting } from "@3f-consulting/spec-controller-core";
2
2
  /**
3
- * Every line in the whole repository's tree carrying `SCN-<part>-`, with its path from the top, wherever
4
- * in the repository it is asked from: `:/` is the top, since git grep otherwise reads only the cwd's subtree.
5
- * Untracked files the repository does not ignore count too: an author allocates because they have just
6
- * written a scenario, and it is not committed yet.
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.
7
6
  */
8
- export declare function readTree(cwd: string, part: string): ScnSighting[];
7
+ export declare function readTree(top: string, part: string): ScnSighting[];
9
8
  /**
10
9
  * Whether a walk without git enters a directory: never a package store, never a hidden directory.
11
10
  * An installed package carries its own codes (spec-controller's own dist carries hundreds), which
@@ -1 +1 @@
1
- {"version":3,"file":"readTree.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAKvE;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAOjE;AASD;;;;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,CAElE"}
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"}
@@ -1,35 +1,82 @@
1
1
  /**
2
2
  * The tree half of the allocator's read: every line of every text file that mentions the part.
3
3
  *
4
- * Git is handed a fixed string only. Which words on those lines are scenario codes is core's
5
- * question, answered with the shared grammar, because git's own regex dialect has no `\d` and
6
- * matches nothing when given one, silently.
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.
7
9
  */
8
10
  import { spawnSync } from "node:child_process";
9
- import { readdirSync, readFileSync } from "node:fs";
11
+ import { accessSync, constants, lstatSync, readdirSync, readFileSync } from "node:fs";
10
12
  import { join, relative, sep } from "node:path";
11
- /** Git's output can be large on a big repository; the default buffer would truncate it. */
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. */
12
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;
13
18
  /**
14
- * Every line in the whole repository's tree carrying `SCN-<part>-`, with its path from the top, wherever
15
- * in the repository it is asked from: `:/` is the top, since git grep otherwise reads only the cwd's subtree.
16
- * Untracked files the repository does not ignore count too: an author allocates because they have just
17
- * written a scenario, and it is not committed yet.
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.
18
22
  */
19
- export function readTree(cwd, part) {
20
- const result = spawnSync("git", ["grep", "-z", "-n", "-I", "--full-name", "--untracked", "-F", "-e", `SCN-${part}-`, "--", ":/"], {
21
- cwd,
22
- encoding: "utf8",
23
- maxBuffer: MAX_OUTPUT_BYTES,
24
- });
25
- return result.stdout.split("\n").flatMap(sightingOf);
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
+ }
26
58
  }
27
- /** One `path\0line\0text` record of `git grep -z -n`, or nothing for a blank line. */
28
- function sightingOf(record) {
29
- const [path, line, text] = record.split("\0");
30
- if (path === undefined || line === undefined || text === undefined)
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))
31
75
  return [];
32
- return [{ path, line: Number(line), text }];
76
+ return content
77
+ .toString("utf8")
78
+ .split("\n")
79
+ .flatMap((line, i) => (line.includes(needle) ? [{ path, line: i + 1, text: line }] : []));
33
80
  }
34
81
  /**
35
82
  * Whether a walk without git enters a directory: never a package store, never a hidden directory.
@@ -41,23 +88,19 @@ export function isWalkedDirectory(name) {
41
88
  }
42
89
  /** Every line carrying `SCN-<part>-` in the text files under `root`, found by walking it without git. */
43
90
  export function walkTree(root, part) {
44
- return filesUnder(root).flatMap((file) => sightingsIn(root, file, `SCN-${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
+ });
45
95
  }
46
- /** Every file under `dir`, entering only the directories a walk enters. */
96
+ /** Every file under `dir`, entering only the directories a walk enters; a refusal for one it cannot list. */
47
97
  function filesUnder(dir) {
48
- return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
98
+ const entries = readOrRefuse("a directory in the tree", dir, () => readdirSync(dir, { withFileTypes: true }));
99
+ return entries.flatMap((entry) => {
49
100
  const path = join(dir, entry.name);
50
101
  if (entry.isDirectory())
51
102
  return isWalkedDirectory(entry.name) ? filesUnder(path) : [];
52
103
  return entry.isFile() ? [path] : [];
53
104
  });
54
105
  }
55
- /** The lines of a text file that carry `needle`; a binary file carries none. */
56
- function sightingsIn(root, file, needle) {
57
- const text = readFileSync(file, "utf8");
58
- if (text.includes("\0"))
59
- return [];
60
- const path = relative(root, file).split(sep).join("/");
61
- return text.split("\n").flatMap((line, i) => (line.includes(needle) ? [{ path, line: i + 1, text: line }] : []));
62
- }
63
106
  //# sourceMappingURL=readTree.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readTree.js","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readTree.js","sourceRoot":"","sources":["../../src/cli-allocate/readTree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Refuse, as history allocate cannot read, a ref store under the repository at `cwd` it may not read. */
2
+ export declare function proveRefsReadable(cwd: string): void;
3
+ //# sourceMappingURL=refStore.d.ts.map
@@ -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,24 @@
1
+ /**
2
+ * A path allocate is meant to read and cannot. It is the invocation being wrong, the same as a
3
+ * malformed request, so it is refused as a usage error naming the path and the reason, and no code
4
+ * is given: an answer computed without the path could hand out a code the unread path carries.
5
+ */
6
+ /** What allocate could not read: which input it was, where (when the read had a path), and the reason. */
7
+ export declare class UnreadableInput extends Error {
8
+ readonly subject: string;
9
+ readonly path: string;
10
+ readonly reason: string;
11
+ readonly name = "UnreadableInput";
12
+ constructor(subject: string, path: string, reason: string);
13
+ }
14
+ /**
15
+ * The read's answer, or the refusal when the operating system refuses it. Any refusal the OS gives
16
+ * is the same fault here: not permitted, not the kind of path the read expects, or not there. A
17
+ * throw that carries no OS code is not a read failing, and is left to surface.
18
+ */
19
+ export declare function readOrRefuse<T>(subject: string, path: string, read: () => T): T;
20
+ /** Throw the refusal a failed read of `path` is, or the throw itself when it is not a read failing. */
21
+ export declare function refuse(subject: string, path: string, err: unknown): never;
22
+ /** The one stderr line a refusal is written as, in allocate's usage-error form. */
23
+ export declare function refusalLine(refusal: UnreadableInput): string;
24
+ //# sourceMappingURL=unreadable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unreadable.d.ts","sourceRoot":"","sources":["../../src/cli-allocate/unreadable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0GAA0G;AAC1G,qBAAa,eAAgB,SAAQ,KAAK;IAItC,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IALzB,SAAkB,IAAI,qBAAqB;gBAGhC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM;CAI1B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAM/E;AAED,uGAAuG;AACvG,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,KAAK,CAIzE;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A path allocate is meant to read and cannot. It is the invocation being wrong, the same as a
3
+ * malformed request, so it is refused as a usage error naming the path and the reason, and no code
4
+ * is given: an answer computed without the path could hand out a code the unread path carries.
5
+ */
6
+ /** What allocate could not read: which input it was, where (when the read had a path), and the reason. */
7
+ export class UnreadableInput extends Error {
8
+ subject;
9
+ path;
10
+ reason;
11
+ name = "UnreadableInput";
12
+ constructor(subject, path, reason) {
13
+ super(path === "" ? `${subject} could not be read (${reason})` : `${subject} could not be read: ${path} (${reason})`);
14
+ this.subject = subject;
15
+ this.path = path;
16
+ this.reason = reason;
17
+ }
18
+ }
19
+ /**
20
+ * The read's answer, or the refusal when the operating system refuses it. Any refusal the OS gives
21
+ * is the same fault here: not permitted, not the kind of path the read expects, or not there. A
22
+ * throw that carries no OS code is not a read failing, and is left to surface.
23
+ */
24
+ export function readOrRefuse(subject, path, read) {
25
+ try {
26
+ return read();
27
+ }
28
+ catch (err) {
29
+ return refuse(subject, path, err);
30
+ }
31
+ }
32
+ /** Throw the refusal a failed read of `path` is, or the throw itself when it is not a read failing. */
33
+ export function refuse(subject, path, err) {
34
+ const errno = err;
35
+ if (!(err instanceof Error) || typeof errno.code !== "string")
36
+ throw err;
37
+ throw new UnreadableInput(subject, errno.path ?? path, err.message);
38
+ }
39
+ /** The one stderr line a refusal is written as, in allocate's usage-error form. */
40
+ export function refusalLine(refusal) {
41
+ return `spec-controller allocate: usage error — ${refusal.message}`;
42
+ }
43
+ //# sourceMappingURL=unreadable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unreadable.js","sourceRoot":"","sources":["../../src/cli-allocate/unreadable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0GAA0G;AAC1G,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAI7B;IACA;IACA;IALO,IAAI,GAAG,iBAAiB,CAAC;IAE3C,YACW,OAAe,EACf,IAAY,EACZ,MAAc;QAEvB,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,uBAAuB,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,uBAAuB,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC;QAJ7G,YAAO,GAAP,OAAO,CAAQ;QACf,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;IAGzB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAI,OAAe,EAAE,IAAY,EAAE,IAAa;IAC1E,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;AACH,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,IAAY,EAAE,GAAY;IAChE,MAAM,KAAK,GAAG,GAA4B,CAAC;IAC3C,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,MAAM,GAAG,CAAC;IACzE,MAAM,IAAI,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AACtE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,WAAW,CAAC,OAAwB;IAClD,OAAO,2CAA2C,OAAO,CAAC,OAAO,EAAE,CAAC;AACtE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-controller",
3
- "version": "0.1.0-alpha.37",
3
+ "version": "0.1.0-alpha.38",
4
4
  "description": "Reconcile the behaviour your scenarios name against the evidence that actually ran — the deterministic `balance` CLI.",
5
5
  "keywords": [
6
6
  "reconciliation",
@@ -25,7 +25,7 @@
25
25
  "@cucumber/gherkin": "^41.0.0",
26
26
  "@cucumber/messages": "^34.0.1",
27
27
  "yaml": "^2.9.0",
28
- "@3f-consulting/spec-controller-core": "0.1.0-alpha.37"
28
+ "@3f-consulting/spec-controller-core": "0.1.0-alpha.38"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"