@enrichlayer/el-linear 1.5.0 → 1.6.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/dist/commands/attachments.js +11 -12
- package/dist/commands/batch.js +15 -14
- package/dist/commands/comments.js +22 -10
- package/dist/commands/cycles.js +5 -4
- package/dist/commands/documents.js +18 -15
- package/dist/commands/embeds.js +4 -6
- package/dist/commands/graphql.js +5 -4
- package/dist/commands/init/aliases.js +0 -14
- package/dist/commands/init/defaults.js +0 -6
- package/dist/commands/init/index.js +0 -12
- package/dist/commands/init/shared.js +0 -1
- package/dist/commands/init/token.js +0 -7
- package/dist/commands/init/workspace.js +0 -2
- package/dist/commands/issue-id.js +3 -1
- package/dist/commands/issues.js +87 -46
- package/dist/commands/labels.js +9 -8
- package/dist/commands/profile/migrate-legacy.js +0 -1
- package/dist/commands/project-milestones.js +13 -12
- package/dist/commands/projects.js +18 -14
- package/dist/commands/read-shortcut.js +8 -7
- package/dist/commands/refs.js +4 -3
- package/dist/commands/releases.js +9 -8
- package/dist/commands/search.js +3 -2
- package/dist/commands/teams.js +3 -2
- package/dist/commands/templates.js +5 -4
- package/dist/commands/users.js +3 -2
- package/dist/config/config.d.ts +18 -0
- package/dist/config/issue-validation.js +1 -1
- package/dist/config/resolver.js +1 -1
- package/dist/main.js +1 -1
- package/dist/utils/download-uploads.d.ts +2 -1
- package/dist/utils/download-uploads.js +2 -4
- package/dist/utils/file-service.d.ts +26 -2
- package/dist/utils/file-service.js +27 -5
- package/dist/utils/footer.d.ts +19 -0
- package/dist/utils/footer.js +27 -0
- package/dist/utils/gdoc-parser.js +1 -1
- package/dist/utils/graphql-attachments-service.d.ts +1 -1
- package/dist/utils/graphql-attachments-service.js +2 -2
- package/dist/utils/graphql-documents-service.d.ts +1 -1
- package/dist/utils/graphql-documents-service.js +2 -2
- package/dist/utils/graphql-service.d.ts +2 -2
- package/dist/utils/graphql-service.js +7 -4
- package/dist/utils/linear-service.d.ts +18 -3
- package/dist/utils/linear-service.js +21 -6
- package/dist/utils/markdown-prosemirror.js +16 -9
- package/dist/utils/root-opts.d.ts +11 -0
- package/dist/utils/root-opts.js +13 -0
- package/dist/utils/validators.d.ts +7 -0
- package/dist/utils/validators.js +14 -14
- package/package.json +1 -1
|
@@ -33,7 +33,6 @@ export async function runWorkspaceStep(token, workspaceUrlKey, existing) {
|
|
|
33
33
|
for (const t of teams) {
|
|
34
34
|
teamMap[t.key] = t.id;
|
|
35
35
|
}
|
|
36
|
-
// biome-ignore lint/suspicious/noConsole: wizard
|
|
37
36
|
console.log(` Workspace: ${workspaceUrlKey} (${teams.length} teams)`);
|
|
38
37
|
const currentDefault = existing.defaultTeam || "";
|
|
39
38
|
// `change` reflects what the user agreed to: `true` means "open the picker
|
|
@@ -53,7 +52,6 @@ export async function runWorkspaceStep(token, workspaceUrlKey, existing) {
|
|
|
53
52
|
};
|
|
54
53
|
}
|
|
55
54
|
if (teams.length === 0) {
|
|
56
|
-
// biome-ignore lint/suspicious/noConsole: wizard
|
|
57
55
|
console.log(" No teams visible to this token. Skipping default-team picker.");
|
|
58
56
|
return {
|
|
59
57
|
workspaceUrlKey,
|
|
@@ -72,7 +72,9 @@ export function setupIssueIdCommand(program) {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
const rootOpts = command.parent?.opts() ?? {};
|
|
75
|
-
const service = createGraphQLService({
|
|
75
|
+
const service = await createGraphQLService({
|
|
76
|
+
apiToken: rootOpts.apiToken,
|
|
77
|
+
});
|
|
76
78
|
const result = (await service.rawRequest(ISSUE_QUERY, {
|
|
77
79
|
id: parsed.issueId,
|
|
78
80
|
}));
|
package/dist/commands/issues.js
CHANGED
|
@@ -7,10 +7,10 @@ import { resolveDefaultStatus } from "../config/status-defaults.js";
|
|
|
7
7
|
import { enforceTerms } from "../config/term-enforcer.js";
|
|
8
8
|
import { LIST_COMMENTS_QUERY } from "../queries/comments.js";
|
|
9
9
|
import { GET_ISSUE_RELATIONS_QUERY, GET_ISSUE_STATE_HISTORY_QUERY, ISSUE_RELATION_CREATE_MUTATION, SCAN_ISSUES_QUERY, UPDATE_ISSUE_MUTATION, } from "../queries/issues.js";
|
|
10
|
-
import { getApiToken } from "../utils/auth.js";
|
|
11
10
|
import { autoLinkReferences, } from "../utils/auto-link-references.js";
|
|
12
11
|
import { downloadLinearUploads } from "../utils/download-uploads.js";
|
|
13
|
-
import {
|
|
12
|
+
import { createFileService } from "../utils/file-service.js";
|
|
13
|
+
import { applyFooter } from "../utils/footer.js";
|
|
14
14
|
import { createGraphQLAttachmentsService } from "../utils/graphql-attachments-service.js";
|
|
15
15
|
import { GraphQLIssuesService } from "../utils/graphql-issues-service.js";
|
|
16
16
|
import { createGraphQLService, } from "../utils/graphql-service.js";
|
|
@@ -19,6 +19,7 @@ import { wrapIssueReferencesAsLinks } from "../utils/issue-reference-wrapper.js"
|
|
|
19
19
|
import { createLinearService, } from "../utils/linear-service.js";
|
|
20
20
|
import { logger } from "../utils/logger.js";
|
|
21
21
|
import { handleAsyncCommand, outputSuccess, outputWarning, } from "../utils/output.js";
|
|
22
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
22
23
|
import { formatCsv, formatMarkdown, formatTable, } from "../utils/table-formatter.js";
|
|
23
24
|
import { validateReferences } from "../utils/validate-references.js";
|
|
24
25
|
import { parsePriorityFilter, splitList, validatePriority, } from "../utils/validators.js";
|
|
@@ -78,14 +79,43 @@ function readDescriptionFile(filePath) {
|
|
|
78
79
|
return fs.readFileSync(filePath, "utf8").trim();
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
|
-
* Resolve the description from --description, --description-file, or
|
|
82
|
-
* --
|
|
82
|
+
* Resolve the description from --description, --description-file, or
|
|
83
|
+
* --template (looked up in `config.descriptionTemplates`).
|
|
84
|
+
*
|
|
85
|
+
* Precedence:
|
|
86
|
+
* --description-file > --description > --template
|
|
87
|
+
*
|
|
88
|
+
* Passing --template alongside --description or --description-file is a
|
|
89
|
+
* usage error — the explicit body and a template both producing content
|
|
90
|
+
* would be silently dropping one. We throw so the user picks one.
|
|
83
91
|
*/
|
|
84
92
|
function resolveDescription(options) {
|
|
85
|
-
|
|
93
|
+
const hasInline = typeof options.description === "string" && options.description.length > 0;
|
|
94
|
+
const hasFile = Boolean(options.descriptionFile);
|
|
95
|
+
const hasTemplate = typeof options.template === "string" && options.template;
|
|
96
|
+
if (hasTemplate && (hasInline || hasFile)) {
|
|
97
|
+
throw new Error("--template is mutually exclusive with --description / --description-file. " +
|
|
98
|
+
"Pick one.");
|
|
99
|
+
}
|
|
100
|
+
if (hasFile) {
|
|
86
101
|
return readDescriptionFile(options.descriptionFile);
|
|
87
102
|
}
|
|
88
|
-
|
|
103
|
+
if (hasInline) {
|
|
104
|
+
return options.description;
|
|
105
|
+
}
|
|
106
|
+
if (hasTemplate) {
|
|
107
|
+
const templates = loadConfig().descriptionTemplates ?? {};
|
|
108
|
+
const body = templates[options.template];
|
|
109
|
+
if (!body) {
|
|
110
|
+
const available = Object.keys(templates).sort();
|
|
111
|
+
const hint = available.length
|
|
112
|
+
? `Available templates: ${available.join(", ")}`
|
|
113
|
+
: "No templates configured. Add one under `descriptionTemplates` in your config.";
|
|
114
|
+
throw new Error(`Template "${options.template}" not found. ${hint}`);
|
|
115
|
+
}
|
|
116
|
+
return body;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
89
119
|
}
|
|
90
120
|
function isImageFile(filename) {
|
|
91
121
|
const ext = filename.lastIndexOf(".");
|
|
@@ -296,9 +326,9 @@ async function handleListIssues(options, command) {
|
|
|
296
326
|
if (options.label && !options.labels) {
|
|
297
327
|
options.labels = options.label;
|
|
298
328
|
}
|
|
299
|
-
const rootOpts = command
|
|
300
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
301
|
-
const linearService = createLinearService(rootOpts);
|
|
329
|
+
const rootOpts = getRootOpts(command);
|
|
330
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
331
|
+
const linearService = await createLinearService(rootOpts);
|
|
302
332
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
303
333
|
const hasFilters = options.team ||
|
|
304
334
|
options.labels ||
|
|
@@ -337,9 +367,9 @@ async function handleSearchIssues(query, options, command) {
|
|
|
337
367
|
if (options.label && !options.labels) {
|
|
338
368
|
options.labels = options.label;
|
|
339
369
|
}
|
|
340
|
-
const rootOpts = command
|
|
341
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
342
|
-
const linearService = createLinearService(rootOpts);
|
|
370
|
+
const rootOpts = getRootOpts(command);
|
|
371
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
372
|
+
const linearService = await createLinearService(rootOpts);
|
|
343
373
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
344
374
|
const searchArgs = {
|
|
345
375
|
query,
|
|
@@ -417,8 +447,7 @@ async function uploadAttachmentsIfNeeded(options, rootOpts) {
|
|
|
417
447
|
const paths = Array.isArray(options.attachment)
|
|
418
448
|
? options.attachment
|
|
419
449
|
: [options.attachment];
|
|
420
|
-
const
|
|
421
|
-
const fileService = new FileService(apiToken);
|
|
450
|
+
const fileService = await createFileService(rootOpts);
|
|
422
451
|
const results = [];
|
|
423
452
|
for (const filePath of paths) {
|
|
424
453
|
const result = await fileService.uploadFile(filePath);
|
|
@@ -442,12 +471,21 @@ function buildDescriptionWithAttachments(baseDescription, uploadResults) {
|
|
|
442
471
|
return description;
|
|
443
472
|
}
|
|
444
473
|
async function handleCreateIssue(title, options, command) {
|
|
445
|
-
const rootOpts = command
|
|
474
|
+
const rootOpts = getRootOpts(command);
|
|
446
475
|
const { teamInput, teamId, assigneeId, labelIds, status, subscriberIds } = await resolveCreateInputs(title, options, rootOpts);
|
|
447
476
|
const uploadResults = await uploadAttachmentsIfNeeded(options, rootOpts);
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
477
|
+
const descriptionWithAttachments = buildDescriptionWithAttachments(resolveDescription(options) || "", uploadResults);
|
|
478
|
+
// Append messageFooter (config or --footer flag) so auto-link picks up any
|
|
479
|
+
// issue refs in the footer too. --no-footer skips both flag and config.
|
|
480
|
+
// Commander parses --no-footer as `options.footer === false`.
|
|
481
|
+
const noFooter = options.footer === false;
|
|
482
|
+
const explicitFooter = typeof options.footer === "string" ? options.footer : undefined;
|
|
483
|
+
const description = applyFooter(descriptionWithAttachments, {
|
|
484
|
+
footer: explicitFooter,
|
|
485
|
+
noFooter,
|
|
486
|
+
}) ?? "";
|
|
487
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
488
|
+
const linearService = await createLinearService(rootOpts);
|
|
451
489
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
452
490
|
// Wrap valid issue identifiers as markdown links before creating, so the description
|
|
453
491
|
// saved on Linear has clickable refs from the start. Self-reference can't apply here
|
|
@@ -486,7 +524,7 @@ async function handleCreateIssue(title, options, command) {
|
|
|
486
524
|
// so only create separate attachment records for non-image files.
|
|
487
525
|
const attachments = [];
|
|
488
526
|
if (uploadResults.length > 0) {
|
|
489
|
-
const attachmentsService = createGraphQLAttachmentsService(rootOpts);
|
|
527
|
+
const attachmentsService = await createGraphQLAttachmentsService(rootOpts);
|
|
490
528
|
for (const uploadResult of uploadResults) {
|
|
491
529
|
if (!isImageFile(uploadResult.filename)) {
|
|
492
530
|
const attachment = await attachmentsService.createAttachment({
|
|
@@ -517,9 +555,9 @@ async function handleCreateIssue(title, options, command) {
|
|
|
517
555
|
outputSuccess(output);
|
|
518
556
|
}
|
|
519
557
|
async function handleHistoryIssue(issueId, _options, command) {
|
|
520
|
-
const rootOpts = command
|
|
521
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
522
|
-
const linearService = createLinearService(rootOpts);
|
|
558
|
+
const rootOpts = getRootOpts(command);
|
|
559
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
560
|
+
const linearService = await createLinearService(rootOpts);
|
|
523
561
|
const resolvedId = await linearService.resolveIssueId(issueId);
|
|
524
562
|
const result = await graphQLService.rawRequest(GET_ISSUE_STATE_HISTORY_QUERY, {
|
|
525
563
|
id: resolvedId,
|
|
@@ -547,9 +585,9 @@ async function handleHistoryIssue(issueId, _options, command) {
|
|
|
547
585
|
});
|
|
548
586
|
}
|
|
549
587
|
async function handleRelateIssue(issueId, options, command) {
|
|
550
|
-
const rootOpts = command
|
|
551
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
552
|
-
const linearService = createLinearService(rootOpts);
|
|
588
|
+
const rootOpts = getRootOpts(command);
|
|
589
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
590
|
+
const linearService = await createLinearService(rootOpts);
|
|
553
591
|
const sourceId = await linearService.resolveIssueId(issueId);
|
|
554
592
|
const relations = await createRelations(sourceId, options, graphQLService, linearService);
|
|
555
593
|
if (relations.length === 0) {
|
|
@@ -620,9 +658,9 @@ function buildRelationEntries(nodes, peerKey, direction, normalizeType) {
|
|
|
620
658
|
return entries;
|
|
621
659
|
}
|
|
622
660
|
async function handleRelatedIssues(issueId, _options, command) {
|
|
623
|
-
const rootOpts = command
|
|
624
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
625
|
-
const linearService = createLinearService(rootOpts);
|
|
661
|
+
const rootOpts = getRootOpts(command);
|
|
662
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
663
|
+
const linearService = await createLinearService(rootOpts);
|
|
626
664
|
const resolvedId = await linearService.resolveIssueId(issueId);
|
|
627
665
|
const result = await graphQLService.rawRequest(GET_ISSUE_RELATIONS_QUERY, {
|
|
628
666
|
id: resolvedId,
|
|
@@ -813,10 +851,10 @@ async function handleLinkReferencesBatch(teamInput, options, graphQLService, lin
|
|
|
813
851
|
},
|
|
814
852
|
});
|
|
815
853
|
}
|
|
816
|
-
function handleLinkReferencesIssue(issueIdOrTeamFlag, options, command) {
|
|
817
|
-
const rootOpts = command
|
|
818
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
819
|
-
const linearService = createLinearService(rootOpts);
|
|
854
|
+
async function handleLinkReferencesIssue(issueIdOrTeamFlag, options, command) {
|
|
855
|
+
const rootOpts = getRootOpts(command);
|
|
856
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
857
|
+
const linearService = await createLinearService(rootOpts);
|
|
820
858
|
if (options.team) {
|
|
821
859
|
if (issueIdOrTeamFlag) {
|
|
822
860
|
throw new Error("Provide either an issueId positional argument OR --team, not both. With --team, omit the issueId.");
|
|
@@ -832,20 +870,20 @@ function handleLinkReferencesIssue(issueIdOrTeamFlag, options, command) {
|
|
|
832
870
|
return handleLinkReferencesSingle(issueIdOrTeamFlag, options, graphQLService, linearService);
|
|
833
871
|
}
|
|
834
872
|
async function handleReadIssue(issueIds, _options, command) {
|
|
835
|
-
const rootOpts = command
|
|
836
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
837
|
-
const linearService = createLinearService(rootOpts);
|
|
873
|
+
const rootOpts = getRootOpts(command);
|
|
874
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
875
|
+
const linearService = await createLinearService(rootOpts);
|
|
838
876
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
839
|
-
const
|
|
877
|
+
const fileService = await createFileService(rootOpts);
|
|
840
878
|
if (issueIds.length === 1) {
|
|
841
879
|
const issue = await issuesService.getIssueById(issueIds[0]);
|
|
842
|
-
const resolved = await downloadLinearUploads(issue,
|
|
880
|
+
const resolved = await downloadLinearUploads(issue, fileService);
|
|
843
881
|
outputSuccess(resolved);
|
|
844
882
|
}
|
|
845
883
|
else {
|
|
846
884
|
const results = await Promise.all(issueIds.map(async (id) => {
|
|
847
885
|
const issue = await issuesService.getIssueById(id);
|
|
848
|
-
return downloadLinearUploads(issue,
|
|
886
|
+
return downloadLinearUploads(issue, fileService);
|
|
849
887
|
}));
|
|
850
888
|
outputSuccess(results);
|
|
851
889
|
}
|
|
@@ -863,9 +901,9 @@ async function handleUpdateIssue(issueId, options, command) {
|
|
|
863
901
|
options.description = readDescriptionFile(options.descriptionFile);
|
|
864
902
|
}
|
|
865
903
|
validateUpdateOptions(options);
|
|
866
|
-
const rootOpts = command
|
|
867
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
868
|
-
const linearService = createLinearService(rootOpts);
|
|
904
|
+
const rootOpts = getRootOpts(command);
|
|
905
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
906
|
+
const linearService = await createLinearService(rootOpts);
|
|
869
907
|
if (options.appendDescription) {
|
|
870
908
|
const resolved = await linearService.resolveIssueId(issueId);
|
|
871
909
|
const current = await graphQLService.rawRequest("query($id: String!) { issue(id: $id) { description } }", { id: resolved });
|
|
@@ -952,9 +990,9 @@ async function handleRetrolink(options, command) {
|
|
|
952
990
|
const description = commits.length > 0
|
|
953
991
|
? commits.map((msg) => `- ${msg}`).join("\n")
|
|
954
992
|
: `Retrolinked from branch: ${currentBranch}`;
|
|
955
|
-
const rootOpts = command
|
|
956
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
957
|
-
const linearService = createLinearService(rootOpts);
|
|
993
|
+
const rootOpts = getRootOpts(command);
|
|
994
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
995
|
+
const linearService = await createLinearService(rootOpts);
|
|
958
996
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
959
997
|
let result;
|
|
960
998
|
if (options.issue) {
|
|
@@ -1035,8 +1073,9 @@ export function setupIssuesCommands(program) {
|
|
|
1035
1073
|
.option("-t, --title <title>", "issue title (alternative to positional argument)")
|
|
1036
1074
|
.option("-d, --description <desc>", "issue description")
|
|
1037
1075
|
.option("--description-file <path>", "read description from file (use - for stdin)")
|
|
1076
|
+
.option("--template <name>", "use a named description template from config.descriptionTemplates")
|
|
1038
1077
|
.option("-a, --assignee <assignee>", "assign to user (name, alias, or UUID)")
|
|
1039
|
-
.option("-p, --priority <priority>", "priority: name (urgent/high/medium/low) or number (
|
|
1078
|
+
.option("-p, --priority <priority>", "priority: name (none/urgent/high/medium/normal/low) or number (0-4)")
|
|
1040
1079
|
.option("--project <project>", "add to project (name or ID)")
|
|
1041
1080
|
.option("--team <team>", "team key or name (default: from config)")
|
|
1042
1081
|
.option("--labels <labels>", "labels (comma-separated names, auto-resolved per team)")
|
|
@@ -1056,6 +1095,8 @@ export function setupIssuesCommands(program) {
|
|
|
1056
1095
|
.option("--checkout", "create and checkout a git branch named after the issue")
|
|
1057
1096
|
.option("--skip-validation", "skip all validation (labels, description, assignee, project)")
|
|
1058
1097
|
.option("--no-auto-link", "skip auto-linking issue references found in the description")
|
|
1098
|
+
.option("--footer <text>", "text appended to the description (overrides config.messageFooter)")
|
|
1099
|
+
.option("--no-footer", "skip the configured messageFooter for this issue")
|
|
1059
1100
|
.action(handleAsyncCommand((titleArg, options, command) => {
|
|
1060
1101
|
// Normalize --label alias to --labels
|
|
1061
1102
|
if (options.label && !options.labels) {
|
|
@@ -1087,7 +1128,7 @@ export function setupIssuesCommands(program) {
|
|
|
1087
1128
|
.option("--append-description <text>", "append text to the existing description")
|
|
1088
1129
|
.option("-s, --status <status>", "new status name or ID")
|
|
1089
1130
|
.option("--state <status>", "alias for --status (new status name or ID)")
|
|
1090
|
-
.option("-p, --priority <priority>", "new priority: name (urgent/high/medium/low) or number (
|
|
1131
|
+
.option("-p, --priority <priority>", "new priority: name (none/urgent/high/medium/normal/low) or number (0-4)")
|
|
1091
1132
|
.option("--assignee <assignee>", "new assignee (name, alias, or UUID)")
|
|
1092
1133
|
.option("--project <project>", "new project (name or ID)")
|
|
1093
1134
|
.option("--labels <labels>", "labels (comma-separated names or IDs)")
|
package/dist/commands/labels.js
CHANGED
|
@@ -3,11 +3,12 @@ import { CREATE_LABEL_MUTATION, FIND_PARENT_LABEL_QUERY, RESTORE_LABEL_MUTATION,
|
|
|
3
3
|
import { createGraphQLService } from "../utils/graphql-service.js";
|
|
4
4
|
import { createLinearService } from "../utils/linear-service.js";
|
|
5
5
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
6
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
6
7
|
import { validateHexColor } from "../utils/validators.js";
|
|
7
8
|
async function handleCreateLabel(name, options, command) {
|
|
8
|
-
const rootOpts = command
|
|
9
|
+
const rootOpts = getRootOpts(command);
|
|
9
10
|
const teamId = resolveTeam(options.team);
|
|
10
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
11
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
11
12
|
const input = { name, teamId };
|
|
12
13
|
if (options.color) {
|
|
13
14
|
input.color = validateHexColor(options.color);
|
|
@@ -37,8 +38,8 @@ async function handleCreateLabel(name, options, command) {
|
|
|
37
38
|
outputSuccess(createResult.issueLabel);
|
|
38
39
|
}
|
|
39
40
|
async function handleRetireLabel(labelId, _options, command) {
|
|
40
|
-
const rootOpts = command
|
|
41
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
41
|
+
const rootOpts = getRootOpts(command);
|
|
42
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
42
43
|
const result = await graphQLService.rawRequest(RETIRE_LABEL_MUTATION, {
|
|
43
44
|
id: labelId,
|
|
44
45
|
});
|
|
@@ -49,8 +50,8 @@ async function handleRetireLabel(labelId, _options, command) {
|
|
|
49
50
|
outputSuccess(retireResult.issueLabel);
|
|
50
51
|
}
|
|
51
52
|
async function handleRestoreLabel(labelId, _options, command) {
|
|
52
|
-
const rootOpts = command
|
|
53
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
53
|
+
const rootOpts = getRootOpts(command);
|
|
54
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
54
55
|
const result = await graphQLService.rawRequest(RESTORE_LABEL_MUTATION, {
|
|
55
56
|
id: labelId,
|
|
56
57
|
});
|
|
@@ -72,9 +73,9 @@ export function setupLabelsCommands(program) {
|
|
|
72
73
|
.option("--team <team>", "filter by team key, name, or ID")
|
|
73
74
|
.option("-l, --limit <number>", "limit results", "100")
|
|
74
75
|
.action(handleAsyncCommand(async (options, command) => {
|
|
75
|
-
const rootOpts = command
|
|
76
|
+
const rootOpts = getRootOpts(command);
|
|
76
77
|
const teamFilter = options.team ? resolveTeam(options.team) : undefined;
|
|
77
|
-
const service = createLinearService(rootOpts);
|
|
78
|
+
const service = await createLinearService(rootOpts);
|
|
78
79
|
const result = await service.getLabels(teamFilter, Number.parseInt(options.limit, 10));
|
|
79
80
|
outputSuccess({
|
|
80
81
|
data: result.labels,
|
|
@@ -157,7 +157,6 @@ async function resolveAndValidateToken(options, prompts, validate) {
|
|
|
157
157
|
}
|
|
158
158
|
catch (err) {
|
|
159
159
|
const raw = err instanceof Error ? err.message : String(err);
|
|
160
|
-
// biome-ignore lint/suspicious/noConsole: interactive prompt feedback
|
|
161
160
|
console.log(` ✗ ${sanitizeForLog(raw)}`);
|
|
162
161
|
}
|
|
163
162
|
}
|
|
@@ -3,6 +3,7 @@ import { multipleMatchesError, notFoundError, } from "../utils/error-messages.js
|
|
|
3
3
|
import { createGraphQLService, } from "../utils/graphql-service.js";
|
|
4
4
|
import { createLinearService, } from "../utils/linear-service.js";
|
|
5
5
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
6
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
6
7
|
import { isUuid } from "../utils/uuid.js";
|
|
7
8
|
import { validateIsoDate } from "../utils/validators.js";
|
|
8
9
|
async function resolveMilestoneId(milestoneNameOrId, graphQLService, linearService, projectNameOrId) {
|
|
@@ -37,9 +38,9 @@ async function resolveMilestoneId(milestoneNameOrId, graphQLService, linearServi
|
|
|
37
38
|
return nodes[0].id;
|
|
38
39
|
}
|
|
39
40
|
async function handleListMilestones(options, command) {
|
|
40
|
-
const rootOpts = command
|
|
41
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
42
|
-
const linearService = createLinearService(rootOpts);
|
|
41
|
+
const rootOpts = getRootOpts(command);
|
|
42
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
43
|
+
const linearService = await createLinearService(rootOpts);
|
|
43
44
|
const projectId = await linearService.resolveProjectId(options.project);
|
|
44
45
|
const result = await graphQLService.rawRequest(LIST_PROJECT_MILESTONES_QUERY, {
|
|
45
46
|
projectId,
|
|
@@ -51,9 +52,9 @@ async function handleListMilestones(options, command) {
|
|
|
51
52
|
outputSuccess({ data: nodes, meta: { count: nodes.length } });
|
|
52
53
|
}
|
|
53
54
|
async function handleReadMilestone(milestoneIdOrName, options, command) {
|
|
54
|
-
const rootOpts = command
|
|
55
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
56
|
-
const linearService = createLinearService(rootOpts);
|
|
55
|
+
const rootOpts = getRootOpts(command);
|
|
56
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
57
|
+
const linearService = await createLinearService(rootOpts);
|
|
57
58
|
const milestoneId = await resolveMilestoneId(milestoneIdOrName, graphQLService, linearService, options.project);
|
|
58
59
|
const result = await graphQLService.rawRequest(GET_PROJECT_MILESTONE_BY_ID_QUERY, {
|
|
59
60
|
id: milestoneId,
|
|
@@ -62,9 +63,9 @@ async function handleReadMilestone(milestoneIdOrName, options, command) {
|
|
|
62
63
|
outputSuccess(result.projectMilestone);
|
|
63
64
|
}
|
|
64
65
|
async function handleCreateMilestone(name, options, command) {
|
|
65
|
-
const rootOpts = command
|
|
66
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
67
|
-
const linearService = createLinearService(rootOpts);
|
|
66
|
+
const rootOpts = getRootOpts(command);
|
|
67
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
68
|
+
const linearService = await createLinearService(rootOpts);
|
|
68
69
|
const projectId = await linearService.resolveProjectId(options.project);
|
|
69
70
|
if (options.targetDate) {
|
|
70
71
|
validateIsoDate(options.targetDate);
|
|
@@ -82,9 +83,9 @@ async function handleCreateMilestone(name, options, command) {
|
|
|
82
83
|
outputSuccess(createData.projectMilestone);
|
|
83
84
|
}
|
|
84
85
|
async function handleUpdateMilestone(milestoneIdOrName, options, command) {
|
|
85
|
-
const rootOpts = command
|
|
86
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
87
|
-
const linearService = createLinearService(rootOpts);
|
|
86
|
+
const rootOpts = getRootOpts(command);
|
|
87
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
88
|
+
const linearService = await createLinearService(rootOpts);
|
|
88
89
|
const milestoneId = await resolveMilestoneId(milestoneIdOrName, graphQLService, linearService, options.project);
|
|
89
90
|
const updateVars = { id: milestoneId };
|
|
90
91
|
if (options.name !== undefined) {
|
|
@@ -4,6 +4,7 @@ import { createGraphQLService } from "../utils/graphql-service.js";
|
|
|
4
4
|
import { createLinearService } from "../utils/linear-service.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
7
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
7
8
|
import { isUuid } from "../utils/uuid.js";
|
|
8
9
|
import { splitList } from "../utils/validators.js";
|
|
9
10
|
function formatProjectsOutput(projects, format, fieldNames) {
|
|
@@ -126,9 +127,10 @@ function formatTeamsOutput(projectUpdate) {
|
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
129
|
async function handleAddTeam(projectNameOrId, teamInput, _options, command) {
|
|
129
|
-
const rootOpts = command
|
|
130
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
131
|
-
const
|
|
130
|
+
const rootOpts = getRootOpts(command);
|
|
131
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
132
|
+
const linearService = await createLinearService(rootOpts);
|
|
133
|
+
const finalTeamId = await linearService.resolveTeamId(resolveTeam(teamInput));
|
|
132
134
|
const { projectId, currentTeams } = await resolveProjectWithTeams(graphQLService, projectNameOrId);
|
|
133
135
|
const currentTeamIds = currentTeams.map((t) => t.id);
|
|
134
136
|
if (currentTeamIds.includes(finalTeamId)) {
|
|
@@ -149,9 +151,10 @@ async function handleAddTeam(projectNameOrId, teamInput, _options, command) {
|
|
|
149
151
|
outputSuccess(formatTeamsOutput(projectUpdate));
|
|
150
152
|
}
|
|
151
153
|
async function handleAddTeams(projectNameOrId, teamInputs, _options, command) {
|
|
152
|
-
const rootOpts = command
|
|
153
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
154
|
-
const
|
|
154
|
+
const rootOpts = getRootOpts(command);
|
|
155
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
156
|
+
const linearService = await createLinearService(rootOpts);
|
|
157
|
+
const resolvedTeamIds = await Promise.all(teamInputs.map((t) => linearService.resolveTeamId(resolveTeam(t))));
|
|
155
158
|
const { projectId, currentTeams } = await resolveProjectWithTeams(graphQLService, projectNameOrId);
|
|
156
159
|
const currentTeamIds = currentTeams.map((t) => t.id);
|
|
157
160
|
const newTeamIds = resolvedTeamIds.filter((id) => !currentTeamIds.includes(id));
|
|
@@ -181,9 +184,10 @@ async function handleAddTeams(projectNameOrId, teamInputs, _options, command) {
|
|
|
181
184
|
});
|
|
182
185
|
}
|
|
183
186
|
async function handleRemoveTeam(projectNameOrId, teamInput, options, command) {
|
|
184
|
-
const rootOpts = command
|
|
185
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
186
|
-
const
|
|
187
|
+
const rootOpts = getRootOpts(command);
|
|
188
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
189
|
+
const linearService = await createLinearService(rootOpts);
|
|
190
|
+
const finalTeamId = await linearService.resolveTeamId(resolveTeam(teamInput));
|
|
187
191
|
const { projectId, projectName, currentTeams } = await resolveProjectWithTeams(graphQLService, projectNameOrId);
|
|
188
192
|
const currentTeamIds = currentTeams.map((t) => t.id);
|
|
189
193
|
if (!currentTeamIds.includes(finalTeamId)) {
|
|
@@ -220,8 +224,8 @@ async function handleRemoveTeam(projectNameOrId, teamInput, options, command) {
|
|
|
220
224
|
});
|
|
221
225
|
}
|
|
222
226
|
async function handleCreateProject(name, options, command) {
|
|
223
|
-
const rootOpts = command
|
|
224
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
227
|
+
const rootOpts = getRootOpts(command);
|
|
228
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
225
229
|
// Step 1: Check for duplicate projects (case-insensitive)
|
|
226
230
|
const searchResult = await graphQLService.rawRequest(SEARCH_PROJECTS_BY_NAME_QUERY, { name });
|
|
227
231
|
const existing = (searchResult.projects
|
|
@@ -236,7 +240,7 @@ async function handleCreateProject(name, options, command) {
|
|
|
236
240
|
`Use --force to create anyway, or use the existing project.`);
|
|
237
241
|
}
|
|
238
242
|
// Step 2: Resolve team IDs
|
|
239
|
-
const service = createLinearService(rootOpts);
|
|
243
|
+
const service = await createLinearService(rootOpts);
|
|
240
244
|
const teamKeys = options.team ? splitList(options.team) : [];
|
|
241
245
|
const teamIds = [];
|
|
242
246
|
for (const key of teamKeys) {
|
|
@@ -300,8 +304,8 @@ export function setupProjectsCommands(program) {
|
|
|
300
304
|
.option("--format <format>", "output format (json, table, md, csv)", "json")
|
|
301
305
|
.option("--fields <fields>", "columns for table/csv (comma-separated: name,state,progress,teams,lead,targetDate)")
|
|
302
306
|
.action(handleAsyncCommand(async (options, command) => {
|
|
303
|
-
const rootOpts = command
|
|
304
|
-
const service = createLinearService(rootOpts);
|
|
307
|
+
const rootOpts = getRootOpts(command);
|
|
308
|
+
const service = await createLinearService(rootOpts);
|
|
305
309
|
const result = await service.getProjects(Number.parseInt(options.limit, 10));
|
|
306
310
|
const format = options.format;
|
|
307
311
|
if (format === "table" ||
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { getApiToken } from "../utils/auth.js";
|
|
2
1
|
import { downloadLinearUploads } from "../utils/download-uploads.js";
|
|
2
|
+
import { createFileService } from "../utils/file-service.js";
|
|
3
3
|
import { GraphQLIssuesService } from "../utils/graphql-issues-service.js";
|
|
4
4
|
import { createGraphQLService } from "../utils/graphql-service.js";
|
|
5
5
|
import { createLinearService } from "../utils/linear-service.js";
|
|
6
6
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
7
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
7
8
|
/**
|
|
8
9
|
* Issue ID pattern: 1-5 uppercase letters, dash, 1+ digits (e.g. ADM-652, DEV-12).
|
|
9
10
|
*/
|
|
@@ -49,20 +50,20 @@ export function setupReadShortcut(program) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
async function readIssues(issueIds, _options, command) {
|
|
52
|
-
const rootOpts = command
|
|
53
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
54
|
-
const linearService = createLinearService(rootOpts);
|
|
53
|
+
const rootOpts = getRootOpts(command);
|
|
54
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
55
|
+
const linearService = await createLinearService(rootOpts);
|
|
55
56
|
const issuesService = new GraphQLIssuesService(graphQLService, linearService);
|
|
56
|
-
const
|
|
57
|
+
const fileService = await createFileService(rootOpts);
|
|
57
58
|
if (issueIds.length === 1) {
|
|
58
59
|
const issue = await issuesService.getIssueById(issueIds[0]);
|
|
59
|
-
const resolved = await downloadLinearUploads(issue,
|
|
60
|
+
const resolved = await downloadLinearUploads(issue, fileService);
|
|
60
61
|
outputSuccess(resolved);
|
|
61
62
|
}
|
|
62
63
|
else {
|
|
63
64
|
const results = await Promise.all(issueIds.map(async (id) => {
|
|
64
65
|
const issue = await issuesService.getIssueById(id);
|
|
65
|
-
return downloadLinearUploads(issue,
|
|
66
|
+
return downloadLinearUploads(issue, fileService);
|
|
66
67
|
}));
|
|
67
68
|
outputSuccess(results);
|
|
68
69
|
}
|
package/dist/commands/refs.js
CHANGED
|
@@ -4,6 +4,7 @@ import { extractIssueReferences } from "../utils/issue-reference-extractor.js";
|
|
|
4
4
|
import { wrapIssueReferencesAsLinks, } from "../utils/issue-reference-wrapper.js";
|
|
5
5
|
import { createLinearService } from "../utils/linear-service.js";
|
|
6
6
|
import { handleAsyncCommand } from "../utils/output.js";
|
|
7
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
7
8
|
import { validateReferences } from "../utils/validate-references.js";
|
|
8
9
|
import { getWorkspaceUrlKey } from "../utils/workspace-url.js";
|
|
9
10
|
const VALID_TARGETS = new Set(["markdown", "slack"]);
|
|
@@ -58,15 +59,15 @@ async function handleWrap(options, command) {
|
|
|
58
59
|
const text = typeof options.file === "string" && options.file.length > 0
|
|
59
60
|
? readFileSync(options.file, "utf8")
|
|
60
61
|
: await readAllStdin();
|
|
61
|
-
const rootOpts = command
|
|
62
|
+
const rootOpts = getRootOpts(command);
|
|
62
63
|
const deps = {
|
|
63
64
|
async resolveValidIdentifiers(ids) {
|
|
64
|
-
const linearService = createLinearService(rootOpts);
|
|
65
|
+
const linearService = await createLinearService(rootOpts);
|
|
65
66
|
const map = await validateReferences(ids, linearService);
|
|
66
67
|
return new Set(map.keys());
|
|
67
68
|
},
|
|
68
69
|
async resolveUrlKey() {
|
|
69
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
70
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
70
71
|
return getWorkspaceUrlKey(graphQLService);
|
|
71
72
|
},
|
|
72
73
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CREATE_RELEASE_MUTATION, GET_RELEASE_BY_ID_QUERY, GET_RELEASE_PIPELINES_QUERY, GET_RELEASES_QUERY, } from "../queries/releases.js";
|
|
2
2
|
import { createGraphQLService } from "../utils/graphql-service.js";
|
|
3
3
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
4
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
4
5
|
function transformRelease(release) {
|
|
5
6
|
return {
|
|
6
7
|
id: release.id,
|
|
@@ -36,8 +37,8 @@ function transformRelease(release) {
|
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
async function handleCreateRelease(name, options, command) {
|
|
39
|
-
const rootOpts = command
|
|
40
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
40
|
+
const rootOpts = getRootOpts(command);
|
|
41
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
41
42
|
const pipelines = await graphQLService.rawRequest(GET_RELEASE_PIPELINES_QUERY, { first: 50 });
|
|
42
43
|
const pipelineNodes = pipelines.releasePipelines
|
|
43
44
|
?.nodes;
|
|
@@ -84,8 +85,8 @@ export function setupReleasesCommands(program) {
|
|
|
84
85
|
.option("--pipeline <pipeline>", "filter by pipeline name or ID")
|
|
85
86
|
.option("-l, --limit <number>", "limit results", "25")
|
|
86
87
|
.action(handleAsyncCommand(async (options, command) => {
|
|
87
|
-
const rootOpts = command
|
|
88
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
88
|
+
const rootOpts = getRootOpts(command);
|
|
89
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
89
90
|
const filter = {};
|
|
90
91
|
if (options.pipeline) {
|
|
91
92
|
filter.pipeline = { name: { eqIgnoreCase: options.pipeline } };
|
|
@@ -102,8 +103,8 @@ export function setupReleasesCommands(program) {
|
|
|
102
103
|
.command("read <releaseId>")
|
|
103
104
|
.description("Get release details including linked documents")
|
|
104
105
|
.action(handleAsyncCommand(async (releaseId, _options, command) => {
|
|
105
|
-
const rootOpts = command
|
|
106
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
106
|
+
const rootOpts = getRootOpts(command);
|
|
107
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
107
108
|
const result = await graphQLService.rawRequest(GET_RELEASE_BY_ID_QUERY, { id: releaseId });
|
|
108
109
|
if (!result.release) {
|
|
109
110
|
throw new Error(`Release "${releaseId}" not found`);
|
|
@@ -122,8 +123,8 @@ export function setupReleasesCommands(program) {
|
|
|
122
123
|
.command("pipelines")
|
|
123
124
|
.description("List release pipelines and their stages")
|
|
124
125
|
.action(handleAsyncCommand(async (_options, command) => {
|
|
125
|
-
const rootOpts = command
|
|
126
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
126
|
+
const rootOpts = getRootOpts(command);
|
|
127
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
127
128
|
const result = await graphQLService.rawRequest(GET_RELEASE_PIPELINES_QUERY, { first: 50 });
|
|
128
129
|
const data = (result.releasePipelines
|
|
129
130
|
?.nodes ?? []).map((p) => ({
|
package/dist/commands/search.js
CHANGED
|
@@ -2,6 +2,7 @@ import { resolveTeam, resolveUserDisplayName } from "../config/resolver.js";
|
|
|
2
2
|
import { SEMANTIC_SEARCH_QUERY } from "../queries/search.js";
|
|
3
3
|
import { createGraphQLService } from "../utils/graphql-service.js";
|
|
4
4
|
import { handleAsyncCommand, outputSuccess } from "../utils/output.js";
|
|
5
|
+
import { getRootOpts } from "../utils/root-opts.js";
|
|
5
6
|
const TEMPLATES_QUERY = `
|
|
6
7
|
query {
|
|
7
8
|
templates {
|
|
@@ -137,8 +138,8 @@ export function setupSearchCommands(program) {
|
|
|
137
138
|
.option("--team <team>", "filter issue results by team key")
|
|
138
139
|
.option("-l, --limit <number>", "max results", "10")
|
|
139
140
|
.action(handleAsyncCommand(async (query, options, command) => {
|
|
140
|
-
const rootOpts = command
|
|
141
|
-
const graphQLService = createGraphQLService(rootOpts);
|
|
141
|
+
const rootOpts = getRootOpts(command);
|
|
142
|
+
const graphQLService = await createGraphQLService(rootOpts);
|
|
142
143
|
const limit = Number.parseInt(options.limit, 10);
|
|
143
144
|
const requestedTypes = options.type
|
|
144
145
|
? options.type.split(",").map((t) => t.trim().toLowerCase())
|