@enrichlayer/el-linear 1.28.0 → 1.29.1
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/batch.js +2 -2
- package/dist/commands/issues/tree.js +7 -6
- package/dist/commands/issues.js +9 -6
- package/dist/config/registry-resolve.d.ts +23 -0
- package/dist/config/registry-resolve.js +59 -0
- package/dist/config/resolver.d.ts +11 -0
- package/dist/config/resolver.js +20 -0
- package/dist/types/linear.d.ts +11 -0
- package/dist/types/linear.js +15 -1
- package/dist/utils/format-tree.js +2 -1
- package/dist/utils/graphql-issues-service.d.ts +2 -1
- package/dist/utils/graphql-issues-service.js +21 -2
- package/package.json +1 -1
package/dist/commands/batch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveAssignee, resolveLabels,
|
|
1
|
+
import { resolveAssignee, resolveLabels, resolveMemberWithRegistry, resolveTeam, } from "../config/resolver.js";
|
|
2
2
|
import { createIssuesService } from "../utils/issues-service-bootstrap.js";
|
|
3
3
|
import { logger } from "../utils/logger.js";
|
|
4
4
|
import { handleAsyncCommand, outputSuccess, outputWarning, } from "../utils/output.js";
|
|
@@ -45,7 +45,7 @@ async function resolveTargetIssues(options, rootOpts) {
|
|
|
45
45
|
? await resolveAssignee(filters.assignee, rootOpts)
|
|
46
46
|
: undefined,
|
|
47
47
|
delegateId: filters.delegate
|
|
48
|
-
?
|
|
48
|
+
? await resolveMemberWithRegistry(filters.delegate)
|
|
49
49
|
: undefined,
|
|
50
50
|
project: filters.project
|
|
51
51
|
? { kind: "id", id: filters.project }
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* connection has no top-level state filter).
|
|
19
19
|
*/
|
|
20
20
|
import { buildIssueTreeQuery, DEFAULT_TREE_DEPTH, MAX_TREE_DEPTH, } from "../../queries/issue-tree.js";
|
|
21
|
+
import { TERMINAL_STATE_TYPES } from "../../types/linear.js";
|
|
21
22
|
import { formatTree } from "../../utils/format-tree.js";
|
|
22
23
|
import { createIssuesService } from "../../utils/issues-service-bootstrap.js";
|
|
23
24
|
import { handleAsyncCommand, outputSuccess } from "../../utils/output.js";
|
|
@@ -74,10 +75,11 @@ function parseDepth(raw) {
|
|
|
74
75
|
return n;
|
|
75
76
|
}
|
|
76
77
|
/**
|
|
77
|
-
* Walk the tree depth-first and drop any node whose `state.type` is
|
|
78
|
-
* `completed`
|
|
79
|
-
* A pruned child takes its entire
|
|
80
|
-
* `issues list --no-include-closed`
|
|
78
|
+
* Walk the tree depth-first and drop any node whose `state.type` is terminal
|
|
79
|
+
* (`completed` / `canceled` / `duplicate` — `TERMINAL_STATE_TYPES`, DEV-4879).
|
|
80
|
+
* Pure function — does not mutate the input. A pruned child takes its entire
|
|
81
|
+
* subtree with it (consistent with how `issues list --no-include-closed`
|
|
82
|
+
* excludes closed work entirely — same shared constant).
|
|
81
83
|
*
|
|
82
84
|
* Assumes Linear's parent → children graph stays single-parent (a tree,
|
|
83
85
|
* not a DAG). If Linear ever ships multi-parent issues, this recursion
|
|
@@ -97,6 +99,5 @@ function pruneTerminalStates(root) {
|
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
function isTerminalState(node) {
|
|
100
|
-
|
|
101
|
-
return t === "completed" || t === "canceled";
|
|
102
|
+
return TERMINAL_STATE_TYPES.includes(node.state?.type ?? "");
|
|
102
103
|
}
|
package/dist/commands/issues.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
|
|
|
2
2
|
import { loadConfig } from "../config/config.js";
|
|
3
3
|
import { enrichProjectResolverError, enrichValidationErrors, } from "../config/error-enrichment.js";
|
|
4
4
|
import { enforceValidation, validateIssueCreation, } from "../config/issue-validation.js";
|
|
5
|
-
import { resolveAssignee, resolveLabels,
|
|
5
|
+
import { resolveAssignee, resolveLabels, resolveMemberWithRegistry, resolveTeam, } from "../config/resolver.js";
|
|
6
6
|
import { resolveDefaultStatus } from "../config/status-defaults.js";
|
|
7
7
|
import { enforceTerms } from "../config/term-enforcer.js";
|
|
8
8
|
import { GET_ISSUE_RELATIONS_QUERY, GET_ISSUE_STATE_HISTORY_QUERY, } from "../queries/issues.js";
|
|
@@ -210,7 +210,7 @@ async function handleListIssues(options, command) {
|
|
|
210
210
|
? await resolveAssignee(options.assignee, rootOpts)
|
|
211
211
|
: undefined,
|
|
212
212
|
delegateId: options.delegate
|
|
213
|
-
?
|
|
213
|
+
? await resolveMemberWithRegistry(options.delegate)
|
|
214
214
|
: undefined,
|
|
215
215
|
project: resolveProjectFlag(options.project),
|
|
216
216
|
labelNames: options.labels ? splitList(options.labels) : undefined,
|
|
@@ -256,7 +256,7 @@ async function handleSearchIssues(query, options, command) {
|
|
|
256
256
|
? await resolveAssignee(options.assignee, rootOpts)
|
|
257
257
|
: undefined,
|
|
258
258
|
delegateId: options.delegate
|
|
259
|
-
?
|
|
259
|
+
? await resolveMemberWithRegistry(options.delegate)
|
|
260
260
|
: undefined,
|
|
261
261
|
project: resolveProjectFlag(options.project),
|
|
262
262
|
status: explicitStatus,
|
|
@@ -384,7 +384,7 @@ async function resolveCreateInputs(title, options, rootOpts) {
|
|
|
384
384
|
? await resolveAssignee(effectiveAssignee, rootOpts)
|
|
385
385
|
: undefined;
|
|
386
386
|
const delegateId = options.delegate
|
|
387
|
-
?
|
|
387
|
+
? await resolveMemberWithRegistry(options.delegate)
|
|
388
388
|
: undefined;
|
|
389
389
|
let labelIds = [];
|
|
390
390
|
if (options.labels) {
|
|
@@ -407,7 +407,10 @@ async function resolveCreateInputs(title, options, rootOpts) {
|
|
|
407
407
|
});
|
|
408
408
|
let subscriberIds;
|
|
409
409
|
if (options.subscriber) {
|
|
410
|
-
|
|
410
|
+
// --subscriber resolves through the opt-in registry too (DEV-4880),
|
|
411
|
+
// matching --assignee / --delegate. Each entry falls back to config on a
|
|
412
|
+
// miss; resolveMemberWithRegistry is fail-open and dormant when unconfigured.
|
|
413
|
+
subscriberIds = await Promise.all(splitList(options.subscriber).map((s) => resolveMemberWithRegistry(s)));
|
|
411
414
|
}
|
|
412
415
|
const priority = effectivePriorityInput
|
|
413
416
|
? validatePriority(effectivePriorityInput)
|
|
@@ -843,7 +846,7 @@ async function handleUpdateIssue(issueId, options, command) {
|
|
|
843
846
|
const delegateId = options.clearDelegate
|
|
844
847
|
? null
|
|
845
848
|
: options.delegate
|
|
846
|
-
?
|
|
849
|
+
? await resolveMemberWithRegistry(options.delegate)
|
|
847
850
|
: undefined;
|
|
848
851
|
const updateArgs = buildUpdateArgs(issueId, options, assigneeId, delegateId);
|
|
849
852
|
const result = await withProjectResolverEnrichment(() => issuesService.updateIssue(updateArgs, options.labelBy || "adding"), {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional, opt-in resolution against the company-wide identity registry
|
|
3
|
+
* (DEV-4827 / DEV-4871). EL-only: this activates **solely** when
|
|
4
|
+
* `EL_IDENTITY_URL` is set. el-linear is MIT/open-source — a non-EL install
|
|
5
|
+
* leaves this dormant (no network, no config, nothing written), and the package
|
|
6
|
+
* never takes a dependency on EL-internal infrastructure. It mirrors the
|
|
7
|
+
* env-gated CF-Access pattern of the tools `@enrichlayer/el-identity` client but
|
|
8
|
+
* is kept self-contained so the OSS package carries no private dependency.
|
|
9
|
+
*
|
|
10
|
+
* The registry is an *enhancement* for EL users: callers try it first, then fall
|
|
11
|
+
* back to the bundled config (`resolveMember`). It must never throw or break a
|
|
12
|
+
* command because the registry is unreachable.
|
|
13
|
+
*/
|
|
14
|
+
/** True only when the registry is explicitly configured (opt-in). */
|
|
15
|
+
export declare function isRegistryConfigured(env?: NodeJS.ProcessEnv): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve any identifier (alias, handle, email, name, Linear UUID) to a Linear
|
|
18
|
+
* UUID via the registry's `GET /api/people/resolve`. Returns `null` when the
|
|
19
|
+
* registry is not configured, when nothing matches, or on **any** failure
|
|
20
|
+
* (unreachable / timeout / CF-Access challenge / malformed response) — the
|
|
21
|
+
* caller falls back to the config-based resolver. Never throws.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveViaRegistry(identifier: string, env?: NodeJS.ProcessEnv, fetchImpl?: typeof fetch): Promise<string | null>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional, opt-in resolution against the company-wide identity registry
|
|
3
|
+
* (DEV-4827 / DEV-4871). EL-only: this activates **solely** when
|
|
4
|
+
* `EL_IDENTITY_URL` is set. el-linear is MIT/open-source — a non-EL install
|
|
5
|
+
* leaves this dormant (no network, no config, nothing written), and the package
|
|
6
|
+
* never takes a dependency on EL-internal infrastructure. It mirrors the
|
|
7
|
+
* env-gated CF-Access pattern of the tools `@enrichlayer/el-identity` client but
|
|
8
|
+
* is kept self-contained so the OSS package carries no private dependency.
|
|
9
|
+
*
|
|
10
|
+
* The registry is an *enhancement* for EL users: callers try it first, then fall
|
|
11
|
+
* back to the bundled config (`resolveMember`). It must never throw or break a
|
|
12
|
+
* command because the registry is unreachable.
|
|
13
|
+
*/
|
|
14
|
+
const URL_ENV = "EL_IDENTITY_URL";
|
|
15
|
+
const CF_ID_ENV = "EL_IDENTITY_CF_ACCESS_CLIENT_ID";
|
|
16
|
+
const CF_SECRET_ENV = "EL_IDENTITY_CF_ACCESS_CLIENT_SECRET";
|
|
17
|
+
const TIMEOUT_MS = 8000;
|
|
18
|
+
/** True only when the registry is explicitly configured (opt-in). */
|
|
19
|
+
export function isRegistryConfigured(env = process.env) {
|
|
20
|
+
return Boolean(env[URL_ENV]?.trim());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve any identifier (alias, handle, email, name, Linear UUID) to a Linear
|
|
24
|
+
* UUID via the registry's `GET /api/people/resolve`. Returns `null` when the
|
|
25
|
+
* registry is not configured, when nothing matches, or on **any** failure
|
|
26
|
+
* (unreachable / timeout / CF-Access challenge / malformed response) — the
|
|
27
|
+
* caller falls back to the config-based resolver. Never throws.
|
|
28
|
+
*/
|
|
29
|
+
export async function resolveViaRegistry(identifier, env = process.env, fetchImpl = fetch) {
|
|
30
|
+
const base = env[URL_ENV]?.trim();
|
|
31
|
+
if (!base) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const headers = { Accept: "application/json" };
|
|
35
|
+
const cfId = env[CF_ID_ENV];
|
|
36
|
+
const cfSecret = env[CF_SECRET_ENV];
|
|
37
|
+
if (cfId && cfSecret) {
|
|
38
|
+
headers["CF-Access-Client-Id"] = cfId;
|
|
39
|
+
headers["CF-Access-Client-Secret"] = cfSecret;
|
|
40
|
+
}
|
|
41
|
+
const url = `${base.replace(/\/+$/, "")}/api/people/resolve?identifier=${encodeURIComponent(identifier)}`;
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetchImpl(url, {
|
|
44
|
+
headers,
|
|
45
|
+
// A 3xx is a Cloudflare Access SSO bounce, not a result — `redirect:
|
|
46
|
+
// "manual"` surfaces it as a non-ok status we treat as a miss.
|
|
47
|
+
redirect: "manual",
|
|
48
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const record = (await res.json());
|
|
54
|
+
return record?.linearId ?? null;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -13,6 +13,17 @@ export declare function resolveMember(input: string): string;
|
|
|
13
13
|
* Falls back to resolveMember for all other inputs.
|
|
14
14
|
*/
|
|
15
15
|
export declare function resolveAssignee(input: string, rootOpts: Record<string, unknown>): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a member identifier (alias / handle / name) to a UUID, consulting the
|
|
18
|
+
* opt-in identity registry first when configured, then the bundled config
|
|
19
|
+
* (`resolveMember`). The shared async resolution path behind `--assignee` and
|
|
20
|
+
* `--delegate` (DEV-4871 / DEV-4872).
|
|
21
|
+
*
|
|
22
|
+
* EL-only and env-gated on `EL_IDENTITY_URL`; fail-open — when unconfigured, on
|
|
23
|
+
* a miss, or on any failure it falls back to config, so a non-EL install and an
|
|
24
|
+
* unreachable registry both behave exactly as before. Never throws.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveMemberWithRegistry(input: string): Promise<string>;
|
|
16
27
|
/**
|
|
17
28
|
* Resolve a user's display name from their UUID via config fullNames map.
|
|
18
29
|
* Returns the full name if found, otherwise returns the original name.
|
package/dist/config/resolver.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createGraphQLService } from "../utils/graphql-service.js";
|
|
|
2
2
|
import { outputWarning } from "../utils/output.js";
|
|
3
3
|
import { isUuid, isUuidPrefix } from "../utils/uuid.js";
|
|
4
4
|
import { loadConfig } from "./config.js";
|
|
5
|
+
import { isRegistryConfigured, resolveViaRegistry, } from "./registry-resolve.js";
|
|
5
6
|
/**
|
|
6
7
|
* Resolve a team key/name/alias to its UUID.
|
|
7
8
|
* Case-insensitive: "fe" → FE UUID, "frontend" → FE UUID via alias.
|
|
@@ -98,6 +99,25 @@ export async function resolveAssignee(input, rootOpts) {
|
|
|
98
99
|
}
|
|
99
100
|
return result.viewer.id;
|
|
100
101
|
}
|
|
102
|
+
return resolveMemberWithRegistry(input);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a member identifier (alias / handle / name) to a UUID, consulting the
|
|
106
|
+
* opt-in identity registry first when configured, then the bundled config
|
|
107
|
+
* (`resolveMember`). The shared async resolution path behind `--assignee` and
|
|
108
|
+
* `--delegate` (DEV-4871 / DEV-4872).
|
|
109
|
+
*
|
|
110
|
+
* EL-only and env-gated on `EL_IDENTITY_URL`; fail-open — when unconfigured, on
|
|
111
|
+
* a miss, or on any failure it falls back to config, so a non-EL install and an
|
|
112
|
+
* unreachable registry both behave exactly as before. Never throws.
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveMemberWithRegistry(input) {
|
|
115
|
+
if (isRegistryConfigured()) {
|
|
116
|
+
const viaRegistry = await resolveViaRegistry(input);
|
|
117
|
+
if (viaRegistry) {
|
|
118
|
+
return viaRegistry;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
101
121
|
return resolveMember(input);
|
|
102
122
|
}
|
|
103
123
|
/**
|
package/dist/types/linear.d.ts
CHANGED
|
@@ -35,6 +35,17 @@ interface StateRef {
|
|
|
35
35
|
*/
|
|
36
36
|
type?: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Workflow-state types treated as terminal / closed for default filtering.
|
|
40
|
+
* `issues list` / `issues search` exclude these unless `--include-closed` (or
|
|
41
|
+
* an explicit `--status`) is passed (DEV-4478).
|
|
42
|
+
*
|
|
43
|
+
* `duplicate` is included (DEV-4879) because this workspace parks resolved
|
|
44
|
+
* duplicates in a `duplicate`-typed state — they carry a `duplicate-of`
|
|
45
|
+
* relation and are semantically closed, so they must not leak into the default
|
|
46
|
+
* open-issues view the way `completed` / `canceled` don't.
|
|
47
|
+
*/
|
|
48
|
+
export declare const TERMINAL_STATE_TYPES: readonly string[];
|
|
38
49
|
interface ProjectRef {
|
|
39
50
|
id: string;
|
|
40
51
|
name: string;
|
package/dist/types/linear.js
CHANGED
|
@@ -3,4 +3,18 @@
|
|
|
3
3
|
* These represent the public API surface — GraphQL responses are
|
|
4
4
|
* transformed into these shapes at the service boundary.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Workflow-state types treated as terminal / closed for default filtering.
|
|
8
|
+
* `issues list` / `issues search` exclude these unless `--include-closed` (or
|
|
9
|
+
* an explicit `--status`) is passed (DEV-4478).
|
|
10
|
+
*
|
|
11
|
+
* `duplicate` is included (DEV-4879) because this workspace parks resolved
|
|
12
|
+
* duplicates in a `duplicate`-typed state — they carry a `duplicate-of`
|
|
13
|
+
* relation and are semantically closed, so they must not leak into the default
|
|
14
|
+
* open-issues view the way `completed` / `canceled` don't.
|
|
15
|
+
*/
|
|
16
|
+
export const TERMINAL_STATE_TYPES = [
|
|
17
|
+
"completed",
|
|
18
|
+
"canceled",
|
|
19
|
+
"duplicate",
|
|
20
|
+
];
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Pure rendering — does not fetch or filter. The depth bound and the
|
|
11
11
|
* terminal-state exclusion are enforced by the caller before this runs.
|
|
12
12
|
*/
|
|
13
|
+
import { TERMINAL_STATE_TYPES } from "../types/linear.js";
|
|
13
14
|
const BRANCH_TEE = "├── ";
|
|
14
15
|
const BRANCH_END = "└── ";
|
|
15
16
|
const VERTICAL = "│ ";
|
|
@@ -40,7 +41,7 @@ function formatStateSuffix(state) {
|
|
|
40
41
|
// Only annotate terminal-typed states; the renderer is otherwise
|
|
41
42
|
// state-name-agnostic so workspace-custom workflow states (e.g. "In
|
|
42
43
|
// Review") don't get a noisy suffix.
|
|
43
|
-
if (
|
|
44
|
+
if (TERMINAL_STATE_TYPES.includes(state.type)) {
|
|
44
45
|
return ` [${state.name}]`;
|
|
45
46
|
}
|
|
46
47
|
return "";
|
|
@@ -42,7 +42,8 @@ export interface SearchIssueArgs {
|
|
|
42
42
|
/** Issue states to include (e.g. `["Todo", "In Progress"]`). */
|
|
43
43
|
status?: string[];
|
|
44
44
|
/**
|
|
45
|
-
* Exclude terminal states (workflow type `completed` or
|
|
45
|
+
* Exclude terminal states (workflow type `completed`, `canceled`, or
|
|
46
|
+
* `duplicate` — see `TERMINAL_STATE_TYPES`).
|
|
46
47
|
* Default `false` for back-compat at the service layer; the CLI's
|
|
47
48
|
* `issues list` / `issues search` flip this to `true` by default
|
|
48
49
|
* (DEV-4478) and accept `--include-closed` to opt back in. When
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { isRegistryConfigured, resolveViaRegistry, } from "../config/registry-resolve.js";
|
|
1
2
|
import { resolveUserDisplayName } from "../config/resolver.js";
|
|
2
3
|
import { ARCHIVE_ISSUE_MUTATION, BATCH_GET_ISSUES_QUERY, BATCH_RESOLVE_FOR_CREATE_QUERY, BATCH_RESOLVE_FOR_SEARCH_QUERY, BATCH_RESOLVE_FOR_UPDATE_QUERY, buildResolveLabelsByNameQuery, CREATE_ISSUE_MUTATION, DELETE_ISSUE_MUTATION, FILTERED_SEARCH_ISSUES_QUERY, GET_ISSUE_BY_ID_QUERY, GET_ISSUE_BY_IDENTIFIER_QUERY, GET_ISSUE_CLAIM_CONTEXT_QUERY, GET_ISSUE_START_CONTEXT_QUERY, GET_ISSUE_TEAM_QUERY, GET_ISSUES_QUERY, SEARCH_ISSUES_QUERY, TEAM_STARTED_STATUSES_QUERY, UPDATE_ISSUE_MUTATION, } from "../queries/issues.js";
|
|
3
4
|
import { CREATE_LABEL_MUTATION } from "../queries/labels.js";
|
|
5
|
+
import { TERMINAL_STATE_TYPES } from "../types/linear.js";
|
|
4
6
|
import { toISOStringOrNow } from "./date-format.js";
|
|
5
7
|
import { extractEmbeds } from "./embed-parser.js";
|
|
6
8
|
import { multipleMatchesError, notFoundError } from "./error-messages.js";
|
|
@@ -980,7 +982,7 @@ export class GraphQLIssuesService {
|
|
|
980
982
|
else if (filters.excludeTerminalStates) {
|
|
981
983
|
// Implicit terminal-state exclusion (DEV-4478). Explicit `status`
|
|
982
984
|
// always wins — the user already named the states they want.
|
|
983
|
-
filtered = filtered.filter((issue) =>
|
|
985
|
+
filtered = filtered.filter((issue) => !TERMINAL_STATE_TYPES.includes(issue.state?.type ?? ""));
|
|
984
986
|
}
|
|
985
987
|
if (filters.labelNames && filters.labelNames.length > 0) {
|
|
986
988
|
const lowerNames = filters.labelNames.map((n) => n.toLowerCase());
|
|
@@ -1023,7 +1025,7 @@ export class GraphQLIssuesService {
|
|
|
1023
1025
|
else if (filters.excludeTerminalStates) {
|
|
1024
1026
|
// Implicit terminal-state exclusion (DEV-4478). Explicit `status`
|
|
1025
1027
|
// always wins — the user already named the states they want.
|
|
1026
|
-
filter.state = { type: { nin: [
|
|
1028
|
+
filter.state = { type: { nin: [...TERMINAL_STATE_TYPES] } };
|
|
1027
1029
|
}
|
|
1028
1030
|
if (filters.labelNames && filters.labelNames.length > 0) {
|
|
1029
1031
|
if (filters.labelNames.length === 1) {
|
|
@@ -1048,6 +1050,15 @@ export class GraphQLIssuesService {
|
|
|
1048
1050
|
if (!assigneeId || isUuid(assigneeId)) {
|
|
1049
1051
|
return assigneeId;
|
|
1050
1052
|
}
|
|
1053
|
+
// Opt-in registry resolution (DEV-4872): try the identity registry first,
|
|
1054
|
+
// falling back to the Linear-API user lookup below on a miss. EL-only,
|
|
1055
|
+
// env-gated, fail-open — a non-EL install never reaches the network here.
|
|
1056
|
+
if (isRegistryConfigured()) {
|
|
1057
|
+
const viaRegistry = await resolveViaRegistry(assigneeId);
|
|
1058
|
+
if (viaRegistry) {
|
|
1059
|
+
return viaRegistry;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1051
1062
|
// A plain name (no `@`) resolves via the user lookup — same as
|
|
1052
1063
|
// `resolveDelegateId`. Without this, a non-config full name like
|
|
1053
1064
|
// "Yury Tsukerman" fell through unchanged and was sent to the
|
|
@@ -1068,6 +1079,14 @@ export class GraphQLIssuesService {
|
|
|
1068
1079
|
if (!delegateId || isUuid(delegateId)) {
|
|
1069
1080
|
return delegateId;
|
|
1070
1081
|
}
|
|
1082
|
+
// Opt-in registry resolution (DEV-4872): registry-first, Linear-API
|
|
1083
|
+
// fallback. EL-only, env-gated, fail-open.
|
|
1084
|
+
if (isRegistryConfigured()) {
|
|
1085
|
+
const viaRegistry = await resolveViaRegistry(delegateId);
|
|
1086
|
+
if (viaRegistry) {
|
|
1087
|
+
return viaRegistry;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1071
1090
|
if (!delegateId.includes("@")) {
|
|
1072
1091
|
return this.linearService.resolveUserId(delegateId);
|
|
1073
1092
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enrichlayer/el-linear",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.1",
|
|
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",
|