@taskset/cli 0.1.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/README.md +31 -11
- package/dist/bin.js +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +643 -108
- package/dist/index.js +1 -1
- package/package.json +20 -6
- package/src/cli.ts +771 -133
package/dist/cli.js
CHANGED
|
@@ -1,64 +1,244 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import {
|
|
4
|
-
import { createTask, discoverRepository, initializeRepository,
|
|
3
|
+
import { TaskIdSchema, TaskPrioritySchema, TaskRiskSchema, TaskStatusSchema, TaskTimestampSchema, } from '@taskset/contracts';
|
|
4
|
+
import { buildTaskIndex, createSnapshot, createTask, deleteTask, diagnoseRepository, discoverRepository, generateViews, initializeRepository, listSnapshots, normalizeRepositoryPath, queryTasks, RepositoryPathError, readTask, restoreSnapshot, serializeTaskFile, TASK_SORT_DIRECTIONS, TASK_SORT_KEYS, TaskQuerySchema, updateTask, } from '@taskset/core';
|
|
5
|
+
import * as z from 'zod';
|
|
5
6
|
const USAGE = `Usage:
|
|
6
7
|
taskset init [--cwd <path>]
|
|
7
8
|
taskset config [--json] [--cwd <path>]
|
|
8
|
-
taskset
|
|
9
|
-
taskset
|
|
10
|
-
taskset
|
|
9
|
+
taskset doctor [--json] [--cwd <path>]
|
|
10
|
+
taskset generate [--json] [--cwd <path>]
|
|
11
|
+
taskset snapshot create|list [--json] [--cwd <path>]
|
|
12
|
+
taskset snapshot restore <snapshot-id> [--apply] [--json] [--cwd <path>]
|
|
13
|
+
taskset task create --title <title> [metadata options]
|
|
14
|
+
taskset task list [query options]
|
|
15
|
+
taskset task show <task-id> [--include-derived] [--json] [--cwd <path>]
|
|
16
|
+
taskset task update <task-id> [metadata options]
|
|
17
|
+
taskset task status <task-id> <status> [--json] [--cwd <path>]
|
|
18
|
+
taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
--status
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
Metadata options:
|
|
21
|
+
--status --priority --order --owner --team --estimate --effort --risk --due-date
|
|
22
|
+
--label --assignee --reviewer --depends-on --related --duplicate
|
|
23
|
+
--parent --file --directory --project --body
|
|
24
|
+
Repeat array options. Update commands also accept matching --clear-* options.
|
|
25
|
+
|
|
26
|
+
List query options:
|
|
27
|
+
--status --priority --label --owner --assignee --reviewer --team --risk
|
|
28
|
+
--project --depends-on --related --duplicate --parent --file --directory
|
|
29
|
+
--estimate-min --estimate-max --effort-min --effort-max
|
|
30
|
+
--due-before --due-after --created-before --created-after
|
|
31
|
+
--updated-before --updated-after --search --sort --direction
|
|
32
|
+
--impact --include-derived --json
|
|
19
33
|
`;
|
|
20
34
|
class CliUsageError extends Error {
|
|
21
|
-
|
|
35
|
+
issues;
|
|
36
|
+
constructor(message, issues = []) {
|
|
22
37
|
super(message);
|
|
23
38
|
this.name = 'CliUsageError';
|
|
39
|
+
this.issues = issues;
|
|
24
40
|
}
|
|
25
41
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
throw new CliUsageError(`Invalid task status "${value}"`);
|
|
32
|
-
}
|
|
33
|
-
return value;
|
|
42
|
+
const TrimmedStringSchema = z.string().trim().min(1);
|
|
43
|
+
function uniqueArray(schema) {
|
|
44
|
+
return z
|
|
45
|
+
.array(schema)
|
|
46
|
+
.refine((values) => new Set(values).size === values.length, 'Values must be unique');
|
|
34
47
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
const StringListSchema = uniqueArray(TrimmedStringSchema);
|
|
49
|
+
const TaskIdListSchema = uniqueArray(TaskIdSchema);
|
|
50
|
+
const CwdSchema = z.string().min(1).optional();
|
|
51
|
+
const JsonSchema = z.boolean().optional();
|
|
52
|
+
const CommonValuesSchema = z.strictObject({
|
|
53
|
+
cwd: CwdSchema,
|
|
54
|
+
json: JsonSchema,
|
|
55
|
+
});
|
|
56
|
+
const CreateValuesSchema = z.strictObject({
|
|
57
|
+
title: TrimmedStringSchema,
|
|
58
|
+
status: TaskStatusSchema.optional(),
|
|
59
|
+
priority: TaskPrioritySchema.optional(),
|
|
60
|
+
order: z.coerce.number().finite().nonnegative().optional(),
|
|
61
|
+
label: StringListSchema.optional(),
|
|
62
|
+
'depends-on': TaskIdListSchema.optional(),
|
|
63
|
+
file: StringListSchema.optional(),
|
|
64
|
+
owner: TrimmedStringSchema.optional(),
|
|
65
|
+
assignee: StringListSchema.optional(),
|
|
66
|
+
reviewer: StringListSchema.optional(),
|
|
67
|
+
team: TrimmedStringSchema.optional(),
|
|
68
|
+
estimate: z.coerce.number().int().nonnegative().optional(),
|
|
69
|
+
effort: z.coerce.number().finite().nonnegative().optional(),
|
|
70
|
+
risk: TaskRiskSchema.optional(),
|
|
71
|
+
'due-date': TaskTimestampSchema.optional(),
|
|
72
|
+
related: TaskIdListSchema.optional(),
|
|
73
|
+
duplicate: TaskIdListSchema.optional(),
|
|
74
|
+
parent: TaskIdSchema.optional(),
|
|
75
|
+
directory: StringListSchema.optional(),
|
|
76
|
+
project: StringListSchema.optional(),
|
|
77
|
+
body: z.string().optional(),
|
|
78
|
+
cwd: CwdSchema,
|
|
79
|
+
json: JsonSchema,
|
|
80
|
+
});
|
|
81
|
+
const UPDATE_CLEAR_PAIRS = [
|
|
82
|
+
['priority', 'clear-priority'],
|
|
83
|
+
['order', 'clear-order'],
|
|
84
|
+
['label', 'clear-labels'],
|
|
85
|
+
['depends-on', 'clear-dependencies'],
|
|
86
|
+
['file', 'clear-files'],
|
|
87
|
+
['owner', 'clear-owner'],
|
|
88
|
+
['assignee', 'clear-assignees'],
|
|
89
|
+
['reviewer', 'clear-reviewers'],
|
|
90
|
+
['team', 'clear-team'],
|
|
91
|
+
['estimate', 'clear-estimate'],
|
|
92
|
+
['effort', 'clear-effort'],
|
|
93
|
+
['risk', 'clear-risk'],
|
|
94
|
+
['due-date', 'clear-due-date'],
|
|
95
|
+
['related', 'clear-related'],
|
|
96
|
+
['duplicate', 'clear-duplicates'],
|
|
97
|
+
['parent', 'clear-parent'],
|
|
98
|
+
['directory', 'clear-directories'],
|
|
99
|
+
['project', 'clear-projects'],
|
|
100
|
+
];
|
|
101
|
+
const UpdateValuesSchema = z
|
|
102
|
+
.strictObject({
|
|
103
|
+
title: TrimmedStringSchema.optional(),
|
|
104
|
+
status: TaskStatusSchema.optional(),
|
|
105
|
+
priority: TaskPrioritySchema.optional(),
|
|
106
|
+
order: z.coerce.number().finite().nonnegative().optional(),
|
|
107
|
+
label: StringListSchema.optional(),
|
|
108
|
+
'depends-on': TaskIdListSchema.optional(),
|
|
109
|
+
file: StringListSchema.optional(),
|
|
110
|
+
owner: TrimmedStringSchema.optional(),
|
|
111
|
+
assignee: StringListSchema.optional(),
|
|
112
|
+
reviewer: StringListSchema.optional(),
|
|
113
|
+
team: TrimmedStringSchema.optional(),
|
|
114
|
+
estimate: z.coerce.number().int().nonnegative().optional(),
|
|
115
|
+
effort: z.coerce.number().finite().nonnegative().optional(),
|
|
116
|
+
risk: TaskRiskSchema.optional(),
|
|
117
|
+
'due-date': TaskTimestampSchema.optional(),
|
|
118
|
+
related: TaskIdListSchema.optional(),
|
|
119
|
+
duplicate: TaskIdListSchema.optional(),
|
|
120
|
+
parent: TaskIdSchema.optional(),
|
|
121
|
+
directory: StringListSchema.optional(),
|
|
122
|
+
project: StringListSchema.optional(),
|
|
123
|
+
body: z.string().optional(),
|
|
124
|
+
'clear-priority': z.boolean().optional(),
|
|
125
|
+
'clear-order': z.boolean().optional(),
|
|
126
|
+
'clear-labels': z.boolean().optional(),
|
|
127
|
+
'clear-dependencies': z.boolean().optional(),
|
|
128
|
+
'clear-files': z.boolean().optional(),
|
|
129
|
+
'clear-owner': z.boolean().optional(),
|
|
130
|
+
'clear-assignees': z.boolean().optional(),
|
|
131
|
+
'clear-reviewers': z.boolean().optional(),
|
|
132
|
+
'clear-team': z.boolean().optional(),
|
|
133
|
+
'clear-estimate': z.boolean().optional(),
|
|
134
|
+
'clear-effort': z.boolean().optional(),
|
|
135
|
+
'clear-risk': z.boolean().optional(),
|
|
136
|
+
'clear-due-date': z.boolean().optional(),
|
|
137
|
+
'clear-related': z.boolean().optional(),
|
|
138
|
+
'clear-duplicates': z.boolean().optional(),
|
|
139
|
+
'clear-parent': z.boolean().optional(),
|
|
140
|
+
'clear-directories': z.boolean().optional(),
|
|
141
|
+
'clear-projects': z.boolean().optional(),
|
|
142
|
+
cwd: CwdSchema,
|
|
143
|
+
json: JsonSchema,
|
|
144
|
+
})
|
|
145
|
+
.superRefine((values, context) => {
|
|
146
|
+
for (const [valueKey, clearKey] of UPDATE_CLEAR_PAIRS) {
|
|
147
|
+
if (values[valueKey] !== undefined && values[clearKey]) {
|
|
148
|
+
context.addIssue({
|
|
149
|
+
code: 'custom',
|
|
150
|
+
path: [clearKey],
|
|
151
|
+
message: `Cannot set and clear ${valueKey} in the same update`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const nonControlKeys = Object.keys(values).filter((key) => key !== 'cwd' && key !== 'json');
|
|
156
|
+
if (nonControlKeys.length === 0) {
|
|
157
|
+
context.addIssue({
|
|
158
|
+
code: 'custom',
|
|
159
|
+
message: 'Task update requires at least one field option',
|
|
160
|
+
});
|
|
38
161
|
}
|
|
39
|
-
|
|
40
|
-
|
|
162
|
+
});
|
|
163
|
+
const ListValuesSchema = z.strictObject({
|
|
164
|
+
status: z.array(TaskStatusSchema).optional(),
|
|
165
|
+
priority: z.array(TaskPrioritySchema).optional(),
|
|
166
|
+
label: StringListSchema.optional(),
|
|
167
|
+
owner: StringListSchema.optional(),
|
|
168
|
+
assignee: StringListSchema.optional(),
|
|
169
|
+
reviewer: StringListSchema.optional(),
|
|
170
|
+
team: StringListSchema.optional(),
|
|
171
|
+
risk: z.array(TaskRiskSchema).optional(),
|
|
172
|
+
project: StringListSchema.optional(),
|
|
173
|
+
'depends-on': TaskIdSchema.optional(),
|
|
174
|
+
related: TaskIdSchema.optional(),
|
|
175
|
+
duplicate: TaskIdSchema.optional(),
|
|
176
|
+
parent: TaskIdSchema.optional(),
|
|
177
|
+
file: StringListSchema.optional(),
|
|
178
|
+
directory: StringListSchema.optional(),
|
|
179
|
+
'estimate-min': z.coerce.number().finite().nonnegative().optional(),
|
|
180
|
+
'estimate-max': z.coerce.number().finite().nonnegative().optional(),
|
|
181
|
+
'effort-min': z.coerce.number().finite().nonnegative().optional(),
|
|
182
|
+
'effort-max': z.coerce.number().finite().nonnegative().optional(),
|
|
183
|
+
'due-before': TaskTimestampSchema.optional(),
|
|
184
|
+
'due-after': TaskTimestampSchema.optional(),
|
|
185
|
+
'created-before': TaskTimestampSchema.optional(),
|
|
186
|
+
'created-after': TaskTimestampSchema.optional(),
|
|
187
|
+
'updated-before': TaskTimestampSchema.optional(),
|
|
188
|
+
'updated-after': TaskTimestampSchema.optional(),
|
|
189
|
+
search: z.string().optional(),
|
|
190
|
+
sort: z.enum(TASK_SORT_KEYS).optional(),
|
|
191
|
+
direction: z.enum(TASK_SORT_DIRECTIONS).optional(),
|
|
192
|
+
impact: z.boolean().optional(),
|
|
193
|
+
'include-derived': z.boolean().optional(),
|
|
194
|
+
cwd: CwdSchema,
|
|
195
|
+
json: JsonSchema,
|
|
196
|
+
});
|
|
197
|
+
const metadataOptionDefinitions = {
|
|
198
|
+
body: { type: 'string' },
|
|
199
|
+
'depends-on': { type: 'string', multiple: true },
|
|
200
|
+
file: { type: 'string', multiple: true },
|
|
201
|
+
label: { type: 'string', multiple: true },
|
|
202
|
+
priority: { type: 'string' },
|
|
203
|
+
order: { type: 'string' },
|
|
204
|
+
status: { type: 'string' },
|
|
205
|
+
title: { type: 'string' },
|
|
206
|
+
owner: { type: 'string' },
|
|
207
|
+
assignee: { type: 'string', multiple: true },
|
|
208
|
+
reviewer: { type: 'string', multiple: true },
|
|
209
|
+
team: { type: 'string' },
|
|
210
|
+
estimate: { type: 'string' },
|
|
211
|
+
effort: { type: 'string' },
|
|
212
|
+
risk: { type: 'string' },
|
|
213
|
+
'due-date': { type: 'string' },
|
|
214
|
+
related: { type: 'string', multiple: true },
|
|
215
|
+
duplicate: { type: 'string', multiple: true },
|
|
216
|
+
parent: { type: 'string' },
|
|
217
|
+
directory: { type: 'string', multiple: true },
|
|
218
|
+
project: { type: 'string', multiple: true },
|
|
219
|
+
};
|
|
220
|
+
const commonOptionDefinitions = {
|
|
221
|
+
cwd: { type: 'string' },
|
|
222
|
+
json: { type: 'boolean' },
|
|
223
|
+
};
|
|
224
|
+
function parseSchema(schema, value, label) {
|
|
225
|
+
const result = schema.safeParse(value);
|
|
226
|
+
if (!result.success) {
|
|
227
|
+
throw new CliUsageError(`Invalid ${label}`, result.error.issues.map((issue) => ({
|
|
228
|
+
field: issue.path.length > 0 ? issue.path.map(String).join('.') : label,
|
|
229
|
+
message: issue.message,
|
|
230
|
+
})));
|
|
41
231
|
}
|
|
42
|
-
return
|
|
232
|
+
return result.data;
|
|
43
233
|
}
|
|
44
234
|
function resolveCommandCwd(baseDirectory, cwdOption) {
|
|
45
235
|
return cwdOption ? path.resolve(baseDirectory, cwdOption) : baseDirectory;
|
|
46
236
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
allowPositionals: false,
|
|
51
|
-
options: {
|
|
52
|
-
cwd: { type: 'string' },
|
|
53
|
-
json: { type: 'boolean' },
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
function requireSinglePositional(positionals, name) {
|
|
58
|
-
if (positionals.length !== 1 || !positionals[0]) {
|
|
59
|
-
throw new CliUsageError(`Expected exactly one ${name}`);
|
|
237
|
+
function requirePositionals(positionals, count, description) {
|
|
238
|
+
if (positionals.length !== count || positionals.some((value) => value.length === 0)) {
|
|
239
|
+
throw new CliUsageError(`Expected ${description}`);
|
|
60
240
|
}
|
|
61
|
-
return positionals
|
|
241
|
+
return positionals;
|
|
62
242
|
}
|
|
63
243
|
function formatError(error) {
|
|
64
244
|
if (!(error instanceof Error)) {
|
|
@@ -73,116 +253,406 @@ function formatError(error) {
|
|
|
73
253
|
: '';
|
|
74
254
|
return `${error.message}${issues}`;
|
|
75
255
|
}
|
|
256
|
+
function taskRecordJson(record, derived) {
|
|
257
|
+
return {
|
|
258
|
+
relativePath: record.relativePath,
|
|
259
|
+
...record.task.metadata,
|
|
260
|
+
...(derived ? { derived } : {}),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function writeTaskRecord(record, json, stdout) {
|
|
264
|
+
if (json) {
|
|
265
|
+
stdout(`${JSON.stringify(taskRecordJson(record), null, 2)}\n`);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
stdout(`${record.task.metadata.id}\n`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function warningWriter(stderr) {
|
|
272
|
+
return (warning) => stderr(`warning: ${warning.message}\n`);
|
|
273
|
+
}
|
|
274
|
+
function normalizeMutationPaths(repository, values) {
|
|
275
|
+
return values?.map((value) => normalizeRepositoryPath(repository, value));
|
|
276
|
+
}
|
|
277
|
+
function updateInputFromValues(repository, values) {
|
|
278
|
+
return {
|
|
279
|
+
...(values.title !== undefined ? { title: values.title } : {}),
|
|
280
|
+
...(values.status !== undefined ? { status: values.status } : {}),
|
|
281
|
+
...(values['clear-priority']
|
|
282
|
+
? { priority: null }
|
|
283
|
+
: values.priority !== undefined
|
|
284
|
+
? { priority: values.priority }
|
|
285
|
+
: {}),
|
|
286
|
+
...(values['clear-order']
|
|
287
|
+
? { order: null }
|
|
288
|
+
: values.order !== undefined
|
|
289
|
+
? { order: values.order }
|
|
290
|
+
: {}),
|
|
291
|
+
...(values['clear-labels']
|
|
292
|
+
? { labels: [] }
|
|
293
|
+
: values.label !== undefined
|
|
294
|
+
? { labels: values.label }
|
|
295
|
+
: {}),
|
|
296
|
+
...(values['clear-dependencies']
|
|
297
|
+
? { dependsOn: [] }
|
|
298
|
+
: values['depends-on'] !== undefined
|
|
299
|
+
? { dependsOn: values['depends-on'] }
|
|
300
|
+
: {}),
|
|
301
|
+
...(values['clear-files']
|
|
302
|
+
? { files: [] }
|
|
303
|
+
: values.file !== undefined
|
|
304
|
+
? { files: normalizeMutationPaths(repository, values.file) }
|
|
305
|
+
: {}),
|
|
306
|
+
...(values['clear-owner']
|
|
307
|
+
? { owner: null }
|
|
308
|
+
: values.owner !== undefined
|
|
309
|
+
? { owner: values.owner }
|
|
310
|
+
: {}),
|
|
311
|
+
...(values['clear-assignees']
|
|
312
|
+
? { assignees: [] }
|
|
313
|
+
: values.assignee !== undefined
|
|
314
|
+
? { assignees: values.assignee }
|
|
315
|
+
: {}),
|
|
316
|
+
...(values['clear-reviewers']
|
|
317
|
+
? { reviewers: [] }
|
|
318
|
+
: values.reviewer !== undefined
|
|
319
|
+
? { reviewers: values.reviewer }
|
|
320
|
+
: {}),
|
|
321
|
+
...(values['clear-team']
|
|
322
|
+
? { team: null }
|
|
323
|
+
: values.team !== undefined
|
|
324
|
+
? { team: values.team }
|
|
325
|
+
: {}),
|
|
326
|
+
...(values['clear-estimate']
|
|
327
|
+
? { estimate: null }
|
|
328
|
+
: values.estimate !== undefined
|
|
329
|
+
? { estimate: values.estimate }
|
|
330
|
+
: {}),
|
|
331
|
+
...(values['clear-effort']
|
|
332
|
+
? { effort: null }
|
|
333
|
+
: values.effort !== undefined
|
|
334
|
+
? { effort: values.effort }
|
|
335
|
+
: {}),
|
|
336
|
+
...(values['clear-risk']
|
|
337
|
+
? { risk: null }
|
|
338
|
+
: values.risk !== undefined
|
|
339
|
+
? { risk: values.risk }
|
|
340
|
+
: {}),
|
|
341
|
+
...(values['clear-due-date']
|
|
342
|
+
? { dueDate: null }
|
|
343
|
+
: values['due-date'] !== undefined
|
|
344
|
+
? { dueDate: values['due-date'] }
|
|
345
|
+
: {}),
|
|
346
|
+
...(values['clear-related']
|
|
347
|
+
? { related: [] }
|
|
348
|
+
: values.related !== undefined
|
|
349
|
+
? { related: values.related }
|
|
350
|
+
: {}),
|
|
351
|
+
...(values['clear-duplicates']
|
|
352
|
+
? { duplicates: [] }
|
|
353
|
+
: values.duplicate !== undefined
|
|
354
|
+
? { duplicates: values.duplicate }
|
|
355
|
+
: {}),
|
|
356
|
+
...(values['clear-parent']
|
|
357
|
+
? { parent: null }
|
|
358
|
+
: values.parent !== undefined
|
|
359
|
+
? { parent: values.parent }
|
|
360
|
+
: {}),
|
|
361
|
+
...(values['clear-directories']
|
|
362
|
+
? { directories: [] }
|
|
363
|
+
: values.directory !== undefined
|
|
364
|
+
? { directories: normalizeMutationPaths(repository, values.directory) }
|
|
365
|
+
: {}),
|
|
366
|
+
...(values['clear-projects']
|
|
367
|
+
? { projects: [] }
|
|
368
|
+
: values.project !== undefined
|
|
369
|
+
? { projects: values.project }
|
|
370
|
+
: {}),
|
|
371
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
76
374
|
export async function runCli(args, context = {}) {
|
|
77
375
|
const cwd = path.resolve(context.cwd ?? process.cwd());
|
|
78
376
|
const stdout = context.stdout ?? ((value) => process.stdout.write(value));
|
|
79
377
|
const stderr = context.stderr ?? ((value) => process.stderr.write(value));
|
|
378
|
+
const onWarning = warningWriter(stderr);
|
|
80
379
|
try {
|
|
81
380
|
const [command, subcommand] = args;
|
|
82
381
|
const commandArgs = args.slice(1);
|
|
83
|
-
const
|
|
382
|
+
const subcommandArgs = args.slice(2);
|
|
84
383
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
85
384
|
stdout(USAGE);
|
|
86
385
|
return 0;
|
|
87
386
|
}
|
|
88
|
-
if (command === 'init') {
|
|
89
|
-
const parsed =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stdout(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
387
|
+
if (command === 'init' || command === 'config' || command === 'doctor') {
|
|
388
|
+
const parsed = parseArgs({
|
|
389
|
+
args: commandArgs,
|
|
390
|
+
allowPositionals: false,
|
|
391
|
+
options: commonOptionDefinitions,
|
|
392
|
+
});
|
|
393
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, `${command} options`);
|
|
394
|
+
const commandCwd = resolveCommandCwd(cwd, values.cwd);
|
|
395
|
+
if (command === 'init') {
|
|
396
|
+
const repository = await initializeRepository(commandCwd);
|
|
397
|
+
stdout(`Initialized Taskset in ${repository.rootDirectory}\n`);
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
const repository = await discoverRepository(commandCwd);
|
|
401
|
+
if (command === 'config') {
|
|
402
|
+
if (values.json) {
|
|
403
|
+
stdout(`${JSON.stringify({
|
|
404
|
+
rootDirectory: repository.rootDirectory,
|
|
405
|
+
configPath: repository.configPath,
|
|
406
|
+
dataDirectory: repository.dataDirectory,
|
|
407
|
+
config: repository.config,
|
|
408
|
+
}, null, 2)}\n`);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
stdout(`${repository.configPath}\n`);
|
|
412
|
+
}
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
const result = await diagnoseRepository(repository);
|
|
416
|
+
if (values.json) {
|
|
417
|
+
stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
418
|
+
}
|
|
419
|
+
else if (result.valid) {
|
|
420
|
+
stdout(`Taskset repository is valid (${result.taskCount} tasks)\n`);
|
|
104
421
|
}
|
|
105
422
|
else {
|
|
106
|
-
|
|
423
|
+
for (const diagnostic of result.diagnostics) {
|
|
424
|
+
stdout(`${diagnostic.code}\t${diagnostic.path ?? '-'}\t${diagnostic.message}\t${diagnostic.remediation}\n`);
|
|
425
|
+
}
|
|
107
426
|
}
|
|
427
|
+
return result.valid ? 0 : 1;
|
|
428
|
+
}
|
|
429
|
+
if (command === 'generate') {
|
|
430
|
+
const parsed = parseArgs({
|
|
431
|
+
args: commandArgs,
|
|
432
|
+
allowPositionals: false,
|
|
433
|
+
options: commonOptionDefinitions,
|
|
434
|
+
});
|
|
435
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'generate options');
|
|
436
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
437
|
+
const result = await generateViews(repository);
|
|
438
|
+
stdout(values.json
|
|
439
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
440
|
+
: `Generated ${result.files.length} views in ${result.directory}\n`);
|
|
108
441
|
return 0;
|
|
109
442
|
}
|
|
443
|
+
if (command === 'snapshot') {
|
|
444
|
+
if (subcommand === 'create' || subcommand === 'list') {
|
|
445
|
+
const parsed = parseArgs({
|
|
446
|
+
args: subcommandArgs,
|
|
447
|
+
allowPositionals: false,
|
|
448
|
+
options: commonOptionDefinitions,
|
|
449
|
+
});
|
|
450
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, `snapshot ${subcommand} options`);
|
|
451
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
452
|
+
if (subcommand === 'create') {
|
|
453
|
+
const result = await createSnapshot(repository);
|
|
454
|
+
stdout(values.json ? `${JSON.stringify(result, null, 2)}\n` : `${result.id}\n`);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
const results = await listSnapshots(repository);
|
|
458
|
+
if (values.json) {
|
|
459
|
+
stdout(`${JSON.stringify(results, null, 2)}\n`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
for (const result of results) {
|
|
463
|
+
stdout(`${result.id}\t${result.createdAt}\t${result.files.length}\n`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
if (subcommand === 'restore') {
|
|
470
|
+
const parsed = parseArgs({
|
|
471
|
+
args: subcommandArgs,
|
|
472
|
+
allowPositionals: true,
|
|
473
|
+
options: {
|
|
474
|
+
...commonOptionDefinitions,
|
|
475
|
+
apply: { type: 'boolean' },
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
const [snapshotId] = requirePositionals(parsed.positionals, 1, 'exactly one snapshot ID');
|
|
479
|
+
const values = parseSchema(z.strictObject({
|
|
480
|
+
apply: z.boolean().optional(),
|
|
481
|
+
cwd: CwdSchema,
|
|
482
|
+
json: JsonSchema,
|
|
483
|
+
}), parsed.values, 'snapshot restore options');
|
|
484
|
+
const validatedSnapshotId = parseSchema(z.string().regex(/^\d{8}T\d{6}Z-[a-f0-9]{12}$/u), snapshotId, 'snapshot ID');
|
|
485
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
486
|
+
const result = await restoreSnapshot(repository, validatedSnapshotId, {
|
|
487
|
+
apply: values.apply,
|
|
488
|
+
onWarning,
|
|
489
|
+
});
|
|
490
|
+
stdout(values.json
|
|
491
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
492
|
+
: `${result.applied ? 'Restored' : 'Would restore'} ${result.changes.length} task files from ${validatedSnapshotId}\n`);
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
throw new CliUsageError(`Unknown snapshot command "${subcommand ?? ''}"`);
|
|
496
|
+
}
|
|
110
497
|
if (command !== 'task') {
|
|
111
498
|
throw new CliUsageError(`Unknown command "${command}"`);
|
|
112
499
|
}
|
|
113
500
|
if (subcommand === 'create') {
|
|
114
501
|
const parsed = parseArgs({
|
|
115
|
-
args:
|
|
502
|
+
args: subcommandArgs,
|
|
503
|
+
allowPositionals: false,
|
|
504
|
+
options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
|
|
505
|
+
});
|
|
506
|
+
const values = parseSchema(CreateValuesSchema, parsed.values, 'task create options');
|
|
507
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
508
|
+
const record = await createTask(repository, {
|
|
509
|
+
title: values.title,
|
|
510
|
+
...(values.status ? { status: values.status } : {}),
|
|
511
|
+
...(values.priority ? { priority: values.priority } : {}),
|
|
512
|
+
...(values.order !== undefined ? { order: values.order } : {}),
|
|
513
|
+
...(values.label ? { labels: values.label } : {}),
|
|
514
|
+
...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
|
|
515
|
+
...(values.file ? { files: normalizeMutationPaths(repository, values.file) } : {}),
|
|
516
|
+
...(values.owner ? { owner: values.owner } : {}),
|
|
517
|
+
...(values.assignee ? { assignees: values.assignee } : {}),
|
|
518
|
+
...(values.reviewer ? { reviewers: values.reviewer } : {}),
|
|
519
|
+
...(values.team ? { team: values.team } : {}),
|
|
520
|
+
...(values.estimate !== undefined ? { estimate: values.estimate } : {}),
|
|
521
|
+
...(values.effort !== undefined ? { effort: values.effort } : {}),
|
|
522
|
+
...(values.risk ? { risk: values.risk } : {}),
|
|
523
|
+
...(values['due-date'] ? { dueDate: values['due-date'] } : {}),
|
|
524
|
+
...(values.related ? { related: values.related } : {}),
|
|
525
|
+
...(values.duplicate ? { duplicates: values.duplicate } : {}),
|
|
526
|
+
...(values.parent ? { parent: values.parent } : {}),
|
|
527
|
+
...(values.directory
|
|
528
|
+
? { directories: normalizeMutationPaths(repository, values.directory) }
|
|
529
|
+
: {}),
|
|
530
|
+
...(values.project ? { projects: values.project } : {}),
|
|
531
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
532
|
+
}, { onWarning });
|
|
533
|
+
writeTaskRecord(record, values.json, stdout);
|
|
534
|
+
return 0;
|
|
535
|
+
}
|
|
536
|
+
if (subcommand === 'list') {
|
|
537
|
+
const parsed = parseArgs({
|
|
538
|
+
args: subcommandArgs,
|
|
116
539
|
allowPositionals: false,
|
|
117
540
|
options: {
|
|
118
|
-
body: { type: 'string' },
|
|
119
541
|
cwd: { type: 'string' },
|
|
120
|
-
'depends-on': { type: 'string', multiple: true },
|
|
121
|
-
file: { type: 'string', multiple: true },
|
|
122
542
|
json: { type: 'boolean' },
|
|
543
|
+
status: { type: 'string', multiple: true },
|
|
544
|
+
priority: { type: 'string', multiple: true },
|
|
123
545
|
label: { type: 'string', multiple: true },
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
546
|
+
owner: { type: 'string', multiple: true },
|
|
547
|
+
assignee: { type: 'string', multiple: true },
|
|
548
|
+
reviewer: { type: 'string', multiple: true },
|
|
549
|
+
team: { type: 'string', multiple: true },
|
|
550
|
+
risk: { type: 'string', multiple: true },
|
|
551
|
+
project: { type: 'string', multiple: true },
|
|
552
|
+
'depends-on': { type: 'string' },
|
|
553
|
+
related: { type: 'string' },
|
|
554
|
+
duplicate: { type: 'string' },
|
|
555
|
+
parent: { type: 'string' },
|
|
556
|
+
file: { type: 'string', multiple: true },
|
|
557
|
+
directory: { type: 'string', multiple: true },
|
|
558
|
+
'estimate-min': { type: 'string' },
|
|
559
|
+
'estimate-max': { type: 'string' },
|
|
560
|
+
'effort-min': { type: 'string' },
|
|
561
|
+
'effort-max': { type: 'string' },
|
|
562
|
+
'due-before': { type: 'string' },
|
|
563
|
+
'due-after': { type: 'string' },
|
|
564
|
+
'created-before': { type: 'string' },
|
|
565
|
+
'created-after': { type: 'string' },
|
|
566
|
+
'updated-before': { type: 'string' },
|
|
567
|
+
'updated-after': { type: 'string' },
|
|
568
|
+
search: { type: 'string' },
|
|
569
|
+
sort: { type: 'string' },
|
|
570
|
+
direction: { type: 'string' },
|
|
571
|
+
impact: { type: 'boolean' },
|
|
572
|
+
'include-derived': { type: 'boolean' },
|
|
127
573
|
},
|
|
128
574
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
...(
|
|
138
|
-
...(
|
|
139
|
-
...(
|
|
140
|
-
...(
|
|
141
|
-
...(
|
|
142
|
-
...(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
575
|
+
const values = parseSchema(ListValuesSchema, parsed.values, 'task list options');
|
|
576
|
+
const query = parseSchema(TaskQuerySchema, {
|
|
577
|
+
...(values.status ? { statuses: values.status } : {}),
|
|
578
|
+
...(values.priority ? { priorities: values.priority } : {}),
|
|
579
|
+
...(values.label ? { labels: values.label } : {}),
|
|
580
|
+
...(values.owner ? { owners: values.owner } : {}),
|
|
581
|
+
...(values.assignee ? { assignees: values.assignee } : {}),
|
|
582
|
+
...(values.reviewer ? { reviewers: values.reviewer } : {}),
|
|
583
|
+
...(values.team ? { teams: values.team } : {}),
|
|
584
|
+
...(values.risk ? { risks: values.risk } : {}),
|
|
585
|
+
...(values.project ? { projects: values.project } : {}),
|
|
586
|
+
...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
|
|
587
|
+
...(values.related ? { related: values.related } : {}),
|
|
588
|
+
...(values.duplicate ? { duplicate: values.duplicate } : {}),
|
|
589
|
+
...(values.parent ? { parent: values.parent } : {}),
|
|
590
|
+
...(values.file ? { files: values.file } : {}),
|
|
591
|
+
...(values.directory ? { directories: values.directory } : {}),
|
|
592
|
+
...(values['estimate-min'] !== undefined ? { estimateMin: values['estimate-min'] } : {}),
|
|
593
|
+
...(values['estimate-max'] !== undefined ? { estimateMax: values['estimate-max'] } : {}),
|
|
594
|
+
...(values['effort-min'] !== undefined ? { effortMin: values['effort-min'] } : {}),
|
|
595
|
+
...(values['effort-max'] !== undefined ? { effortMax: values['effort-max'] } : {}),
|
|
596
|
+
...(values['due-before'] ? { dueBefore: values['due-before'] } : {}),
|
|
597
|
+
...(values['due-after'] ? { dueAfter: values['due-after'] } : {}),
|
|
598
|
+
...(values['created-before'] ? { createdBefore: values['created-before'] } : {}),
|
|
599
|
+
...(values['created-after'] ? { createdAfter: values['created-after'] } : {}),
|
|
600
|
+
...(values['updated-before'] ? { updatedBefore: values['updated-before'] } : {}),
|
|
601
|
+
...(values['updated-after'] ? { updatedAfter: values['updated-after'] } : {}),
|
|
602
|
+
...(values.search !== undefined ? { text: values.search } : {}),
|
|
603
|
+
...(values.sort ? { sortBy: values.sort } : {}),
|
|
604
|
+
...(values.direction ? { direction: values.direction } : {}),
|
|
605
|
+
...(values.impact ? { impact: true } : {}),
|
|
606
|
+
}, 'task list query');
|
|
607
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
608
|
+
const result = await queryTasks(repository, query);
|
|
609
|
+
const graph = values['include-derived'] ? (await buildTaskIndex(repository)).graph : undefined;
|
|
610
|
+
const serialize = (record) => taskRecordJson(record, graph?.derive(record.task.metadata.id));
|
|
611
|
+
if (values.json) {
|
|
612
|
+
stdout(`${JSON.stringify(values.impact
|
|
613
|
+
? {
|
|
614
|
+
direct: result.direct.map(serialize),
|
|
615
|
+
impacted: result.impacted.map(serialize),
|
|
616
|
+
}
|
|
617
|
+
: result.direct.map(serialize), null, 2)}\n`);
|
|
161
618
|
}
|
|
162
619
|
else {
|
|
163
|
-
for (const record of
|
|
164
|
-
stdout(`${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`);
|
|
620
|
+
for (const record of result.direct) {
|
|
621
|
+
stdout(`${values.impact ? 'direct\t' : ''}${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`);
|
|
622
|
+
}
|
|
623
|
+
for (const record of result.impacted) {
|
|
624
|
+
stdout(`impact\t${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`);
|
|
165
625
|
}
|
|
166
626
|
}
|
|
167
627
|
return 0;
|
|
168
628
|
}
|
|
169
629
|
if (subcommand === 'show') {
|
|
170
630
|
const parsed = parseArgs({
|
|
171
|
-
args:
|
|
631
|
+
args: subcommandArgs,
|
|
172
632
|
allowPositionals: true,
|
|
173
633
|
options: {
|
|
174
|
-
|
|
175
|
-
|
|
634
|
+
...commonOptionDefinitions,
|
|
635
|
+
'include-derived': { type: 'boolean' },
|
|
176
636
|
},
|
|
177
637
|
});
|
|
178
|
-
const taskId =
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
638
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID');
|
|
639
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID');
|
|
640
|
+
const values = parseSchema(z.strictObject({
|
|
641
|
+
cwd: CwdSchema,
|
|
642
|
+
json: JsonSchema,
|
|
643
|
+
'include-derived': z.boolean().optional(),
|
|
644
|
+
}), parsed.values, 'task show options');
|
|
645
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
646
|
+
const record = await readTask(repository, validatedTaskId);
|
|
647
|
+
if (values.json) {
|
|
648
|
+
const derived = values['include-derived']
|
|
649
|
+
? (await buildTaskIndex(repository)).graph.derive(validatedTaskId)
|
|
650
|
+
: undefined;
|
|
182
651
|
stdout(`${JSON.stringify({
|
|
183
652
|
relativePath: record.relativePath,
|
|
184
653
|
metadata: record.task.metadata,
|
|
185
654
|
body: record.task.body,
|
|
655
|
+
...(derived ? { derived } : {}),
|
|
186
656
|
}, null, 2)}\n`);
|
|
187
657
|
}
|
|
188
658
|
else {
|
|
@@ -190,10 +660,75 @@ export async function runCli(args, context = {}) {
|
|
|
190
660
|
}
|
|
191
661
|
return 0;
|
|
192
662
|
}
|
|
663
|
+
if (subcommand === 'update') {
|
|
664
|
+
const clearDefinitions = Object.fromEntries(UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' }]));
|
|
665
|
+
const parsed = parseArgs({
|
|
666
|
+
args: subcommandArgs,
|
|
667
|
+
allowPositionals: true,
|
|
668
|
+
options: {
|
|
669
|
+
...metadataOptionDefinitions,
|
|
670
|
+
...clearDefinitions,
|
|
671
|
+
...commonOptionDefinitions,
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID');
|
|
675
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID');
|
|
676
|
+
const values = parseSchema(UpdateValuesSchema, parsed.values, 'task update options');
|
|
677
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
678
|
+
const record = await updateTask(repository, validatedTaskId, updateInputFromValues(repository, values), { onWarning });
|
|
679
|
+
writeTaskRecord(record, values.json, stdout);
|
|
680
|
+
return 0;
|
|
681
|
+
}
|
|
682
|
+
if (subcommand === 'status') {
|
|
683
|
+
const parsed = parseArgs({
|
|
684
|
+
args: subcommandArgs,
|
|
685
|
+
allowPositionals: true,
|
|
686
|
+
options: commonOptionDefinitions,
|
|
687
|
+
});
|
|
688
|
+
const [taskId, status] = requirePositionals(parsed.positionals, 2, 'a task ID and status');
|
|
689
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID');
|
|
690
|
+
const validatedStatus = parseSchema(TaskStatusSchema, status, 'task status');
|
|
691
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'task status options');
|
|
692
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
693
|
+
const record = await updateTask(repository, validatedTaskId, { status: validatedStatus }, { onWarning });
|
|
694
|
+
writeTaskRecord(record, values.json, stdout);
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
697
|
+
if (subcommand === 'delete') {
|
|
698
|
+
const parsed = parseArgs({
|
|
699
|
+
args: subcommandArgs,
|
|
700
|
+
allowPositionals: true,
|
|
701
|
+
options: {
|
|
702
|
+
...commonOptionDefinitions,
|
|
703
|
+
'remove-dependencies': { type: 'boolean' },
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID');
|
|
707
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID');
|
|
708
|
+
const values = parseSchema(z.strictObject({
|
|
709
|
+
cwd: CwdSchema,
|
|
710
|
+
json: JsonSchema,
|
|
711
|
+
'remove-dependencies': z.boolean().optional(),
|
|
712
|
+
}), parsed.values, 'task delete options');
|
|
713
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd));
|
|
714
|
+
const record = await deleteTask(repository, validatedTaskId, {
|
|
715
|
+
removeDependencies: values['remove-dependencies'],
|
|
716
|
+
onWarning,
|
|
717
|
+
});
|
|
718
|
+
if (values.json) {
|
|
719
|
+
stdout(`${JSON.stringify({ deleted: true, ...taskRecordJson(record) }, null, 2)}\n`);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
stdout(`${validatedTaskId}\n`);
|
|
723
|
+
}
|
|
724
|
+
return 0;
|
|
725
|
+
}
|
|
193
726
|
throw new CliUsageError(`Unknown task command "${subcommand ?? ''}"`);
|
|
194
727
|
}
|
|
195
728
|
catch (error) {
|
|
196
|
-
if (error instanceof CliUsageError ||
|
|
729
|
+
if (error instanceof CliUsageError ||
|
|
730
|
+
error instanceof RepositoryPathError ||
|
|
731
|
+
error instanceof TypeError) {
|
|
197
732
|
stderr(`${formatError(error)}\n\n${USAGE}`);
|
|
198
733
|
return 2;
|
|
199
734
|
}
|