@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
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
path = require_rolldown_runtime.__toESM(path);
|
|
5
|
-
let fs = require("fs");
|
|
6
|
-
fs = require_rolldown_runtime.__toESM(fs);
|
|
7
|
-
let os = require("os");
|
|
8
|
-
os = require_rolldown_runtime.__toESM(os);
|
|
9
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
10
4
|
//#region src/lib/configure.ts
|
|
11
5
|
function getConfigDir(suffix) {
|
|
12
|
-
const configBaseDir = process.env.XDG_CONFIG_HOME || path.
|
|
13
|
-
return path.
|
|
6
|
+
const configBaseDir = process.env.XDG_CONFIG_HOME || path.resolve(process.env.XDG_DATA_HOME || os.homedir(), ".config");
|
|
7
|
+
return path.resolve(configBaseDir, suffix);
|
|
14
8
|
}
|
|
15
9
|
const configDir = getConfigDir("shortcut-cli");
|
|
16
|
-
const configFile = path.
|
|
17
|
-
const legacyConfigDirs = [getConfigDir("clubhouse-cli"), path.
|
|
10
|
+
const configFile = path.resolve(configDir, "config.json");
|
|
11
|
+
const legacyConfigDirs = [getConfigDir("clubhouse-cli"), path.resolve(os.homedir(), ".clubhouse-cli")];
|
|
18
12
|
let CONFIG_CACHE = null;
|
|
19
13
|
/**
|
|
20
14
|
* Config load function to be used in most-cases.
|
|
@@ -49,13 +43,14 @@ const loadCachedConfig = () => {
|
|
|
49
43
|
let config = {};
|
|
50
44
|
const token = process.env.SHORTCUT_API_TOKEN || process.env.CLUBHOUSE_API_TOKEN;
|
|
51
45
|
legacyConfigDirs.forEach((dir) => {
|
|
52
|
-
if (fs.
|
|
46
|
+
if (fs.existsSync(dir)) {
|
|
53
47
|
createConfigDir();
|
|
54
|
-
fs.
|
|
48
|
+
fs.renameSync(dir, configDir);
|
|
55
49
|
}
|
|
56
50
|
});
|
|
57
|
-
if (fs.
|
|
58
|
-
|
|
51
|
+
if (fs.existsSync(configFile)) try {
|
|
52
|
+
const rawConfig = fs.readFileSync(configFile, "utf8").trim();
|
|
53
|
+
config = rawConfig ? JSON.parse(rawConfig) : {};
|
|
59
54
|
} catch (e) {
|
|
60
55
|
console.error(e);
|
|
61
56
|
process.exit(10);
|
|
@@ -69,14 +64,14 @@ const loadCachedConfig = () => {
|
|
|
69
64
|
return config;
|
|
70
65
|
};
|
|
71
66
|
const createConfigDir = () => {
|
|
72
|
-
const dir = path.
|
|
73
|
-
if (!fs.
|
|
74
|
-
if (!fs.
|
|
67
|
+
const dir = path.dirname(configDir);
|
|
68
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
|
69
|
+
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir);
|
|
75
70
|
};
|
|
76
71
|
const saveConfig = (config) => {
|
|
77
72
|
try {
|
|
78
73
|
createConfigDir();
|
|
79
|
-
fs.
|
|
74
|
+
fs.writeFileSync(configFile, JSON.stringify(config), { flag: "w" });
|
|
80
75
|
CONFIG_CACHE = { ...config };
|
|
81
76
|
return true;
|
|
82
77
|
} catch (e) {
|
|
@@ -102,7 +97,7 @@ const saveWorkspace = (name, workspace) => {
|
|
|
102
97
|
};
|
|
103
98
|
const removeWorkspace = (name) => {
|
|
104
99
|
const extant = loadCachedConfig();
|
|
105
|
-
delete extant.workspaces[name];
|
|
100
|
+
delete extant.workspaces?.[name];
|
|
106
101
|
return saveConfig(Object.assign({}, extant));
|
|
107
102
|
};
|
|
108
103
|
var configure_default = {
|
|
@@ -111,9 +106,5 @@ var configure_default = {
|
|
|
111
106
|
saveWorkspace,
|
|
112
107
|
removeWorkspace
|
|
113
108
|
};
|
|
114
|
-
|
|
115
109
|
//#endregion
|
|
116
|
-
|
|
117
|
-
exports.loadCachedConfig = loadCachedConfig;
|
|
118
|
-
exports.loadConfig = loadConfig;
|
|
119
|
-
exports.updateConfig = updateConfig;
|
|
110
|
+
export { configure_default as default, loadCachedConfig, loadConfig, updateConfig };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Spinner } from "cli-spinner";
|
|
2
|
+
//#region src/lib/spinner.ts
|
|
3
|
+
const spinner = (text = "") => {
|
|
4
|
+
const spin = new Spinner({
|
|
5
|
+
text: text ? text : "Loading... %s ",
|
|
6
|
+
stream: process.stderr
|
|
7
|
+
});
|
|
8
|
+
spin.setSpinnerString(27);
|
|
9
|
+
return spin;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { spinner as default };
|
|
@@ -1,46 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
debug = require_rolldown_runtime.__toESM(debug);
|
|
7
|
-
let child_process = require("child_process");
|
|
8
|
-
let chalk = require("chalk");
|
|
9
|
-
chalk = require_rolldown_runtime.__toESM(chalk);
|
|
10
|
-
|
|
1
|
+
import { loadConfig } from "./configure.js";
|
|
2
|
+
import client from "./client.js";
|
|
3
|
+
import debugging from "debug";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import chalk from "chalk";
|
|
11
6
|
//#region src/lib/stories.ts
|
|
12
|
-
const debug
|
|
13
|
-
const config =
|
|
7
|
+
const debug = debugging("club");
|
|
8
|
+
const config = loadConfig();
|
|
14
9
|
const log = console.log;
|
|
15
10
|
async function fetchEntities() {
|
|
16
|
-
const [projectsById, statesById, membersById, groupsById, epicsById, iterationsById, labels] = await Promise.all([
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
const [projectsById, statesById, membersById, groupsById, epicsById, objectivesById, iterationsById, labels] = await Promise.all([
|
|
12
|
+
client.listProjects().then((r) => r.data).then(mapByItemId),
|
|
13
|
+
client.listWorkflows().then((r) => r.data).then((wfs) => wfs.reduce((states, wf) => states.concat(wf.states), [])).then(mapByItemId),
|
|
14
|
+
client.listMembers().then((r) => r.data).then(mapByItemStringId),
|
|
15
|
+
client.listGroups().then((r) => r.data).then(mapByItemStringId),
|
|
16
|
+
client.listEpics().then((r) => r.data).then(mapByItemId),
|
|
17
|
+
client.listObjectives().then((r) => r.data).then(mapByItemId),
|
|
18
|
+
client.listIterations().then((r) => r.data).then(mapByItemId),
|
|
19
|
+
client.listLabels().then((r) => r.data)
|
|
24
20
|
]).catch((err) => {
|
|
25
21
|
log(`Error fetching workflows: ${err}`);
|
|
26
22
|
process.exit(2);
|
|
27
23
|
});
|
|
28
|
-
debug
|
|
24
|
+
debug("response workflows, members, groups, projects, epics, iterations");
|
|
29
25
|
return {
|
|
30
26
|
projectsById,
|
|
31
27
|
statesById,
|
|
32
28
|
membersById,
|
|
33
29
|
groupsById,
|
|
34
30
|
epicsById,
|
|
31
|
+
objectivesById,
|
|
35
32
|
iterationsById,
|
|
36
33
|
labels
|
|
37
34
|
};
|
|
38
35
|
}
|
|
39
36
|
const listStories = async (options) => {
|
|
40
|
-
debug
|
|
37
|
+
debug("request workflows, members, projects, epics");
|
|
41
38
|
const entities = await fetchEntities();
|
|
42
39
|
const stories = await fetchStories(options, entities);
|
|
43
|
-
debug
|
|
40
|
+
debug("filtering stories");
|
|
44
41
|
return filterStories(options, stories, entities).sort(sortStories(options));
|
|
45
42
|
};
|
|
46
43
|
function mapByItemId(items) {
|
|
@@ -51,22 +48,31 @@ function mapByItemStringId(items) {
|
|
|
51
48
|
}
|
|
52
49
|
async function fetchStories(options, entities) {
|
|
53
50
|
if ((options.args ?? []).length) {
|
|
54
|
-
debug
|
|
51
|
+
debug("using the search endpoint");
|
|
55
52
|
return searchStories(options);
|
|
56
53
|
}
|
|
57
|
-
|
|
54
|
+
const groups = entities.groupsById ? [...entities.groupsById.values()] : [];
|
|
55
|
+
if (groups.length > 0) {
|
|
56
|
+
debug("request all stories for group(s)", groups.map((g) => g.name).join(", "));
|
|
57
|
+
return Promise.all(groups.map((g) => client.listGroupStories(g.id))).then((groupStories) => {
|
|
58
|
+
return dedupeStoriesById(groupStories.reduce((acc, group) => acc.concat(group.data), []));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
debug("filtering projects");
|
|
58
62
|
const regexProject = new RegExp(options.project ?? "", "i");
|
|
59
|
-
const projectIds = (entities.projectsById ? [...entities.projectsById.values()] : []).filter((p) =>
|
|
60
|
-
debug
|
|
61
|
-
return Promise.all(projectIds.map((p) =>
|
|
63
|
+
const projectIds = (entities.projectsById ? [...entities.projectsById.values()] : []).filter((p) => !!`${p.id} ${p.name}`.match(regexProject));
|
|
64
|
+
debug("request all stories for project(s)", projectIds.map((p) => p.name).join(", "));
|
|
65
|
+
return Promise.all(projectIds.map((p) => client.listStories(p.id))).then((projectStories) => {
|
|
66
|
+
return dedupeStoriesById(projectStories.reduce((acc, project) => acc.concat(project.data), []));
|
|
67
|
+
});
|
|
62
68
|
}
|
|
63
69
|
async function searchStories(options) {
|
|
64
70
|
const query = (options.args ?? []).join(" ").replace("%self%", config.mentionName ?? "");
|
|
65
|
-
let result = await
|
|
71
|
+
let result = await client.searchStories({ query });
|
|
66
72
|
let stories = result.data.data.map(storySearchResultToStory);
|
|
67
73
|
while (result.data.next) {
|
|
68
|
-
const nextCursor = new URLSearchParams(result.data.next).get("next");
|
|
69
|
-
result = await
|
|
74
|
+
const nextCursor = new URLSearchParams(result.data.next).get("next") ?? void 0;
|
|
75
|
+
result = await client.searchStories({
|
|
70
76
|
query,
|
|
71
77
|
next: nextCursor
|
|
72
78
|
});
|
|
@@ -88,16 +94,16 @@ const storySearchResultToStory = (storySearchResult) => {
|
|
|
88
94
|
};
|
|
89
95
|
};
|
|
90
96
|
const hydrateStory = (entities, story) => {
|
|
91
|
-
debug
|
|
97
|
+
debug("hydrating story");
|
|
92
98
|
const augmented = story;
|
|
93
|
-
augmented.project = entities.projectsById?.get(story.project_id);
|
|
99
|
+
if (story.project_id !== null && story.project_id !== void 0) augmented.project = entities.projectsById?.get(story.project_id);
|
|
94
100
|
augmented.state = entities.statesById?.get(story.workflow_state_id);
|
|
95
|
-
augmented.epic = entities.epicsById?.get(story.epic_id);
|
|
96
|
-
augmented.iteration = entities.iterationsById?.get(story.iteration_id);
|
|
101
|
+
if (story.epic_id !== null && story.epic_id !== void 0) augmented.epic = entities.epicsById?.get(story.epic_id);
|
|
102
|
+
if (story.iteration_id !== null && story.iteration_id !== void 0) augmented.iteration = entities.iterationsById?.get(story.iteration_id);
|
|
97
103
|
augmented.owners = story.owner_ids.map((id) => entities.membersById?.get(id));
|
|
98
|
-
augmented.requester = entities.membersById?.get(story.requested_by_id);
|
|
99
|
-
augmented.group = entities.groupsById?.get(story.group_id);
|
|
100
|
-
debug
|
|
104
|
+
if (story.requested_by_id) augmented.requester = entities.membersById?.get(story.requested_by_id);
|
|
105
|
+
if (story.group_id) augmented.group = entities.groupsById?.get(story.group_id);
|
|
106
|
+
debug("hydrated story");
|
|
101
107
|
return augmented;
|
|
102
108
|
};
|
|
103
109
|
const isNumber = (val) => !!(val || val === 0) && !isNaN(Number(val.toString()));
|
|
@@ -109,10 +115,27 @@ const findEntity = (entities, id) => {
|
|
|
109
115
|
return Array.from(entities.values()).filter((s) => !!s.name.match(match))[0];
|
|
110
116
|
};
|
|
111
117
|
const findProject = (entities, project) => findEntity(entities.projectsById, project);
|
|
112
|
-
const findGroup = (entities, group) =>
|
|
118
|
+
const findGroup = (entities, group) => entities.groupsById?.get(String(group)) || Array.from(entities.groupsById?.values() || []).find((g) => !!`${g.id} ${g.name} ${g.mention_name}`.match(new RegExp(`${group}`, "i")));
|
|
119
|
+
const findMember = (entities, member) => {
|
|
120
|
+
if (!entities.membersById) return void 0;
|
|
121
|
+
if (entities.membersById.get(String(member))) return entities.membersById.get(String(member));
|
|
122
|
+
const match = new RegExp(`${member}`, "i");
|
|
123
|
+
return Array.from(entities.membersById.values()).find((m) => !!`${m.id} ${m.profile.name} ${m.profile.mention_name}`.match(match));
|
|
124
|
+
};
|
|
125
|
+
const findLabel = (entities, label) => {
|
|
126
|
+
const labels = entities.labels ?? [];
|
|
127
|
+
const exact = labels.find((l) => `${l.id}` === `${label}`);
|
|
128
|
+
if (exact) return exact;
|
|
129
|
+
const match = new RegExp(`${label}`, "i");
|
|
130
|
+
return labels.find((l) => !!`${l.id} ${l.name}`.match(match));
|
|
131
|
+
};
|
|
113
132
|
const findState = (entities, state) => findEntity(entities.statesById, state);
|
|
114
133
|
const findEpic = (entities, epicName) => findEntity(entities.epicsById, epicName);
|
|
134
|
+
const findObjective = (entities, objectiveName) => findEntity(entities.objectivesById, objectiveName);
|
|
115
135
|
const findIteration = (entities, iterationName) => findEntity(entities.iterationsById, iterationName);
|
|
136
|
+
const findObjectiveIds = (entities, objectives) => {
|
|
137
|
+
return objectives.split(",").map((objective) => objective.trim()).filter(Boolean).map((objective) => findObjective(entities, objective)?.id).filter((id) => typeof id === "number");
|
|
138
|
+
};
|
|
116
139
|
const findOwnerIds = (entities, owners) => {
|
|
117
140
|
const ownerMatch = new RegExp(owners.split(",").join("|"), "i");
|
|
118
141
|
return (entities.membersById ? Array.from(entities.membersById.values()) : []).filter((m) => !!`${m.id} ${m.profile.name} ${m.profile.mention_name}`.match(ownerMatch)).map((m) => m.id);
|
|
@@ -121,6 +144,14 @@ const findLabelNames = (entities, label) => {
|
|
|
121
144
|
const labelMatch = new RegExp(label.split(",").join("|"), "i");
|
|
122
145
|
return (entities.labels ?? []).filter((m) => !!`${m.id} ${m.name}`.match(labelMatch)).map((m) => ({ name: m.name }));
|
|
123
146
|
};
|
|
147
|
+
const dedupeStoriesById = (stories) => {
|
|
148
|
+
const seen = /* @__PURE__ */ new Set();
|
|
149
|
+
return stories.filter((story) => {
|
|
150
|
+
if (seen.has(story.id)) return false;
|
|
151
|
+
seen.add(story.id);
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
};
|
|
124
155
|
const filterStories = (options, stories, entities) => {
|
|
125
156
|
let createdAtFilter;
|
|
126
157
|
if (options.created) createdAtFilter = parseDateComparator(options.created);
|
|
@@ -135,14 +166,16 @@ const filterStories = (options, stories, entities) => {
|
|
|
135
166
|
const regexType = new RegExp(options.type ?? "", "i");
|
|
136
167
|
const regexEpic = new RegExp(options.epic ?? "", "i");
|
|
137
168
|
const regexIteration = new RegExp(options.iteration ?? "", "i");
|
|
169
|
+
const regexProject = new RegExp(options.project ?? "", "i");
|
|
138
170
|
return stories.map((story) => hydrateStory(entities, story)).filter((s) => {
|
|
139
171
|
if (!options.archived && s.archived) return false;
|
|
140
172
|
if (!(s.labels.map((l) => `${l.id},${l.name}`).join(",") + "").match(regexLabel)) return false;
|
|
141
173
|
if (!(s.workflow_state_id + " " + (s.state?.name ?? "")).match(regexState)) return false;
|
|
142
174
|
if (!(s.epic_id + " " + (s.epic?.name ?? "")).match(regexEpic)) return false;
|
|
143
175
|
if (!(s.iteration_id + " " + (s.iteration?.name ?? "")).match(regexIteration)) return false;
|
|
176
|
+
if (!(s.project_id + " " + (s.project?.name ?? "")).match(regexProject)) return false;
|
|
144
177
|
if (options.owner) {
|
|
145
|
-
if (!(s.owners
|
|
178
|
+
if (!((s.owners ?? []).filter((o) => {
|
|
146
179
|
return !!`${o?.profile.name} ${o?.profile.mention_name}`.match(regexOwner);
|
|
147
180
|
}).length > 0)) return false;
|
|
148
181
|
}
|
|
@@ -156,14 +189,15 @@ const filterStories = (options, stories, entities) => {
|
|
|
156
189
|
const sortStories = (options) => {
|
|
157
190
|
const fields = (options.sort ?? "").split(",").map((s) => {
|
|
158
191
|
const parts = s.split(":");
|
|
159
|
-
return [parts[0].split("."), parts[1]?.split(".")];
|
|
192
|
+
return [(parts[0] ?? "").split("."), parts[1]?.split(".")];
|
|
160
193
|
});
|
|
161
194
|
const pluck = (acc, val) => {
|
|
195
|
+
if (!acc || typeof acc !== "object") return;
|
|
162
196
|
const value = acc[val];
|
|
163
|
-
if (value === void 0) return
|
|
197
|
+
if (value === void 0 || value === null) return void 0;
|
|
164
198
|
return value;
|
|
165
199
|
};
|
|
166
|
-
debug
|
|
200
|
+
debug("sorting stories");
|
|
167
201
|
return (a, b) => {
|
|
168
202
|
return fields.reduce((acc, field) => {
|
|
169
203
|
if (acc !== 0) return acc;
|
|
@@ -171,7 +205,9 @@ const sortStories = (options) => {
|
|
|
171
205
|
const bp = field[0].reduce(pluck, b);
|
|
172
206
|
if (ap === bp) return 0;
|
|
173
207
|
const direction = (field[1]?.[0] ?? "").match(/des/i) ? 1 : -1;
|
|
174
|
-
if (ap
|
|
208
|
+
if (ap === void 0 || ap === null) return direction > 0 ? 1 : -1;
|
|
209
|
+
if (bp === void 0 || bp === null) return direction > 0 ? -1 : 1;
|
|
210
|
+
if (String(ap) > String(bp)) {
|
|
175
211
|
if (direction > 0) return -1;
|
|
176
212
|
} else if (direction < 0) return -1;
|
|
177
213
|
return 1;
|
|
@@ -203,7 +239,7 @@ const printFormattedStory = (options) => {
|
|
|
203
239
|
log(format.replace(/%j/, JSON.stringify({
|
|
204
240
|
...story,
|
|
205
241
|
url
|
|
206
|
-
}, null, 2)).replace(/%id/, chalk.
|
|
242
|
+
}, null, 2)).replace(/%id/, chalk.blue.bold(`${story.id}`)).replace(/%t/, chalk.blue(`${story.name}`)).replace(/%d/, story.description || "").replace(/%y/, story.story_type).replace(/%l/, labels.join(", ") || "_").replace(/%epic/, story.epic_id ? `${story.epic?.name ?? ""} (#${story.epic_id})` : "_").replace(/%e/, `${story.estimate || "_"}`).replace(/%i/, story.iteration_id ? `${story.iteration?.name ?? ""} (#${story.iteration_id})` : "_").replace(/%p/, project).replace(/%T/, story.group?.name || "_").replace(/%o/, owners.join(", ") || "_").replace(/%r/, requesterStr).replace(/%s/, `${story.state?.name ?? ""} (#${story.workflow_state_id})`).replace(/%c/, `${story.created_at}`).replace(/%updated/, `${story.updated_at !== story.created_at ? story.updated_at : "_"}`).replace(/%u/, url).replace(/%a/, `${story.archived}`).replace(/%gbs/, `${buildStoryBranch(story, `${config.mentionName}/sc-${story.id}/`)}`).replace(/%gb/, `${buildStoryBranch(story)}`));
|
|
207
243
|
return story;
|
|
208
244
|
};
|
|
209
245
|
};
|
|
@@ -217,51 +253,51 @@ const buildURL = (...segments) => {
|
|
|
217
253
|
const storyURL = (story) => buildURL("story", story.id);
|
|
218
254
|
const printDetailedStory = (story, entities = {}) => {
|
|
219
255
|
const labels = story.labels.map((l) => {
|
|
220
|
-
return chalk.
|
|
256
|
+
return chalk.bold(`#${l.id}`) + ` ${l.name}`;
|
|
221
257
|
});
|
|
222
258
|
const owners = story.owners?.map((o) => {
|
|
223
259
|
if (!o) return "Unknown";
|
|
224
|
-
const mentionName = chalk.
|
|
260
|
+
const mentionName = chalk.bold(`${o.profile.mention_name}`);
|
|
225
261
|
return `${o.profile.name} (${mentionName})`;
|
|
226
262
|
}) ?? [];
|
|
227
|
-
log(chalk.
|
|
228
|
-
log(chalk.
|
|
229
|
-
log(chalk.
|
|
230
|
-
log(chalk.
|
|
263
|
+
log(chalk.blue.bold(`#${story.id}`) + chalk.blue(` ${story.name}`));
|
|
264
|
+
log(chalk.bold("Desc:") + ` ${formatLong(story.description || "_")}`);
|
|
265
|
+
log(chalk.bold("Team:") + ` ${story.group?.name || "_"}`);
|
|
266
|
+
log(chalk.bold("Owners:") + ` ${owners.join(", ") || "_"}`);
|
|
231
267
|
const requesterStr = story.requester ? `${story.requester.profile.name} (${story.requester.profile.mention_name})` : "_";
|
|
232
|
-
log(chalk.
|
|
233
|
-
log(chalk.
|
|
234
|
-
log(chalk.
|
|
235
|
-
if (story.project) log(chalk.
|
|
236
|
-
if (story.group) log(chalk.
|
|
237
|
-
if (story.epic) log(chalk.
|
|
238
|
-
else log(chalk.
|
|
239
|
-
if (story.iteration) log(chalk.
|
|
240
|
-
else log(chalk.
|
|
241
|
-
log(chalk.
|
|
242
|
-
log(chalk.
|
|
243
|
-
if (story.created_at !== story.updated_at) log(chalk.
|
|
244
|
-
log(chalk.
|
|
245
|
-
if (story.archived) log(chalk.
|
|
246
|
-
if (story.completed) log(chalk.
|
|
268
|
+
log(chalk.bold("Requester:") + ` ${requesterStr}`);
|
|
269
|
+
log(chalk.bold("Type:") + ` ${story.story_type}/${story.estimate || "_"}`);
|
|
270
|
+
log(chalk.bold("Label:") + ` ${labels.join(", ") || "_"}`);
|
|
271
|
+
if (story.project) log(chalk.bold("Project:") + chalk.bold(` #${story.project_id} `) + story.project.name);
|
|
272
|
+
if (story.group) log(chalk.bold("Team:") + chalk.bold(` #${story.group_id} `) + story.group.name);
|
|
273
|
+
if (story.epic) log(chalk.bold("Epic:") + chalk.bold(` #${story.epic_id} `) + story.epic.name);
|
|
274
|
+
else log(chalk.bold("Epic:") + " _");
|
|
275
|
+
if (story.iteration) log(chalk.bold("Iteration:") + chalk.bold(` #${story.iteration_id} `) + story.iteration.name);
|
|
276
|
+
else log(chalk.bold("Iteration:") + " _");
|
|
277
|
+
log(chalk.bold("State:") + chalk.bold(` #${story.workflow_state_id} `) + (story.state?.name ?? ""));
|
|
278
|
+
log(chalk.bold("Created:") + ` ${story.created_at}`);
|
|
279
|
+
if (story.created_at !== story.updated_at) log(chalk.bold("Updated:") + ` ${story.updated_at}`);
|
|
280
|
+
log(chalk.bold("URL:") + ` ${storyURL(story)}`);
|
|
281
|
+
if (story.archived) log(chalk.bold("Archived: ") + chalk.bold(`${story.archived}`));
|
|
282
|
+
if (story.completed) log(chalk.bold("Completed: ") + chalk.bold(`${story.completed_at}`));
|
|
247
283
|
if ("tasks" in story) story.tasks.map((c) => {
|
|
248
|
-
log(chalk.
|
|
284
|
+
log(chalk.bold("Task: ") + (c.complete ? "[X]" : "[ ]") + " " + formatLong(c.description));
|
|
249
285
|
return c;
|
|
250
286
|
});
|
|
251
287
|
if ("comments" in story) story.comments.filter((comment) => !comment.deleted).map((c) => {
|
|
252
|
-
const author = entities.membersById?.get(c.author_id);
|
|
253
|
-
log(chalk.
|
|
254
|
-
log(` ${author?.profile.name ?? "Unknown"} ` + chalk.
|
|
288
|
+
const author = c.author_id ? entities.membersById?.get(c.author_id) : void 0;
|
|
289
|
+
log(chalk.bold("Comment:") + ` ${formatLong(c.text)}`);
|
|
290
|
+
log(` ${author?.profile.name ?? "Unknown"} ` + chalk.bold("at:") + ` ${c.updated_at}`);
|
|
255
291
|
return c;
|
|
256
292
|
});
|
|
257
293
|
if ("files" in story) story.files.map((file) => {
|
|
258
|
-
log(chalk.
|
|
294
|
+
log(chalk.bold("File:") + ` ${file.name}`);
|
|
259
295
|
log(` ${file.url}`);
|
|
260
296
|
return file;
|
|
261
297
|
});
|
|
262
298
|
log();
|
|
263
299
|
};
|
|
264
|
-
const formatLong = (str) => str.split("\n").join("\n ");
|
|
300
|
+
const formatLong = (str) => (str ?? "").split("\n").join("\n ");
|
|
265
301
|
const parseDateComparator = (arg) => {
|
|
266
302
|
const match = arg.match(/[0-9].*/) || {
|
|
267
303
|
index: 0,
|
|
@@ -270,10 +306,10 @@ const parseDateComparator = (arg) => {
|
|
|
270
306
|
const parsedDate = new Date(arg.slice(match.index));
|
|
271
307
|
const comparator = arg.slice(0, match.index);
|
|
272
308
|
return (date) => {
|
|
309
|
+
if (!date) return false;
|
|
273
310
|
switch (comparator) {
|
|
274
311
|
case "<": return new Date(date) < parsedDate;
|
|
275
312
|
case ">": return new Date(date) > parsedDate;
|
|
276
|
-
case "=":
|
|
277
313
|
default: return new Date(date.slice(0, match[0].length)).getTime() === parsedDate.getTime();
|
|
278
314
|
}
|
|
279
315
|
};
|
|
@@ -289,7 +325,6 @@ const parseNumberComparator = (arg) => {
|
|
|
289
325
|
switch (comparator) {
|
|
290
326
|
case "<": return Number(n) < parsedNumber;
|
|
291
327
|
case ">": return Number(n) > parsedNumber;
|
|
292
|
-
case "=":
|
|
293
328
|
default: return Number(n) === parsedNumber;
|
|
294
329
|
}
|
|
295
330
|
};
|
|
@@ -301,8 +336,8 @@ const buildStoryBranch = (story, prefix = "") => {
|
|
|
301
336
|
};
|
|
302
337
|
const checkoutStoryBranch = (story, prefix = "") => {
|
|
303
338
|
const branch = buildStoryBranch(story, prefix);
|
|
304
|
-
debug
|
|
305
|
-
|
|
339
|
+
debug("checking out git branch: " + branch);
|
|
340
|
+
execSync(`git checkout ${branch} 2> /dev/null || git checkout -b ${branch}`);
|
|
306
341
|
};
|
|
307
342
|
const fileURL = (file) => `${file.url}?token=${config.token}`;
|
|
308
343
|
var stories_default = {
|
|
@@ -314,8 +349,12 @@ var stories_default = {
|
|
|
314
349
|
hydrateStory,
|
|
315
350
|
findProject,
|
|
316
351
|
findGroup,
|
|
352
|
+
findMember,
|
|
353
|
+
findLabel,
|
|
317
354
|
findState,
|
|
318
355
|
findEpic,
|
|
356
|
+
findObjective,
|
|
357
|
+
findObjectiveIds,
|
|
319
358
|
findIteration,
|
|
320
359
|
findOwnerIds,
|
|
321
360
|
findLabelNames,
|
|
@@ -323,6 +362,5 @@ var stories_default = {
|
|
|
323
362
|
storyURL,
|
|
324
363
|
buildURL
|
|
325
364
|
};
|
|
326
|
-
|
|
327
365
|
//#endregion
|
|
328
|
-
|
|
366
|
+
export { stories_default as default };
|
package/build/package.js
ADDED
package/package.json
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shortcut-cli/shortcut-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A community-driven command line tool for viewing, creating, and updating shortcut.com
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "A community-driven command line tool for viewing, creating, and updating shortcut.com",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"shortcut",
|
|
7
|
+
"cli"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/shortcut-cli/shortcut-cli",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/shortcut-cli/shortcut-cli/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/shortcut-cli/shortcut-cli.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "email@andjosh.com",
|
|
19
|
+
"packageManager": "pnpm@10.32.0",
|
|
20
|
+
"type": "module",
|
|
5
21
|
"engines": {
|
|
6
|
-
"node": ">=
|
|
22
|
+
"node": ">=20.19.0"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"short": "./build/bin/short.js"
|
|
7
26
|
},
|
|
8
|
-
"packageManager": "pnpm@10.28.0",
|
|
9
27
|
"files": [
|
|
10
28
|
"build"
|
|
11
29
|
],
|
|
12
30
|
"scripts": {
|
|
13
31
|
"build": "tsdown",
|
|
14
32
|
"build:watch": "pnpm run build --watch",
|
|
15
|
-
"
|
|
33
|
+
"ci": "pnpm run build && pnpm run test && pnpm run test:format",
|
|
16
34
|
"clean": "rm -rf build",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
35
|
+
"format": "oxfmt",
|
|
36
|
+
"lint": "oxlint .",
|
|
37
|
+
"prepublishOnly": "pnpm run clean && pnpm run build",
|
|
38
|
+
"start": "node -r source-map-support/register build/bin/short.js",
|
|
39
|
+
"test": "vitest run --coverage",
|
|
40
|
+
"test:format": "oxfmt --check",
|
|
41
|
+
"test:update-spec": "curl -sL -o test/fixtures/shortcut.swagger.json https://developer.shortcut.com/api/rest/v3/shortcut.swagger.json && node test/scripts/patch-spec.mjs",
|
|
42
|
+
"test:watch": "vitest",
|
|
23
43
|
"type-check": "tsc --noEmit"
|
|
24
44
|
},
|
|
25
|
-
"bin": {
|
|
26
|
-
"short": "./build/bin/short.cjs"
|
|
27
|
-
},
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/shortcut-cli/shortcut-cli.git"
|
|
31
|
-
},
|
|
32
|
-
"keywords": [
|
|
33
|
-
"clubhouse",
|
|
34
|
-
"cli",
|
|
35
|
-
"shortcut"
|
|
36
|
-
],
|
|
37
|
-
"author": "email@andjosh.com",
|
|
38
|
-
"license": "MIT",
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/shortcut-cli/shortcut-cli/issues"
|
|
41
|
-
},
|
|
42
|
-
"homepage": "https://github.com/shortcut-cli/shortcut-cli",
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@shortcut/client": "^3.
|
|
45
|
-
"chalk": "^
|
|
46
|
+
"@shortcut/client": "^3.2.0",
|
|
47
|
+
"chalk": "^5.6.2",
|
|
46
48
|
"cli-spinner": "^0.2.10",
|
|
47
|
-
"commander": "^
|
|
48
|
-
"debug": "^4.4.3"
|
|
49
|
-
"prompt": "^1.3.0"
|
|
49
|
+
"commander": "^14.0.3",
|
|
50
|
+
"debug": "^4.4.3"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@
|
|
53
|
+
"@stoplight/http-spec": "7.1.0",
|
|
54
|
+
"@stoplight/prism-http": "5.12.0",
|
|
55
|
+
"@stoplight/prism-http-server": "5.12.2",
|
|
53
56
|
"@total-typescript/tsconfig": "^1.0.4",
|
|
54
|
-
"@types/chalk": "2.2.4",
|
|
55
57
|
"@types/cli-spinner": "0.2.3",
|
|
56
58
|
"@types/debug": "4.1.12",
|
|
57
59
|
"@types/node": "24.10.1",
|
|
58
|
-
"@
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"globals": "^16.5.0",
|
|
63
|
-
"prettier": "3.7.4",
|
|
60
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
61
|
+
"oxfmt": "0.37.0",
|
|
62
|
+
"oxlint": "1.52.0",
|
|
63
|
+
"pino": "10.3.1",
|
|
64
64
|
"source-map-support": "0.5.21",
|
|
65
|
-
"tsdown": "0.
|
|
65
|
+
"tsdown": "0.21.1",
|
|
66
66
|
"typescript": "5.9.3",
|
|
67
|
-
"
|
|
67
|
+
"vitest": "4.0.18"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
//#region rolldown:runtime
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: ((k) => from[k]).bind(null, key),
|
|
15
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
-
value: mod,
|
|
24
|
-
enumerable: true
|
|
25
|
-
}) : target, mod));
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
|
|
29
|
-
exports.__toESM = __toESM;
|