@shortcut-cli/shortcut-cli 4.0.0 → 5.0.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/README.md +428 -8
- package/build/bin/{short-api.cjs → short-api.js} +13 -16
- package/build/bin/short-create.js +76 -0
- package/build/bin/short-custom-field.js +50 -0
- package/build/bin/short-custom-fields.js +29 -0
- package/build/bin/{short-doc.cjs → short-doc.js} +34 -36
- package/build/bin/{short-docs.cjs → short-docs.js} +23 -15
- package/build/bin/short-epic.js +186 -0
- package/build/bin/short-epics.js +36 -0
- package/build/bin/short-find.js +6 -0
- package/build/bin/short-install.js +87 -0
- package/build/bin/{short-iteration.cjs → short-iteration.js} +41 -45
- package/build/bin/{short-iterations.cjs → short-iterations.js} +15 -19
- package/build/bin/short-label.js +130 -0
- package/build/bin/short-labels.js +27 -0
- package/build/bin/short-members.js +31 -0
- package/build/bin/short-objective.js +151 -0
- package/build/bin/short-objectives.js +63 -0
- package/build/bin/short-projects.js +31 -0
- package/build/bin/short-search.js +45 -0
- package/build/bin/short-story.js +458 -0
- package/build/bin/short-team.js +78 -0
- package/build/bin/short-teams.js +28 -0
- package/build/bin/short-workflows.js +29 -0
- package/build/bin/short-workspace.js +63 -0
- package/build/bin/short.js +8 -0
- package/build/lib/client.js +9 -0
- package/build/lib/{configure.cjs → configure.js} +18 -27
- package/build/lib/spinner.js +12 -0
- package/build/lib/{stories.cjs → stories.js} +116 -78
- package/build/package.js +5 -0
- package/package.json +44 -44
- package/build/_virtual/rolldown_runtime.cjs +0 -29
- package/build/bin/short-create.cjs +0 -58
- package/build/bin/short-epic.cjs +0 -74
- package/build/bin/short-epics.cjs +0 -36
- package/build/bin/short-find.cjs +0 -7
- package/build/bin/short-install.cjs +0 -42
- package/build/bin/short-members.cjs +0 -34
- package/build/bin/short-projects.cjs +0 -34
- package/build/bin/short-search.cjs +0 -49
- package/build/bin/short-story.cjs +0 -213
- package/build/bin/short-workflows.cjs +0 -32
- package/build/bin/short-workspace.cjs +0 -64
- package/build/bin/short.cjs +0 -10
- package/build/lib/client.cjs +0 -11
- package/build/lib/spinner.cjs +0 -17
- package/build/package.cjs +0 -18
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadCachedConfig, updateConfig } from "../lib/configure.js";
|
|
3
|
+
import { version } from "../package.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { ShortcutClient } from "@shortcut/client";
|
|
6
|
+
import { createInterface } from "readline/promises";
|
|
7
|
+
import { Writable } from "stream";
|
|
8
|
+
//#region src/bin/short-install.ts
|
|
9
|
+
const extant = loadCachedConfig();
|
|
10
|
+
const log = console.log;
|
|
11
|
+
const TOKEN_PROMPT = "Enter your Shortcut API token. You can get one at https://app.shortcut.com/settings/account/api-tokens\nAPI token: ";
|
|
12
|
+
const opts = new Command().version(version).description("Install access token and other settings for the Shortcut API").option("-f, --force", "Force install/reinstall").option("-r, --refresh", "Refresh the configuration with details from Shortcut.").option("-t, --token <token>", "Shortcut API token to save without prompting").parse(process.argv).opts();
|
|
13
|
+
const enrichConfigWithMemberDetails = async (config) => {
|
|
14
|
+
log("Fetching user/member details from Shortcut...");
|
|
15
|
+
const clientConfig = {};
|
|
16
|
+
if (process.env.SHORTCUT_API_BASE_URL) clientConfig.baseURL = process.env.SHORTCUT_API_BASE_URL;
|
|
17
|
+
const member = await new ShortcutClient(config.token ?? "", clientConfig).getCurrentMemberInfo().then((r) => r.data);
|
|
18
|
+
return {
|
|
19
|
+
...config,
|
|
20
|
+
mentionName: member.mention_name,
|
|
21
|
+
urlSlug: member.workspace2.url_slug
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
var MaskedWritable = class extends Writable {
|
|
25
|
+
muted = false;
|
|
26
|
+
_write(chunk, encoding, callback) {
|
|
27
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString();
|
|
28
|
+
if (!this.muted) {
|
|
29
|
+
process.stdout.write(text);
|
|
30
|
+
callback();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (text.includes("\n")) process.stdout.write("\n");
|
|
34
|
+
callback();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const promptForToken = async () => {
|
|
38
|
+
const output = new MaskedWritable();
|
|
39
|
+
const rl = createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output,
|
|
42
|
+
terminal: true
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
process.stdout.write(TOKEN_PROMPT);
|
|
46
|
+
output.muted = true;
|
|
47
|
+
const token = (await rl.question("")).trim();
|
|
48
|
+
output.muted = false;
|
|
49
|
+
process.stdout.write("\n");
|
|
50
|
+
return token;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
output.muted = false;
|
|
53
|
+
process.stdout.write("\n");
|
|
54
|
+
if (error instanceof Error && "code" in error && error.code === "ABORT_ERR") process.exit(130);
|
|
55
|
+
throw error;
|
|
56
|
+
} finally {
|
|
57
|
+
rl.close();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const resolveToken = async () => {
|
|
61
|
+
if (opts.token) return opts.token.trim();
|
|
62
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
63
|
+
console.error("No API token provided. Pass --token when running non-interactively.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
return promptForToken();
|
|
67
|
+
};
|
|
68
|
+
const main = async () => {
|
|
69
|
+
if (opts.refresh) updateConfig(await enrichConfigWithMemberDetails(extant));
|
|
70
|
+
else if (!extant.token || opts.force) {
|
|
71
|
+
const token = await resolveToken();
|
|
72
|
+
if (!token) {
|
|
73
|
+
console.error("No API token provided.");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const config = await enrichConfigWithMemberDetails({
|
|
77
|
+
...extant,
|
|
78
|
+
token
|
|
79
|
+
});
|
|
80
|
+
log("Saving config...");
|
|
81
|
+
if (updateConfig(config)) log("Saved config");
|
|
82
|
+
else log("Error saving config");
|
|
83
|
+
} else if (extant.token) log("A configuration/token is already saved. To override, re-run with --force");
|
|
84
|
+
};
|
|
85
|
+
main();
|
|
86
|
+
//#endregion
|
|
87
|
+
export {};
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let chalk = require("chalk");
|
|
10
|
-
chalk = require_rolldown_runtime.__toESM(chalk);
|
|
11
|
-
|
|
2
|
+
import { loadConfig } from "../lib/configure.js";
|
|
3
|
+
import client from "../lib/client.js";
|
|
4
|
+
import spinner from "../lib/spinner.js";
|
|
5
|
+
import stories_default from "../lib/stories.js";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { exec } from "child_process";
|
|
8
|
+
import chalk from "chalk";
|
|
12
9
|
//#region src/bin/short-iteration.ts
|
|
13
|
-
const config =
|
|
14
|
-
const spin =
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const spin = spinner();
|
|
15
12
|
const log = console.log;
|
|
16
|
-
const program = new
|
|
13
|
+
const program = new Command().usage("[command] [options]").description("view, create, update, or delete iterations");
|
|
17
14
|
program.command("view <id>").description("view an iteration by id").option("-O, --open", "Open iteration in browser").action(viewIteration);
|
|
18
15
|
program.command("create").description("create a new iteration").option("-n, --name [text]", "Set name of iteration, required", "").option("-d, --description [text]", "Set description of iteration", "").option("--start-date [date]", "Set start date (YYYY-MM-DD), required", "").option("--end-date [date]", "Set end date (YYYY-MM-DD), required", "").option("-T, --team [id|name]", "Set team/group of iteration", "").option("-I, --idonly", "Print only ID of iteration result").option("-O, --open", "Open iteration in browser").action(createIteration);
|
|
19
16
|
program.command("update <id>").description("update an existing iteration").option("-n, --name [text]", "Set name of iteration", "").option("-d, --description [text]", "Set description of iteration", "").option("--start-date [date]", "Set start date (YYYY-MM-DD)", "").option("--end-date [date]", "Set end date (YYYY-MM-DD)", "").option("-T, --team [id|name]", "Set team/group of iteration", "").option("-O, --open", "Open iteration in browser").action(updateIteration);
|
|
@@ -23,10 +20,10 @@ program.parse(process.argv);
|
|
|
23
20
|
async function viewIteration(id, options) {
|
|
24
21
|
spin.start();
|
|
25
22
|
try {
|
|
26
|
-
const iteration = await
|
|
23
|
+
const iteration = await client.getIteration(parseInt(id, 10)).then((r) => r.data);
|
|
27
24
|
spin.stop(true);
|
|
28
25
|
printIteration(iteration);
|
|
29
|
-
if (options.open)
|
|
26
|
+
if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
|
|
30
27
|
} catch (e) {
|
|
31
28
|
spin.stop(true);
|
|
32
29
|
const error = e;
|
|
@@ -36,7 +33,7 @@ async function viewIteration(id, options) {
|
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
async function createIteration(options) {
|
|
39
|
-
const entities = await
|
|
36
|
+
const entities = await stories_default.fetchEntities();
|
|
40
37
|
if (!options.idonly) spin.start();
|
|
41
38
|
const iterationData = {
|
|
42
39
|
name: options.name || "",
|
|
@@ -45,7 +42,7 @@ async function createIteration(options) {
|
|
|
45
42
|
};
|
|
46
43
|
if (options.description) iterationData.description = options.description;
|
|
47
44
|
if (options.team) {
|
|
48
|
-
const group =
|
|
45
|
+
const group = stories_default.findGroup(entities, options.team);
|
|
49
46
|
if (group?.id) iterationData.group_ids = [group.id];
|
|
50
47
|
}
|
|
51
48
|
if (!iterationData.name) {
|
|
@@ -65,7 +62,7 @@ async function createIteration(options) {
|
|
|
65
62
|
}
|
|
66
63
|
let iteration;
|
|
67
64
|
try {
|
|
68
|
-
iteration = await
|
|
65
|
+
iteration = await client.createIteration(iterationData).then((r) => r.data);
|
|
69
66
|
} catch (e) {
|
|
70
67
|
if (!options.idonly) spin.stop(true);
|
|
71
68
|
log("Error creating iteration:", e.message ?? String(e));
|
|
@@ -75,11 +72,11 @@ async function createIteration(options) {
|
|
|
75
72
|
if (iteration) if (options.idonly) log(iteration.id);
|
|
76
73
|
else {
|
|
77
74
|
printIteration(iteration);
|
|
78
|
-
if (options.open)
|
|
75
|
+
if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
|
|
79
76
|
}
|
|
80
77
|
}
|
|
81
78
|
async function updateIteration(id, options) {
|
|
82
|
-
const entities = await
|
|
79
|
+
const entities = await stories_default.fetchEntities();
|
|
83
80
|
spin.start();
|
|
84
81
|
const updateData = {};
|
|
85
82
|
if (options.name) updateData.name = options.name;
|
|
@@ -87,7 +84,7 @@ async function updateIteration(id, options) {
|
|
|
87
84
|
if (options.startDate) updateData.start_date = options.startDate;
|
|
88
85
|
if (options.endDate) updateData.end_date = options.endDate;
|
|
89
86
|
if (options.team) {
|
|
90
|
-
const group =
|
|
87
|
+
const group = stories_default.findGroup(entities, options.team);
|
|
91
88
|
if (group?.id) updateData.group_ids = [group.id];
|
|
92
89
|
}
|
|
93
90
|
if (Object.keys(updateData).length === 0) {
|
|
@@ -97,7 +94,7 @@ async function updateIteration(id, options) {
|
|
|
97
94
|
}
|
|
98
95
|
let iteration;
|
|
99
96
|
try {
|
|
100
|
-
iteration = await
|
|
97
|
+
iteration = await client.updateIteration(parseInt(id, 10), updateData).then((r) => r.data);
|
|
101
98
|
} catch (e) {
|
|
102
99
|
spin.stop(true);
|
|
103
100
|
const error = e;
|
|
@@ -108,13 +105,13 @@ async function updateIteration(id, options) {
|
|
|
108
105
|
spin.stop(true);
|
|
109
106
|
if (iteration) {
|
|
110
107
|
printIteration(iteration);
|
|
111
|
-
if (options.open)
|
|
108
|
+
if (options.open) exec(`open https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`);
|
|
112
109
|
}
|
|
113
110
|
}
|
|
114
111
|
async function deleteIteration(id) {
|
|
115
112
|
spin.start();
|
|
116
113
|
try {
|
|
117
|
-
await
|
|
114
|
+
await client.deleteIteration(parseInt(id, 10));
|
|
118
115
|
spin.stop(true);
|
|
119
116
|
log(`Iteration #${id} deleted successfully`);
|
|
120
117
|
} catch (e) {
|
|
@@ -128,21 +125,21 @@ async function deleteIteration(id) {
|
|
|
128
125
|
async function listIterationStories(id, options) {
|
|
129
126
|
spin.start();
|
|
130
127
|
try {
|
|
131
|
-
const [stories, entities] = await Promise.all([
|
|
128
|
+
const [stories, entities] = await Promise.all([client.listIterationStories(parseInt(id, 10)).then((r) => r.data), stories_default.fetchEntities()]);
|
|
132
129
|
spin.stop(true);
|
|
133
130
|
if (stories.length === 0) {
|
|
134
131
|
log(`No stories found in iteration #${id}`);
|
|
135
132
|
return;
|
|
136
133
|
}
|
|
137
|
-
log(chalk.
|
|
134
|
+
log(chalk.bold(`Stories in iteration #${id}:`));
|
|
138
135
|
log();
|
|
139
136
|
stories.forEach((story) => {
|
|
140
|
-
const hydrated =
|
|
141
|
-
if (options.format)
|
|
137
|
+
const hydrated = stories_default.hydrateStory(entities, story);
|
|
138
|
+
if (options.format) stories_default.printFormattedStory({ format: options.format })(hydrated);
|
|
142
139
|
else {
|
|
143
140
|
const state = hydrated.state?.name ?? "Unknown";
|
|
144
141
|
const owners = hydrated.owners?.map((o) => o?.profile.mention_name).filter(Boolean).join(", ");
|
|
145
|
-
log(`${chalk.
|
|
142
|
+
log(`${chalk.bold("#" + story.id)} ${chalk.blue(story.name)}`);
|
|
146
143
|
log(` Type: ${story.story_type} | State: ${state} | Owners: ${owners || "_"}`);
|
|
147
144
|
log(` Points: ${story.estimate ?? "_"}`);
|
|
148
145
|
log();
|
|
@@ -160,25 +157,24 @@ function printIteration(iteration) {
|
|
|
160
157
|
const stats = iteration.stats;
|
|
161
158
|
const totalStories = stats.num_stories_done + stats.num_stories_started + stats.num_stories_unstarted + stats.num_stories_backlog;
|
|
162
159
|
const completionPct = stats.num_points > 0 ? Math.round(stats.num_points_done / stats.num_points * 100) : 0;
|
|
163
|
-
log(chalk.
|
|
164
|
-
if (iteration.description) log(chalk.
|
|
165
|
-
log(chalk.
|
|
166
|
-
log(chalk.
|
|
167
|
-
log(chalk.
|
|
168
|
-
if (iteration.group_ids && iteration.group_ids.length > 0) log(chalk.
|
|
169
|
-
log(chalk.
|
|
170
|
-
log(chalk.
|
|
171
|
-
log(chalk.
|
|
172
|
-
log(chalk.
|
|
160
|
+
log(chalk.blue.bold(`#${iteration.id}`) + chalk.blue(` ${iteration.name}`));
|
|
161
|
+
if (iteration.description) log(chalk.bold("Description:") + ` ${iteration.description}`);
|
|
162
|
+
log(chalk.bold("Status:") + ` ${formatStatus(iteration.status)}`);
|
|
163
|
+
log(chalk.bold("Start Date:") + ` ${iteration.start_date}`);
|
|
164
|
+
log(chalk.bold("End Date:") + ` ${iteration.end_date}`);
|
|
165
|
+
if (iteration.group_ids && iteration.group_ids.length > 0) log(chalk.bold("Teams:") + ` ${iteration.group_ids.join(", ")}`);
|
|
166
|
+
log(chalk.bold("Stories:") + ` ${totalStories} (${stats.num_stories_done} done)`);
|
|
167
|
+
log(chalk.bold("Points:") + ` ${stats.num_points} (${stats.num_points_done} done)`);
|
|
168
|
+
log(chalk.bold("Completion:") + ` ${completionPct}%`);
|
|
169
|
+
log(chalk.bold("URL:") + ` ${iteration.app_url}`);
|
|
173
170
|
log();
|
|
174
171
|
}
|
|
175
172
|
function formatStatus(status) {
|
|
176
173
|
switch (status) {
|
|
177
|
-
case "started": return chalk.
|
|
178
|
-
case "done": return chalk.
|
|
179
|
-
|
|
180
|
-
default: return chalk.default.yellow(status);
|
|
174
|
+
case "started": return chalk.green(status);
|
|
175
|
+
case "done": return chalk.gray(status);
|
|
176
|
+
default: return chalk.yellow(status);
|
|
181
177
|
}
|
|
182
178
|
}
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
//#endregion
|
|
180
|
+
export {};
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let chalk = require("chalk");
|
|
8
|
-
chalk = require_rolldown_runtime.__toESM(chalk);
|
|
9
|
-
|
|
2
|
+
import { loadConfig } from "../lib/configure.js";
|
|
3
|
+
import client from "../lib/client.js";
|
|
4
|
+
import spinner from "../lib/spinner.js";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
10
7
|
//#region src/bin/short-iterations.ts
|
|
11
|
-
const config =
|
|
8
|
+
const config = loadConfig();
|
|
12
9
|
const log = console.log;
|
|
13
|
-
const spin =
|
|
14
|
-
const opts = new
|
|
10
|
+
const spin = spinner();
|
|
11
|
+
const opts = new Command().description("Display iterations available for stories").option("-S, --status [status]", "Filter by status (unstarted, started, done)", "").option("-T, --team [id|name]", "Filter by team/group id or name", "").option("-C, --current", "Show only current/active iterations", false).option("-t, --title [query]", "Filter iterations with name containing query", "").option("-d, --detailed", "Show more details for each iteration", false).option("-f, --format [template]", "Format each iteration output by template", "").parse(process.argv).opts();
|
|
15
12
|
const main = async () => {
|
|
16
13
|
spin.start();
|
|
17
|
-
const [iterations, groups] = await Promise.all([
|
|
14
|
+
const [iterations, groups] = await Promise.all([client.listIterations().then((r) => r.data), client.listGroups().then((r) => r.data)]);
|
|
18
15
|
spin.stop(true);
|
|
19
16
|
const textMatch = new RegExp(opts.title ?? "", "i");
|
|
20
17
|
const statusMatch = opts.status ? new RegExp(opts.status, "i") : null;
|
|
@@ -51,16 +48,15 @@ const printItem = (iteration, groupsById) => {
|
|
|
51
48
|
const format = opts.format || defaultFormat;
|
|
52
49
|
const url = `https://app.shortcut.com/${config.urlSlug}/iteration/${iteration.id}`;
|
|
53
50
|
const completionPct = stats.num_points > 0 ? Math.round(stats.num_points_done / stats.num_points * 100) : 0;
|
|
54
|
-
log(format.replace(/%id/, chalk.
|
|
51
|
+
log(format.replace(/%id/, chalk.bold(`${iteration.id}`)).replace(/%t/, chalk.blue(`${iteration.name}`)).replace(/%s/, formatStatus(iteration.status)).replace(/%start/, `${iteration.start_date}`).replace(/%end/, `${iteration.end_date}`).replace(/%teams/, groups.join(", ") || "_").replace(/%stories/, `${totalStories}`).replace(/%done/, `${stats.num_stories_done}`).replace(/%points/, `${stats.num_points}`).replace(/%pdone/, `${stats.num_points_done}`).replace(/%completion/, `${completionPct}`).replace(/%url/, url));
|
|
55
52
|
};
|
|
56
53
|
const formatStatus = (status) => {
|
|
57
54
|
switch (status) {
|
|
58
|
-
case "started": return chalk.
|
|
59
|
-
case "done": return chalk.
|
|
60
|
-
|
|
61
|
-
default: return chalk.default.yellow(status);
|
|
55
|
+
case "started": return chalk.green(status);
|
|
56
|
+
case "done": return chalk.gray(status);
|
|
57
|
+
default: return chalk.yellow(status);
|
|
62
58
|
}
|
|
63
59
|
};
|
|
64
60
|
main();
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
//#endregion
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import client from "../lib/client.js";
|
|
3
|
+
import spinner from "../lib/spinner.js";
|
|
4
|
+
import stories_default from "../lib/stories.js";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
//#region src/bin/short-label.ts
|
|
8
|
+
const spin = spinner();
|
|
9
|
+
const log = console.log;
|
|
10
|
+
const program = new Command().usage("[command] [options]").description("create labels or view stories for a label");
|
|
11
|
+
program.command("create").description("create a new label").option("-n, --name [text]", "Set name of label, required", "").option("-d, --description [text]", "Set description of label", "").option("-c, --color [hex]", "Set label color in hex format like #3366cc", "").option("-I, --idonly", "Print only ID of label result").action(createLabel);
|
|
12
|
+
program.command("update <idOrName>").description("update an existing label").option("-n, --name [text]", "Set name of label", "").option("-d, --description [text]", "Set description of label", "").option("-c, --color [hex]", "Set label color in hex format like #3366cc", "").option("-a, --archived", "Archive label").action(updateLabel);
|
|
13
|
+
program.command("stories <idOrName>").description("list stories for a label by id or name").option("-d, --detailed", "Show more details for each story").option("-f, --format [template]", "Format each story output by template", "").action(listLabelStories);
|
|
14
|
+
program.command("epics <idOrName>").description("list epics for a label by id or name").action(listLabelEpics);
|
|
15
|
+
program.parse(process.argv);
|
|
16
|
+
async function createLabel(options) {
|
|
17
|
+
if (!options.name) {
|
|
18
|
+
log("Must provide --name");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (!options.idonly) spin.start();
|
|
22
|
+
try {
|
|
23
|
+
const input = { name: options.name };
|
|
24
|
+
if (options.description) input.description = options.description;
|
|
25
|
+
if (options.color) input.color = options.color;
|
|
26
|
+
const label = await client.createLabel(input).then((r) => r.data);
|
|
27
|
+
if (!options.idonly) spin.stop(true);
|
|
28
|
+
if (options.idonly) {
|
|
29
|
+
log(label.id);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
printLabel(label);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (!options.idonly) spin.stop(true);
|
|
35
|
+
log(`Error creating label: ${e.message ?? String(e)}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function updateLabel(idOrName, options) {
|
|
40
|
+
spin.start();
|
|
41
|
+
try {
|
|
42
|
+
const entities = await stories_default.fetchEntities();
|
|
43
|
+
const label = stories_default.findLabel(entities, idOrName);
|
|
44
|
+
if (!label) {
|
|
45
|
+
spin.stop(true);
|
|
46
|
+
log(`Label ${idOrName} not found`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const input = {};
|
|
50
|
+
if (options.name) input.name = options.name;
|
|
51
|
+
if (options.description) input.description = options.description;
|
|
52
|
+
if (options.color) input.color = options.color;
|
|
53
|
+
if (options.archived) input.archived = true;
|
|
54
|
+
if (Object.keys(input).length === 0) {
|
|
55
|
+
spin.stop(true);
|
|
56
|
+
log("No updates provided. Use --name, --description, --color, or --archived");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const updatedLabel = await client.updateLabel(label.id, input).then((r) => r.data);
|
|
60
|
+
spin.stop(true);
|
|
61
|
+
printLabel(updatedLabel);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
spin.stop(true);
|
|
64
|
+
log(`Error updating label: ${e.message ?? String(e)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function listLabelStories(idOrName, options) {
|
|
69
|
+
spin.start();
|
|
70
|
+
try {
|
|
71
|
+
const entities = await stories_default.fetchEntities();
|
|
72
|
+
const label = stories_default.findLabel(entities, idOrName);
|
|
73
|
+
if (!label) {
|
|
74
|
+
spin.stop(true);
|
|
75
|
+
log(`Label ${idOrName} not found`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const stories = await client.listLabelStories(label.id).then((r) => r.data);
|
|
79
|
+
spin.stop(true);
|
|
80
|
+
if (stories.length === 0) {
|
|
81
|
+
log(`No stories found for label #${label.id} ${label.name}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
stories.map((story) => stories_default.hydrateStory(entities, story)).forEach(options.detailed ? (story) => stories_default.printDetailedStory(story, entities) : stories_default.printFormattedStory({ format: options.format }));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
spin.stop(true);
|
|
87
|
+
log(`Error fetching label stories: ${e.message ?? String(e)}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function listLabelEpics(idOrName) {
|
|
92
|
+
spin.start();
|
|
93
|
+
try {
|
|
94
|
+
const entities = await stories_default.fetchEntities();
|
|
95
|
+
const label = stories_default.findLabel(entities, idOrName);
|
|
96
|
+
if (!label) {
|
|
97
|
+
spin.stop(true);
|
|
98
|
+
log(`Label ${idOrName} not found`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const epics = await client.listLabelEpics(label.id).then((r) => r.data);
|
|
102
|
+
spin.stop(true);
|
|
103
|
+
if (epics.length === 0) {
|
|
104
|
+
log(`No epics found for label #${label.id} ${label.name}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
epics.forEach(printEpic);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
spin.stop(true);
|
|
110
|
+
log(`Error fetching label epics: ${e.message ?? String(e)}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function printLabel(label) {
|
|
115
|
+
log(chalk.bold(`#${label.id}`) + chalk.blue(` ${label.name}`));
|
|
116
|
+
if (label.color) log(chalk.bold("Color: ") + ` ${label.color}`);
|
|
117
|
+
if (label.description) log(chalk.bold("Description: ") + ` ${label.description}`);
|
|
118
|
+
if (label.archived) log(chalk.bold("Archived: ") + ` ${label.archived}`);
|
|
119
|
+
log();
|
|
120
|
+
}
|
|
121
|
+
function printEpic(epic) {
|
|
122
|
+
log(chalk.bold(`#${epic.id}`) + chalk.blue(` ${epic.name}`));
|
|
123
|
+
log(chalk.bold("State: ") + ` ${epic.state}`);
|
|
124
|
+
log(chalk.bold("Started: ") + ` ${epic.started}`);
|
|
125
|
+
log(chalk.bold("Completed: ") + ` ${epic.completed}`);
|
|
126
|
+
log(chalk.bold("URL: ") + ` ${epic.app_url}`);
|
|
127
|
+
log();
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import client from "../lib/client.js";
|
|
3
|
+
import spinner from "../lib/spinner.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
//#region src/bin/short-labels.ts
|
|
7
|
+
const spin = spinner();
|
|
8
|
+
const log = console.log;
|
|
9
|
+
const opts = new Command().description("Display labels available for stories and epics").option("-a, --archived", "List labels including archived", "").option("-s, --search [query]", "List labels with name containing query", "").parse(process.argv).opts();
|
|
10
|
+
async function main() {
|
|
11
|
+
spin.start();
|
|
12
|
+
const labels = await client.listLabels().then((r) => r.data);
|
|
13
|
+
spin.stop(true);
|
|
14
|
+
const searchMatch = new RegExp(opts.search ?? "", "i");
|
|
15
|
+
labels.filter((label) => !!`${label.id} ${label.name}`.match(searchMatch)).forEach(printLabel);
|
|
16
|
+
}
|
|
17
|
+
function printLabel(label) {
|
|
18
|
+
if (label.archived && !opts.archived) return;
|
|
19
|
+
log(chalk.bold(`#${label.id}`) + chalk.blue(` ${label.name}`));
|
|
20
|
+
if (label.color) log(chalk.bold("Color: ") + ` ${label.color}`);
|
|
21
|
+
if (label.description) log(chalk.bold("Description: ") + ` ${label.description}`);
|
|
22
|
+
if (label.archived) log(chalk.bold("Archived: ") + ` ${label.archived}`);
|
|
23
|
+
log();
|
|
24
|
+
}
|
|
25
|
+
main();
|
|
26
|
+
//#endregion
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import client from "../lib/client.js";
|
|
3
|
+
import spinner from "../lib/spinner.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
//#region src/bin/short-members.ts
|
|
7
|
+
const spin = spinner("Loading... %s ");
|
|
8
|
+
const log = console.log;
|
|
9
|
+
const opts = new Command().description("Display members available for stories").option("-s, --search [query]", "List members with name containing query", "").option("-d, --disabled", "List members including disabled", "").parse(process.argv).opts();
|
|
10
|
+
const main = async () => {
|
|
11
|
+
spin.start();
|
|
12
|
+
const members = await client.listMembers().then((r) => r.data);
|
|
13
|
+
spin.stop(true);
|
|
14
|
+
const ownerMatch = new RegExp(opts.search ?? "", "i");
|
|
15
|
+
members.filter((o) => {
|
|
16
|
+
return !!`${o.profile.name} ${o.profile.mention_name}`.match(ownerMatch);
|
|
17
|
+
}).map(printMember);
|
|
18
|
+
};
|
|
19
|
+
const printMember = (member) => {
|
|
20
|
+
if (member.disabled && !opts.disabled) return;
|
|
21
|
+
log(chalk.bold(`#${member.id}`));
|
|
22
|
+
log(chalk.bold("Name: ") + ` ${member.profile.name}`);
|
|
23
|
+
log(chalk.bold("Mention Name: ") + ` ${member.profile.mention_name}`);
|
|
24
|
+
log(chalk.bold("Role: ") + ` ${member.role}`);
|
|
25
|
+
log(chalk.bold("Email: ") + ` ${member.profile.email_address}`);
|
|
26
|
+
if (member.disabled) log(chalk.bold("Disabled: ") + ` ${member.disabled}`);
|
|
27
|
+
log();
|
|
28
|
+
};
|
|
29
|
+
main();
|
|
30
|
+
//#endregion
|
|
31
|
+
export {};
|