linmux 0.1.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/LICENSE +21 -0
- package/README.md +240 -0
- package/bin/run.js +4 -0
- package/dist/commands/comment/create.js +94 -0
- package/dist/commands/comment/delete.js +74 -0
- package/dist/commands/comment/list.js +84 -0
- package/dist/commands/comment/update.js +80 -0
- package/dist/commands/cycle/current.js +78 -0
- package/dist/commands/cycle/list.js +84 -0
- package/dist/commands/cycle/move.js +91 -0
- package/dist/commands/describe.js +65 -0
- package/dist/commands/graphql/index.js +92 -0
- package/dist/commands/install-skill.js +54 -0
- package/dist/commands/issue/archive.js +75 -0
- package/dist/commands/issue/create.js +115 -0
- package/dist/commands/issue/get.js +84 -0
- package/dist/commands/issue/list.js +93 -0
- package/dist/commands/issue/purge.js +81 -0
- package/dist/commands/issue/search.js +109 -0
- package/dist/commands/issue/transition.js +91 -0
- package/dist/commands/issue/trash.js +75 -0
- package/dist/commands/issue/update.js +126 -0
- package/dist/commands/label/create.js +91 -0
- package/dist/commands/label/list.js +76 -0
- package/dist/commands/list-tools.js +47 -0
- package/dist/commands/me.js +71 -0
- package/dist/commands/project/create.js +101 -0
- package/dist/commands/project/get.js +83 -0
- package/dist/commands/project/list.js +75 -0
- package/dist/commands/project/update-status.js +99 -0
- package/dist/commands/project/update.js +99 -0
- package/dist/commands/raw/batch.js +85 -0
- package/dist/commands/raw/index.js +72 -0
- package/dist/commands/schema.js +69 -0
- package/dist/commands/state/list.js +77 -0
- package/dist/commands/team/get.js +73 -0
- package/dist/commands/team/list.js +73 -0
- package/dist/commands/whoami.js +71 -0
- package/dist/commands/workspace/add.js +97 -0
- package/dist/commands/workspace/list.js +47 -0
- package/dist/commands/workspace/remove.js +63 -0
- package/dist/commands/workspace/replace-token.js +89 -0
- package/dist/commands/workspace/use.js +54 -0
- package/dist/core/client/factory.js +28 -0
- package/dist/core/client/index.js +2 -0
- package/dist/core/config/index.js +4 -0
- package/dist/core/config/paths.js +30 -0
- package/dist/core/config/schema.js +36 -0
- package/dist/core/config/store.js +149 -0
- package/dist/core/errors/error.js +142 -0
- package/dist/core/errors/exit-codes.js +70 -0
- package/dist/core/output/envelope.js +53 -0
- package/dist/core/output/format.js +42 -0
- package/dist/core/output/index.js +3 -0
- package/dist/core/pagination/flags.js +29 -0
- package/dist/core/pagination/index.js +2 -0
- package/dist/core/projection/presets.js +116 -0
- package/dist/core/projection/project.js +282 -0
- package/dist/core/redact/redact.js +45 -0
- package/dist/core/resolvers/cycle.js +60 -0
- package/dist/core/resolvers/index.js +7 -0
- package/dist/core/resolvers/label.js +54 -0
- package/dist/core/resolvers/project-status.js +42 -0
- package/dist/core/resolvers/project.js +43 -0
- package/dist/core/resolvers/state.js +46 -0
- package/dist/core/resolvers/team.js +50 -0
- package/dist/core/transport/fetch-interceptor.js +109 -0
- package/dist/core/transport/index.js +3 -0
- package/dist/core/transport/rate-limit.js +167 -0
- package/dist/core/workspace/resolver.js +70 -0
- package/dist/core/workspace/write-guard.js +43 -0
- package/dist/generated/graphql.js +89428 -0
- package/dist/generated/operations.js +3013 -0
- package/dist/lib/comment-create-runtime.js +96 -0
- package/dist/lib/comment-delete-runtime.js +46 -0
- package/dist/lib/comment-list-runtime.js +182 -0
- package/dist/lib/comment-update-runtime.js +93 -0
- package/dist/lib/cycle-current-runtime.js +90 -0
- package/dist/lib/cycle-list-runtime.js +151 -0
- package/dist/lib/cycle-move-runtime.js +142 -0
- package/dist/lib/describe-runtime.js +180 -0
- package/dist/lib/filter-heuristics.js +59 -0
- package/dist/lib/graphql-runtime.js +202 -0
- package/dist/lib/include-fragments.js +73 -0
- package/dist/lib/install-skill-runtime.js +228 -0
- package/dist/lib/introspection-registry.js +488 -0
- package/dist/lib/issue-archive-runtime.js +89 -0
- package/dist/lib/issue-create-runtime.js +175 -0
- package/dist/lib/issue-get-runtime.js +153 -0
- package/dist/lib/issue-list-runtime.js +164 -0
- package/dist/lib/issue-purge-runtime.js +89 -0
- package/dist/lib/issue-search-runtime.js +114 -0
- package/dist/lib/issue-transition-runtime.js +131 -0
- package/dist/lib/issue-trash-runtime.js +84 -0
- package/dist/lib/issue-update-runtime.js +164 -0
- package/dist/lib/label-create-runtime.js +113 -0
- package/dist/lib/label-list-runtime.js +97 -0
- package/dist/lib/levenshtein.js +42 -0
- package/dist/lib/list-tools-runtime.js +38 -0
- package/dist/lib/me-runtime.js +55 -0
- package/dist/lib/project-create-runtime.js +103 -0
- package/dist/lib/project-get-runtime.js +134 -0
- package/dist/lib/project-list-runtime.js +84 -0
- package/dist/lib/project-update-runtime.js +110 -0
- package/dist/lib/project-update-status-runtime.js +91 -0
- package/dist/lib/raw-batch-runtime.js +229 -0
- package/dist/lib/raw-runtime.js +171 -0
- package/dist/lib/schema-loader.js +41 -0
- package/dist/lib/schema-runtime.js +65 -0
- package/dist/lib/state-list-runtime.js +93 -0
- package/dist/lib/team-get-runtime.js +55 -0
- package/dist/lib/team-list-runtime.js +52 -0
- package/dist/lib/workspace-runtime.js +112 -0
- package/dist/operations/_registry.zod.js +5337 -0
- package/oclif.manifest.json +3631 -0
- package/package.json +99 -0
- package/schema.graphql +30772 -0
- package/skills/linmux/SKILL.md +186 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
3
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
4
|
+
import "../core/transport/index.js";
|
|
5
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
6
|
+
import "../core/client/index.js";
|
|
7
|
+
import { loadConfig } from "../core/config/store.js";
|
|
8
|
+
import "../core/config/index.js";
|
|
9
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
10
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
11
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
12
|
+
import { resolveProjectId } from "../core/resolvers/project.js";
|
|
13
|
+
import { resolveProjectStatusId } from "../core/resolvers/project-status.js";
|
|
14
|
+
import "../core/resolvers/index.js";
|
|
15
|
+
//#region src/lib/project-update-status-runtime.ts
|
|
16
|
+
async function projectUpdateStatusRuntime(input) {
|
|
17
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
18
|
+
const envForResolver = {};
|
|
19
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
20
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
21
|
+
const resolved = resolveWorkspace({
|
|
22
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
23
|
+
env: envForResolver,
|
|
24
|
+
config
|
|
25
|
+
});
|
|
26
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
27
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "project");
|
|
28
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
29
|
+
return withFetchInterception(async () => {
|
|
30
|
+
const workspaceKey = resolved.name ?? "_api-key-env_";
|
|
31
|
+
const [projectId, statusId] = await Promise.all([resolveProjectId(client, workspaceKey, input.args.ref, input.retryOptsOverride), resolveProjectStatusId(client, workspaceKey, input.args.status, input.retryOptsOverride)]);
|
|
32
|
+
const payload = await withRateLimitRetry(() => client.updateProject(projectId, { statusId }), input.retryOptsOverride);
|
|
33
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
34
|
+
message: "updateProject returned success=false",
|
|
35
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
36
|
+
});
|
|
37
|
+
let updated;
|
|
38
|
+
if (payload.project !== void 0) {
|
|
39
|
+
const u = await Promise.resolve(payload.project);
|
|
40
|
+
if (u !== void 0 && u !== null) updated = u;
|
|
41
|
+
}
|
|
42
|
+
let data;
|
|
43
|
+
if (updated) data = project(await hydrateForProjection(updated, fields), fields);
|
|
44
|
+
else data = {
|
|
45
|
+
id: projectId,
|
|
46
|
+
statusId
|
|
47
|
+
};
|
|
48
|
+
const complexity = getLastComplexity();
|
|
49
|
+
const meta = {
|
|
50
|
+
workspace: resolved.name,
|
|
51
|
+
workspaceSource: resolved.source,
|
|
52
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
data,
|
|
56
|
+
meta
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const RELATION_KEYS = new Set(["lead", "creator"]);
|
|
61
|
+
async function hydrateForProjection(proj, spec) {
|
|
62
|
+
const needs = neededRelations(spec);
|
|
63
|
+
if (needs.size === 0) {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const k of Object.keys(proj)) if (!RELATION_KEYS.has(k)) out[k] = proj[k];
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const hydrated = {};
|
|
69
|
+
for (const k of Object.keys(proj)) if (RELATION_KEYS.has(k)) {
|
|
70
|
+
if (needs.has(k)) {
|
|
71
|
+
const value = proj[k];
|
|
72
|
+
hydrated[k] = await resolveLazy(value);
|
|
73
|
+
}
|
|
74
|
+
} else hydrated[k] = proj[k];
|
|
75
|
+
return hydrated;
|
|
76
|
+
}
|
|
77
|
+
function neededRelations(spec) {
|
|
78
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
79
|
+
const out = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const path of spec) {
|
|
81
|
+
const head = path.split(".")[0];
|
|
82
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
async function resolveLazy(value) {
|
|
87
|
+
if (value && typeof value.then === "function") return await value;
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { projectUpdateStatusRuntime };
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { OPERATION_REGISTRY } from "../generated/operations.js";
|
|
3
|
+
import { loadConfig } from "../core/config/store.js";
|
|
4
|
+
import "../core/config/index.js";
|
|
5
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
6
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
7
|
+
import { runRaw } from "./raw-runtime.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
//#region src/lib/raw-batch-runtime.ts
|
|
11
|
+
/**
|
|
12
|
+
* `raw batch` runtime — Phase 3 PLAN 03-05, RAW-05.
|
|
13
|
+
*
|
|
14
|
+
* Safety-gated batch dispatcher. Runs a JSON plan of operations in sequence.
|
|
15
|
+
* Owns:
|
|
16
|
+
* - RAW-05: `raw batch --plan=@file.json` with default dry-run + explicit --yes to execute
|
|
17
|
+
*
|
|
18
|
+
* **9-step pipeline:**
|
|
19
|
+
* 1. resolveWorkspace (S2 verbatim)
|
|
20
|
+
* 2. Validate plan flag has @ prefix → load + parse + Zod-validate → BATCH_PLAN_INVALID
|
|
21
|
+
* 3. Registry lookup each entry's operation → BATCH_PLAN_INVALID with details.entry_index on miss
|
|
22
|
+
* 4. Compute kinds = { query: N, mutation: M }
|
|
23
|
+
* 5. If M > 0: requireExplicitWorkspaceForWrite (WSP-06 FIRST — Pitfall 7)
|
|
24
|
+
* 6. If M > 0 && !allow-mutations: RAW_MUTATION_REQUIRES_FLAG
|
|
25
|
+
* 7. Determine intent:
|
|
26
|
+
* - dryRun = flags['dry-run'] !== false (default true; --dry-run --yes still dry-run)
|
|
27
|
+
* - if dryRun: emit dry-run envelope; STOP (ZERO SDK calls)
|
|
28
|
+
* - if !dryRun && !yes: BATCH_REQUIRES_YES (exit 2)
|
|
29
|
+
* - if !dryRun && yes: dispatch sequentially via runRaw
|
|
30
|
+
* 8. Sequential dispatch (NOT parallel — RESEARCH line 769; keeps rate-limit pressure manageable)
|
|
31
|
+
* 9. Aggregate results; top-level ok:true (batch ran); per-entry ok:false on failure
|
|
32
|
+
*
|
|
33
|
+
* **Threat mitigations:**
|
|
34
|
+
* - T-03-05-WSP06: WSP-06 fires AFTER plan validation but BEFORE any dispatch (Pitfall 7)
|
|
35
|
+
* - T-03-05-DRY-RUN: --dry-run is the DEFAULT; both --dry-run + --yes → still dry-run
|
|
36
|
+
* - T-03-05-PLAN-FILE-INJECTION: Zod validates plan shape; max(100) caps blast radius (Pitfall 6)
|
|
37
|
+
* - T-03-05-D-RATELIMIT: Sequential dispatch + 100-entry cap
|
|
38
|
+
*
|
|
39
|
+
* **Mutation gates apply to dry-run too (REVIEW WR-03 — intentional):**
|
|
40
|
+
* Mutation-containing plans require --workspace (WSP-06) and
|
|
41
|
+
* --allow-mutations to dry-run, not just to execute. Rationale: the
|
|
42
|
+
* safety contract is "any plan touching mutations declares both target
|
|
43
|
+
* and intent upfront, regardless of whether it would run". A dry-run
|
|
44
|
+
* that side-steps the gates would let agents inspect plans they're not
|
|
45
|
+
* authorized to run. Uniform contract beats UX convenience.
|
|
46
|
+
*/
|
|
47
|
+
const PlanEntrySchema = z.object({
|
|
48
|
+
operation: z.string(),
|
|
49
|
+
vars: z.record(z.string(), z.unknown())
|
|
50
|
+
});
|
|
51
|
+
const PlanSchema = z.array(PlanEntrySchema).min(1).max(100);
|
|
52
|
+
async function runRawBatch(input) {
|
|
53
|
+
const { flags, env = process.env } = input;
|
|
54
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
55
|
+
const envForResolver = {};
|
|
56
|
+
if (env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = env.LINEAR_WORKSPACE;
|
|
57
|
+
if (env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = env.LINEAR_API_KEY;
|
|
58
|
+
const resolved = resolveWorkspace({
|
|
59
|
+
flags: flags.workspace ? { workspace: flags.workspace } : {},
|
|
60
|
+
env: envForResolver,
|
|
61
|
+
config
|
|
62
|
+
});
|
|
63
|
+
const plan = await loadAndValidatePlan(flags.plan);
|
|
64
|
+
const registry = OPERATION_REGISTRY;
|
|
65
|
+
const enriched = plan.map((entry, index) => {
|
|
66
|
+
const reg = registry[entry.operation];
|
|
67
|
+
if (!reg) throw new LinearAgentError({
|
|
68
|
+
code: "BATCH_PLAN_INVALID",
|
|
69
|
+
message: `entry ${index}: unknown operation '${entry.operation}'`,
|
|
70
|
+
details: {
|
|
71
|
+
entry_index: index,
|
|
72
|
+
reason: `unknown_operation: '${entry.operation}' not in OPERATION_REGISTRY`,
|
|
73
|
+
operation: entry.operation
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const varsResult = reg.varsSchema.safeParse(entry.vars);
|
|
77
|
+
if (!varsResult.success) throw new LinearAgentError({
|
|
78
|
+
code: "BATCH_PLAN_INVALID",
|
|
79
|
+
message: `entry ${index}: vars failed validation for operation '${entry.operation}'`,
|
|
80
|
+
details: {
|
|
81
|
+
entry_index: index,
|
|
82
|
+
reason: "vars_invalid",
|
|
83
|
+
operation: entry.operation,
|
|
84
|
+
issues: varsResult.error.issues.map((i) => ({
|
|
85
|
+
path: i.path,
|
|
86
|
+
message: i.message,
|
|
87
|
+
code: i.code
|
|
88
|
+
}))
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
...entry,
|
|
93
|
+
kind: reg.kind
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const kinds = {
|
|
97
|
+
query: enriched.filter((e) => e.kind === "query").length,
|
|
98
|
+
mutation: enriched.filter((e) => e.kind === "mutation").length
|
|
99
|
+
};
|
|
100
|
+
const batchMeta = {
|
|
101
|
+
count: enriched.length,
|
|
102
|
+
kinds
|
|
103
|
+
};
|
|
104
|
+
if (kinds.mutation > 0) requireExplicitWorkspaceForWrite(resolved, flags["allow-active-workspace-write"] ?? false);
|
|
105
|
+
if (kinds.mutation > 0 && !flags["allow-mutations"]) throw new LinearAgentError({
|
|
106
|
+
code: "RAW_MUTATION_REQUIRES_FLAG",
|
|
107
|
+
message: `batch contains ${kinds.mutation} mutation(s) and requires --allow-mutations`,
|
|
108
|
+
details: { mutation_count: kinds.mutation }
|
|
109
|
+
});
|
|
110
|
+
if (flags["dry-run"] !== false) return {
|
|
111
|
+
data: { plan: enriched.map((e) => ({
|
|
112
|
+
operation: e.operation,
|
|
113
|
+
vars: e.vars,
|
|
114
|
+
kind: e.kind,
|
|
115
|
+
workspace: resolved.name
|
|
116
|
+
})) },
|
|
117
|
+
meta: {
|
|
118
|
+
workspace: resolved.name,
|
|
119
|
+
workspaceSource: resolved.source,
|
|
120
|
+
batch: batchMeta
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
if (!flags.yes) throw new LinearAgentError({
|
|
124
|
+
code: "BATCH_REQUIRES_YES",
|
|
125
|
+
message: "batch execution requires --yes to confirm (or remove --no-dry-run for a dry-run preview)",
|
|
126
|
+
details: { hint: "pass --yes to confirm execution, or remove --no-dry-run for a dry-run preview" }
|
|
127
|
+
});
|
|
128
|
+
const results = [];
|
|
129
|
+
for (const entry of enriched) try {
|
|
130
|
+
const result = await runRaw({
|
|
131
|
+
args: { operation: entry.operation },
|
|
132
|
+
flags: {
|
|
133
|
+
workspace: flags.workspace,
|
|
134
|
+
"allow-active-workspace-write": flags["allow-active-workspace-write"],
|
|
135
|
+
"allow-mutations": flags["allow-mutations"],
|
|
136
|
+
vars: JSON.stringify(entry.vars)
|
|
137
|
+
},
|
|
138
|
+
env,
|
|
139
|
+
loadConfigOverride: input.loadConfigOverride,
|
|
140
|
+
retryOptsOverride: input.retryOptsOverride
|
|
141
|
+
});
|
|
142
|
+
results.push({
|
|
143
|
+
ok: true,
|
|
144
|
+
operation: entry.operation,
|
|
145
|
+
data: result.data
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err instanceof LinearAgentError) results.push({
|
|
149
|
+
ok: false,
|
|
150
|
+
operation: entry.operation,
|
|
151
|
+
error: {
|
|
152
|
+
code: err.code,
|
|
153
|
+
message: err.message,
|
|
154
|
+
...err.details !== void 0 ? { details: err.details } : {}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
else {
|
|
158
|
+
const e = err;
|
|
159
|
+
results.push({
|
|
160
|
+
ok: false,
|
|
161
|
+
operation: entry.operation,
|
|
162
|
+
error: {
|
|
163
|
+
code: e.code ?? "GENERIC_ERROR",
|
|
164
|
+
message: e.message ?? "unknown error"
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
data: { results },
|
|
171
|
+
meta: {
|
|
172
|
+
workspace: resolved.name,
|
|
173
|
+
workspaceSource: resolved.source,
|
|
174
|
+
batch: batchMeta
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Load, parse, and Zod-validate the plan file.
|
|
180
|
+
* Plan flag MUST start with '@' (it's a required file ref).
|
|
181
|
+
*/
|
|
182
|
+
async function loadAndValidatePlan(planFlag) {
|
|
183
|
+
if (!planFlag.startsWith("@")) throw new LinearAgentError({
|
|
184
|
+
code: "BATCH_PLAN_INVALID",
|
|
185
|
+
message: `--plan must be an @file path (e.g. --plan=@plan.json); got: '${planFlag}'`,
|
|
186
|
+
details: { reason: "@file path required: prefix the path with @" }
|
|
187
|
+
});
|
|
188
|
+
const filePath = planFlag.slice(1);
|
|
189
|
+
let rawContent;
|
|
190
|
+
try {
|
|
191
|
+
rawContent = await readFile(filePath, "utf8");
|
|
192
|
+
} catch (err) {
|
|
193
|
+
const code = err.code;
|
|
194
|
+
throw new LinearAgentError({
|
|
195
|
+
code: "BATCH_PLAN_INVALID",
|
|
196
|
+
message: `plan file not found or not readable: ${filePath}`,
|
|
197
|
+
details: {
|
|
198
|
+
path: filePath,
|
|
199
|
+
reason: "file_not_readable",
|
|
200
|
+
cause: code ?? err.message
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
let parsed;
|
|
205
|
+
try {
|
|
206
|
+
parsed = JSON.parse(rawContent);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
throw new LinearAgentError({
|
|
209
|
+
code: "BATCH_PLAN_INVALID",
|
|
210
|
+
message: `plan file is not valid JSON: ${err.message}`,
|
|
211
|
+
details: { reason: "parse_error" }
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const result = PlanSchema.safeParse(parsed);
|
|
215
|
+
if (!result.success) {
|
|
216
|
+
const firstIssue = result.error.issues[0];
|
|
217
|
+
const entryIndex = firstIssue?.path[0] !== void 0 && typeof firstIssue.path[0] === "number" ? firstIssue.path[0] : void 0;
|
|
218
|
+
const details = { reason: firstIssue?.message ?? "validation_failed" };
|
|
219
|
+
if (entryIndex !== void 0) details.entry_index = entryIndex;
|
|
220
|
+
throw new LinearAgentError({
|
|
221
|
+
code: "BATCH_PLAN_INVALID",
|
|
222
|
+
message: `plan file validation failed: ${firstIssue?.message ?? "invalid shape"}`,
|
|
223
|
+
details
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return result.data;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
export { runRawBatch };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { OPERATION_REGISTRY } from "../generated/operations.js";
|
|
3
|
+
import { suggestClosest } from "./levenshtein.js";
|
|
4
|
+
import { redact } from "../core/redact/redact.js";
|
|
5
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
6
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
7
|
+
import "../core/transport/index.js";
|
|
8
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
9
|
+
import "../core/client/index.js";
|
|
10
|
+
import { loadConfig } from "../core/config/store.js";
|
|
11
|
+
import "../core/config/index.js";
|
|
12
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
13
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
//#region src/lib/raw-runtime.ts
|
|
16
|
+
/**
|
|
17
|
+
* `raw <Operation>` runtime — Phase 3 PLAN 03-02, RAW-01 / RAW-02.
|
|
18
|
+
*
|
|
19
|
+
* Dispatches any operation in the generated GraphQL registry via
|
|
20
|
+
* `client.client.rawRequest(entry.source, vars)`. Owns:
|
|
21
|
+
* - RAW-01: full registry surface (501 ops) accessible from CLI
|
|
22
|
+
* - RAW-02: mutation safety with WSP-06 + --allow-mutations gates
|
|
23
|
+
*
|
|
24
|
+
* **Gate ordering is load-bearing (Test 3 — WSP-06 BEFORE --allow-mutations):**
|
|
25
|
+
* 1. resolveWorkspace
|
|
26
|
+
* 2. registry lookup (RAW_OPERATION_NOT_FOUND on miss; closest-match suggestions)
|
|
27
|
+
* 3. subscription guard (OPERATION_SUBSCRIPTIONS_UNSUPPORTED — defensive)
|
|
28
|
+
* 4. requireExplicitWorkspaceForWrite (WSP-06 FIRST — precedent: issue-purge-runtime.ts:99 Test 11)
|
|
29
|
+
* 5. --allow-mutations check (RAW_MUTATION_REQUIRES_FLAG)
|
|
30
|
+
* 6. vars parse + Zod validation (RAW_VARS_INVALID)
|
|
31
|
+
* 7. createLinearClient
|
|
32
|
+
* 8. client.client.rawRequest(entry.source, vars) wrapped in S3 transport
|
|
33
|
+
* 9. response.error → LINEAR_API_ERROR (Pitfall 2: STRING not LinearError instance)
|
|
34
|
+
* 10. meta build with opt-in complexity spread + return envelope
|
|
35
|
+
*
|
|
36
|
+
* Why WSP-06 first: a missing --workspace is the more dangerous mistake
|
|
37
|
+
* (cross-workspace permanent write) vs a missing --allow-mutations flag.
|
|
38
|
+
* Test 3 pins this ordering. Precedent: issue-purge-runtime.ts:99 Test 11.
|
|
39
|
+
*
|
|
40
|
+
* Pitfall 2 (RESEARCH § Pitfall 2): `client.client.rawRequest` does NOT
|
|
41
|
+
* throw on GraphQL errors — it returns `LinearRawResponse` with `error?`
|
|
42
|
+
* populated as a STRING. withRateLimitRetry still handles HTTP-layer
|
|
43
|
+
* rate-limits via `instanceof RatelimitedLinearError`. Runtime checks
|
|
44
|
+
* `response.error` explicitly → LINEAR_API_ERROR with `details.cause`.
|
|
45
|
+
*/
|
|
46
|
+
async function runRaw(input) {
|
|
47
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
48
|
+
const env = input.env ?? process.env;
|
|
49
|
+
const envForResolver = {};
|
|
50
|
+
if (env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = env.LINEAR_WORKSPACE;
|
|
51
|
+
if (env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = env.LINEAR_API_KEY;
|
|
52
|
+
const resolved = resolveWorkspace({
|
|
53
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
54
|
+
env: envForResolver,
|
|
55
|
+
config
|
|
56
|
+
});
|
|
57
|
+
const registry = OPERATION_REGISTRY;
|
|
58
|
+
const entry = Object.hasOwn(registry, input.args.operation) ? registry[input.args.operation] : void 0;
|
|
59
|
+
if (!entry) {
|
|
60
|
+
const suggestions = suggestClosest(input.args.operation, Object.keys(registry));
|
|
61
|
+
throw new LinearAgentError({
|
|
62
|
+
code: "RAW_OPERATION_NOT_FOUND",
|
|
63
|
+
message: `unknown operation: ${input.args.operation}. Did you mean: ${suggestions.join(", ")}?`,
|
|
64
|
+
details: {
|
|
65
|
+
operation: input.args.operation,
|
|
66
|
+
suggestions
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (entry.kind === "subscription") throw new LinearAgentError({
|
|
71
|
+
code: "OPERATION_SUBSCRIPTIONS_UNSUPPORTED",
|
|
72
|
+
message: `subscription operations are not supported: ${input.args.operation}`,
|
|
73
|
+
details: {
|
|
74
|
+
operation: input.args.operation,
|
|
75
|
+
kind: "subscription"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (entry.kind === "mutation") requireExplicitWorkspaceForWrite(resolved, input.flags["allow-active-workspace-write"] ?? false);
|
|
79
|
+
if (entry.kind === "mutation" && !input.flags["allow-mutations"]) throw new LinearAgentError({
|
|
80
|
+
code: "RAW_MUTATION_REQUIRES_FLAG",
|
|
81
|
+
message: `mutation '${input.args.operation}' requires --allow-mutations`,
|
|
82
|
+
details: {
|
|
83
|
+
operation: input.args.operation,
|
|
84
|
+
kind: "mutation"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const rawVars = await loadVars(input.flags.vars);
|
|
88
|
+
const parsed = entry.varsSchema.safeParse(rawVars);
|
|
89
|
+
if (!parsed.success) throw new LinearAgentError({
|
|
90
|
+
code: "RAW_VARS_INVALID",
|
|
91
|
+
message: `vars failed validation for operation '${input.args.operation}'`,
|
|
92
|
+
details: {
|
|
93
|
+
operation: input.args.operation,
|
|
94
|
+
issues: parsed.error.issues.map((i) => ({
|
|
95
|
+
path: i.path,
|
|
96
|
+
message: i.message,
|
|
97
|
+
code: i.code
|
|
98
|
+
}))
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
102
|
+
return withFetchInterception(async () => {
|
|
103
|
+
const response = await withRateLimitRetry(() => client.client.rawRequest(entry.source, parsed.data), input.retryOptsOverride);
|
|
104
|
+
if (response.error !== void 0 || response.data === void 0) throw new LinearAgentError({
|
|
105
|
+
code: "LINEAR_API_ERROR",
|
|
106
|
+
message: redact(response.error ?? `rawRequest returned no data for operation '${input.args.operation}'`),
|
|
107
|
+
details: {
|
|
108
|
+
cause: response.error !== void 0 ? redact(response.error) : void 0,
|
|
109
|
+
operation: input.args.operation,
|
|
110
|
+
status: response.status
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const complexity = getLastComplexity();
|
|
114
|
+
const meta = {
|
|
115
|
+
workspace: resolved.name,
|
|
116
|
+
workspaceSource: resolved.source,
|
|
117
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
data: response.data,
|
|
121
|
+
meta
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load and parse vars from inline JSON string or @file.json path.
|
|
127
|
+
* File path takes precedence (CONTEXT.md line 51 contract).
|
|
128
|
+
*/
|
|
129
|
+
async function loadVars(varsArg) {
|
|
130
|
+
if (!varsArg) return {};
|
|
131
|
+
if (varsArg.startsWith("@")) {
|
|
132
|
+
const path = varsArg.slice(1);
|
|
133
|
+
let raw;
|
|
134
|
+
try {
|
|
135
|
+
raw = await readFile(path, "utf8");
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const code = err.code;
|
|
138
|
+
throw new LinearAgentError({
|
|
139
|
+
code: "GRAPHQL_QUERY_FILE_NOT_FOUND",
|
|
140
|
+
message: `vars file not found or not readable: ${path}`,
|
|
141
|
+
details: {
|
|
142
|
+
path,
|
|
143
|
+
cause: code ?? err.message
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(raw);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
throw new LinearAgentError({
|
|
151
|
+
code: "RAW_VARS_INVALID",
|
|
152
|
+
message: `vars file is not valid JSON: ${err.message}`,
|
|
153
|
+
details: {
|
|
154
|
+
path,
|
|
155
|
+
reason: "parse_error"
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(varsArg);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new LinearAgentError({
|
|
164
|
+
code: "RAW_VARS_INVALID",
|
|
165
|
+
message: `--vars is not valid JSON: ${err.message}`,
|
|
166
|
+
details: { reason: "parse_error" }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { runRaw };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildSchema } from "graphql";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/lib/schema-loader.ts
|
|
6
|
+
/**
|
|
7
|
+
* Lazy module-level cached Linear schema loader.
|
|
8
|
+
*
|
|
9
|
+
* Only the `graphql` command pays the ~50–150ms `buildSchema` cost. The `raw`
|
|
10
|
+
* and `raw batch` commands never import this module, so the cold-start budget
|
|
11
|
+
* for the dominant use case remains unaffected (Phase 5 DST-03).
|
|
12
|
+
*
|
|
13
|
+
* The path resolver works from BOTH:
|
|
14
|
+
* - `dist/lib/schema-loader.js` (built CLI) → ../../schema.graphql
|
|
15
|
+
* - `src/lib/schema-loader.ts` (vitest) → ../../schema.graphql
|
|
16
|
+
*
|
|
17
|
+
* Both sit two levels under the repo root, so the relative path `../../schema.graphql`
|
|
18
|
+
* resolves correctly in both contexts.
|
|
19
|
+
*/
|
|
20
|
+
let cachedSchema;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the Linear GraphQLSchema, building it lazily on first call
|
|
23
|
+
* and returning the cached instance on subsequent calls.
|
|
24
|
+
*/
|
|
25
|
+
function getLinearSchema() {
|
|
26
|
+
if (cachedSchema) return cachedSchema;
|
|
27
|
+
cachedSchema = buildSchema(readFileSync(resolveSchemaPath(), "utf8"));
|
|
28
|
+
return cachedSchema;
|
|
29
|
+
}
|
|
30
|
+
function resolveSchemaPath() {
|
|
31
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "schema.graphql");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reset the module-level schema cache. For test isolation only — do not use
|
|
35
|
+
* in production code.
|
|
36
|
+
*/
|
|
37
|
+
function _resetSchemaCacheForTesting() {
|
|
38
|
+
cachedSchema = void 0;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { _resetSchemaCacheForTesting, getLinearSchema };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getLinearSchema } from "./schema-loader.js";
|
|
2
|
+
import { introspectionFromSchema, printSchema } from "graphql";
|
|
3
|
+
//#region src/lib/schema-runtime.ts
|
|
4
|
+
/**
|
|
5
|
+
* Schema runtime — Phase 4 PLAN 04-04, INT-03.
|
|
6
|
+
*
|
|
7
|
+
* Exposes the Linear GraphQL schema in two forms:
|
|
8
|
+
* - Default: compact SDL (triple-quoted descriptions stripped) for token-budget efficiency
|
|
9
|
+
* - --full: SDL with descriptions included (opt-in verbose mode)
|
|
10
|
+
* - --json: Standard introspection JSON (__schema format) via `introspectionFromSchema`
|
|
11
|
+
*
|
|
12
|
+
* Zero network calls — reads the committed vendored `schema.graphql` via
|
|
13
|
+
* `getLinearSchema()` (lazy-cached in schema-loader.ts).
|
|
14
|
+
*
|
|
15
|
+
* Counts included in all modes:
|
|
16
|
+
* types_count = 1081 (user types, excluding __* introspection builtins)
|
|
17
|
+
* queries_count = 151
|
|
18
|
+
* mutations_count = 351
|
|
19
|
+
* subscriptions_count = 75
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Returns type counts from the Linear GraphQL schema.
|
|
23
|
+
* Excludes built-in `__*` introspection types.
|
|
24
|
+
*/
|
|
25
|
+
function getTypeCounts(schema) {
|
|
26
|
+
const typeMap = schema.getTypeMap();
|
|
27
|
+
return {
|
|
28
|
+
types_count: Object.values(typeMap).filter((t) => !t.name.startsWith("__")).length,
|
|
29
|
+
queries_count: Object.keys(schema.getQueryType()?.getFields() ?? {}).length,
|
|
30
|
+
mutations_count: Object.keys(schema.getMutationType()?.getFields() ?? {}).length,
|
|
31
|
+
subscriptions_count: Object.keys(schema.getSubscriptionType()?.getFields() ?? {}).length
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns the Linear schema as compact SDL (descriptions stripped) or with descriptions,
|
|
36
|
+
* or as standard introspection JSON.
|
|
37
|
+
*
|
|
38
|
+
* This is the named runtime export used by tests and the oclif command shim.
|
|
39
|
+
*
|
|
40
|
+
* Returns `{ data }` only — the command shim wraps the result in the Phase 1
|
|
41
|
+
* envelope via `runCommand`, which sets `meta.command` from the commandPath.
|
|
42
|
+
* No `meta` is returned here on purpose: the runtime owns the data shape and
|
|
43
|
+
* the kernel owns the envelope.
|
|
44
|
+
*/
|
|
45
|
+
async function schemaRuntime(args) {
|
|
46
|
+
const { flags } = args;
|
|
47
|
+
const schema = getLinearSchema();
|
|
48
|
+
const counts = getTypeCounts(schema);
|
|
49
|
+
if (flags.json) return { data: {
|
|
50
|
+
schema: introspectionFromSchema(schema, {
|
|
51
|
+
descriptions: flags.full ?? false,
|
|
52
|
+
specifiedByUrl: false,
|
|
53
|
+
directiveIsRepeatable: false,
|
|
54
|
+
schemaDescription: false
|
|
55
|
+
}),
|
|
56
|
+
...counts
|
|
57
|
+
} };
|
|
58
|
+
const printed = printSchema(schema);
|
|
59
|
+
return { data: {
|
|
60
|
+
schema: flags.full ? printed : printed.replace(/"""[\s\S]*?"""\n?/g, ""),
|
|
61
|
+
...counts
|
|
62
|
+
} };
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { schemaRuntime };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
2
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
3
|
+
import "../core/transport/index.js";
|
|
4
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
5
|
+
import "../core/client/index.js";
|
|
6
|
+
import { loadConfig } from "../core/config/store.js";
|
|
7
|
+
import "../core/config/index.js";
|
|
8
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
9
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
10
|
+
import { parsePagination } from "../core/pagination/flags.js";
|
|
11
|
+
import "../core/pagination/index.js";
|
|
12
|
+
import { TEAM_KEY_RE, UUID_RE } from "./filter-heuristics.js";
|
|
13
|
+
//#region src/lib/state-list-runtime.ts
|
|
14
|
+
async function stateListRuntime(input) {
|
|
15
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
16
|
+
const envForResolver = {};
|
|
17
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
18
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
19
|
+
const resolved = resolveWorkspace({
|
|
20
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
21
|
+
env: envForResolver,
|
|
22
|
+
config
|
|
23
|
+
});
|
|
24
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "state");
|
|
25
|
+
const { first, after } = parsePagination({
|
|
26
|
+
...input.flags.limit !== void 0 ? { limit: input.flags.limit } : {},
|
|
27
|
+
...input.flags.cursor !== void 0 ? { cursor: input.flags.cursor } : {}
|
|
28
|
+
});
|
|
29
|
+
let filter;
|
|
30
|
+
if (input.flags.team !== void 0 && input.flags.team !== "") filter = buildTeamFilter(input.flags.team);
|
|
31
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
32
|
+
return withFetchInterception(async () => {
|
|
33
|
+
const args = { first };
|
|
34
|
+
if (after !== void 0) args.after = after;
|
|
35
|
+
if (filter !== void 0) args.filter = filter;
|
|
36
|
+
const connection = await withRateLimitRetry(() => client.workflowStates(args), input.retryOptsOverride);
|
|
37
|
+
const projected = await Promise.all(connection.nodes.map(async (node) => {
|
|
38
|
+
return project(await hydrateForProjection(node, fields), fields);
|
|
39
|
+
}));
|
|
40
|
+
const complexity = getLastComplexity();
|
|
41
|
+
return {
|
|
42
|
+
data: projected,
|
|
43
|
+
meta: {
|
|
44
|
+
workspace: resolved.name,
|
|
45
|
+
workspaceSource: resolved.source,
|
|
46
|
+
pageInfo: {
|
|
47
|
+
hasNextPage: Boolean(connection.pageInfo?.hasNextPage),
|
|
48
|
+
endCursor: connection.pageInfo?.endCursor ?? null,
|
|
49
|
+
hasPreviousPage: Boolean(connection.pageInfo?.hasPreviousPage),
|
|
50
|
+
startCursor: connection.pageInfo?.startCursor ?? null
|
|
51
|
+
},
|
|
52
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function buildTeamFilter(t) {
|
|
58
|
+
if (UUID_RE.test(t)) return { team: { id: { eq: t } } };
|
|
59
|
+
if (TEAM_KEY_RE.test(t)) return { team: { key: { eq: t.toUpperCase() } } };
|
|
60
|
+
return { team: { name: { eq: t } } };
|
|
61
|
+
}
|
|
62
|
+
const RELATION_KEYS = new Set(["team"]);
|
|
63
|
+
async function hydrateForProjection(state, spec) {
|
|
64
|
+
const needs = neededRelations(spec);
|
|
65
|
+
if (needs.size === 0) {
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const k of Object.keys(state)) if (!RELATION_KEYS.has(k)) out[k] = state[k];
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
const hydrated = {};
|
|
71
|
+
for (const k of Object.keys(state)) if (RELATION_KEYS.has(k)) {
|
|
72
|
+
if (needs.has(k)) {
|
|
73
|
+
const value = state[k];
|
|
74
|
+
hydrated[k] = await resolveLazy(value);
|
|
75
|
+
}
|
|
76
|
+
} else hydrated[k] = state[k];
|
|
77
|
+
return hydrated;
|
|
78
|
+
}
|
|
79
|
+
function neededRelations(spec) {
|
|
80
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
81
|
+
const out = /* @__PURE__ */ new Set();
|
|
82
|
+
for (const path of spec) {
|
|
83
|
+
const head = path.split(".")[0];
|
|
84
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
async function resolveLazy(value) {
|
|
89
|
+
if (value && typeof value.then === "function") return await value;
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { stateListRuntime };
|