@nonameolsson/nightjar 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +405 -2
- package/dist/account.d.ts +12 -0
- package/dist/account.js +57 -0
- package/dist/account.js.map +1 -0
- package/dist/adapter-module.d.ts +18 -0
- package/dist/adapter-module.js +83 -0
- package/dist/adapter-module.js.map +1 -0
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +160 -0
- package/dist/adapters.js.map +1 -0
- package/dist/args.d.ts +13 -0
- package/dist/args.js +84 -0
- package/dist/args.js.map +1 -0
- package/dist/ask.d.ts +6 -0
- package/dist/ask.js +27 -0
- package/dist/ask.js.map +1 -0
- package/dist/azure-devops/api.d.ts +34 -0
- package/dist/azure-devops/api.js +98 -0
- package/dist/azure-devops/api.js.map +1 -0
- package/dist/azure-devops/code-host.d.ts +36 -0
- package/dist/azure-devops/code-host.js +185 -0
- package/dist/azure-devops/code-host.js.map +1 -0
- package/dist/azure-devops/fake-azure-devops.d.ts +90 -0
- package/dist/azure-devops/fake-azure-devops.js +728 -0
- package/dist/azure-devops/fake-azure-devops.js.map +1 -0
- package/dist/azure-devops/index.d.ts +8 -0
- package/dist/azure-devops/index.js +11 -0
- package/dist/azure-devops/index.js.map +1 -0
- package/dist/azure-devops/sign-in.d.ts +11 -0
- package/dist/azure-devops/sign-in.js +68 -0
- package/dist/azure-devops/sign-in.js.map +1 -0
- package/dist/azure-devops/text.d.ts +1 -0
- package/dist/azure-devops/text.js +41 -0
- package/dist/azure-devops/text.js.map +1 -0
- package/dist/azure-devops/tracker.d.ts +44 -0
- package/dist/azure-devops/tracker.js +333 -0
- package/dist/azure-devops/tracker.js.map +1 -0
- package/dist/build.d.ts +75 -0
- package/dist/build.js +698 -0
- package/dist/build.js.map +1 -0
- package/dist/check.d.ts +8 -0
- package/dist/check.js +25 -0
- package/dist/check.js.map +1 -0
- package/dist/checks.d.ts +45 -0
- package/dist/checks.js +243 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/code-host.d.ts +19 -0
- package/dist/code-host.js +2 -0
- package/dist/code-host.js.map +1 -0
- package/dist/config.d.ts +86 -0
- package/dist/config.js +294 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/case.d.ts +4 -0
- package/dist/contract/case.js +2 -0
- package/dist/contract/case.js.map +1 -0
- package/dist/contract/code-host.d.ts +24 -0
- package/dist/contract/code-host.js +108 -0
- package/dist/contract/code-host.js.map +1 -0
- package/dist/contract/command.d.ts +2 -0
- package/dist/contract/command.js +36 -0
- package/dist/contract/command.js.map +1 -0
- package/dist/contract/feature-writer.d.ts +3 -0
- package/dist/contract/feature-writer.js +101 -0
- package/dist/contract/feature-writer.js.map +1 -0
- package/dist/contract/run.d.ts +18 -0
- package/dist/contract/run.js +89 -0
- package/dist/contract/run.js.map +1 -0
- package/dist/contract/tracker.d.ts +30 -0
- package/dist/contract/tracker.js +250 -0
- package/dist/contract/tracker.js.map +1 -0
- package/dist/create.d.ts +9 -0
- package/dist/create.js +112 -0
- package/dist/create.js.map +1 -0
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fakes/code-host.d.ts +21 -0
- package/dist/fakes/code-host.js +42 -0
- package/dist/fakes/code-host.js.map +1 -0
- package/dist/fakes/harness.d.ts +7 -0
- package/dist/fakes/harness.js +68 -0
- package/dist/fakes/harness.js.map +1 -0
- package/dist/fakes/machine.d.ts +11 -0
- package/dist/fakes/machine.js +101 -0
- package/dist/fakes/machine.js.map +1 -0
- package/dist/fakes/tracker.d.ts +40 -0
- package/dist/fakes/tracker.js +116 -0
- package/dist/fakes/tracker.js.map +1 -0
- package/dist/feature-file.d.ts +12 -0
- package/dist/feature-file.js +84 -0
- package/dist/feature-file.js.map +1 -0
- package/dist/git.d.ts +9 -0
- package/dist/git.js +12 -0
- package/dist/git.js.map +1 -0
- package/dist/github/api.d.ts +37 -0
- package/dist/github/api.js +66 -0
- package/dist/github/api.js.map +1 -0
- package/dist/github/code-host.d.ts +19 -0
- package/dist/github/code-host.js +105 -0
- package/dist/github/code-host.js.map +1 -0
- package/dist/github/fake-gh.d.ts +17 -0
- package/dist/github/fake-gh.js +51 -0
- package/dist/github/fake-gh.js.map +1 -0
- package/dist/github/fake-github.d.ts +58 -0
- package/dist/github/fake-github.js +285 -0
- package/dist/github/fake-github.js.map +1 -0
- package/dist/github/labels.d.ts +3 -0
- package/dist/github/labels.js +44 -0
- package/dist/github/labels.js.map +1 -0
- package/dist/github/tracker.d.ts +28 -0
- package/dist/github/tracker.js +208 -0
- package/dist/github/tracker.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +19 -0
- package/dist/init.js +225 -0
- package/dist/init.js.map +1 -0
- package/dist/ledger.d.ts +13 -0
- package/dist/ledger.js +49 -0
- package/dist/ledger.js.map +1 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +215 -0
- package/dist/main.js.map +1 -0
- package/dist/person-config.d.ts +9 -0
- package/dist/person-config.js +46 -0
- package/dist/person-config.js.map +1 -0
- package/dist/plan.d.ts +9 -0
- package/dist/plan.js +27 -0
- package/dist/plan.js.map +1 -0
- package/dist/ports.d.ts +16 -0
- package/dist/ports.js +104 -0
- package/dist/ports.js.map +1 -0
- package/dist/prompt.d.ts +166 -0
- package/dist/prompt.js +217 -0
- package/dist/prompt.js.map +1 -0
- package/dist/queue.d.ts +20 -0
- package/dist/queue.js +94 -0
- package/dist/queue.js.map +1 -0
- package/dist/say.d.ts +2 -0
- package/dist/say.js +4 -0
- package/dist/say.js.map +1 -0
- package/dist/serve.d.ts +10 -0
- package/dist/serve.js +85 -0
- package/dist/serve.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.js +144 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +10 -0
- package/dist/settings.js +92 -0
- package/dist/settings.js.map +1 -0
- package/dist/signals.d.ts +5 -0
- package/dist/signals.js +13 -0
- package/dist/signals.js.map +1 -0
- package/dist/skills.d.ts +4 -0
- package/dist/skills.js +51 -0
- package/dist/skills.js.map +1 -0
- package/dist/status-shape.d.ts +53 -0
- package/dist/status-shape.js +3 -0
- package/dist/status-shape.js.map +1 -0
- package/dist/status.d.ts +23 -0
- package/dist/status.js +56 -0
- package/dist/status.js.map +1 -0
- package/dist/tracker.d.ts +56 -0
- package/dist/tracker.js +12 -0
- package/dist/tracker.js.map +1 -0
- package/dist/workflow.d.ts +33 -0
- package/dist/workflow.js +53 -0
- package/dist/workflow.js.map +1 -0
- package/dist/worktree.d.ts +44 -0
- package/dist/worktree.js +193 -0
- package/dist/worktree.js.map +1 -0
- package/docs/ticket-contract.md +95 -0
- package/package.json +74 -5
- package/plugin/nightjar-status/.claude-plugin/plugin.json +5 -0
- package/plugin/nightjar-status/hooks/format.ts +48 -0
- package/plugin/nightjar-status/hooks/hooks.json +1 -0
- package/plugin/nightjar-status/hooks/register.ts +51 -0
- package/schema/factory.schema.json +437 -0
- package/skills/nightjar-feature/SKILL.md +73 -0
- package/src/status-shape.ts +44 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// factory.json; schema/factory.schema.json is generated from the same definition.
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { DEFAULT_SIGNAL_NAMES, RESERVED_NAMES } from './signals.js';
|
|
6
|
+
import { why } from './say.js';
|
|
7
|
+
export class ConfigError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export const CONFIG_FILE = 'factory.json';
|
|
10
|
+
const repo = z
|
|
11
|
+
.string()
|
|
12
|
+
.regex(/^[^/\s]+\/[^/\s]+$/, 'must be owner/name')
|
|
13
|
+
.optional()
|
|
14
|
+
.meta({ description: 'owner/name on GitHub; when left out, read from the origin remote' });
|
|
15
|
+
const GitHubTracker = z
|
|
16
|
+
.strictObject({ type: z.literal('github'), repo })
|
|
17
|
+
.meta({ description: 'GitHub Issues: a Feature is an issue with sub-issues, its Tickets' });
|
|
18
|
+
const missingOr = (what) => (issue) => issue.input === undefined ? `missing: ${what}` : undefined;
|
|
19
|
+
const named = (what) => z.string({ error: missingOr(what) }).regex(/\S/, what);
|
|
20
|
+
const organization = named('name the organization')
|
|
21
|
+
.regex(/^[^/\s:]+$/, 'the organization name alone, as in dev.azure.com/<organization>')
|
|
22
|
+
.meta({ description: 'The organization, as in dev.azure.com/<organization>' });
|
|
23
|
+
const AzureDevOpsTracker = z
|
|
24
|
+
.strictObject({
|
|
25
|
+
type: z.literal('azure-devops'),
|
|
26
|
+
organization,
|
|
27
|
+
project: named('name the project').meta({ description: 'The project that holds the Features' }),
|
|
28
|
+
featureType: named('name the work item type that plays the Feature').meta({
|
|
29
|
+
description: 'The work item type that plays the Feature, such as Feature, Epic or Product Backlog Item'
|
|
30
|
+
}),
|
|
31
|
+
ticketType: z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(/\S/, 'name the work item type of a Ticket, or leave ticketType out')
|
|
34
|
+
.optional()
|
|
35
|
+
.meta({
|
|
36
|
+
description: 'The work item type nightjar create makes each Ticket as, such as User Story, Product Backlog Item or Issue. Left out, nightjar create makes no Tickets.'
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
.meta({
|
|
40
|
+
description: 'Azure DevOps Boards: a Feature is a work item of featureType, its child work items its Tickets'
|
|
41
|
+
});
|
|
42
|
+
const GitHubCodeHost = z
|
|
43
|
+
.strictObject({ type: z.literal('github'), repo })
|
|
44
|
+
.meta({ description: 'GitHub: the branch is pushed to origin and a draft pull request opened' });
|
|
45
|
+
const AzureReposCodeHost = z
|
|
46
|
+
.strictObject({
|
|
47
|
+
type: z.literal('azure-devops'),
|
|
48
|
+
organization,
|
|
49
|
+
project: named('name the project').meta({ description: 'The project that holds the repo' }),
|
|
50
|
+
repo: named('name the repo').meta({ description: 'The Git repo on Azure Repos, by name' })
|
|
51
|
+
})
|
|
52
|
+
.meta({
|
|
53
|
+
description: 'Azure Repos: the branch is pushed to the repo and a draft pull request opened, signed in as for Azure DevOps Boards'
|
|
54
|
+
});
|
|
55
|
+
// An Adapter the repo brings itself: a module (docs/custom-adapters.md) that builds the Tracker or Code host.
|
|
56
|
+
const adapterModule = (what) => z
|
|
57
|
+
.strictObject({
|
|
58
|
+
type: z.literal('module'),
|
|
59
|
+
path: z
|
|
60
|
+
.string({ error: missingOr('name the module') })
|
|
61
|
+
.regex(/\S/, 'name the module')
|
|
62
|
+
.meta({
|
|
63
|
+
description: `The module that builds the ${what}, as a path from the repo's root`
|
|
64
|
+
}),
|
|
65
|
+
options: z
|
|
66
|
+
.record(z.string(), z.unknown())
|
|
67
|
+
.optional()
|
|
68
|
+
.meta({ description: 'Handed to the module as it is, for its own settings' })
|
|
69
|
+
})
|
|
70
|
+
.meta({ description: `A ${what} of the repo's own, built by a module` });
|
|
71
|
+
const modelField = z.string().regex(/\S/, 'a model is empty').optional();
|
|
72
|
+
const effortField = z.string().regex(/\S/, 'an effort is empty').optional();
|
|
73
|
+
const oneLine = (what) => z.string().regex(/^[^\r\n]*\S[^\r\n]*$/, `${what} is one line`);
|
|
74
|
+
// How many rounds a Step with an `until` runs when it names no maxRounds.
|
|
75
|
+
export const DEFAULT_ROUNDS = 3;
|
|
76
|
+
const Step = z
|
|
77
|
+
.strictObject({
|
|
78
|
+
name: oneLine('a Step name').meta({
|
|
79
|
+
description: 'The name of the Step; its commit and the pull request body name it'
|
|
80
|
+
}),
|
|
81
|
+
prompt: z
|
|
82
|
+
.string({ error: missingOr('give the Step a prompt') })
|
|
83
|
+
.regex(/\S/, 'give the Step a prompt')
|
|
84
|
+
.meta({
|
|
85
|
+
description: 'What the agent is told to do, such as "/some-skill critique"; it may name any skill. Nightjar adds the rule that nobody is at the keyboard.'
|
|
86
|
+
}),
|
|
87
|
+
point: z
|
|
88
|
+
.enum(['afterEachTicket', 'afterAllTickets'], {
|
|
89
|
+
error: missingOr('say when the Step runs: afterEachTicket or afterAllTickets')
|
|
90
|
+
})
|
|
91
|
+
.meta({
|
|
92
|
+
description: 'afterEachTicket runs after each Ticket is built; afterAllTickets runs once, after every Ticket, before the review'
|
|
93
|
+
}),
|
|
94
|
+
model: modelField.meta({ description: "The Step's own model; else the default model" }),
|
|
95
|
+
effort: effortField.meta({ description: "The Step's own effort; else the default effort" }),
|
|
96
|
+
budgetUsd: z.number().positive().optional().meta({
|
|
97
|
+
description: "The most one run of the Step may cost in dollars; else a Ticket's budget"
|
|
98
|
+
}),
|
|
99
|
+
until: z.string().regex(/\S/, 'until is empty').optional().meta({
|
|
100
|
+
description: 'A condition in plain words. After each round the agent reports whether it holds, and Nightjar runs the Step again until it does or maxRounds is reached'
|
|
101
|
+
}),
|
|
102
|
+
maxRounds: z
|
|
103
|
+
.number()
|
|
104
|
+
.int()
|
|
105
|
+
.min(1)
|
|
106
|
+
.max(10)
|
|
107
|
+
.optional()
|
|
108
|
+
.meta({
|
|
109
|
+
description: `The most rounds of a Step with an until; ${String(DEFAULT_ROUNDS)} when left out`
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
.refine((step) => step.maxRounds === undefined || step.until !== undefined, {
|
|
113
|
+
path: ['maxRounds'],
|
|
114
|
+
error: 'maxRounds needs an until'
|
|
115
|
+
});
|
|
116
|
+
const Workflow = z
|
|
117
|
+
.strictObject({
|
|
118
|
+
steps: z
|
|
119
|
+
.array(Step, { error: missingOr('list the Steps') })
|
|
120
|
+
.min(1, 'a Workflow needs at least one Step')
|
|
121
|
+
.refine((steps) => new Set(steps.map((s) => s.name)).size === steps.length, {
|
|
122
|
+
error: 'a Step name is used twice'
|
|
123
|
+
}),
|
|
124
|
+
serve: z.string().regex(/\S/, 'serve is empty').optional().meta({
|
|
125
|
+
description: "A command started in the background in the worktree before the Workflow's Steps and stopped after them, such as a dev server. It reads its port from the variables of ports.env, so it needs ports."
|
|
126
|
+
}),
|
|
127
|
+
allowedCommands: z
|
|
128
|
+
.array(z.string().regex(/\S/, 'an allowed command is empty'))
|
|
129
|
+
.optional()
|
|
130
|
+
.meta({
|
|
131
|
+
description: "Commands the Workflow's Steps may run besides the top-level allowedCommands, as Bash permission patterns without Bash( )"
|
|
132
|
+
}),
|
|
133
|
+
allowedTools: z.array(z.string().regex(/\S/, 'an allowed tool is empty')).optional().meta({
|
|
134
|
+
description: "Tools the Workflow's Steps may use besides the usual ones, as Claude Code permission rules. Committing, pushing, switching branches, the outside network and gh stay denied."
|
|
135
|
+
}),
|
|
136
|
+
localNetwork: z.boolean().optional().meta({
|
|
137
|
+
description: "true lets the Workflow's Steps reach localhost, and nothing else on the network"
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
.meta({ description: 'A named list of Steps that a Feature picks with a **Workflow:** line' });
|
|
141
|
+
const signalName = (fallback, what) => z
|
|
142
|
+
.string()
|
|
143
|
+
.regex(/\S/, 'a Signal name is empty')
|
|
144
|
+
.refine((name) => !RESERVED_NAMES.includes(name.trim().toLowerCase()), {
|
|
145
|
+
error: 'this is a triage role; Nightjar never reads or sets it'
|
|
146
|
+
})
|
|
147
|
+
.default(fallback)
|
|
148
|
+
.meta({ description: what });
|
|
149
|
+
const Signals = z
|
|
150
|
+
.strictObject({
|
|
151
|
+
go: signalName(DEFAULT_SIGNAL_NAMES.go, 'The Go: put on a Feature assigned to you to have it built. Give it the name of ready to use one mark for both'),
|
|
152
|
+
ready: signalName(DEFAULT_SIGNAL_NAMES.ready, 'A Ticket that is ready to be built'),
|
|
153
|
+
working: signalName(DEFAULT_SIGNAL_NAMES.working, 'Nightjar is working on it'),
|
|
154
|
+
needsInfo: signalName(DEFAULT_SIGNAL_NAMES.needsInfo, 'Nightjar needs an answer to go on'),
|
|
155
|
+
human: signalName(DEFAULT_SIGNAL_NAMES.human, 'A person has to take it from here')
|
|
156
|
+
})
|
|
157
|
+
.prefault({})
|
|
158
|
+
.meta({
|
|
159
|
+
description: 'The name each Signal has on a Tracker: a label on GitHub, a tag on Azure DevOps. Leave out the ones you do not rename. needs-triage and wontfix are refused.'
|
|
160
|
+
});
|
|
161
|
+
export const ConfigSchema = z
|
|
162
|
+
.strictObject({
|
|
163
|
+
$schema: z.string().optional(),
|
|
164
|
+
tracker: z
|
|
165
|
+
.discriminatedUnion('type', [GitHubTracker, AzureDevOpsTracker, adapterModule('Tracker')], {
|
|
166
|
+
error: missingOr('name the Tracker')
|
|
167
|
+
})
|
|
168
|
+
.meta({ description: 'Where the Features and Tickets live' }),
|
|
169
|
+
codeHost: z
|
|
170
|
+
.discriminatedUnion('type', [GitHubCodeHost, AzureReposCodeHost, adapterModule('Code host')], {
|
|
171
|
+
error: missingOr('name the Code host')
|
|
172
|
+
})
|
|
173
|
+
.meta({ description: 'Where the branch is pushed and the draft pull request opened' }),
|
|
174
|
+
check: z
|
|
175
|
+
.string({ error: missingOr('name the command that checks the work') })
|
|
176
|
+
.regex(/\S/, 'name the command that checks the work')
|
|
177
|
+
.meta({
|
|
178
|
+
description: 'The shell command that checks the work in the worktree, run after each Ticket; it fails with a non-zero exit'
|
|
179
|
+
}),
|
|
180
|
+
allowedCommands: z
|
|
181
|
+
.array(z.string().regex(/\S/, 'an allowed command is empty'), {
|
|
182
|
+
error: missingOr('list the commands the agent may run')
|
|
183
|
+
})
|
|
184
|
+
.meta({
|
|
185
|
+
description: 'The commands the agent may run, as Claude Code Bash permission patterns without Bash( ), such as "make test" or "make lint *". Committing, pushing, switching branches, the network and gh stay denied.'
|
|
186
|
+
}),
|
|
187
|
+
rules: z.string().regex(/\S/, 'a rules file path is empty').optional().meta({
|
|
188
|
+
description: 'A text file, relative to the repo root, whose text is added to the agent prompt. Conventions normally live in CLAUDE.md or AGENTS.md.'
|
|
189
|
+
}),
|
|
190
|
+
model: modelField.meta({
|
|
191
|
+
description: "The default model of every session; a Ticket's **Model:** line overrides it and --model overrides both"
|
|
192
|
+
}),
|
|
193
|
+
effort: effortField.meta({
|
|
194
|
+
description: "The default effort of every session; a Ticket's **Effort:** line overrides it and --effort overrides both"
|
|
195
|
+
}),
|
|
196
|
+
review: z
|
|
197
|
+
.strictObject({
|
|
198
|
+
enabled: z
|
|
199
|
+
.boolean()
|
|
200
|
+
.optional()
|
|
201
|
+
.meta({ description: 'false switches the review off; on by default' }),
|
|
202
|
+
model: modelField.meta({
|
|
203
|
+
description: "The review session's own model; else the default model"
|
|
204
|
+
}),
|
|
205
|
+
effort: effortField.meta({
|
|
206
|
+
description: "The review session's own effort; else the default effort"
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
.optional()
|
|
210
|
+
.meta({
|
|
211
|
+
description: 'The read-only review of the whole diff, run before the draft pull request opens'
|
|
212
|
+
}),
|
|
213
|
+
signals: Signals,
|
|
214
|
+
workflows: z.record(oneLine('a Workflow name'), Workflow).optional().meta({
|
|
215
|
+
description: 'Work a repo adds to the build loop, by name. A Feature picks one with a **Workflow:** line; Nightjar still keeps the branch, the commits, the Signals and the pull request.'
|
|
216
|
+
}),
|
|
217
|
+
defaultWorkflow: z.string().regex(/\S/, 'defaultWorkflow is empty').optional().meta({
|
|
218
|
+
description: 'The Workflow of a Feature without a **Workflow:** line; none when left out'
|
|
219
|
+
}),
|
|
220
|
+
trusted: z.array(z.string().regex(/\S/, 'a trusted login is empty')).optional().meta({
|
|
221
|
+
description: "The logins whose comments reach the agent (on Azure DevOps, sign-in names such as ann@contoso.com). When set, it replaces the Tracker default (GitHub: write access; Azure DevOps: the project's Contributors group); leave it out to keep the default."
|
|
222
|
+
}),
|
|
223
|
+
// worktree setup and reserved ports (N5)
|
|
224
|
+
setup: z.string().regex(/\S/, 'name the setup command, or leave setup out').optional().meta({
|
|
225
|
+
description: 'The shell command run once in each new worktree, before the first session, such as one that installs dependencies. When it fails, the Feature stops with a comment saying why. Left out, nothing is run.'
|
|
226
|
+
}),
|
|
227
|
+
ports: z
|
|
228
|
+
.strictObject({
|
|
229
|
+
count: z.number().int().min(1).max(100).meta({ description: 'How many ports to reserve' }),
|
|
230
|
+
env: z
|
|
231
|
+
.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'not a valid variable name'), {
|
|
232
|
+
error: missingOr('name the environment variables that carry the ports')
|
|
233
|
+
})
|
|
234
|
+
.min(1, 'name at least one environment variable')
|
|
235
|
+
.refine((names) => new Set(names).size === names.length, 'a name is used twice')
|
|
236
|
+
.meta({
|
|
237
|
+
description: 'The environment variables that carry the ports to every session, the check and the setup command: the first name gets the first reserved port, the next name the next port, and so on'
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
.refine((p) => p.env.length <= p.count, {
|
|
241
|
+
path: ['env'],
|
|
242
|
+
error: 'more names than reserved ports'
|
|
243
|
+
})
|
|
244
|
+
.optional()
|
|
245
|
+
.meta({
|
|
246
|
+
description: 'Ports reserved for each run and held for its whole life, so that two runs on one machine never share a port. Left out, no port is held.'
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
.refine((c) => c.defaultWorkflow === undefined || c.workflows?.[c.defaultWorkflow] !== undefined, {
|
|
250
|
+
path: ['defaultWorkflow'],
|
|
251
|
+
error: 'names no Workflow of workflows'
|
|
252
|
+
})
|
|
253
|
+
.refine((c) => c.ports !== undefined || Object.values(c.workflows ?? {}).every((w) => w.serve === undefined), { path: ['workflows'], error: 'a Workflow with serve needs ports, to reserve its port' })
|
|
254
|
+
.meta({ title: 'Nightjar factory.json' });
|
|
255
|
+
export function configJsonSchema() {
|
|
256
|
+
return z.toJSONSchema(ConfigSchema, { io: 'input' });
|
|
257
|
+
}
|
|
258
|
+
// The text of the rules file factory.json names, or null when it names none.
|
|
259
|
+
export function loadRules(root, config) {
|
|
260
|
+
if (config.rules === undefined)
|
|
261
|
+
return null;
|
|
262
|
+
try {
|
|
263
|
+
return readFileSync(join(root, config.rules), 'utf8').trim();
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
throw new ConfigError(`${CONFIG_FILE}: rules: cannot read ${config.rules} in ${root}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export const where = (path) => path.length === 0 ? '' : `${path.map(String).join('.')}: `;
|
|
270
|
+
// The repo's factory.json, or a ConfigError naming each field that is wrong.
|
|
271
|
+
export function loadConfig(root) {
|
|
272
|
+
let text;
|
|
273
|
+
try {
|
|
274
|
+
text = readFileSync(join(root, CONFIG_FILE), 'utf8');
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
throw new ConfigError(`no ${CONFIG_FILE} in ${root}; run \`nightjar init\` to write one`);
|
|
278
|
+
}
|
|
279
|
+
let value;
|
|
280
|
+
try {
|
|
281
|
+
value = JSON.parse(text);
|
|
282
|
+
}
|
|
283
|
+
catch (fault) {
|
|
284
|
+
throw new ConfigError(`${CONFIG_FILE} is not JSON: ${why(fault)}`);
|
|
285
|
+
}
|
|
286
|
+
const parsed = ConfigSchema.safeParse(value);
|
|
287
|
+
if (!parsed.success) {
|
|
288
|
+
const lines = parsed.error.issues.map((i) => `${CONFIG_FILE}: ${where(i.path)}${i.message}`);
|
|
289
|
+
throw new ConfigError(lines.join('\n'));
|
|
290
|
+
}
|
|
291
|
+
const { $schema: _schema, ...config } = parsed.data;
|
|
292
|
+
return config;
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAoB,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAE1C,MAAM,IAAI,GAAG,CAAC;KACZ,MAAM,EAAE;KACR,KAAK,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;KACjD,QAAQ,EAAE;KACV,IAAI,CAAC,EAAE,WAAW,EAAE,kEAAkE,EAAE,CAAC,CAAC;AAE5F,MAAM,aAAa,GAAG,CAAC;KACrB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;KACjD,IAAI,CAAC,EAAE,WAAW,EAAE,mEAAmE,EAAE,CAAC,CAAC;AAE7F,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,KAAyB,EAAE,EAAE,CACjE,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAE5D,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAEvF,MAAM,YAAY,GAAG,KAAK,CAAC,uBAAuB,CAAC;KACjD,KAAK,CAAC,YAAY,EAAE,iEAAiE,CAAC;KACtF,IAAI,CAAC,EAAE,WAAW,EAAE,sDAAsD,EAAE,CAAC,CAAC;AAEhF,MAAM,kBAAkB,GAAG,CAAC;KAC1B,YAAY,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC;IAC/B,YAAY;IACZ,OAAO,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;IAC/F,WAAW,EAAE,KAAK,CAAC,gDAAgD,CAAC,CAAC,IAAI,CAAC;QACzE,WAAW,EACV,0FAA0F;KAC3F,CAAC;IACF,UAAU,EAAE,CAAC;SACX,MAAM,EAAE;SACR,KAAK,CAAC,IAAI,EAAE,8DAA8D,CAAC;SAC3E,QAAQ,EAAE;SACV,IAAI,CAAC;QACL,WAAW,EACV,yJAAyJ;KAC1J,CAAC;CACH,CAAC;KACD,IAAI,CAAC;IACL,WAAW,EACV,gGAAgG;CACjG,CAAC,CAAC;AAEJ,MAAM,cAAc,GAAG,CAAC;KACtB,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;KACjD,IAAI,CAAC,EAAE,WAAW,EAAE,wEAAwE,EAAE,CAAC,CAAC;AAElG,MAAM,kBAAkB,GAAG,CAAC;KAC1B,YAAY,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC;IAC/B,YAAY;IACZ,OAAO,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;IAC3F,IAAI,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,sCAAsC,EAAE,CAAC;CAC1F,CAAC;KACD,IAAI,CAAC;IACL,WAAW,EACV,qHAAqH;CACtH,CAAC,CAAC;AAEJ,8GAA8G;AAC9G,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE,CACtC,CAAC;KACC,YAAY,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACzB,IAAI,EAAE,CAAC;SACL,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,EAAE,CAAC;SAC/C,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC;SAC9B,IAAI,CAAC;QACL,WAAW,EAAE,8BAA8B,IAAI,kCAAkC;KACjF,CAAC;IACH,OAAO,EAAE,CAAC;SACR,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,QAAQ,EAAE;SACV,IAAI,CAAC,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC;CAC9E,CAAC;KACD,IAAI,CAAC,EAAE,WAAW,EAAE,KAAK,IAAI,uCAAuC,EAAE,CAAC,CAAC;AAE3E,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,QAAQ,EAAE,CAAC;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC,QAAQ,EAAE,CAAC;AAE5E,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC;AAElG,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,IAAI,GAAG,CAAC;KACZ,YAAY,CAAC;IACb,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC;QACjC,WAAW,EAAE,oEAAoE;KACjF,CAAC;IACF,MAAM,EAAE,CAAC;SACP,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,wBAAwB,CAAC,EAAE,CAAC;SACtD,KAAK,CAAC,IAAI,EAAE,wBAAwB,CAAC;SACrC,IAAI,CAAC;QACL,WAAW,EACV,6IAA6I;KAC9I,CAAC;IACH,KAAK,EAAE,CAAC;SACN,IAAI,CAAC,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,EAAE;QAC7C,KAAK,EAAE,SAAS,CAAC,4DAA4D,CAAC;KAC9E,CAAC;SACD,IAAI,CAAC;QACL,WAAW,EACV,mHAAmH;KACpH,CAAC;IACH,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;IACvF,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC;IAC3F,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAChD,WAAW,EAAE,0EAA0E;KACvF,CAAC;IACF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAC/D,WAAW,EACV,yJAAyJ;KAC1J,CAAC;IACF,SAAS,EAAE,CAAC;SACV,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,IAAI,CAAC;QACL,WAAW,EAAE,4CAA4C,MAAM,CAAC,cAAc,CAAC,gBAAgB;KAC/F,CAAC;CACH,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE;IAC3E,IAAI,EAAE,CAAC,WAAW,CAAC;IACnB,KAAK,EAAE,0BAA0B;CACjC,CAAC,CAAC;AAEJ,MAAM,QAAQ,GAAG,CAAC;KAChB,YAAY,CAAC;IACb,KAAK,EAAE,CAAC;SACN,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;SACnD,GAAG,CAAC,CAAC,EAAE,oCAAoC,CAAC;SAC5C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,EAAE;QAC3E,KAAK,EAAE,2BAA2B;KAClC,CAAC;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAC/D,WAAW,EACV,qMAAqM;KACtM,CAAC;IACF,eAAe,EAAE,CAAC;SAChB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC;SAC5D,QAAQ,EAAE;SACV,IAAI,CAAC;QACL,WAAW,EACV,0HAA0H;KAC3H,CAAC;IACH,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QACzF,WAAW,EACV,8KAA8K;KAC/K,CAAC;IACF,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QACzC,WAAW,EAAE,iFAAiF;KAC9F,CAAC;CACF,CAAC;KACD,IAAI,CAAC,EAAE,WAAW,EAAE,sEAAsE,EAAE,CAAC,CAAC;AAEhG,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAE,IAAY,EAAE,EAAE,CACrD,CAAC;KACC,MAAM,EAAE;KACR,KAAK,CAAC,IAAI,EAAE,wBAAwB,CAAC;KACrC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAE,cAAoC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE;IAC7F,KAAK,EAAE,wDAAwD;CAC/D,CAAC;KACD,OAAO,CAAC,QAAQ,CAAC;KACjB,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/B,MAAM,OAAO,GAAG,CAAC;KACf,YAAY,CAAC;IACb,EAAE,EAAE,UAAU,CACb,oBAAoB,CAAC,EAAE,EACvB,+GAA+G,CAC/G;IACD,KAAK,EAAE,UAAU,CAAC,oBAAoB,CAAC,KAAK,EAAE,oCAAoC,CAAC;IACnF,OAAO,EAAE,UAAU,CAAC,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAC9E,SAAS,EAAE,UAAU,CAAC,oBAAoB,CAAC,SAAS,EAAE,mCAAmC,CAAC;IAC1F,KAAK,EAAE,UAAU,CAAC,oBAAoB,CAAC,KAAK,EAAE,mCAAmC,CAAC;CAClF,CAAC;KACD,QAAQ,CAAC,EAAE,CAAC;KACZ,IAAI,CAAC;IACL,WAAW,EACV,8JAA8J;CAC/J,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC;KAC3B,YAAY,CAAC;IACb,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC;SACR,kBAAkB,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,kBAAkB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE;QAC1F,KAAK,EAAE,SAAS,CAAC,kBAAkB,CAAC;KACpC,CAAC;SACD,IAAI,CAAC,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;IAC9D,QAAQ,EAAE,CAAC;SACT,kBAAkB,CAClB,MAAM,EACN,CAAC,cAAc,EAAE,kBAAkB,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,EAChE;QACC,KAAK,EAAE,SAAS,CAAC,oBAAoB,CAAC;KACtC,CACD;SACA,IAAI,CAAC,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC;IACvF,KAAK,EAAE,CAAC;SACN,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,uCAAuC,CAAC,EAAE,CAAC;SACrE,KAAK,CAAC,IAAI,EAAE,uCAAuC,CAAC;SACpD,IAAI,CAAC;QACL,WAAW,EACV,8GAA8G;KAC/G,CAAC;IACH,eAAe,EAAE,CAAC;SAChB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,6BAA6B,CAAC,EAAE;QAC7D,KAAK,EAAE,SAAS,CAAC,qCAAqC,CAAC;KACvD,CAAC;SACD,IAAI,CAAC;QACL,WAAW,EACV,yMAAyM;KAC1M,CAAC;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAC3E,WAAW,EACV,uIAAuI;KACxI,CAAC;IACF,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC;QACtB,WAAW,EACV,wGAAwG;KACzG,CAAC;IACF,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;QACxB,WAAW,EACV,2GAA2G;KAC5G,CAAC;IACF,MAAM,EAAE,CAAC;SACP,YAAY,CAAC;QACb,OAAO,EAAE,CAAC;aACR,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,IAAI,CAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;QACvE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC;YACtB,WAAW,EAAE,wDAAwD;SACrE,CAAC;QACF,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC;YACxB,WAAW,EAAE,0DAA0D;SACvE,CAAC;KACF,CAAC;SACD,QAAQ,EAAE;SACV,IAAI,CAAC;QACL,WAAW,EACV,iFAAiF;KAClF,CAAC;IACH,OAAO,EAAE,OAAO;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QACzE,WAAW,EACV,6KAA6K;KAC9K,CAAC;IACF,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QACnF,WAAW,EAAE,4EAA4E;KACzF,CAAC;IACF,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QACpF,WAAW,EACV,yPAAyP;KAC1P,CAAC;IACF,yCAAyC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,4CAA4C,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAC3F,WAAW,EACV,0MAA0M;KAC3M,CAAC;IACF,KAAK,EAAE,CAAC;SACN,YAAY,CAAC;QACb,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;QAC1F,GAAG,EAAE,CAAC;aACJ,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,0BAA0B,EAAE,2BAA2B,CAAC,EAAE;YACjF,KAAK,EAAE,SAAS,CAAC,qDAAqD,CAAC;SACvE,CAAC;aACD,GAAG,CAAC,CAAC,EAAE,wCAAwC,CAAC;aAChD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC;aAC/E,IAAI,CAAC;YACL,WAAW,EACV,uLAAuL;SACxL,CAAC;KACH,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,EAAE;QACvC,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,KAAK,EAAE,gCAAgC;KACvC,CAAC;SACD,QAAQ,EAAE;SACV,IAAI,CAAC;QACL,WAAW,EACV,yIAAyI;KAC1I,CAAC;CACH,CAAC;KACD,MAAM,CACN,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,SAAS,EACxF;IACC,IAAI,EAAE,CAAC,iBAAiB,CAAC;IACzB,KAAK,EAAE,gCAAgC;CACvC,CACD;KACA,MAAM,CACN,CAAC,CAAC,EAAE,EAAE,CACL,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,EAC9F,EAAE,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,wDAAwD,EAAE,CACxF;KACA,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAM3C,MAAM,UAAU,gBAAgB;IAC/B,OAAO,CAAC,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,MAAc;IACrD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,CAAC;QACJ,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,wBAAwB,MAAM,CAAC,KAAK,OAAO,IAAI,EAAE,CAAC,CAAC;IACxF,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,IAA4B,EAAE,EAAE,CACrD,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACJ,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,WAAW,CAAC,MAAM,WAAW,OAAO,IAAI,sCAAsC,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACJ,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,iBAAiB,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;IACpD,OAAO,MAAM,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"case.js","sourceRoot":"","sources":["../../src/contract/case.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CodeHost, Pull } from '../code-host.js';
|
|
2
|
+
import type { ContractCase } from './case.js';
|
|
3
|
+
export interface PullView {
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly head: string;
|
|
6
|
+
readonly base: string;
|
|
7
|
+
readonly body: string;
|
|
8
|
+
readonly draft: boolean;
|
|
9
|
+
readonly references: readonly string[];
|
|
10
|
+
readonly comments: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface CodeHostSubject {
|
|
13
|
+
readonly codeHost: CodeHost;
|
|
14
|
+
readonly base: string;
|
|
15
|
+
readonly commit: () => Promise<{
|
|
16
|
+
readonly folder: string;
|
|
17
|
+
readonly commit: string;
|
|
18
|
+
}>;
|
|
19
|
+
readonly branch: (name: string) => Promise<string | null>;
|
|
20
|
+
readonly pull: (pull: Pull) => Promise<PullView>;
|
|
21
|
+
readonly close?: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export type CodeHostHarness = () => Promise<CodeHostSubject>;
|
|
24
|
+
export declare const codeHostContract: readonly ContractCase<CodeHostHarness>[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// The Code host contract (docs/adr/0001): what every Code host Adapter must do, fake, built in or a repo's own.
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
function codeHostCase(name, check) {
|
|
4
|
+
return {
|
|
5
|
+
name,
|
|
6
|
+
run: async (harness) => {
|
|
7
|
+
const subject = await harness();
|
|
8
|
+
try {
|
|
9
|
+
await check(subject);
|
|
10
|
+
}
|
|
11
|
+
finally {
|
|
12
|
+
await subject.close?.();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const codeHostContract = [
|
|
18
|
+
codeHostCase("pushes a worktree's HEAD to a branch, and moves it on with a later push", async ({ codeHost, commit, branch }) => {
|
|
19
|
+
const first = await commit();
|
|
20
|
+
await codeHost.push(first.folder, 'factory/1-a');
|
|
21
|
+
assert.equal(await branch('factory/1-a'), first.commit);
|
|
22
|
+
assert.equal(await branch('factory/2-b'), null);
|
|
23
|
+
const second = await commit();
|
|
24
|
+
await codeHost.push(second.folder, 'factory/1-a');
|
|
25
|
+
assert.equal(await branch('factory/1-a'), second.commit);
|
|
26
|
+
}),
|
|
27
|
+
codeHostCase('opens a draft pull request with the title, branch, base and body it is given', async ({ codeHost, commit, base, pull }) => {
|
|
28
|
+
await codeHost.push((await commit()).folder, 'factory/1-a');
|
|
29
|
+
const body = 'Nightjar built all of it.\n\n## Built\n\n- `$(x)` "quoted" & <b>more</b>';
|
|
30
|
+
const opened = await codeHost.createDraft({
|
|
31
|
+
title: 'One $(escape)',
|
|
32
|
+
head: 'factory/1-a',
|
|
33
|
+
base,
|
|
34
|
+
body
|
|
35
|
+
});
|
|
36
|
+
assert.ok(opened.url !== '', 'the pull request has a URL');
|
|
37
|
+
const shown = await pull(opened);
|
|
38
|
+
assert.deepEqual({ title: shown.title, head: shown.head, base: shown.base, draft: shown.draft }, { title: 'One $(escape)', head: 'factory/1-a', base, draft: true });
|
|
39
|
+
assert.ok(shown.body.includes(body), `the body holds what it was given:\n${shown.body}`);
|
|
40
|
+
assert.deepEqual(shown.references, []);
|
|
41
|
+
assert.deepEqual(shown.comments, []);
|
|
42
|
+
}),
|
|
43
|
+
codeHostCase('finds the open pull request of a branch, and none for a branch without one', async ({ codeHost, commit, base }) => {
|
|
44
|
+
const { folder } = await commit();
|
|
45
|
+
assert.equal(await codeHost.openPull('factory/1-a'), null);
|
|
46
|
+
await codeHost.push(folder, 'factory/1-a');
|
|
47
|
+
await codeHost.push(folder, 'factory/2-b');
|
|
48
|
+
await codeHost.push(folder, 'factory/3-c');
|
|
49
|
+
const one = await codeHost.createDraft({ title: 'A', head: 'factory/1-a', base, body: 'a' });
|
|
50
|
+
const two = await codeHost.createDraft({ title: 'B', head: 'factory/2-b', base, body: 'b' });
|
|
51
|
+
assert.notEqual(one.id, two.id);
|
|
52
|
+
assert.deepEqual(await codeHost.openPull('factory/1-a'), one);
|
|
53
|
+
assert.deepEqual(await codeHost.openPull('factory/2-b'), two);
|
|
54
|
+
assert.equal(await codeHost.openPull('factory/3-c'), null);
|
|
55
|
+
}),
|
|
56
|
+
codeHostCase('replaces the body of a pull request', async ({ codeHost, commit, base, pull }) => {
|
|
57
|
+
await codeHost.push((await commit()).folder, 'factory/1-a');
|
|
58
|
+
const opened = await codeHost.createDraft({
|
|
59
|
+
title: 'A',
|
|
60
|
+
head: 'factory/1-a',
|
|
61
|
+
base,
|
|
62
|
+
body: 'First body'
|
|
63
|
+
});
|
|
64
|
+
await codeHost.editBody(opened, 'Second body, with `$(x)` & "quotes"');
|
|
65
|
+
const { body } = await pull(opened);
|
|
66
|
+
assert.ok(body.includes('Second body, with `$(x)` & "quotes"'), `the new body:\n${body}`);
|
|
67
|
+
assert.ok(!body.includes('First body'), `the old body is gone:\n${body}`);
|
|
68
|
+
}),
|
|
69
|
+
codeHostCase('places each Ticket reference once, and keeps them all when the body is replaced', async ({ codeHost, commit, base, pull }) => {
|
|
70
|
+
await codeHost.push((await commit()).folder, 'factory/1-a');
|
|
71
|
+
const opened = await codeHost.createDraft({
|
|
72
|
+
title: 'A',
|
|
73
|
+
head: 'factory/1-a',
|
|
74
|
+
base,
|
|
75
|
+
body: 'First body'
|
|
76
|
+
});
|
|
77
|
+
const sorted = async () => [...(await pull(opened)).references].sort();
|
|
78
|
+
await codeHost.placeReferences(opened, [
|
|
79
|
+
{ id: '2', text: '#2' },
|
|
80
|
+
{ id: '3', text: '#3' }
|
|
81
|
+
]);
|
|
82
|
+
assert.deepEqual(await sorted(), ['#2', '#3']);
|
|
83
|
+
await codeHost.placeReferences(opened, [
|
|
84
|
+
{ id: '1', text: '#1' },
|
|
85
|
+
{ id: '3', text: '#3' }
|
|
86
|
+
]);
|
|
87
|
+
assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
|
|
88
|
+
await codeHost.editBody(opened, 'Second body');
|
|
89
|
+
assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
|
|
90
|
+
await codeHost.placeReferences(opened, []);
|
|
91
|
+
assert.deepEqual(await sorted(), ['#1', '#2', '#3']);
|
|
92
|
+
assert.ok((await pull(opened)).body.includes('Second body'));
|
|
93
|
+
}),
|
|
94
|
+
codeHostCase('comments on a pull request, oldest first, with the body unchanged', async ({ codeHost, commit, base, pull }) => {
|
|
95
|
+
await codeHost.push((await commit()).folder, 'factory/1-a');
|
|
96
|
+
const opened = await codeHost.createDraft({
|
|
97
|
+
title: 'A',
|
|
98
|
+
head: 'factory/1-a',
|
|
99
|
+
base,
|
|
100
|
+
body: 'a'
|
|
101
|
+
});
|
|
102
|
+
const review = '**Nightjar review: changes**\n\n- `src/a.ts:3`: $(x) & "y"';
|
|
103
|
+
await codeHost.comment(opened, review);
|
|
104
|
+
await codeHost.comment(opened, 'Second');
|
|
105
|
+
assert.deepEqual((await pull(opened)).comments, [review, 'Second']);
|
|
106
|
+
})
|
|
107
|
+
];
|
|
108
|
+
//# sourceMappingURL=code-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-host.js","sourceRoot":"","sources":["../../src/contract/code-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const CONTRACT_USAGE = "usage: nightjar contract MODULE\n\n MODULE a module, as a path from here, that exports trackerHarness and/or codeHostHarness;\n runs the Tracker and Code host contract suites over them and names each behaviour that fails";
|
|
2
|
+
export declare function runContractCommand(argv: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// `nightjar contract MODULE`: checks a module's Adapters against the contract suites.
|
|
2
|
+
import { importModule } from '../adapter-module.js';
|
|
3
|
+
import { ContractRefusal, formatReport, runContract } from './run.js';
|
|
4
|
+
import { why } from '../say.js';
|
|
5
|
+
export const CONTRACT_USAGE = `usage: nightjar contract MODULE
|
|
6
|
+
|
|
7
|
+
MODULE a module, as a path from here, that exports trackerHarness and/or codeHostHarness;
|
|
8
|
+
runs the Tracker and Code host contract suites over them and names each behaviour that fails`;
|
|
9
|
+
// 0 when every behaviour holds, 1 when one fails, 2 when the module could not be checked.
|
|
10
|
+
export async function runContractCommand(argv) {
|
|
11
|
+
const [path, ...rest] = argv;
|
|
12
|
+
if (path === undefined || rest.length > 0) {
|
|
13
|
+
console.error(`nightjar: ${path === undefined ? 'say which module' : 'one module at a time'}\n\n${CONTRACT_USAGE}`);
|
|
14
|
+
return 2;
|
|
15
|
+
}
|
|
16
|
+
let exports;
|
|
17
|
+
try {
|
|
18
|
+
exports = await importModule(path, process.cwd());
|
|
19
|
+
}
|
|
20
|
+
catch (fault) {
|
|
21
|
+
console.error(`nightjar: contract: cannot load ${path}: ${why(fault)}`);
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const report = await runContract(exports);
|
|
26
|
+
console.log(formatReport(report));
|
|
27
|
+
return report.failed === 0 ? 0 : 1;
|
|
28
|
+
}
|
|
29
|
+
catch (fault) {
|
|
30
|
+
if (!(fault instanceof ContractRefusal))
|
|
31
|
+
throw fault;
|
|
32
|
+
console.error(`nightjar: contract: ${path} ${fault.message}`);
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/contract/command.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,MAAM,CAAC,MAAM,cAAc,GAAG;;;uGAGyE,CAAC;AAExG,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAuB;IAC/D,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CACZ,aAAa,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,sBAAsB,OAAO,cAAc,EAAE,CACpG,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,OAAgC,CAAC;IACrC,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,mCAAmC,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,uBAAuB,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,CAAC;IACV,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The creation contract (docs/ticket-contract.md): what it makes is read back through the Tracker's own methods.
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { canCreate } from '../tracker.js';
|
|
4
|
+
const ANN = 'ann';
|
|
5
|
+
function writerCase(name, items, check) {
|
|
6
|
+
return {
|
|
7
|
+
name,
|
|
8
|
+
run: async (harness) => {
|
|
9
|
+
const subject = await harness({ person: ANN, trusted: [ANN], items });
|
|
10
|
+
try {
|
|
11
|
+
const { tracker } = subject;
|
|
12
|
+
assert.ok(canCreate(tracker), 'the Tracker has createFeature, createTicket and block');
|
|
13
|
+
await check({ ...subject, tracker });
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
await subject.close?.();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// A Ticket in the contract's shape, with what a person might type in it.
|
|
22
|
+
const TICKET_BODY = [
|
|
23
|
+
'**Model:** opus',
|
|
24
|
+
'**Effort:** high',
|
|
25
|
+
'',
|
|
26
|
+
'Share the escape with `escapeHtml(text)` & keep <b>bold</b> as text.',
|
|
27
|
+
'',
|
|
28
|
+
'## Acceptance criteria',
|
|
29
|
+
'',
|
|
30
|
+
'- [ ] one escape for both',
|
|
31
|
+
'- [ ] a "quoted" value stays quoted'
|
|
32
|
+
].join('\n');
|
|
33
|
+
export const featureWriterContract = [
|
|
34
|
+
writerCase('makes a Feature with its title and body, and each Ticket its last child, in the order made', [
|
|
35
|
+
{ key: 'other', title: 'Another Feature', children: ['old'] },
|
|
36
|
+
{ key: 'old', title: 'An older Ticket' }
|
|
37
|
+
], async ({ tracker, id }) => {
|
|
38
|
+
const feature = await tracker.createFeature({
|
|
39
|
+
title: 'Share the escape',
|
|
40
|
+
body: 'The spec.\n\n## Solution\n\nOne escape.'
|
|
41
|
+
});
|
|
42
|
+
const made = [];
|
|
43
|
+
for (const title of ['First', 'Second', 'Third']) {
|
|
44
|
+
made.push(await tracker.createTicket(feature, { title, body: `Build ${title}.` }));
|
|
45
|
+
}
|
|
46
|
+
assert.equal(new Set([feature, ...made, id('other'), id('old')]).size, 6, 'each id is new');
|
|
47
|
+
assert.deepEqual(await tracker.feature(feature), {
|
|
48
|
+
id: feature,
|
|
49
|
+
title: 'Share the escape',
|
|
50
|
+
body: 'The spec.\n\n## Solution\n\nOne escape.'
|
|
51
|
+
});
|
|
52
|
+
assert.deepEqual(await tracker.tickets(feature), ['First', 'Second', 'Third'].map((title, i) => ({
|
|
53
|
+
id: made[i],
|
|
54
|
+
title,
|
|
55
|
+
body: `Build ${title}.`,
|
|
56
|
+
open: true,
|
|
57
|
+
signals: [],
|
|
58
|
+
blockedBy: []
|
|
59
|
+
})));
|
|
60
|
+
assert.deepEqual((await tracker.tickets(id('other'))).map((t) => t.id), [id('old')]);
|
|
61
|
+
}),
|
|
62
|
+
writerCase("keeps a Ticket's body as written, so its Model and Effort lines and acceptance criteria read back", [], async ({ tracker }) => {
|
|
63
|
+
const feature = await tracker.createFeature({ title: 'Feature', body: 'The spec.' });
|
|
64
|
+
await tracker.createTicket(feature, { title: 'Ticket', body: TICKET_BODY });
|
|
65
|
+
assert.deepEqual((await tracker.tickets(feature)).map((t) => t.body), [TICKET_BODY]);
|
|
66
|
+
}),
|
|
67
|
+
writerCase('links a Ticket as blocked by another it made, or by an item already there', [
|
|
68
|
+
{ key: 'other', title: 'Another Feature', children: ['done'] },
|
|
69
|
+
{ key: 'done', title: 'A closed Ticket', open: false }
|
|
70
|
+
], async ({ tracker, id }) => {
|
|
71
|
+
const feature = await tracker.createFeature({ title: 'Feature', body: '' });
|
|
72
|
+
const a = await tracker.createTicket(feature, { title: 'a', body: 'Build a.' });
|
|
73
|
+
const b = await tracker.createTicket(feature, { title: 'b', body: 'Build b.' });
|
|
74
|
+
const c = await tracker.createTicket(feature, { title: 'c', body: 'Build c.' });
|
|
75
|
+
await tracker.block(b, a);
|
|
76
|
+
await tracker.block(c, a);
|
|
77
|
+
await tracker.block(c, id('done'));
|
|
78
|
+
const byId = (x, y) => x.id.localeCompare(y.id);
|
|
79
|
+
assert.deepEqual((await tracker.tickets(feature)).map((t) => ({
|
|
80
|
+
id: t.id,
|
|
81
|
+
blockedBy: [...t.blockedBy].sort(byId)
|
|
82
|
+
})), [
|
|
83
|
+
{ id: a, blockedBy: [] },
|
|
84
|
+
{ id: b, blockedBy: [{ id: a, open: true }] },
|
|
85
|
+
{
|
|
86
|
+
id: c,
|
|
87
|
+
blockedBy: [
|
|
88
|
+
{ id: a, open: true },
|
|
89
|
+
{ id: id('done'), open: false }
|
|
90
|
+
].sort(byId)
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
}),
|
|
94
|
+
writerCase('takes a Signal on a Ticket it made', [], async ({ tracker }) => {
|
|
95
|
+
const feature = await tracker.createFeature({ title: 'Feature', body: 'The spec.' });
|
|
96
|
+
const ticket = await tracker.createTicket(feature, { title: 'Ticket', body: 'Build it.' });
|
|
97
|
+
await tracker.addSignal(ticket, 'ready');
|
|
98
|
+
assert.deepEqual((await tracker.tickets(feature)).map((t) => t.signals), [['ready']]);
|
|
99
|
+
})
|
|
100
|
+
];
|
|
101
|
+
//# sourceMappingURL=feature-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-writer.js","sourceRoot":"","sources":["../../src/contract/feature-writer.ts"],"names":[],"mappings":"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"}
|