feat: add issue create and comment (task 0004)
Introduce the first mutations, along with the shared machinery the later
issue and PR mutation slices reuse.
- `issue create` with --title/--body/--body-file/--assignee/--label/
--milestone/--fields, emitting `issue: { number, title, state, url }`
- `issue comment <n>`, echoing the created comment from the POST response
with the body cleaned and truncated at 800 chars
- body-source resolution (--body vs --body-file), label and milestone
name->ID lookup, repeatable flags, and the `joined`/`selectExtraFields`
field extractors
Label lookup pages until exhausted, since a repo with more labels than one
page would otherwise fail to resolve a valid name. The end-to-end tier seeds
a mixed-case label and milestone and passes both in a different case, so the
case-insensitive lookup is verified against live Gitea rather than only
against fixtures.
This commit was merged in pull request #3.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { axiError } from "./errors.js";
|
||||
import { relativeTime } from "./time.js";
|
||||
|
||||
export interface ExtractContext {
|
||||
@@ -31,6 +32,26 @@ export function lowercased<T>(name: string, path: string = name): FieldDef<T> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a field holding an array of objects (labels, assignees) into a single
|
||||
* string of each element's `key` property.
|
||||
*/
|
||||
export function joined<T>(name: string, path: string, key: string): FieldDef<T> {
|
||||
return {
|
||||
name,
|
||||
extract: (raw) => {
|
||||
const value = pluckPath(raw, path);
|
||||
if (!Array.isArray(value)) {
|
||||
return "";
|
||||
}
|
||||
return value
|
||||
.map((item) => pluckPath(item, key))
|
||||
.filter((item): item is string => typeof item === "string" && item.length > 0)
|
||||
.join(", ");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function relativeTimeField<T>(name: string, path: string): FieldDef<T> {
|
||||
return {
|
||||
name,
|
||||
@@ -41,6 +62,48 @@ export function relativeTimeField<T>(name: string, path: string): FieldDef<T> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a comma-separated `--fields` value against the extra fields a command
|
||||
* offers on top of its defaults. Unknown names are a `VALIDATION_ERROR` naming
|
||||
* what is available, since a silently ignored field would look like a command
|
||||
* that returned nothing for it.
|
||||
*/
|
||||
export function selectExtraFields<T>(
|
||||
value: string | undefined,
|
||||
registry: Record<string, FieldDef<T>>,
|
||||
command: string,
|
||||
): FieldDef<T>[] {
|
||||
if (value === undefined) {
|
||||
return [];
|
||||
}
|
||||
const available = Object.keys(registry);
|
||||
const suggestion = [
|
||||
`Run \`gitea-axi ${command} --fields <a,b,c>\` with any of: ${available.join(", ")}`,
|
||||
];
|
||||
const selected: FieldDef<T>[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const raw of value.split(",")) {
|
||||
const name = raw.trim();
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
const field = registry[name];
|
||||
if (!field) {
|
||||
throw axiError(
|
||||
`Unknown --fields name: ${name} (available: ${available.join(", ")})`,
|
||||
"VALIDATION_ERROR",
|
||||
suggestion,
|
||||
);
|
||||
}
|
||||
if (seen.has(name)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(name);
|
||||
selected.push(field);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
export function extractRow<T>(
|
||||
raw: T,
|
||||
fields: FieldDef<T>[],
|
||||
|
||||
Reference in New Issue
Block a user