@pmelab/gtd 58.0.0 → 58.2.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -2
- package/dist/chunks/{Schema-C6fLlQzx.mjs → Schema-B3o6eRNU.mjs} +637 -3957
- package/dist/chunks/{reserved-BzAFXuz_.mjs → reserved-lMYCaapY.mjs} +1 -1
- package/dist/chunks/{review-CVqRS0mc.mjs → review-Bfaror38.mjs} +2 -2
- package/dist/chunks/{tail-JQzB21Q3.mjs → tail-Bd7L940p.mjs} +1 -1
- package/dist/chunks/testkit-JDtpNKyi.mjs +10410 -0
- package/dist/gtd.bundle.mjs +8 -8
- package/dist/testkit/gherkin.mjs +283 -0
- package/dist/testkit.mjs +2 -15461
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +3 -3
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/package.json +8 -1
- package/src/cli/Cli.test.ts +1 -1
- package/src/cli/History.test.ts +1 -1
- package/src/cli/Metrics.test.ts +1 -1
- package/src/cli/Program.test.ts +1 -1
- package/src/cli/Strays.test.ts +1 -1
- package/src/edge/Edge.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/emit/GitScript.ts +1 -1
- package/src/emit/OutcomeScript.ts +1 -1
- package/src/gherkin/git-control.feature +78 -0
- package/src/{testkit/steps.ts → gherkin/index.ts} +55 -4
- package/src/gherkin/module-config.feature +24 -0
- package/src/gherkin/tiny.fixture.ts +8 -0
- package/src/platform/Git.test.ts +2 -2
- package/src/platform/RetainedHistory.test.ts +2 -2
- package/src/platform/Workspace.test.ts +2 -2
- package/src/platform/WorktreeState.test.ts +1 -1
- package/src/step/planStart.test.ts +1 -1
- package/src/testkit/TestRepo.test.ts +118 -2
- package/src/testkit/TestRepo.ts +64 -10
- package/src/testkit/index.ts +16 -9
- package/src/{testkit → twins}/EmittedScriptRecognizer.ts +12 -14
- package/src/{testkit → twins}/FlowScripts.ts +9 -8
- package/src/{testkit → twins}/InMemRepo.ts +26 -1
- package/src/{testkit → twins}/Layers.ts +14 -3
- package/src/twins/index.ts +13 -0
- package/src/workflow/discovery.test.ts +1 -1
- package/src/workflow/discovery.ts +3 -3
- package/src/workflow/load.ts +3 -3
- /package/src/{testkit → gherkin}/testkit.feature +0 -0
- /package/src/{testkit → twins}/EmittedScriptRecognizer.test.ts +0 -0
- /package/src/{testkit → twins}/EmittedScripts.test.ts +0 -0
- /package/src/{testkit → twins}/FakeGitOperations.ts +0 -0
- /package/src/{testkit → twins}/FlowScripts.test.ts +0 -0
- /package/src/{testkit → twins}/GitTiers.test.ts +0 -0
- /package/src/{testkit → twins}/GitTiers.ts +0 -0
- /package/src/{testkit → twins}/Layers.test.ts +0 -0
- /package/src/{testkit → twins}/StrictGitOperations.ts +0 -0
- /package/src/{testkit → twins}/cliIo.ts +0 -0
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -38979,7 +38979,7 @@ const WORKFLOW_MODULE = "gtd.config.ts";
|
|
|
38979
38979
|
* The file names gtd read repository settings from before `gtd.config.ts` took
|
|
38980
38980
|
* them over. Still looked for, only to refuse them: a leftover one would
|
|
38981
38981
|
* otherwise be silently ignored. Shared with the in-memory counterpart in
|
|
38982
|
-
* `src/
|
|
38982
|
+
* `src/twins/Layers.ts`, so both tiers refuse the same names.
|
|
38983
38983
|
*/
|
|
38984
38984
|
const LEGACY_RC_FILES = [
|
|
38985
38985
|
".gtdrc",
|
|
@@ -39011,7 +39011,7 @@ const legacyRcFileAt = (path) => {
|
|
|
39011
39011
|
* the user's home dir (inclusive, when it is an ancestor) or after reaching the
|
|
39012
39012
|
* filesystem root — whichever comes first. Returned innermost→outermost.
|
|
39013
39013
|
* Exported (via the barrel) for the same reason `SEARCH_PLACES` is — one
|
|
39014
|
-
* shared implementation, not a second copy in `src/
|
|
39014
|
+
* shared implementation, not a second copy in `src/twins/Layers.ts` that
|
|
39015
39015
|
* could silently diverge.
|
|
39016
39016
|
*/
|
|
39017
39017
|
const walkUp = (from, home) => {
|
|
@@ -39045,7 +39045,7 @@ const workflowModule = (root, home) => try_({
|
|
|
39045
39045
|
* reads; discovery is effectful and environment-dependent: `.Live` reads
|
|
39046
39046
|
* REAL disk, so an `@inmem` scenario's fake
|
|
39047
39047
|
* `Workspace` — a pure in-memory `Map`, no real files anywhere on disk —
|
|
39048
|
-
* cannot back it. `src/
|
|
39048
|
+
* cannot back it. `src/twins/Layers.ts` provides the in-memory
|
|
39049
39049
|
* counterpart instead, mirroring how `Workspace`/`Host`/`GitService` already
|
|
39050
39050
|
* each have a production and a fake layer behind one Tag.
|
|
39051
39051
|
*/
|
|
@@ -39295,13 +39295,13 @@ const shippedModules = () => shippedMemo ??= (async () => {
|
|
|
39295
39295
|
missing
|
|
39296
39296
|
};
|
|
39297
39297
|
})();
|
|
39298
|
-
/** The file each subpath a config may import serves: all but `flows`, the engine's own, and
|
|
39298
|
+
/** The file each subpath a config may import serves: all but `flows`, the engine's own, and the testkit's. */
|
|
39299
39299
|
const subpathFiles = (condition) => {
|
|
39300
39300
|
const { root, pkg } = ownPackage();
|
|
39301
39301
|
const files = /* @__PURE__ */ new Map();
|
|
39302
39302
|
for (const [subpath, target] of Object.entries(pkg.exports)) {
|
|
39303
39303
|
const file = typeof target === "string" ? target : target[condition];
|
|
39304
|
-
if (subpath === "./flows" || subpath
|
|
39304
|
+
if (subpath === "./flows" || subpath.startsWith("./testkit") || file === void 0) continue;
|
|
39305
39305
|
files.set(`${PACKAGE}/${subpath.replace(/^\.\//, "")}`, join(root, file));
|
|
39306
39306
|
}
|
|
39307
39307
|
return files;
|
|
@@ -40918,7 +40918,7 @@ const fetchHistoryRefs = (remote) => [
|
|
|
40918
40918
|
`after=$(git for-each-ref --format='x' 'refs/gtd/history/' | wc -l)`,
|
|
40919
40919
|
`printf '%s history refs arrived\\n' "$((after - before))"`
|
|
40920
40920
|
].join("\n");
|
|
40921
|
-
/** Marks an emitted block as print-only — mirrors `OutcomeScript.ts`'s `OUTCOME_MARKER` verbatim, so `src/
|
|
40921
|
+
/** Marks an emitted block as print-only — mirrors `OutcomeScript.ts`'s `OUTCOME_MARKER` verbatim, so `src/twins/EmittedScriptRecognizer.ts` recognizes either path's output identically. */
|
|
40922
40922
|
const OUTCOME_MARKER$1 = "# gtd: outcome (print-only)";
|
|
40923
40923
|
const printfLine$1 = (fmt, args) => `${OUTCOME_MARKER$1}\nprintf ${shellQuote$1(fmt.replace(/\n/g, "\\n"))} ${args.join(" ")}`;
|
|
40924
40924
|
const renderOutcome = (outcome) => {
|
|
@@ -41032,7 +41032,7 @@ const emitScripts = (required = [], optional = []) => ({
|
|
|
41032
41032
|
required: assembleScript(required),
|
|
41033
41033
|
optional: assembleScript(optional)
|
|
41034
41034
|
});
|
|
41035
|
-
/** Exported so `src/
|
|
41035
|
+
/** Exported so `src/twins/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
|
|
41036
41036
|
const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — pipe it into `sh` to land the turn";
|
|
41037
41037
|
const PRESENTATION_ONLY_COMMENT = "# presentation only — safe to skip";
|
|
41038
41038
|
const PRESENTATION_FAILURE_WARNING = "printf 'gtd: presentation-only follow-up failed — continuing\\n' >&2";
|
|
@@ -41070,7 +41070,7 @@ const combinedScript = (required, optional) => {
|
|
|
41070
41070
|
const printfLine = (fmt, args) => `${OUTCOME_MARKER}\nprintf ${shellQuote$1(fmt.replace(/\n/g, "\\n"))} ${args.join(" ")}`;
|
|
41071
41071
|
/**
|
|
41072
41072
|
* Marks an emitted block as print-only. Load-bearing beyond documentation:
|
|
41073
|
-
* `src/
|
|
41073
|
+
* `src/twins/EmittedScriptRecognizer.ts` recognizes an outcome block by
|
|
41074
41074
|
* this line, and a bare `printf` would be ambiguous with a workflow
|
|
41075
41075
|
* `script:` command that happens to start the same way.
|
|
41076
41076
|
*/
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { n as testRepo, t as runsItsScript } from "../chunks/testkit-JDtpNKyi.mjs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import assert from "node:assert";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { Given, QuickPickleWorld, Then, When, setWorldConstructor } from "quickpickle";
|
|
6
|
+
//#region src/gherkin/index.ts
|
|
7
|
+
var TestkitWorld = class extends QuickPickleWorld {
|
|
8
|
+
constructor(context, info) {
|
|
9
|
+
super(context, info);
|
|
10
|
+
}
|
|
11
|
+
gtdConfig = void 0;
|
|
12
|
+
env = {};
|
|
13
|
+
commands = [];
|
|
14
|
+
/** Per step, what its scripted actor does, in the order the scenario declared it. */
|
|
15
|
+
actions = /* @__PURE__ */ new Map();
|
|
16
|
+
verdicts = /* @__PURE__ */ new Map();
|
|
17
|
+
/** Per judge step, a verdict for each text its questions may contain; the first match answers. */
|
|
18
|
+
conditionalVerdicts = /* @__PURE__ */ new Map();
|
|
19
|
+
created = void 0;
|
|
20
|
+
configure(config) {
|
|
21
|
+
this.assertNotCreated("a gtd config");
|
|
22
|
+
this.gtdConfig = config;
|
|
23
|
+
}
|
|
24
|
+
extendConfig(tail) {
|
|
25
|
+
this.assertNotCreated("a gtd config");
|
|
26
|
+
if (this.gtdConfig === void 0) throw new Error("no gtd config to extend");
|
|
27
|
+
if (typeof this.gtdConfig !== "string") throw new Error("a gtd config from a module cannot be extended");
|
|
28
|
+
this.gtdConfig += `\n${tail}`;
|
|
29
|
+
}
|
|
30
|
+
command(command) {
|
|
31
|
+
if (this.created === void 0) this.commands.push(command);
|
|
32
|
+
else this.created.command(command);
|
|
33
|
+
}
|
|
34
|
+
setEnv(name, value) {
|
|
35
|
+
this.assertNotCreated(`the environment variable ${name}`);
|
|
36
|
+
this.env[name] = value;
|
|
37
|
+
}
|
|
38
|
+
assertNotCreated(what) {
|
|
39
|
+
if (this.created !== void 0) throw new Error(`${what} must be set before any step that touches the repository`);
|
|
40
|
+
}
|
|
41
|
+
repo() {
|
|
42
|
+
if (this.gtdConfig === void 0) throw new Error("no gtd config: add a `gtd config` step first");
|
|
43
|
+
return this.created ??= testRepo({
|
|
44
|
+
workflow: this.gtdConfig,
|
|
45
|
+
env: this.env,
|
|
46
|
+
commands: this.commands
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
act(step, action) {
|
|
50
|
+
this.actions.set(step, [...this.actions.get(step) ?? [], action]);
|
|
51
|
+
}
|
|
52
|
+
async drive() {
|
|
53
|
+
const actors = {};
|
|
54
|
+
for (const [step, actions] of this.actions) actors[step] = async (beat, repo) => {
|
|
55
|
+
for (const action of actions) await action(beat, repo);
|
|
56
|
+
};
|
|
57
|
+
for (const [step, verdict] of this.verdicts) {
|
|
58
|
+
if (this.actions.has(step)) throw new Error(`step "${step}" has a judge verdict and queued actions; the actions would never run`);
|
|
59
|
+
actors[step] = () => verdict;
|
|
60
|
+
}
|
|
61
|
+
for (const [step, choices] of this.conditionalVerdicts) {
|
|
62
|
+
if (this.actions.has(step) || this.verdicts.has(step)) throw new Error(`step "${step}" has a conditional verdict and another script`);
|
|
63
|
+
actors[step] = async (_, repo) => {
|
|
64
|
+
const { stdout } = await repo.gtd("judge");
|
|
65
|
+
const hit = choices.find(([text]) => stdout.includes(text));
|
|
66
|
+
if (hit === void 0) throw new Error(`judge step "${step}" matched no verdict:\n${stdout}`);
|
|
67
|
+
return hit[1];
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
await this.repo().drive(actors);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
setWorldConstructor(TestkitWorld);
|
|
74
|
+
const withNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
|
|
75
|
+
const args = (list) => list.split(" ").filter((arg) => arg !== "");
|
|
76
|
+
Given("a gtd config with:", (world, source) => {
|
|
77
|
+
world.configure(withNewline(source));
|
|
78
|
+
});
|
|
79
|
+
Given("a gtd config from the module {string}", async (world, path) => {
|
|
80
|
+
world.configure({ ...await import(pathToFileURL(resolve(path)).href) });
|
|
81
|
+
});
|
|
82
|
+
Given("the gtd config ends with:", (world, source) => {
|
|
83
|
+
world.extendConfig(withNewline(source));
|
|
84
|
+
});
|
|
85
|
+
Given("an environment variable {string} set to {string}", (world, name, value) => {
|
|
86
|
+
world.setEnv(name, value);
|
|
87
|
+
});
|
|
88
|
+
Given("the command {string} exits {int}", (world, run, exit) => {
|
|
89
|
+
world.command({
|
|
90
|
+
run,
|
|
91
|
+
exit
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
Given("the command {string} exits {int} with:", (world, run, exit, output) => {
|
|
95
|
+
world.command({
|
|
96
|
+
run,
|
|
97
|
+
exit,
|
|
98
|
+
output: withNewline(output)
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
Given("a branch {string}", (world, name) => {
|
|
102
|
+
world.repo().branch(name);
|
|
103
|
+
});
|
|
104
|
+
Given("{string} is checked out", (world, name) => {
|
|
105
|
+
world.repo().checkout(name);
|
|
106
|
+
});
|
|
107
|
+
Given("the ref {string} points at HEAD", (world, name) => {
|
|
108
|
+
world.repo().ref(name);
|
|
109
|
+
});
|
|
110
|
+
Given("a file {string} with:", (world, path, content) => {
|
|
111
|
+
world.repo().write(path, withNewline(content));
|
|
112
|
+
});
|
|
113
|
+
Given("the file {string} is deleted", (world, path) => {
|
|
114
|
+
world.repo().remove(path);
|
|
115
|
+
});
|
|
116
|
+
Given("the text {string} in {string} is replaced with {string}", (world, text, path, replacement) => {
|
|
117
|
+
const content = world.repo().read(path) ?? "";
|
|
118
|
+
assert.ok(content.includes(text), `${path}: ${content}`);
|
|
119
|
+
world.repo().write(path, content.replace(text, () => replacement));
|
|
120
|
+
});
|
|
121
|
+
Given("{string} gets appended:", (world, path, text) => {
|
|
122
|
+
const content = world.repo().read(path) ?? "";
|
|
123
|
+
world.repo().write(path, `${content.trimEnd()}\n\n${text}\n`);
|
|
124
|
+
});
|
|
125
|
+
Given("the working tree is committed as {string}", (world, message) => {
|
|
126
|
+
world.repo().commit(message);
|
|
127
|
+
});
|
|
128
|
+
Given("the working tree is committed with the message:", (world, message) => {
|
|
129
|
+
world.repo().commit(message);
|
|
130
|
+
});
|
|
131
|
+
Given("step {string} changes nothing", (world, step) => {
|
|
132
|
+
world.act(step, () => void 0);
|
|
133
|
+
});
|
|
134
|
+
Given("step {string} from now on changes nothing", (world, step) => {
|
|
135
|
+
world.actions.set(step, [() => void 0]);
|
|
136
|
+
});
|
|
137
|
+
Given("step {string} is no longer scripted", (world, step) => {
|
|
138
|
+
world.actions.delete(step);
|
|
139
|
+
});
|
|
140
|
+
Given("step {string} writes {string} with:", (world, step, path, content) => {
|
|
141
|
+
world.act(step, (_, repo) => repo.write(path, withNewline(content)));
|
|
142
|
+
});
|
|
143
|
+
Given("step {string}, when prompted with {string}, writes {string} with:", (world, step, text, path, content) => {
|
|
144
|
+
world.act(step, (beat, repo) => {
|
|
145
|
+
if (beat.content.includes(text)) repo.write(path, withNewline(content));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
Given("step {string} appends to {string}:", (world, step, path, text) => {
|
|
149
|
+
world.act(step, (_, repo) => {
|
|
150
|
+
repo.write(path, `${(repo.read(path) ?? "").trimEnd()}\n\n${withNewline(text)}`);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
Given("step {string} replaces the text {string} in {string} with {string}", (world, step, text, path, replacement) => {
|
|
154
|
+
world.act(step, (_, repo) => {
|
|
155
|
+
const content = repo.read(path) ?? "";
|
|
156
|
+
assert.ok(content.includes(text), `${path}: ${content}`);
|
|
157
|
+
repo.write(path, content.replace(text, () => replacement));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
Given("step {string} deletes {string}", (world, step, path) => {
|
|
161
|
+
world.act(step, (_, repo) => repo.remove(path));
|
|
162
|
+
});
|
|
163
|
+
Given("step {string} is prompted with {string}", (world, step, text) => {
|
|
164
|
+
world.act(step, (beat) => {
|
|
165
|
+
if (!beat.content.includes(text)) throw new Error(`step "${step}" was not prompted with "${text}":\n${beat.content}`);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
Given("step {string} rewrites {string} with:", (world, step, path, content) => {
|
|
169
|
+
world.act(step, (_, repo) => {
|
|
170
|
+
if (repo.read(path) !== void 0) repo.write(path, withNewline(content));
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
Given("step {string} runs its script", (world, step) => {
|
|
174
|
+
world.act(step, runsItsScript);
|
|
175
|
+
});
|
|
176
|
+
Given("judge step {string} answers:", (world, step, verdict) => {
|
|
177
|
+
world.verdicts.set(step, String(verdict));
|
|
178
|
+
});
|
|
179
|
+
Given("judge step {string}, when its questions contain {string}, answers:", (world, step, text, verdict) => {
|
|
180
|
+
world.conditionalVerdicts.set(step, [...world.conditionalVerdicts.get(step) ?? [], [text, String(verdict)]]);
|
|
181
|
+
});
|
|
182
|
+
When("gtd starts workflow {string}", async (world, workflow) => {
|
|
183
|
+
await world.repo().start(workflow);
|
|
184
|
+
});
|
|
185
|
+
When("gtd starts workflow {string} with {string}", async (world, workflow, list) => {
|
|
186
|
+
await world.repo().start(workflow, ...args(list));
|
|
187
|
+
});
|
|
188
|
+
When("gtd opens door {string}", async (world, door) => {
|
|
189
|
+
await world.repo().door(door);
|
|
190
|
+
});
|
|
191
|
+
When("gtd opens door {string} with {string}", async (world, door, list) => {
|
|
192
|
+
await world.repo().door(door, ...args(list));
|
|
193
|
+
});
|
|
194
|
+
When("gtd lands", async (world) => {
|
|
195
|
+
await world.repo().land();
|
|
196
|
+
});
|
|
197
|
+
When("the process is driven", async (world) => {
|
|
198
|
+
await world.drive();
|
|
199
|
+
});
|
|
200
|
+
Then("opening door {string} is refused with {string}", async (world, door, message) => {
|
|
201
|
+
await assert.rejects(world.repo().door(door), (error) => error.message.includes(message));
|
|
202
|
+
});
|
|
203
|
+
Then("starting workflow {string} with {string} is refused with {string}", async (world, workflow, list, message) => {
|
|
204
|
+
await assert.rejects(world.repo().start(workflow, ...args(list)), (error) => error.message.includes(message));
|
|
205
|
+
});
|
|
206
|
+
Then("landing is refused with {string}", async (world, message) => {
|
|
207
|
+
await assert.rejects(world.repo().land(), (error) => error.message.includes(message));
|
|
208
|
+
});
|
|
209
|
+
Then("driving the process is refused with {string}", async (world, message) => {
|
|
210
|
+
await assert.rejects(world.drive(), (error) => error.message.includes(message));
|
|
211
|
+
});
|
|
212
|
+
Then("driving the process is refused without {string}", async (world, message) => {
|
|
213
|
+
await assert.rejects(world.drive(), (error) => !error.message.includes(message));
|
|
214
|
+
});
|
|
215
|
+
Then("the process rests at {string}", async (world, step) => {
|
|
216
|
+
assert.strictEqual((await world.repo().next()).state, step);
|
|
217
|
+
});
|
|
218
|
+
Then("the resting step's content contains {string}", async (world, text) => {
|
|
219
|
+
const { content } = await world.repo().next();
|
|
220
|
+
assert.ok(content.includes(text), content);
|
|
221
|
+
});
|
|
222
|
+
Then("the resting step's content contains the hash of the commit {string}", async (world, subject) => {
|
|
223
|
+
const repo = world.repo();
|
|
224
|
+
const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
|
|
225
|
+
assert.ok(hash !== void 0, `no commit "${subject}"`);
|
|
226
|
+
const { content } = await repo.next();
|
|
227
|
+
assert.ok(content.includes(hash), content);
|
|
228
|
+
});
|
|
229
|
+
Then("the resting step's content does not contain {string}", async (world, text) => {
|
|
230
|
+
const { content } = await world.repo().next();
|
|
231
|
+
assert.ok(!content.includes(text), content);
|
|
232
|
+
});
|
|
233
|
+
Then("the last commit subject is {string}", (world, subject) => {
|
|
234
|
+
assert.strictEqual(world.repo().subjects().at(-1), subject);
|
|
235
|
+
});
|
|
236
|
+
Then("the commit subjects end with:", (world, list) => {
|
|
237
|
+
const expected = list.split("\n").filter((line) => line.trim() !== "");
|
|
238
|
+
assert.deepStrictEqual(world.repo().subjects().slice(-expected.length), expected);
|
|
239
|
+
});
|
|
240
|
+
const outputOf = async (world, list) => {
|
|
241
|
+
const result = await world.repo().gtd(...args(list));
|
|
242
|
+
assert.strictEqual(result.exitCode, 0, result.stderr);
|
|
243
|
+
return result.stdout;
|
|
244
|
+
};
|
|
245
|
+
Then("the output of gtd {string} contains {string}", async (world, list, text) => {
|
|
246
|
+
const out = await outputOf(world, list);
|
|
247
|
+
assert.ok(out.includes(text), out);
|
|
248
|
+
});
|
|
249
|
+
Then("the output of gtd {string} does not contain {string}", async (world, list, text) => {
|
|
250
|
+
const out = await outputOf(world, list);
|
|
251
|
+
assert.ok(!out.includes(text), out);
|
|
252
|
+
});
|
|
253
|
+
Then("the output lines of gtd {string} start with:", async (world, list, prefixes) => {
|
|
254
|
+
const out = (await outputOf(world, list)).split("\n").filter((line) => line !== "");
|
|
255
|
+
const expected = prefixes.split("\n").filter((line) => line.trim() !== "");
|
|
256
|
+
assert.strictEqual(out.length, expected.length, out.join("\n"));
|
|
257
|
+
out.forEach((line, i) => assert.ok(line.startsWith(expected[i]), out.join("\n")));
|
|
258
|
+
});
|
|
259
|
+
Then("{string} contains {string}", (world, path, text) => {
|
|
260
|
+
assert.ok(world.repo().read(path)?.includes(text), `${path}: ${world.repo().read(path)}`);
|
|
261
|
+
});
|
|
262
|
+
Then("{string} contains:", (world, path, text) => {
|
|
263
|
+
assert.ok(world.repo().read(path)?.includes(text), `${path}: ${world.repo().read(path)}`);
|
|
264
|
+
});
|
|
265
|
+
Then("{string} contains the short hash of the commit {string}", (world, path, subject) => {
|
|
266
|
+
const repo = world.repo();
|
|
267
|
+
const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
|
|
268
|
+
assert.ok(hash !== void 0, `no commit "${subject}"`);
|
|
269
|
+
assert.ok(repo.read(path)?.includes(hash.slice(0, 7)), `${path}: ${repo.read(path)}`);
|
|
270
|
+
});
|
|
271
|
+
Then("{string} contains {string} exactly once", (world, path, text) => {
|
|
272
|
+
const content = world.repo().read(path) ?? "";
|
|
273
|
+
assert.strictEqual(content.split(text).length - 1, 1, `${path}: ${content}`);
|
|
274
|
+
});
|
|
275
|
+
Then("{string} does not contain {string}", (world, path, text) => {
|
|
276
|
+
const content = world.repo().read(path);
|
|
277
|
+
assert.ok(content !== void 0 && !content.includes(text), `${path}: ${content}`);
|
|
278
|
+
});
|
|
279
|
+
Then("{string} does not exist", (world, path) => {
|
|
280
|
+
assert.strictEqual(world.repo().read(path), void 0);
|
|
281
|
+
});
|
|
282
|
+
//#endregion
|
|
283
|
+
export { TestkitWorld };
|