@enrichlayer/el-linear 1.7.0 → 1.10.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 +82 -1
- package/claude-skills/linear-operations/SKILL.md +55 -0
- package/dist/auth/oauth-callback.js +14 -7
- package/dist/auth/oauth-fs.d.ts +22 -0
- package/dist/auth/oauth-fs.js +77 -6
- package/dist/auth/oauth-headless.d.ts +13 -8
- package/dist/auth/oauth-headless.js +18 -11
- package/dist/auth/oauth-storage.d.ts +11 -3
- package/dist/auth/oauth-storage.js +15 -8
- package/dist/auth/token-resolver.d.ts +9 -0
- package/dist/auth/token-resolver.js +57 -37
- package/dist/commands/attachments.js +2 -1
- package/dist/commands/comments.js +17 -28
- package/dist/commands/cycles.js +2 -1
- package/dist/commands/documents.js +2 -1
- package/dist/commands/graphql.js +4 -6
- package/dist/commands/init/index.js +2 -0
- package/dist/commands/init/oauth.d.ts +6 -0
- package/dist/commands/init/oauth.js +8 -2
- package/dist/commands/init/token.d.ts +0 -8
- package/dist/commands/init/token.js +13 -1
- package/dist/commands/issue-id.js +1 -3
- package/dist/commands/issues/branch.d.ts +17 -0
- package/dist/commands/issues/branch.js +40 -0
- package/dist/commands/issues/description.d.ts +89 -0
- package/dist/commands/issues/description.js +183 -0
- package/dist/commands/issues/link-references.d.ts +21 -0
- package/dist/commands/issues/link-references.js +171 -0
- package/dist/commands/issues/relations.d.ts +55 -0
- package/dist/commands/issues/relations.js +132 -0
- package/dist/commands/issues.js +97 -497
- package/dist/commands/labels.js +20 -28
- package/dist/commands/profile/migrate-legacy.js +10 -33
- package/dist/commands/profile.js +1 -10
- package/dist/commands/project-milestones.js +13 -20
- package/dist/commands/projects.d.ts +5 -1
- package/dist/commands/projects.js +202 -102
- package/dist/commands/read-shortcut.d.ts +6 -0
- package/dist/commands/read-shortcut.js +6 -1
- package/dist/commands/refs.js +10 -1
- package/dist/commands/releases.js +26 -30
- package/dist/commands/search.js +21 -30
- package/dist/commands/teams.js +2 -1
- package/dist/commands/templates.js +128 -7
- package/dist/commands/users.js +3 -1
- package/dist/config/config.js +31 -9
- package/dist/config/issue-validation.js +1 -1
- package/dist/config/paths.d.ts +11 -0
- package/dist/config/paths.js +44 -6
- package/dist/config/resolver.js +2 -3
- package/dist/config/term-enforcer.js +1 -1
- package/dist/main.js +37 -2
- package/dist/queries/attachments-types.d.ts +30 -0
- package/dist/queries/attachments-types.js +5 -0
- package/dist/queries/comments-types.d.ts +49 -0
- package/dist/queries/comments-types.js +5 -0
- package/dist/queries/documents-types.d.ts +61 -0
- package/dist/queries/documents-types.js +9 -0
- package/dist/queries/introspect-types.d.ts +57 -0
- package/dist/queries/introspect-types.js +10 -0
- package/dist/queries/issues-types.d.ts +416 -0
- package/dist/queries/issues-types.js +23 -0
- package/dist/queries/issues.d.ts +2 -0
- package/dist/queries/issues.js +22 -0
- package/dist/queries/labels-types.d.ts +64 -0
- package/dist/queries/labels-types.js +5 -0
- package/dist/queries/project-milestones-types.d.ts +91 -0
- package/dist/queries/project-milestones-types.js +10 -0
- package/dist/queries/projects-types.d.ts +75 -0
- package/dist/queries/projects-types.js +5 -0
- package/dist/queries/projects.d.ts +2 -0
- package/dist/queries/projects.js +22 -0
- package/dist/queries/releases-types.d.ts +84 -0
- package/dist/queries/releases-types.js +5 -0
- package/dist/queries/search-types.d.ts +86 -0
- package/dist/queries/search-types.js +6 -0
- package/dist/queries/templates-types.d.ts +61 -0
- package/dist/queries/templates-types.js +9 -0
- package/dist/queries/templates.d.ts +3 -0
- package/dist/queries/templates.js +43 -0
- package/dist/types/linear.d.ts +8 -2
- package/dist/utils/auth.js +7 -9
- package/dist/utils/auto-link-references.js +44 -25
- package/dist/utils/disk-cache.js +2 -1
- package/dist/utils/formatters/summary.d.ts +62 -0
- package/dist/utils/formatters/summary.js +755 -0
- package/dist/utils/graphql-attachments-service.js +6 -9
- package/dist/utils/graphql-documents-service.js +19 -25
- package/dist/utils/graphql-issues-service.d.ts +118 -5
- package/dist/utils/graphql-issues-service.js +202 -209
- package/dist/utils/issue-reference-extractor.d.ts +9 -1
- package/dist/utils/issue-reference-extractor.js +16 -9
- package/dist/utils/issue-reference-wrapper.js +1 -54
- package/dist/utils/linear-service.d.ts +7 -3
- package/dist/utils/linear-service.js +27 -5
- package/dist/utils/markdown-prosemirror.js +17 -1
- package/dist/utils/mention-resolver.js +17 -5
- package/dist/utils/output.d.ts +7 -1
- package/dist/utils/output.js +38 -1
- package/dist/utils/protected-ranges.d.ts +33 -0
- package/dist/utils/protected-ranges.js +73 -0
- package/dist/utils/table-formatter.d.ts +36 -0
- package/dist/utils/table-formatter.js +46 -24
- package/dist/utils/validators.d.ts +9 -1
- package/dist/utils/validators.js +10 -0
- package/dist/utils/workspace-url.d.ts +5 -1
- package/dist/utils/workspace-url.js +35 -5
- package/package.json +1 -1
|
@@ -129,39 +129,54 @@ function pad(str, width) {
|
|
|
129
129
|
const truncated = truncate(str, width);
|
|
130
130
|
return truncated + " ".repeat(Math.max(0, width - truncated.length));
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Render `rows` as a fixed-width text table using `columns`. Generic
|
|
134
|
+
* over the row type `T` so issues, projects, and other resources
|
|
135
|
+
* share the same width-padding + colorize machinery.
|
|
136
|
+
*/
|
|
137
|
+
export function renderFixedWidthTable(rows, columns) {
|
|
137
138
|
if (columns.length === 0) {
|
|
138
139
|
return "No valid columns specified.";
|
|
139
140
|
}
|
|
140
141
|
const header = columns.map((col) => pad(col.header, col.width)).join(" ");
|
|
141
142
|
const separator = columns.map((col) => "─".repeat(col.width)).join("──");
|
|
142
|
-
const
|
|
143
|
+
const body = rows.map((row) => columns
|
|
143
144
|
.map((col) => {
|
|
144
|
-
const padded = pad(col.extract(
|
|
145
|
-
return col.colorize ? col.colorize(padded,
|
|
145
|
+
const padded = pad(col.extract(row), col.width);
|
|
146
|
+
return col.colorize ? col.colorize(padded, row) : padded;
|
|
146
147
|
})
|
|
147
148
|
.join(" "));
|
|
148
|
-
return [header, separator, ...
|
|
149
|
+
return [header, separator, ...body].join("\n");
|
|
149
150
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Render `rows` as CSV using `columns`. Quotes any value containing
|
|
153
|
+
* `,` or `"` (RFC 4180-style escaping). Generic over the row type.
|
|
154
|
+
*/
|
|
155
|
+
export function renderCsv(rows, columns) {
|
|
155
156
|
const header = columns.map((col) => col.header).join(",");
|
|
156
|
-
const
|
|
157
|
+
const body = rows.map((row) => columns
|
|
157
158
|
.map((col) => {
|
|
158
|
-
const value = col.extract(
|
|
159
|
+
const value = col.extract(row);
|
|
159
160
|
return value.includes(",") || value.includes('"')
|
|
160
161
|
? `"${value.replace(/"/g, '""')}"`
|
|
161
162
|
: value;
|
|
162
163
|
})
|
|
163
164
|
.join(","));
|
|
164
|
-
return [header, ...
|
|
165
|
+
return [header, ...body].join("\n");
|
|
166
|
+
}
|
|
167
|
+
export function formatTable(issues, fieldNames) {
|
|
168
|
+
const columnKeys = fieldNames ?? DEFAULT_COLUMNS;
|
|
169
|
+
const columns = columnKeys
|
|
170
|
+
.map((key) => ALL_COLUMNS[key])
|
|
171
|
+
.filter((col) => col !== undefined);
|
|
172
|
+
return renderFixedWidthTable(issues, columns);
|
|
173
|
+
}
|
|
174
|
+
export function formatCsv(issues, fieldNames) {
|
|
175
|
+
const columnKeys = fieldNames ?? DEFAULT_COLUMNS;
|
|
176
|
+
const columns = columnKeys
|
|
177
|
+
.map((key) => ALL_COLUMNS[key])
|
|
178
|
+
.filter((col) => col !== undefined);
|
|
179
|
+
return renderCsv(issues, columns);
|
|
165
180
|
}
|
|
166
181
|
// ── Markdown formatting ────────────────────────────────────────
|
|
167
182
|
const STATUS_MD = {
|
|
@@ -234,16 +249,23 @@ const MD_COLUMNS = {
|
|
|
234
249
|
function escapeMarkdownCell(str) {
|
|
235
250
|
return str.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
236
251
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Render `rows` as a markdown table using `columns`. Generic over
|
|
254
|
+
* the row type. Escapes `|` and newlines in cell values.
|
|
255
|
+
*/
|
|
256
|
+
export function renderMarkdownTable(rows, columns) {
|
|
242
257
|
if (columns.length === 0) {
|
|
243
258
|
return "No valid columns specified.";
|
|
244
259
|
}
|
|
245
260
|
const header = `| ${columns.map((col) => col.header).join(" | ")} |`;
|
|
246
261
|
const divider = `| ${columns.map((col) => (col.align === "right" ? "---:" : "---")).join(" | ")} |`;
|
|
247
|
-
const
|
|
248
|
-
return [header, divider, ...
|
|
262
|
+
const body = rows.map((row) => `| ${columns.map((col) => escapeMarkdownCell(col.extract(row))).join(" | ")} |`);
|
|
263
|
+
return [header, divider, ...body].join("\n");
|
|
264
|
+
}
|
|
265
|
+
export function formatMarkdown(issues, fieldNames) {
|
|
266
|
+
const columnKeys = fieldNames ?? DEFAULT_COLUMNS;
|
|
267
|
+
const columns = columnKeys
|
|
268
|
+
.map((key) => MD_COLUMNS[key])
|
|
269
|
+
.filter((col) => col !== undefined);
|
|
270
|
+
return renderMarkdownTable(issues, columns);
|
|
249
271
|
}
|
|
@@ -9,6 +9,14 @@ export declare function parsePositiveInt(value: string, flagName: string): numbe
|
|
|
9
9
|
export declare function validatePriority(value: string): number;
|
|
10
10
|
export declare function validateHexColor(value: string): string;
|
|
11
11
|
export declare function validateIsoDate(value: string): string;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Split a comma-separated list into trimmed, non-empty items.
|
|
14
|
+
*
|
|
15
|
+
* Accepts `undefined` so call sites can pipe through commander's
|
|
16
|
+
* optional flag values without an explicit truthy guard. `false`
|
|
17
|
+
* (commander's representation of `--no-foo`) and empty string both
|
|
18
|
+
* resolve to `[]`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function splitList(value: string | undefined | null | false): string[];
|
|
13
21
|
export declare function parsePriorityFilter(value: string): number[];
|
|
14
22
|
export declare const PRIORITY_LABELS: Record<number, string>;
|
package/dist/utils/validators.js
CHANGED
|
@@ -42,7 +42,17 @@ export function validateIsoDate(value) {
|
|
|
42
42
|
}
|
|
43
43
|
return value;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Split a comma-separated list into trimmed, non-empty items.
|
|
47
|
+
*
|
|
48
|
+
* Accepts `undefined` so call sites can pipe through commander's
|
|
49
|
+
* optional flag values without an explicit truthy guard. `false`
|
|
50
|
+
* (commander's representation of `--no-foo`) and empty string both
|
|
51
|
+
* resolve to `[]`.
|
|
52
|
+
*/
|
|
45
53
|
export function splitList(value) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return [];
|
|
46
56
|
return value
|
|
47
57
|
.split(",")
|
|
48
58
|
.map((s) => s.trim())
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { GraphQLService } from "./graphql-service.js";
|
|
2
|
-
export
|
|
2
|
+
export interface GetWorkspaceUrlKeyOptions {
|
|
3
|
+
/** Highest-priority source. Typically the `--workspace-url-key` flag. */
|
|
4
|
+
override?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getWorkspaceUrlKey(graphQLService?: GraphQLService, options?: GetWorkspaceUrlKeyOptions): Promise<string>;
|
|
3
7
|
/** Test helper: clear the cached URL key. Not called from production code. */
|
|
4
8
|
export declare function _resetWorkspaceUrlKeyCache(): void;
|
|
@@ -4,11 +4,20 @@ import { loadConfig } from "../config/config.js";
|
|
|
4
4
|
* issue URLs). Used to build canonical markdown link URLs like
|
|
5
5
|
* `https://linear.app/<urlKey>/issue/<identifier>/`.
|
|
6
6
|
*
|
|
7
|
-
* Resolution order:
|
|
8
|
-
* 1. `config.workspaceUrlKey` if explicitly set in el-linear config
|
|
9
|
-
* 2. `viewer.organization.urlKey` from the Linear API (fetched once, cached)
|
|
7
|
+
* Resolution order (highest priority first):
|
|
10
8
|
*
|
|
11
|
-
*
|
|
9
|
+
* 1. `options.override` — typically wired from a `--workspace-url-key` CLI flag
|
|
10
|
+
* (per-invocation, wins over everything).
|
|
11
|
+
* 2. `EL_LINEAR_WORKSPACE_URL_KEY` env var.
|
|
12
|
+
* 3. `config.workspaceUrlKey` from `~/.config/el-linear/config.json`.
|
|
13
|
+
* 4. Live `viewer.organization.urlKey` GraphQL query (cached in-process).
|
|
14
|
+
*
|
|
15
|
+
* Layers 1–3 short-circuit without touching the network — this makes
|
|
16
|
+
* `el-linear refs wrap --no-validate` work offline once any one of them
|
|
17
|
+
* is set. Layer 4 is the original behavior preserved as a fallback.
|
|
18
|
+
*
|
|
19
|
+
* The graphQLService is only needed for layer 4 — pass `undefined` (or omit it)
|
|
20
|
+
* when the caller knows the network will not be used.
|
|
12
21
|
*/
|
|
13
22
|
const VIEWER_ORG_URL_KEY_QUERY = /* GraphQL */ `
|
|
14
23
|
query ViewerOrgUrlKey {
|
|
@@ -19,16 +28,37 @@ const VIEWER_ORG_URL_KEY_QUERY = /* GraphQL */ `
|
|
|
19
28
|
}
|
|
20
29
|
}
|
|
21
30
|
`;
|
|
31
|
+
const WORKSPACE_URL_KEY_ENV = "EL_LINEAR_WORKSPACE_URL_KEY";
|
|
22
32
|
let cachedUrlKey;
|
|
23
|
-
export async function getWorkspaceUrlKey(graphQLService) {
|
|
33
|
+
export async function getWorkspaceUrlKey(graphQLService, options = {}) {
|
|
34
|
+
// 1. Per-invocation override — highest priority.
|
|
35
|
+
if (options.override) {
|
|
36
|
+
return options.override;
|
|
37
|
+
}
|
|
38
|
+
// 2. Env var. Read on every call (cheap; lets tests/CI flip the value
|
|
39
|
+
// mid-process without restarting). Not cached, so a test that sets
|
|
40
|
+
// EL_LINEAR_WORKSPACE_URL_KEY then unsets it sees the unset state.
|
|
41
|
+
const envKey = process.env[WORKSPACE_URL_KEY_ENV];
|
|
42
|
+
if (envKey) {
|
|
43
|
+
return envKey;
|
|
44
|
+
}
|
|
45
|
+
// Layers 3-4 are cached in-process — config and the live lookup are
|
|
46
|
+
// both stable for the CLI invocation's lifetime.
|
|
24
47
|
if (cachedUrlKey) {
|
|
25
48
|
return cachedUrlKey;
|
|
26
49
|
}
|
|
50
|
+
// 3. Config override.
|
|
27
51
|
const { workspaceUrlKey } = loadConfig();
|
|
28
52
|
if (workspaceUrlKey) {
|
|
29
53
|
cachedUrlKey = workspaceUrlKey;
|
|
30
54
|
return workspaceUrlKey;
|
|
31
55
|
}
|
|
56
|
+
// 4. Live API lookup — requires a GraphQL service.
|
|
57
|
+
if (!graphQLService) {
|
|
58
|
+
throw new Error("Could not resolve Linear workspace URL key — no override, env, or config set, " +
|
|
59
|
+
"and no GraphQL service was provided for the live lookup. " +
|
|
60
|
+
`Set \`workspaceUrlKey\` in your el-linear config, or \`${WORKSPACE_URL_KEY_ENV}\` in the environment.`);
|
|
61
|
+
}
|
|
32
62
|
const data = await graphQLService.rawRequest(VIEWER_ORG_URL_KEY_QUERY);
|
|
33
63
|
const fetched = data?.viewer?.organization?.urlKey;
|
|
34
64
|
if (!fetched) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enrichlayer/el-linear",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "A pragmatic CLI for Linear.app — deterministic team/label/member resolution, structured issue validation, configurable term enforcement, and a GraphQL escape hatch.",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"types": "dist/main.d.ts",
|