@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.
Files changed (48) hide show
  1. package/README.md +428 -8
  2. package/build/bin/{short-api.cjs → short-api.js} +13 -16
  3. package/build/bin/short-create.js +76 -0
  4. package/build/bin/short-custom-field.js +50 -0
  5. package/build/bin/short-custom-fields.js +29 -0
  6. package/build/bin/{short-doc.cjs → short-doc.js} +34 -36
  7. package/build/bin/{short-docs.cjs → short-docs.js} +23 -15
  8. package/build/bin/short-epic.js +186 -0
  9. package/build/bin/short-epics.js +36 -0
  10. package/build/bin/short-find.js +6 -0
  11. package/build/bin/short-install.js +87 -0
  12. package/build/bin/{short-iteration.cjs → short-iteration.js} +41 -45
  13. package/build/bin/{short-iterations.cjs → short-iterations.js} +15 -19
  14. package/build/bin/short-label.js +130 -0
  15. package/build/bin/short-labels.js +27 -0
  16. package/build/bin/short-members.js +31 -0
  17. package/build/bin/short-objective.js +151 -0
  18. package/build/bin/short-objectives.js +63 -0
  19. package/build/bin/short-projects.js +31 -0
  20. package/build/bin/short-search.js +45 -0
  21. package/build/bin/short-story.js +458 -0
  22. package/build/bin/short-team.js +78 -0
  23. package/build/bin/short-teams.js +28 -0
  24. package/build/bin/short-workflows.js +29 -0
  25. package/build/bin/short-workspace.js +63 -0
  26. package/build/bin/short.js +8 -0
  27. package/build/lib/client.js +9 -0
  28. package/build/lib/{configure.cjs → configure.js} +18 -27
  29. package/build/lib/spinner.js +12 -0
  30. package/build/lib/{stories.cjs → stories.js} +116 -78
  31. package/build/package.js +5 -0
  32. package/package.json +44 -44
  33. package/build/_virtual/rolldown_runtime.cjs +0 -29
  34. package/build/bin/short-create.cjs +0 -58
  35. package/build/bin/short-epic.cjs +0 -74
  36. package/build/bin/short-epics.cjs +0 -36
  37. package/build/bin/short-find.cjs +0 -7
  38. package/build/bin/short-install.cjs +0 -42
  39. package/build/bin/short-members.cjs +0 -34
  40. package/build/bin/short-projects.cjs +0 -34
  41. package/build/bin/short-search.cjs +0 -49
  42. package/build/bin/short-story.cjs +0 -213
  43. package/build/bin/short-workflows.cjs +0 -32
  44. package/build/bin/short-workspace.cjs +0 -64
  45. package/build/bin/short.cjs +0 -10
  46. package/build/lib/client.cjs +0 -11
  47. package/build/lib/spinner.cjs +0 -17
  48. package/build/package.cjs +0 -18
@@ -1,20 +1,14 @@
1
- Object.defineProperty(exports, '__esModule', { value: true });
2
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
3
- let path = require("path");
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.default.resolve(process.env.XDG_DATA_HOME || os.default.homedir(), ".config");
13
- return path.default.resolve(configBaseDir, suffix);
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.default.resolve(configDir, "config.json");
17
- const legacyConfigDirs = [getConfigDir("clubhouse-cli"), path.default.resolve(os.default.homedir(), ".clubhouse-cli")];
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.default.existsSync(dir)) {
46
+ if (fs.existsSync(dir)) {
53
47
  createConfigDir();
54
- fs.default.renameSync(dir, configDir);
48
+ fs.renameSync(dir, configDir);
55
49
  }
56
50
  });
57
- if (fs.default.existsSync(configFile)) try {
58
- config = JSON.parse(fs.default.readFileSync(configFile, "utf8"));
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.default.dirname(configDir);
73
- if (!fs.default.existsSync(dir)) fs.default.mkdirSync(dir);
74
- if (!fs.default.existsSync(configDir)) fs.default.mkdirSync(configDir);
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.default.writeFileSync(configFile, JSON.stringify(config), { flag: "w" });
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
- exports.default = configure_default;
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
- Object.defineProperty(exports, '__esModule', { value: true });
2
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
3
- const require_lib_configure = require('./configure.cjs');
4
- const require_lib_client = require('./client.cjs');
5
- let debug = require("debug");
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$1 = (0, debug.default)("club");
13
- const config = require_lib_configure.loadConfig();
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
- require_lib_client.default.listProjects().then((r) => r.data).then(mapByItemId),
18
- require_lib_client.default.listWorkflows().then((r) => r.data).then((wfs) => wfs.reduce((states, wf) => states.concat(wf.states), [])).then(mapByItemId),
19
- require_lib_client.default.listMembers(null).then((r) => r.data).then(mapByItemStringId),
20
- require_lib_client.default.listGroups().then((r) => r.data).then(mapByItemStringId),
21
- require_lib_client.default.listEpics(null).then((r) => r.data).then(mapByItemId),
22
- require_lib_client.default.listIterations(null).then((r) => r.data).then(mapByItemId),
23
- require_lib_client.default.listLabels(null).then((r) => r.data)
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$1("response workflows, members, groups, projects, epics, iterations");
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$1("request workflows, members, projects, epics");
37
+ debug("request workflows, members, projects, epics");
41
38
  const entities = await fetchEntities();
42
39
  const stories = await fetchStories(options, entities);
43
- debug$1("filtering stories");
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$1("using the search endpoint");
51
+ debug("using the search endpoint");
55
52
  return searchStories(options);
56
53
  }
57
- debug$1("filtering projects");
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) => !!(p.id + p.name).match(regexProject));
60
- debug$1("request all stories for project(s)", projectIds.map((p) => p.name).join(", "));
61
- return Promise.all(projectIds.map((p) => require_lib_client.default.listStories(p.id, null))).then((projectStories) => projectStories.reduce((acc, stories) => acc.concat(stories.data), []));
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 require_lib_client.default.searchStories({ query });
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 require_lib_client.default.searchStories({
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$1("hydrating story");
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$1("hydrated story");
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) => findEntity(entities.groupsById, 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?.filter((o) => {
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$1("sorting stories");
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 > bp) {
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.default.blue.bold(`${story.id}`)).replace(/%t/, chalk.default.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)}`));
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.default.bold(`#${l.id}`) + ` ${l.name}`;
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.default.bold(`${o.profile.mention_name}`);
260
+ const mentionName = chalk.bold(`${o.profile.mention_name}`);
225
261
  return `${o.profile.name} (${mentionName})`;
226
262
  }) ?? [];
227
- log(chalk.default.blue.bold(`#${story.id}`) + chalk.default.blue(` ${story.name}`));
228
- log(chalk.default.bold("Desc:") + ` ${formatLong(story.description || "_")}`);
229
- log(chalk.default.bold("Team:") + ` ${story.group?.name || "_"}`);
230
- log(chalk.default.bold("Owners:") + ` ${owners.join(", ") || "_"}`);
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.default.bold("Requester:") + ` ${requesterStr}`);
233
- log(chalk.default.bold("Type:") + ` ${story.story_type}/${story.estimate || "_"}`);
234
- log(chalk.default.bold("Label:") + ` ${labels.join(", ") || "_"}`);
235
- if (story.project) log(chalk.default.bold("Project:") + chalk.default.bold(` #${story.project_id} `) + story.project.name);
236
- if (story.group) log(chalk.default.bold("Team:") + chalk.default.bold(` #${story.group_id} `) + story.group.name);
237
- if (story.epic) log(chalk.default.bold("Epic:") + chalk.default.bold(` #${story.epic_id} `) + story.epic.name);
238
- else log(chalk.default.bold("Epic:") + " _");
239
- if (story.iteration) log(chalk.default.bold("Iteration:") + chalk.default.bold(` #${story.iteration_id} `) + story.iteration.name);
240
- else log(chalk.default.bold("Iteration:") + " _");
241
- log(chalk.default.bold("State:") + chalk.default.bold(` #${story.workflow_state_id} `) + (story.state?.name ?? ""));
242
- log(chalk.default.bold("Created:") + ` ${story.created_at}`);
243
- if (story.created_at !== story.updated_at) log(chalk.default.bold("Updated:") + ` ${story.updated_at}`);
244
- log(chalk.default.bold("URL:") + ` ${storyURL(story)}`);
245
- if (story.archived) log(chalk.default.bold("Archived: ") + chalk.default.bold(`${story.archived}`));
246
- if (story.completed) log(chalk.default.bold("Completed: ") + chalk.default.bold(`${story.completed_at}`));
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.default.bold("Task: ") + (c.complete ? "[X]" : "[ ]") + " " + formatLong(c.description));
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.default.bold("Comment:") + ` ${formatLong(c.text)}`);
254
- log(` ${author?.profile.name ?? "Unknown"} ` + chalk.default.bold("at:") + ` ${c.updated_at}`);
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.default.bold("File:") + ` ${file.name}`);
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$1("checking out git branch: " + branch);
305
- (0, child_process.execSync)(`git checkout ${branch} 2> /dev/null || git checkout -b ${branch}`);
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
- exports.default = stories_default;
366
+ export { stories_default as default };
@@ -0,0 +1,5 @@
1
+ //#region package.json
2
+ var version = "5.0.0";
3
+ var description = "A community-driven command line tool for viewing, creating, and updating shortcut.com";
4
+ //#endregion
5
+ export { description, version };
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
1
  {
2
2
  "name": "@shortcut-cli/shortcut-cli",
3
- "version": "4.0.0",
4
- "description": "A community-driven command line tool for viewing, creating, and updating shortcut.com stories",
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": ">=18"
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
- "prepublishOnly": "pnpm run clean && pnpm run build",
33
+ "ci": "pnpm run build && pnpm run test && pnpm run test:format",
16
34
  "clean": "rm -rf build",
17
- "start": "node -r source-map-support/register build/bin/short.cjs",
18
- "test": "pnpm run test:format",
19
- "test:format": "prettier --list-different src/**/*.ts",
20
- "format": "prettier --write src/**/*.ts",
21
- "ci": "pnpm run build && pnpm run test",
22
- "lint": "eslint .",
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.1.0",
45
- "chalk": "^2.2.0",
46
+ "@shortcut/client": "^3.2.0",
47
+ "chalk": "^5.6.2",
46
48
  "cli-spinner": "^0.2.10",
47
- "commander": "^12.1.0",
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
- "@eslint/js": "^9.39.2",
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
- "@types/node-fetch": "3.0.3",
59
- "@types/prompt": "^1.1.9",
60
- "eslint": "^9.39.2",
61
- "eslint-plugin-import": "^2.32.0",
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.19.0",
65
+ "tsdown": "0.21.1",
66
66
  "typescript": "5.9.3",
67
- "typescript-eslint": "^8.53.0"
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;