@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/src/cli.ts
CHANGED
|
@@ -1,34 +1,66 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import { parseArgs } from 'node:util'
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
TaskIdSchema,
|
|
5
|
+
TaskPrioritySchema,
|
|
6
|
+
TaskRiskSchema,
|
|
7
|
+
TaskStatusSchema,
|
|
8
|
+
TaskTimestampSchema,
|
|
8
9
|
} from '@taskset/contracts'
|
|
9
10
|
import {
|
|
11
|
+
buildTaskIndex,
|
|
12
|
+
createSnapshot,
|
|
10
13
|
createTask,
|
|
14
|
+
type DerivedTaskRelationships,
|
|
15
|
+
deleteTask,
|
|
16
|
+
diagnoseRepository,
|
|
11
17
|
discoverRepository,
|
|
18
|
+
generateViews,
|
|
12
19
|
initializeRepository,
|
|
13
|
-
|
|
20
|
+
listSnapshots,
|
|
21
|
+
normalizeRepositoryPath,
|
|
22
|
+
queryTasks,
|
|
23
|
+
RepositoryPathError,
|
|
14
24
|
readTask,
|
|
25
|
+
restoreSnapshot,
|
|
15
26
|
serializeTaskFile,
|
|
27
|
+
TASK_SORT_DIRECTIONS,
|
|
28
|
+
TASK_SORT_KEYS,
|
|
29
|
+
type TaskQuery,
|
|
30
|
+
TaskQuerySchema,
|
|
31
|
+
type TaskRecord,
|
|
32
|
+
type UpdateTaskInput,
|
|
33
|
+
updateTask,
|
|
16
34
|
} from '@taskset/core'
|
|
35
|
+
import * as z from 'zod'
|
|
17
36
|
|
|
18
37
|
const USAGE = `Usage:
|
|
19
38
|
taskset init [--cwd <path>]
|
|
20
39
|
taskset config [--json] [--cwd <path>]
|
|
21
|
-
taskset
|
|
22
|
-
taskset
|
|
23
|
-
taskset
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
--
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
--
|
|
31
|
-
|
|
40
|
+
taskset doctor [--json] [--cwd <path>]
|
|
41
|
+
taskset generate [--json] [--cwd <path>]
|
|
42
|
+
taskset snapshot create|list [--json] [--cwd <path>]
|
|
43
|
+
taskset snapshot restore <snapshot-id> [--apply] [--json] [--cwd <path>]
|
|
44
|
+
taskset task create --title <title> [metadata options]
|
|
45
|
+
taskset task list [query options]
|
|
46
|
+
taskset task show <task-id> [--include-derived] [--json] [--cwd <path>]
|
|
47
|
+
taskset task update <task-id> [metadata options]
|
|
48
|
+
taskset task status <task-id> <status> [--json] [--cwd <path>]
|
|
49
|
+
taskset task delete <task-id> [--remove-dependencies] [--json] [--cwd <path>]
|
|
50
|
+
|
|
51
|
+
Metadata options:
|
|
52
|
+
--status --priority --order --owner --team --estimate --effort --risk --due-date
|
|
53
|
+
--label --assignee --reviewer --depends-on --related --duplicate
|
|
54
|
+
--parent --file --directory --project --body
|
|
55
|
+
Repeat array options. Update commands also accept matching --clear-* options.
|
|
56
|
+
|
|
57
|
+
List query options:
|
|
58
|
+
--status --priority --label --owner --assignee --reviewer --team --risk
|
|
59
|
+
--project --depends-on --related --duplicate --parent --file --directory
|
|
60
|
+
--estimate-min --estimate-max --effort-min --effort-max
|
|
61
|
+
--due-before --due-after --created-before --created-after
|
|
62
|
+
--updated-before --updated-after --search --sort --direction
|
|
63
|
+
--impact --include-derived --json
|
|
32
64
|
`
|
|
33
65
|
|
|
34
66
|
export interface CliContext {
|
|
@@ -37,58 +69,244 @@ export interface CliContext {
|
|
|
37
69
|
readonly stdout?: (value: string) => void
|
|
38
70
|
}
|
|
39
71
|
|
|
72
|
+
interface CliIssue {
|
|
73
|
+
readonly field: string
|
|
74
|
+
readonly message: string
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
class CliUsageError extends Error {
|
|
41
|
-
|
|
78
|
+
readonly issues: readonly CliIssue[]
|
|
79
|
+
|
|
80
|
+
constructor(message: string, issues: readonly CliIssue[] = []) {
|
|
42
81
|
super(message)
|
|
43
82
|
this.name = 'CliUsageError'
|
|
83
|
+
this.issues = issues
|
|
44
84
|
}
|
|
45
85
|
}
|
|
46
86
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
const TrimmedStringSchema = z.string().trim().min(1)
|
|
88
|
+
function uniqueArray<T>(schema: z.ZodType<T>) {
|
|
89
|
+
return z
|
|
90
|
+
.array(schema)
|
|
91
|
+
.refine((values) => new Set(values).size === values.length, 'Values must be unique')
|
|
92
|
+
}
|
|
51
93
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
const StringListSchema = uniqueArray(TrimmedStringSchema)
|
|
95
|
+
const TaskIdListSchema = uniqueArray(TaskIdSchema)
|
|
96
|
+
const CwdSchema = z.string().min(1).optional()
|
|
97
|
+
const JsonSchema = z.boolean().optional()
|
|
98
|
+
|
|
99
|
+
const CommonValuesSchema = z.strictObject({
|
|
100
|
+
cwd: CwdSchema,
|
|
101
|
+
json: JsonSchema,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const CreateValuesSchema = z.strictObject({
|
|
105
|
+
title: TrimmedStringSchema,
|
|
106
|
+
status: TaskStatusSchema.optional(),
|
|
107
|
+
priority: TaskPrioritySchema.optional(),
|
|
108
|
+
order: z.coerce.number().finite().nonnegative().optional(),
|
|
109
|
+
label: StringListSchema.optional(),
|
|
110
|
+
'depends-on': TaskIdListSchema.optional(),
|
|
111
|
+
file: StringListSchema.optional(),
|
|
112
|
+
owner: TrimmedStringSchema.optional(),
|
|
113
|
+
assignee: StringListSchema.optional(),
|
|
114
|
+
reviewer: StringListSchema.optional(),
|
|
115
|
+
team: TrimmedStringSchema.optional(),
|
|
116
|
+
estimate: z.coerce.number().int().nonnegative().optional(),
|
|
117
|
+
effort: z.coerce.number().finite().nonnegative().optional(),
|
|
118
|
+
risk: TaskRiskSchema.optional(),
|
|
119
|
+
'due-date': TaskTimestampSchema.optional(),
|
|
120
|
+
related: TaskIdListSchema.optional(),
|
|
121
|
+
duplicate: TaskIdListSchema.optional(),
|
|
122
|
+
parent: TaskIdSchema.optional(),
|
|
123
|
+
directory: StringListSchema.optional(),
|
|
124
|
+
project: StringListSchema.optional(),
|
|
125
|
+
body: z.string().optional(),
|
|
126
|
+
cwd: CwdSchema,
|
|
127
|
+
json: JsonSchema,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const UPDATE_CLEAR_PAIRS = [
|
|
131
|
+
['priority', 'clear-priority'],
|
|
132
|
+
['order', 'clear-order'],
|
|
133
|
+
['label', 'clear-labels'],
|
|
134
|
+
['depends-on', 'clear-dependencies'],
|
|
135
|
+
['file', 'clear-files'],
|
|
136
|
+
['owner', 'clear-owner'],
|
|
137
|
+
['assignee', 'clear-assignees'],
|
|
138
|
+
['reviewer', 'clear-reviewers'],
|
|
139
|
+
['team', 'clear-team'],
|
|
140
|
+
['estimate', 'clear-estimate'],
|
|
141
|
+
['effort', 'clear-effort'],
|
|
142
|
+
['risk', 'clear-risk'],
|
|
143
|
+
['due-date', 'clear-due-date'],
|
|
144
|
+
['related', 'clear-related'],
|
|
145
|
+
['duplicate', 'clear-duplicates'],
|
|
146
|
+
['parent', 'clear-parent'],
|
|
147
|
+
['directory', 'clear-directories'],
|
|
148
|
+
['project', 'clear-projects'],
|
|
149
|
+
] as const
|
|
150
|
+
|
|
151
|
+
const UpdateValuesSchema = z
|
|
152
|
+
.strictObject({
|
|
153
|
+
title: TrimmedStringSchema.optional(),
|
|
154
|
+
status: TaskStatusSchema.optional(),
|
|
155
|
+
priority: TaskPrioritySchema.optional(),
|
|
156
|
+
order: z.coerce.number().finite().nonnegative().optional(),
|
|
157
|
+
label: StringListSchema.optional(),
|
|
158
|
+
'depends-on': TaskIdListSchema.optional(),
|
|
159
|
+
file: StringListSchema.optional(),
|
|
160
|
+
owner: TrimmedStringSchema.optional(),
|
|
161
|
+
assignee: StringListSchema.optional(),
|
|
162
|
+
reviewer: StringListSchema.optional(),
|
|
163
|
+
team: TrimmedStringSchema.optional(),
|
|
164
|
+
estimate: z.coerce.number().int().nonnegative().optional(),
|
|
165
|
+
effort: z.coerce.number().finite().nonnegative().optional(),
|
|
166
|
+
risk: TaskRiskSchema.optional(),
|
|
167
|
+
'due-date': TaskTimestampSchema.optional(),
|
|
168
|
+
related: TaskIdListSchema.optional(),
|
|
169
|
+
duplicate: TaskIdListSchema.optional(),
|
|
170
|
+
parent: TaskIdSchema.optional(),
|
|
171
|
+
directory: StringListSchema.optional(),
|
|
172
|
+
project: StringListSchema.optional(),
|
|
173
|
+
body: z.string().optional(),
|
|
174
|
+
'clear-priority': z.boolean().optional(),
|
|
175
|
+
'clear-order': z.boolean().optional(),
|
|
176
|
+
'clear-labels': z.boolean().optional(),
|
|
177
|
+
'clear-dependencies': z.boolean().optional(),
|
|
178
|
+
'clear-files': z.boolean().optional(),
|
|
179
|
+
'clear-owner': z.boolean().optional(),
|
|
180
|
+
'clear-assignees': z.boolean().optional(),
|
|
181
|
+
'clear-reviewers': z.boolean().optional(),
|
|
182
|
+
'clear-team': z.boolean().optional(),
|
|
183
|
+
'clear-estimate': z.boolean().optional(),
|
|
184
|
+
'clear-effort': z.boolean().optional(),
|
|
185
|
+
'clear-risk': z.boolean().optional(),
|
|
186
|
+
'clear-due-date': z.boolean().optional(),
|
|
187
|
+
'clear-related': z.boolean().optional(),
|
|
188
|
+
'clear-duplicates': z.boolean().optional(),
|
|
189
|
+
'clear-parent': z.boolean().optional(),
|
|
190
|
+
'clear-directories': z.boolean().optional(),
|
|
191
|
+
'clear-projects': z.boolean().optional(),
|
|
192
|
+
cwd: CwdSchema,
|
|
193
|
+
json: JsonSchema,
|
|
194
|
+
})
|
|
195
|
+
.superRefine((values, context) => {
|
|
196
|
+
for (const [valueKey, clearKey] of UPDATE_CLEAR_PAIRS) {
|
|
197
|
+
if (values[valueKey] !== undefined && values[clearKey]) {
|
|
198
|
+
context.addIssue({
|
|
199
|
+
code: 'custom',
|
|
200
|
+
path: [clearKey],
|
|
201
|
+
message: `Cannot set and clear ${valueKey} in the same update`,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
55
205
|
|
|
56
|
-
|
|
57
|
-
}
|
|
206
|
+
const nonControlKeys = Object.keys(values).filter((key) => key !== 'cwd' && key !== 'json')
|
|
58
207
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
208
|
+
if (nonControlKeys.length === 0) {
|
|
209
|
+
context.addIssue({
|
|
210
|
+
code: 'custom',
|
|
211
|
+
message: 'Task update requires at least one field option',
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
})
|
|
63
215
|
|
|
64
|
-
|
|
65
|
-
|
|
216
|
+
const ListValuesSchema = z.strictObject({
|
|
217
|
+
status: z.array(TaskStatusSchema).optional(),
|
|
218
|
+
priority: z.array(TaskPrioritySchema).optional(),
|
|
219
|
+
label: StringListSchema.optional(),
|
|
220
|
+
owner: StringListSchema.optional(),
|
|
221
|
+
assignee: StringListSchema.optional(),
|
|
222
|
+
reviewer: StringListSchema.optional(),
|
|
223
|
+
team: StringListSchema.optional(),
|
|
224
|
+
risk: z.array(TaskRiskSchema).optional(),
|
|
225
|
+
project: StringListSchema.optional(),
|
|
226
|
+
'depends-on': TaskIdSchema.optional(),
|
|
227
|
+
related: TaskIdSchema.optional(),
|
|
228
|
+
duplicate: TaskIdSchema.optional(),
|
|
229
|
+
parent: TaskIdSchema.optional(),
|
|
230
|
+
file: StringListSchema.optional(),
|
|
231
|
+
directory: StringListSchema.optional(),
|
|
232
|
+
'estimate-min': z.coerce.number().finite().nonnegative().optional(),
|
|
233
|
+
'estimate-max': z.coerce.number().finite().nonnegative().optional(),
|
|
234
|
+
'effort-min': z.coerce.number().finite().nonnegative().optional(),
|
|
235
|
+
'effort-max': z.coerce.number().finite().nonnegative().optional(),
|
|
236
|
+
'due-before': TaskTimestampSchema.optional(),
|
|
237
|
+
'due-after': TaskTimestampSchema.optional(),
|
|
238
|
+
'created-before': TaskTimestampSchema.optional(),
|
|
239
|
+
'created-after': TaskTimestampSchema.optional(),
|
|
240
|
+
'updated-before': TaskTimestampSchema.optional(),
|
|
241
|
+
'updated-after': TaskTimestampSchema.optional(),
|
|
242
|
+
search: z.string().optional(),
|
|
243
|
+
sort: z.enum(TASK_SORT_KEYS).optional(),
|
|
244
|
+
direction: z.enum(TASK_SORT_DIRECTIONS).optional(),
|
|
245
|
+
impact: z.boolean().optional(),
|
|
246
|
+
'include-derived': z.boolean().optional(),
|
|
247
|
+
cwd: CwdSchema,
|
|
248
|
+
json: JsonSchema,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const metadataOptionDefinitions = {
|
|
252
|
+
body: { type: 'string' },
|
|
253
|
+
'depends-on': { type: 'string', multiple: true },
|
|
254
|
+
file: { type: 'string', multiple: true },
|
|
255
|
+
label: { type: 'string', multiple: true },
|
|
256
|
+
priority: { type: 'string' },
|
|
257
|
+
order: { type: 'string' },
|
|
258
|
+
status: { type: 'string' },
|
|
259
|
+
title: { type: 'string' },
|
|
260
|
+
owner: { type: 'string' },
|
|
261
|
+
assignee: { type: 'string', multiple: true },
|
|
262
|
+
reviewer: { type: 'string', multiple: true },
|
|
263
|
+
team: { type: 'string' },
|
|
264
|
+
estimate: { type: 'string' },
|
|
265
|
+
effort: { type: 'string' },
|
|
266
|
+
risk: { type: 'string' },
|
|
267
|
+
'due-date': { type: 'string' },
|
|
268
|
+
related: { type: 'string', multiple: true },
|
|
269
|
+
duplicate: { type: 'string', multiple: true },
|
|
270
|
+
parent: { type: 'string' },
|
|
271
|
+
directory: { type: 'string', multiple: true },
|
|
272
|
+
project: { type: 'string', multiple: true },
|
|
273
|
+
} as const
|
|
274
|
+
|
|
275
|
+
const commonOptionDefinitions = {
|
|
276
|
+
cwd: { type: 'string' },
|
|
277
|
+
json: { type: 'boolean' },
|
|
278
|
+
} as const
|
|
279
|
+
|
|
280
|
+
function parseSchema<T>(schema: z.ZodType<T>, value: unknown, label: string): T {
|
|
281
|
+
const result = schema.safeParse(value)
|
|
282
|
+
|
|
283
|
+
if (!result.success) {
|
|
284
|
+
throw new CliUsageError(
|
|
285
|
+
`Invalid ${label}`,
|
|
286
|
+
result.error.issues.map((issue) => ({
|
|
287
|
+
field: issue.path.length > 0 ? issue.path.map(String).join('.') : label,
|
|
288
|
+
message: issue.message,
|
|
289
|
+
})),
|
|
290
|
+
)
|
|
66
291
|
}
|
|
67
292
|
|
|
68
|
-
return
|
|
293
|
+
return result.data
|
|
69
294
|
}
|
|
70
295
|
|
|
71
296
|
function resolveCommandCwd(baseDirectory: string, cwdOption: string | undefined): string {
|
|
72
297
|
return cwdOption ? path.resolve(baseDirectory, cwdOption) : baseDirectory
|
|
73
298
|
}
|
|
74
299
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function requireSinglePositional(positionals: readonly string[], name: string): string {
|
|
87
|
-
if (positionals.length !== 1 || !positionals[0]) {
|
|
88
|
-
throw new CliUsageError(`Expected exactly one ${name}`)
|
|
300
|
+
function requirePositionals(
|
|
301
|
+
positionals: readonly string[],
|
|
302
|
+
count: number,
|
|
303
|
+
description: string,
|
|
304
|
+
): readonly string[] {
|
|
305
|
+
if (positionals.length !== count || positionals.some((value) => value.length === 0)) {
|
|
306
|
+
throw new CliUsageError(`Expected ${description}`)
|
|
89
307
|
}
|
|
90
308
|
|
|
91
|
-
return positionals
|
|
309
|
+
return positionals
|
|
92
310
|
}
|
|
93
311
|
|
|
94
312
|
function formatError(error: unknown): string {
|
|
@@ -110,50 +328,298 @@ function formatError(error: unknown): string {
|
|
|
110
328
|
return `${error.message}${issues}`
|
|
111
329
|
}
|
|
112
330
|
|
|
331
|
+
function taskRecordJson(
|
|
332
|
+
record: TaskRecord,
|
|
333
|
+
derived?: DerivedTaskRelationships,
|
|
334
|
+
): Record<string, unknown> {
|
|
335
|
+
return {
|
|
336
|
+
relativePath: record.relativePath,
|
|
337
|
+
...record.task.metadata,
|
|
338
|
+
...(derived ? { derived } : {}),
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function writeTaskRecord(
|
|
343
|
+
record: TaskRecord,
|
|
344
|
+
json: boolean | undefined,
|
|
345
|
+
stdout: (value: string) => void,
|
|
346
|
+
): void {
|
|
347
|
+
if (json) {
|
|
348
|
+
stdout(`${JSON.stringify(taskRecordJson(record), null, 2)}\n`)
|
|
349
|
+
} else {
|
|
350
|
+
stdout(`${record.task.metadata.id}\n`)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function warningWriter(
|
|
355
|
+
stderr: (value: string) => void,
|
|
356
|
+
): (warning: { readonly message: string }) => void {
|
|
357
|
+
return (warning) => stderr(`warning: ${warning.message}\n`)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeMutationPaths(
|
|
361
|
+
repository: Awaited<ReturnType<typeof discoverRepository>>,
|
|
362
|
+
values: readonly string[] | undefined,
|
|
363
|
+
): string[] | undefined {
|
|
364
|
+
return values?.map((value) => normalizeRepositoryPath(repository, value))
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function updateInputFromValues(
|
|
368
|
+
repository: Awaited<ReturnType<typeof discoverRepository>>,
|
|
369
|
+
values: z.infer<typeof UpdateValuesSchema>,
|
|
370
|
+
): UpdateTaskInput {
|
|
371
|
+
return {
|
|
372
|
+
...(values.title !== undefined ? { title: values.title } : {}),
|
|
373
|
+
...(values.status !== undefined ? { status: values.status } : {}),
|
|
374
|
+
...(values['clear-priority']
|
|
375
|
+
? { priority: null }
|
|
376
|
+
: values.priority !== undefined
|
|
377
|
+
? { priority: values.priority }
|
|
378
|
+
: {}),
|
|
379
|
+
...(values['clear-order']
|
|
380
|
+
? { order: null }
|
|
381
|
+
: values.order !== undefined
|
|
382
|
+
? { order: values.order }
|
|
383
|
+
: {}),
|
|
384
|
+
...(values['clear-labels']
|
|
385
|
+
? { labels: [] }
|
|
386
|
+
: values.label !== undefined
|
|
387
|
+
? { labels: values.label }
|
|
388
|
+
: {}),
|
|
389
|
+
...(values['clear-dependencies']
|
|
390
|
+
? { dependsOn: [] }
|
|
391
|
+
: values['depends-on'] !== undefined
|
|
392
|
+
? { dependsOn: values['depends-on'] }
|
|
393
|
+
: {}),
|
|
394
|
+
...(values['clear-files']
|
|
395
|
+
? { files: [] }
|
|
396
|
+
: values.file !== undefined
|
|
397
|
+
? { files: normalizeMutationPaths(repository, values.file) }
|
|
398
|
+
: {}),
|
|
399
|
+
...(values['clear-owner']
|
|
400
|
+
? { owner: null }
|
|
401
|
+
: values.owner !== undefined
|
|
402
|
+
? { owner: values.owner }
|
|
403
|
+
: {}),
|
|
404
|
+
...(values['clear-assignees']
|
|
405
|
+
? { assignees: [] }
|
|
406
|
+
: values.assignee !== undefined
|
|
407
|
+
? { assignees: values.assignee }
|
|
408
|
+
: {}),
|
|
409
|
+
...(values['clear-reviewers']
|
|
410
|
+
? { reviewers: [] }
|
|
411
|
+
: values.reviewer !== undefined
|
|
412
|
+
? { reviewers: values.reviewer }
|
|
413
|
+
: {}),
|
|
414
|
+
...(values['clear-team']
|
|
415
|
+
? { team: null }
|
|
416
|
+
: values.team !== undefined
|
|
417
|
+
? { team: values.team }
|
|
418
|
+
: {}),
|
|
419
|
+
...(values['clear-estimate']
|
|
420
|
+
? { estimate: null }
|
|
421
|
+
: values.estimate !== undefined
|
|
422
|
+
? { estimate: values.estimate }
|
|
423
|
+
: {}),
|
|
424
|
+
...(values['clear-effort']
|
|
425
|
+
? { effort: null }
|
|
426
|
+
: values.effort !== undefined
|
|
427
|
+
? { effort: values.effort }
|
|
428
|
+
: {}),
|
|
429
|
+
...(values['clear-risk']
|
|
430
|
+
? { risk: null }
|
|
431
|
+
: values.risk !== undefined
|
|
432
|
+
? { risk: values.risk }
|
|
433
|
+
: {}),
|
|
434
|
+
...(values['clear-due-date']
|
|
435
|
+
? { dueDate: null }
|
|
436
|
+
: values['due-date'] !== undefined
|
|
437
|
+
? { dueDate: values['due-date'] }
|
|
438
|
+
: {}),
|
|
439
|
+
...(values['clear-related']
|
|
440
|
+
? { related: [] }
|
|
441
|
+
: values.related !== undefined
|
|
442
|
+
? { related: values.related }
|
|
443
|
+
: {}),
|
|
444
|
+
...(values['clear-duplicates']
|
|
445
|
+
? { duplicates: [] }
|
|
446
|
+
: values.duplicate !== undefined
|
|
447
|
+
? { duplicates: values.duplicate }
|
|
448
|
+
: {}),
|
|
449
|
+
...(values['clear-parent']
|
|
450
|
+
? { parent: null }
|
|
451
|
+
: values.parent !== undefined
|
|
452
|
+
? { parent: values.parent }
|
|
453
|
+
: {}),
|
|
454
|
+
...(values['clear-directories']
|
|
455
|
+
? { directories: [] }
|
|
456
|
+
: values.directory !== undefined
|
|
457
|
+
? { directories: normalizeMutationPaths(repository, values.directory) }
|
|
458
|
+
: {}),
|
|
459
|
+
...(values['clear-projects']
|
|
460
|
+
? { projects: [] }
|
|
461
|
+
: values.project !== undefined
|
|
462
|
+
? { projects: values.project }
|
|
463
|
+
: {}),
|
|
464
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
113
468
|
export async function runCli(args: readonly string[], context: CliContext = {}): Promise<number> {
|
|
114
469
|
const cwd = path.resolve(context.cwd ?? process.cwd())
|
|
115
470
|
const stdout = context.stdout ?? ((value: string) => process.stdout.write(value))
|
|
116
471
|
const stderr = context.stderr ?? ((value: string) => process.stderr.write(value))
|
|
472
|
+
const onWarning = warningWriter(stderr)
|
|
117
473
|
|
|
118
474
|
try {
|
|
119
475
|
const [command, subcommand] = args
|
|
120
476
|
const commandArgs = args.slice(1)
|
|
121
|
-
const
|
|
477
|
+
const subcommandArgs = args.slice(2)
|
|
122
478
|
|
|
123
479
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
124
480
|
stdout(USAGE)
|
|
125
481
|
return 0
|
|
126
482
|
}
|
|
127
483
|
|
|
128
|
-
if (command === 'init') {
|
|
129
|
-
const parsed =
|
|
130
|
-
|
|
131
|
-
|
|
484
|
+
if (command === 'init' || command === 'config' || command === 'doctor') {
|
|
485
|
+
const parsed = parseArgs({
|
|
486
|
+
args: commandArgs,
|
|
487
|
+
allowPositionals: false,
|
|
488
|
+
options: commonOptionDefinitions,
|
|
489
|
+
})
|
|
490
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, `${command} options`)
|
|
491
|
+
const commandCwd = resolveCommandCwd(cwd, values.cwd)
|
|
492
|
+
|
|
493
|
+
if (command === 'init') {
|
|
494
|
+
const repository = await initializeRepository(commandCwd)
|
|
495
|
+
stdout(`Initialized Taskset in ${repository.rootDirectory}\n`)
|
|
496
|
+
return 0
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const repository = await discoverRepository(commandCwd)
|
|
500
|
+
|
|
501
|
+
if (command === 'config') {
|
|
502
|
+
if (values.json) {
|
|
503
|
+
stdout(
|
|
504
|
+
`${JSON.stringify(
|
|
505
|
+
{
|
|
506
|
+
rootDirectory: repository.rootDirectory,
|
|
507
|
+
configPath: repository.configPath,
|
|
508
|
+
dataDirectory: repository.dataDirectory,
|
|
509
|
+
config: repository.config,
|
|
510
|
+
},
|
|
511
|
+
null,
|
|
512
|
+
2,
|
|
513
|
+
)}\n`,
|
|
514
|
+
)
|
|
515
|
+
} else {
|
|
516
|
+
stdout(`${repository.configPath}\n`)
|
|
517
|
+
}
|
|
518
|
+
return 0
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const result = await diagnoseRepository(repository)
|
|
522
|
+
|
|
523
|
+
if (values.json) {
|
|
524
|
+
stdout(`${JSON.stringify(result, null, 2)}\n`)
|
|
525
|
+
} else if (result.valid) {
|
|
526
|
+
stdout(`Taskset repository is valid (${result.taskCount} tasks)\n`)
|
|
527
|
+
} else {
|
|
528
|
+
for (const diagnostic of result.diagnostics) {
|
|
529
|
+
stdout(
|
|
530
|
+
`${diagnostic.code}\t${diagnostic.path ?? '-'}\t${diagnostic.message}\t${diagnostic.remediation}\n`,
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return result.valid ? 0 : 1
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (command === 'generate') {
|
|
539
|
+
const parsed = parseArgs({
|
|
540
|
+
args: commandArgs,
|
|
541
|
+
allowPositionals: false,
|
|
542
|
+
options: commonOptionDefinitions,
|
|
543
|
+
})
|
|
544
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'generate options')
|
|
545
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
546
|
+
const result = await generateViews(repository)
|
|
547
|
+
stdout(
|
|
548
|
+
values.json
|
|
549
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
550
|
+
: `Generated ${result.files.length} views in ${result.directory}\n`,
|
|
551
|
+
)
|
|
132
552
|
return 0
|
|
133
553
|
}
|
|
134
554
|
|
|
135
|
-
if (command === '
|
|
136
|
-
|
|
137
|
-
|
|
555
|
+
if (command === 'snapshot') {
|
|
556
|
+
if (subcommand === 'create' || subcommand === 'list') {
|
|
557
|
+
const parsed = parseArgs({
|
|
558
|
+
args: subcommandArgs,
|
|
559
|
+
allowPositionals: false,
|
|
560
|
+
options: commonOptionDefinitions,
|
|
561
|
+
})
|
|
562
|
+
const values = parseSchema(
|
|
563
|
+
CommonValuesSchema,
|
|
564
|
+
parsed.values,
|
|
565
|
+
`snapshot ${subcommand} options`,
|
|
566
|
+
)
|
|
567
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
568
|
+
|
|
569
|
+
if (subcommand === 'create') {
|
|
570
|
+
const result = await createSnapshot(repository)
|
|
571
|
+
stdout(values.json ? `${JSON.stringify(result, null, 2)}\n` : `${result.id}\n`)
|
|
572
|
+
} else {
|
|
573
|
+
const results = await listSnapshots(repository)
|
|
574
|
+
if (values.json) {
|
|
575
|
+
stdout(`${JSON.stringify(results, null, 2)}\n`)
|
|
576
|
+
} else {
|
|
577
|
+
for (const result of results) {
|
|
578
|
+
stdout(`${result.id}\t${result.createdAt}\t${result.files.length}\n`)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return 0
|
|
583
|
+
}
|
|
138
584
|
|
|
139
|
-
if (
|
|
585
|
+
if (subcommand === 'restore') {
|
|
586
|
+
const parsed = parseArgs({
|
|
587
|
+
args: subcommandArgs,
|
|
588
|
+
allowPositionals: true,
|
|
589
|
+
options: {
|
|
590
|
+
...commonOptionDefinitions,
|
|
591
|
+
apply: { type: 'boolean' },
|
|
592
|
+
},
|
|
593
|
+
})
|
|
594
|
+
const [snapshotId] = requirePositionals(parsed.positionals, 1, 'exactly one snapshot ID')
|
|
595
|
+
const values = parseSchema(
|
|
596
|
+
z.strictObject({
|
|
597
|
+
apply: z.boolean().optional(),
|
|
598
|
+
cwd: CwdSchema,
|
|
599
|
+
json: JsonSchema,
|
|
600
|
+
}),
|
|
601
|
+
parsed.values,
|
|
602
|
+
'snapshot restore options',
|
|
603
|
+
)
|
|
604
|
+
const validatedSnapshotId = parseSchema(
|
|
605
|
+
z.string().regex(/^\d{8}T\d{6}Z-[a-f0-9]{12}$/u),
|
|
606
|
+
snapshotId,
|
|
607
|
+
'snapshot ID',
|
|
608
|
+
)
|
|
609
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
610
|
+
const result = await restoreSnapshot(repository, validatedSnapshotId, {
|
|
611
|
+
apply: values.apply,
|
|
612
|
+
onWarning,
|
|
613
|
+
})
|
|
140
614
|
stdout(
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
configPath: repository.configPath,
|
|
145
|
-
dataDirectory: repository.dataDirectory,
|
|
146
|
-
config: repository.config,
|
|
147
|
-
},
|
|
148
|
-
null,
|
|
149
|
-
2,
|
|
150
|
-
)}\n`,
|
|
615
|
+
values.json
|
|
616
|
+
? `${JSON.stringify(result, null, 2)}\n`
|
|
617
|
+
: `${result.applied ? 'Restored' : 'Would restore'} ${result.changes.length} task files from ${validatedSnapshotId}\n`,
|
|
151
618
|
)
|
|
152
|
-
|
|
153
|
-
stdout(`${repository.configPath}\n`)
|
|
619
|
+
return 0
|
|
154
620
|
}
|
|
155
621
|
|
|
156
|
-
|
|
622
|
+
throw new CliUsageError(`Unknown snapshot command "${subcommand ?? ''}"`)
|
|
157
623
|
}
|
|
158
624
|
|
|
159
625
|
if (command !== 'task') {
|
|
@@ -162,101 +628,188 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
|
|
|
162
628
|
|
|
163
629
|
if (subcommand === 'create') {
|
|
164
630
|
const parsed = parseArgs({
|
|
165
|
-
args:
|
|
631
|
+
args: subcommandArgs,
|
|
632
|
+
allowPositionals: false,
|
|
633
|
+
options: { ...metadataOptionDefinitions, ...commonOptionDefinitions },
|
|
634
|
+
})
|
|
635
|
+
const values = parseSchema(CreateValuesSchema, parsed.values, 'task create options')
|
|
636
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
637
|
+
const record = await createTask(
|
|
638
|
+
repository,
|
|
639
|
+
{
|
|
640
|
+
title: values.title,
|
|
641
|
+
...(values.status ? { status: values.status } : {}),
|
|
642
|
+
...(values.priority ? { priority: values.priority } : {}),
|
|
643
|
+
...(values.order !== undefined ? { order: values.order } : {}),
|
|
644
|
+
...(values.label ? { labels: values.label } : {}),
|
|
645
|
+
...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
|
|
646
|
+
...(values.file ? { files: normalizeMutationPaths(repository, values.file) } : {}),
|
|
647
|
+
...(values.owner ? { owner: values.owner } : {}),
|
|
648
|
+
...(values.assignee ? { assignees: values.assignee } : {}),
|
|
649
|
+
...(values.reviewer ? { reviewers: values.reviewer } : {}),
|
|
650
|
+
...(values.team ? { team: values.team } : {}),
|
|
651
|
+
...(values.estimate !== undefined ? { estimate: values.estimate } : {}),
|
|
652
|
+
...(values.effort !== undefined ? { effort: values.effort } : {}),
|
|
653
|
+
...(values.risk ? { risk: values.risk } : {}),
|
|
654
|
+
...(values['due-date'] ? { dueDate: values['due-date'] } : {}),
|
|
655
|
+
...(values.related ? { related: values.related } : {}),
|
|
656
|
+
...(values.duplicate ? { duplicates: values.duplicate } : {}),
|
|
657
|
+
...(values.parent ? { parent: values.parent } : {}),
|
|
658
|
+
...(values.directory
|
|
659
|
+
? { directories: normalizeMutationPaths(repository, values.directory) }
|
|
660
|
+
: {}),
|
|
661
|
+
...(values.project ? { projects: values.project } : {}),
|
|
662
|
+
...(values.body !== undefined ? { body: values.body } : {}),
|
|
663
|
+
},
|
|
664
|
+
{ onWarning },
|
|
665
|
+
)
|
|
666
|
+
writeTaskRecord(record, values.json, stdout)
|
|
667
|
+
return 0
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (subcommand === 'list') {
|
|
671
|
+
const parsed = parseArgs({
|
|
672
|
+
args: subcommandArgs,
|
|
166
673
|
allowPositionals: false,
|
|
167
674
|
options: {
|
|
168
|
-
body: { type: 'string' },
|
|
169
675
|
cwd: { type: 'string' },
|
|
170
|
-
'depends-on': { type: 'string', multiple: true },
|
|
171
|
-
file: { type: 'string', multiple: true },
|
|
172
676
|
json: { type: 'boolean' },
|
|
677
|
+
status: { type: 'string', multiple: true },
|
|
678
|
+
priority: { type: 'string', multiple: true },
|
|
173
679
|
label: { type: 'string', multiple: true },
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
680
|
+
owner: { type: 'string', multiple: true },
|
|
681
|
+
assignee: { type: 'string', multiple: true },
|
|
682
|
+
reviewer: { type: 'string', multiple: true },
|
|
683
|
+
team: { type: 'string', multiple: true },
|
|
684
|
+
risk: { type: 'string', multiple: true },
|
|
685
|
+
project: { type: 'string', multiple: true },
|
|
686
|
+
'depends-on': { type: 'string' },
|
|
687
|
+
related: { type: 'string' },
|
|
688
|
+
duplicate: { type: 'string' },
|
|
689
|
+
parent: { type: 'string' },
|
|
690
|
+
file: { type: 'string', multiple: true },
|
|
691
|
+
directory: { type: 'string', multiple: true },
|
|
692
|
+
'estimate-min': { type: 'string' },
|
|
693
|
+
'estimate-max': { type: 'string' },
|
|
694
|
+
'effort-min': { type: 'string' },
|
|
695
|
+
'effort-max': { type: 'string' },
|
|
696
|
+
'due-before': { type: 'string' },
|
|
697
|
+
'due-after': { type: 'string' },
|
|
698
|
+
'created-before': { type: 'string' },
|
|
699
|
+
'created-after': { type: 'string' },
|
|
700
|
+
'updated-before': { type: 'string' },
|
|
701
|
+
'updated-after': { type: 'string' },
|
|
702
|
+
search: { type: 'string' },
|
|
703
|
+
sort: { type: 'string' },
|
|
704
|
+
direction: { type: 'string' },
|
|
705
|
+
impact: { type: 'boolean' },
|
|
706
|
+
'include-derived': { type: 'boolean' },
|
|
177
707
|
},
|
|
178
708
|
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const repository = await discoverRepository(resolveCommandCwd(cwd,
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
709
|
+
const values = parseSchema(ListValuesSchema, parsed.values, 'task list options')
|
|
710
|
+
const query = parseSchema(
|
|
711
|
+
TaskQuerySchema,
|
|
712
|
+
{
|
|
713
|
+
...(values.status ? { statuses: values.status } : {}),
|
|
714
|
+
...(values.priority ? { priorities: values.priority } : {}),
|
|
715
|
+
...(values.label ? { labels: values.label } : {}),
|
|
716
|
+
...(values.owner ? { owners: values.owner } : {}),
|
|
717
|
+
...(values.assignee ? { assignees: values.assignee } : {}),
|
|
718
|
+
...(values.reviewer ? { reviewers: values.reviewer } : {}),
|
|
719
|
+
...(values.team ? { teams: values.team } : {}),
|
|
720
|
+
...(values.risk ? { risks: values.risk } : {}),
|
|
721
|
+
...(values.project ? { projects: values.project } : {}),
|
|
722
|
+
...(values['depends-on'] ? { dependsOn: values['depends-on'] } : {}),
|
|
723
|
+
...(values.related ? { related: values.related } : {}),
|
|
724
|
+
...(values.duplicate ? { duplicate: values.duplicate } : {}),
|
|
725
|
+
...(values.parent ? { parent: values.parent } : {}),
|
|
726
|
+
...(values.file ? { files: values.file } : {}),
|
|
727
|
+
...(values.directory ? { directories: values.directory } : {}),
|
|
728
|
+
...(values['estimate-min'] !== undefined ? { estimateMin: values['estimate-min'] } : {}),
|
|
729
|
+
...(values['estimate-max'] !== undefined ? { estimateMax: values['estimate-max'] } : {}),
|
|
730
|
+
...(values['effort-min'] !== undefined ? { effortMin: values['effort-min'] } : {}),
|
|
731
|
+
...(values['effort-max'] !== undefined ? { effortMax: values['effort-max'] } : {}),
|
|
732
|
+
...(values['due-before'] ? { dueBefore: values['due-before'] } : {}),
|
|
733
|
+
...(values['due-after'] ? { dueAfter: values['due-after'] } : {}),
|
|
734
|
+
...(values['created-before'] ? { createdBefore: values['created-before'] } : {}),
|
|
735
|
+
...(values['created-after'] ? { createdAfter: values['created-after'] } : {}),
|
|
736
|
+
...(values['updated-before'] ? { updatedBefore: values['updated-before'] } : {}),
|
|
737
|
+
...(values['updated-after'] ? { updatedAfter: values['updated-after'] } : {}),
|
|
738
|
+
...(values.search !== undefined ? { text: values.search } : {}),
|
|
739
|
+
...(values.sort ? { sortBy: values.sort } : {}),
|
|
740
|
+
...(values.direction ? { direction: values.direction } : {}),
|
|
741
|
+
...(values.impact ? { impact: true } : {}),
|
|
742
|
+
} satisfies TaskQuery,
|
|
743
|
+
'task list query',
|
|
744
|
+
)
|
|
745
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
746
|
+
const result = await queryTasks(repository, query)
|
|
747
|
+
const graph = values['include-derived'] ? (await buildTaskIndex(repository)).graph : undefined
|
|
748
|
+
const serialize = (record: TaskRecord) =>
|
|
749
|
+
taskRecordJson(record, graph?.derive(record.task.metadata.id))
|
|
750
|
+
|
|
751
|
+
if (values.json) {
|
|
219
752
|
stdout(
|
|
220
753
|
`${JSON.stringify(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
754
|
+
values.impact
|
|
755
|
+
? {
|
|
756
|
+
direct: result.direct.map(serialize),
|
|
757
|
+
impacted: result.impacted.map(serialize),
|
|
758
|
+
}
|
|
759
|
+
: result.direct.map(serialize),
|
|
225
760
|
null,
|
|
226
761
|
2,
|
|
227
762
|
)}\n`,
|
|
228
763
|
)
|
|
229
764
|
} else {
|
|
230
|
-
for (const record of
|
|
765
|
+
for (const record of result.direct) {
|
|
231
766
|
stdout(
|
|
232
|
-
`${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`,
|
|
767
|
+
`${values.impact ? 'direct\t' : ''}${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`,
|
|
768
|
+
)
|
|
769
|
+
}
|
|
770
|
+
for (const record of result.impacted) {
|
|
771
|
+
stdout(
|
|
772
|
+
`impact\t${record.task.metadata.id}\t${record.task.metadata.status}\t${record.task.metadata.title}\n`,
|
|
233
773
|
)
|
|
234
774
|
}
|
|
235
775
|
}
|
|
236
|
-
|
|
237
776
|
return 0
|
|
238
777
|
}
|
|
239
778
|
|
|
240
779
|
if (subcommand === 'show') {
|
|
241
780
|
const parsed = parseArgs({
|
|
242
|
-
args:
|
|
781
|
+
args: subcommandArgs,
|
|
243
782
|
allowPositionals: true,
|
|
244
783
|
options: {
|
|
245
|
-
|
|
246
|
-
|
|
784
|
+
...commonOptionDefinitions,
|
|
785
|
+
'include-derived': { type: 'boolean' },
|
|
247
786
|
},
|
|
248
787
|
})
|
|
249
|
-
const taskId =
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
788
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID')
|
|
789
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID')
|
|
790
|
+
const values = parseSchema(
|
|
791
|
+
z.strictObject({
|
|
792
|
+
cwd: CwdSchema,
|
|
793
|
+
json: JsonSchema,
|
|
794
|
+
'include-derived': z.boolean().optional(),
|
|
795
|
+
}),
|
|
796
|
+
parsed.values,
|
|
797
|
+
'task show options',
|
|
798
|
+
)
|
|
799
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
800
|
+
const record = await readTask(repository, validatedTaskId)
|
|
801
|
+
|
|
802
|
+
if (values.json) {
|
|
803
|
+
const derived = values['include-derived']
|
|
804
|
+
? (await buildTaskIndex(repository)).graph.derive(validatedTaskId)
|
|
805
|
+
: undefined
|
|
254
806
|
stdout(
|
|
255
807
|
`${JSON.stringify(
|
|
256
808
|
{
|
|
257
809
|
relativePath: record.relativePath,
|
|
258
810
|
metadata: record.task.metadata,
|
|
259
811
|
body: record.task.body,
|
|
812
|
+
...(derived ? { derived } : {}),
|
|
260
813
|
},
|
|
261
814
|
null,
|
|
262
815
|
2,
|
|
@@ -265,13 +818,98 @@ export async function runCli(args: readonly string[], context: CliContext = {}):
|
|
|
265
818
|
} else {
|
|
266
819
|
stdout(serializeTaskFile(record.task, { filePath: record.relativePath }))
|
|
267
820
|
}
|
|
821
|
+
return 0
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (subcommand === 'update') {
|
|
825
|
+
const clearDefinitions = Object.fromEntries(
|
|
826
|
+
UPDATE_CLEAR_PAIRS.map(([, clear]) => [clear, { type: 'boolean' as const }]),
|
|
827
|
+
)
|
|
828
|
+
const parsed = parseArgs({
|
|
829
|
+
args: subcommandArgs,
|
|
830
|
+
allowPositionals: true,
|
|
831
|
+
options: {
|
|
832
|
+
...metadataOptionDefinitions,
|
|
833
|
+
...clearDefinitions,
|
|
834
|
+
...commonOptionDefinitions,
|
|
835
|
+
},
|
|
836
|
+
})
|
|
837
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID')
|
|
838
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID')
|
|
839
|
+
const values = parseSchema(UpdateValuesSchema, parsed.values, 'task update options')
|
|
840
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
841
|
+
const record = await updateTask(
|
|
842
|
+
repository,
|
|
843
|
+
validatedTaskId,
|
|
844
|
+
updateInputFromValues(repository, values),
|
|
845
|
+
{ onWarning },
|
|
846
|
+
)
|
|
847
|
+
writeTaskRecord(record, values.json, stdout)
|
|
848
|
+
return 0
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (subcommand === 'status') {
|
|
852
|
+
const parsed = parseArgs({
|
|
853
|
+
args: subcommandArgs,
|
|
854
|
+
allowPositionals: true,
|
|
855
|
+
options: commonOptionDefinitions,
|
|
856
|
+
})
|
|
857
|
+
const [taskId, status] = requirePositionals(parsed.positionals, 2, 'a task ID and status')
|
|
858
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID')
|
|
859
|
+
const validatedStatus = parseSchema(TaskStatusSchema, status, 'task status')
|
|
860
|
+
const values = parseSchema(CommonValuesSchema, parsed.values, 'task status options')
|
|
861
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
862
|
+
const record = await updateTask(
|
|
863
|
+
repository,
|
|
864
|
+
validatedTaskId,
|
|
865
|
+
{ status: validatedStatus },
|
|
866
|
+
{ onWarning },
|
|
867
|
+
)
|
|
868
|
+
writeTaskRecord(record, values.json, stdout)
|
|
869
|
+
return 0
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (subcommand === 'delete') {
|
|
873
|
+
const parsed = parseArgs({
|
|
874
|
+
args: subcommandArgs,
|
|
875
|
+
allowPositionals: true,
|
|
876
|
+
options: {
|
|
877
|
+
...commonOptionDefinitions,
|
|
878
|
+
'remove-dependencies': { type: 'boolean' },
|
|
879
|
+
},
|
|
880
|
+
})
|
|
881
|
+
const [taskId] = requirePositionals(parsed.positionals, 1, 'exactly one task ID')
|
|
882
|
+
const validatedTaskId = parseSchema(TaskIdSchema, taskId, 'task ID')
|
|
883
|
+
const values = parseSchema(
|
|
884
|
+
z.strictObject({
|
|
885
|
+
cwd: CwdSchema,
|
|
886
|
+
json: JsonSchema,
|
|
887
|
+
'remove-dependencies': z.boolean().optional(),
|
|
888
|
+
}),
|
|
889
|
+
parsed.values,
|
|
890
|
+
'task delete options',
|
|
891
|
+
)
|
|
892
|
+
const repository = await discoverRepository(resolveCommandCwd(cwd, values.cwd))
|
|
893
|
+
const record = await deleteTask(repository, validatedTaskId, {
|
|
894
|
+
removeDependencies: values['remove-dependencies'],
|
|
895
|
+
onWarning,
|
|
896
|
+
})
|
|
268
897
|
|
|
898
|
+
if (values.json) {
|
|
899
|
+
stdout(`${JSON.stringify({ deleted: true, ...taskRecordJson(record) }, null, 2)}\n`)
|
|
900
|
+
} else {
|
|
901
|
+
stdout(`${validatedTaskId}\n`)
|
|
902
|
+
}
|
|
269
903
|
return 0
|
|
270
904
|
}
|
|
271
905
|
|
|
272
906
|
throw new CliUsageError(`Unknown task command "${subcommand ?? ''}"`)
|
|
273
907
|
} catch (error) {
|
|
274
|
-
if (
|
|
908
|
+
if (
|
|
909
|
+
error instanceof CliUsageError ||
|
|
910
|
+
error instanceof RepositoryPathError ||
|
|
911
|
+
error instanceof TypeError
|
|
912
|
+
) {
|
|
275
913
|
stderr(`${formatError(error)}\n\n${USAGE}`)
|
|
276
914
|
return 2
|
|
277
915
|
}
|