@enrichlayer/el-linear 1.15.0 → 1.16.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.
@@ -196,7 +196,28 @@ async function handleUpdateComment(commentId, options, command) {
196
196
  else {
197
197
  input.body = body;
198
198
  }
199
- const result = await graphQLService.rawRequest(UPDATE_COMMENT_MUTATION, { id: commentId, input });
199
+ let result;
200
+ try {
201
+ result = await graphQLService.rawRequest(UPDATE_COMMENT_MUTATION, { id: commentId, input });
202
+ }
203
+ catch (err) {
204
+ // Same defense-in-depth fallback as `handleCreateComment` above: when
205
+ // Linear rejects `bodyData` (schema drift, unsupported node, validator
206
+ // quirk), retry the mutation with raw markdown `body` so the update
207
+ // goes through even if our prosemirror converter has fallen behind a
208
+ // schema change. The asymmetry between create and update used to mean
209
+ // any future drift would silently break updates while creates kept
210
+ // working — DEV-4261 closes that gap. Two call sites is the floor for
211
+ // extraction; copy-then-refactor on the third per the repo convention.
212
+ const msg = err instanceof Error ? err.message : String(err);
213
+ if (input.bodyData && BODY_DATA_ERROR_RE.test(msg)) {
214
+ const fallbackInput = { body };
215
+ result = await graphQLService.rawRequest(UPDATE_COMMENT_MUTATION, { id: commentId, input: fallbackInput });
216
+ }
217
+ else {
218
+ throw err;
219
+ }
220
+ }
200
221
  const mutation = result.commentUpdate;
201
222
  if (!mutation.success || !mutation.comment) {
202
223
  throw new Error("Failed to update comment");
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Public output utilities — secondary entry point for cross-CLI reuse.
3
+ *
4
+ * Other Enrich Layer CLIs (el-slack, el-sheets, el-audit, el-git,
5
+ * el-elasticsearch — currently in the `tools` monorepo) should depend on
6
+ * `@enrichlayer/el-linear` and import this module to share the same
7
+ * `--jq` / `--fields` / `--raw` / `--format summary` behavior and the
8
+ * canonical `{ data, meta }` envelope shape. DEV-3799.
9
+ *
10
+ * Why this exists rather than a separate package: linctl is the OSS
11
+ * canonical source of these patterns (DEV-3619, DEV-3637, DEV-3798); it
12
+ * already publishes to npm and ships compiled JS in `dist/`. Treating it
13
+ * as the host of the shared utilities means there is exactly one
14
+ * implementation, exactly one set of tests, and exactly one place where
15
+ * the envelope contract is documented. Tools-repo CLIs pick it up via
16
+ * `pnpm add @enrichlayer/el-linear` and `import { outputSuccess, ... }
17
+ * from "@enrichlayer/el-linear/output"`.
18
+ *
19
+ * Stable API — semver-tracked. Anything re-exported here MUST stay
20
+ * backwards-compatible across minor releases. Adding exports is fine;
21
+ * renaming or removing them is a breaking change.
22
+ *
23
+ * # Wiring (consumer recipe)
24
+ *
25
+ * Each CLI's `main.ts` registers four global options and a `preAction`
26
+ * hook to read them into the shared state:
27
+ *
28
+ * ```ts
29
+ * import { Command } from "commander";
30
+ * import {
31
+ * setRawMode, setJqFilter, setFieldsFilter, setOutputFormat,
32
+ * } from "@enrichlayer/el-linear/output";
33
+ *
34
+ * const program = new Command()
35
+ * .option("--raw", "strip { data, meta } wrapper from list output")
36
+ * .option("--jq <filter>", "apply a jq filter to the JSON output")
37
+ * .option("--fields <fields>", "comma-separated field allow-list")
38
+ * .option("--format <fmt>", "json | summary", "json");
39
+ *
40
+ * program.hook("preAction", (_thisCommand, actionCommand) => {
41
+ * const o = actionCommand.optsWithGlobals();
42
+ * if (o.raw) setRawMode(true);
43
+ * if (o.jq) setJqFilter(o.jq);
44
+ * if (o.fields) {
45
+ * setFieldsFilter(o.fields.split(",").map((s: string) => s.trim()));
46
+ * }
47
+ * setOutputFormat(o.format === "summary" ? "summary" : "json");
48
+ * });
49
+ * ```
50
+ *
51
+ * Then in each subcommand handler emit results via `outputList(data)` /
52
+ * `outputSingle(data)` / `outputSuccess(payload)`, wrap async actions in
53
+ * `handleAsyncCommand`, and buffer informational messages via
54
+ * `outputWarning`. The shared state in this module takes care of `--jq`,
55
+ * `--fields`, `--raw`, and summary rendering uniformly.
56
+ *
57
+ * Adapt the option names and flags to your CLI's existing conventions —
58
+ * the contract is the four setter calls (`setRawMode`, `setJqFilter`,
59
+ * `setFieldsFilter`, `setOutputFormat`), not the literal `--jq` /
60
+ * `--fields` / `--raw` / `--format` names. A CLI that already exposes
61
+ * `--json` / `--summary` shorthands can keep them as long as the
62
+ * preAction hook ends up calling the same setters with equivalent values.
63
+ *
64
+ * # What is NOT exported
65
+ *
66
+ * - `--format summary` dispatch tables are linctl-specific (Linear
67
+ * resources). Other CLIs that want summary rendering should either
68
+ * ship `--format json` only or register their own per-command summary
69
+ * path that does not consume this module's `setOutputFormat("summary")`.
70
+ * - `outputError` is intentionally internal — it calls `process.exit(1)`
71
+ * directly. Consumers funnel errors through `handleAsyncCommand`, which
72
+ * in turn calls `outputError`. Don't re-export the bare function; the
73
+ * wrapper is the contract.
74
+ * - Token-sanitization (`sanitizeForLog`) is internal to the error path.
75
+ * Consumers that route errors through `handleAsyncCommand` automatically
76
+ * get token redaction on the error path — no separate redactor needed
77
+ * for that case. If a consumer wants sanitization for something OTHER
78
+ * than thrown errors (e.g. a custom debug log), it should pull a small
79
+ * redactor of its own; coupling token redaction to the output layer
80
+ * would make this API surface stickier than it needs to be.
81
+ */
82
+ export { type CliListEnvelope, getOutputFormat, handleAsyncCommand, type ListExtraMeta, type ListMeta, outputList, outputSingle, outputSuccess, outputWarning, resetWarnings, setFieldsFilter, setJqFilter, setOutputFormat, setRawMode, warnIfTruncated, } from "./utils/output.js";
package/dist/output.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Public output utilities — secondary entry point for cross-CLI reuse.
3
+ *
4
+ * Other Enrich Layer CLIs (el-slack, el-sheets, el-audit, el-git,
5
+ * el-elasticsearch — currently in the `tools` monorepo) should depend on
6
+ * `@enrichlayer/el-linear` and import this module to share the same
7
+ * `--jq` / `--fields` / `--raw` / `--format summary` behavior and the
8
+ * canonical `{ data, meta }` envelope shape. DEV-3799.
9
+ *
10
+ * Why this exists rather than a separate package: linctl is the OSS
11
+ * canonical source of these patterns (DEV-3619, DEV-3637, DEV-3798); it
12
+ * already publishes to npm and ships compiled JS in `dist/`. Treating it
13
+ * as the host of the shared utilities means there is exactly one
14
+ * implementation, exactly one set of tests, and exactly one place where
15
+ * the envelope contract is documented. Tools-repo CLIs pick it up via
16
+ * `pnpm add @enrichlayer/el-linear` and `import { outputSuccess, ... }
17
+ * from "@enrichlayer/el-linear/output"`.
18
+ *
19
+ * Stable API — semver-tracked. Anything re-exported here MUST stay
20
+ * backwards-compatible across minor releases. Adding exports is fine;
21
+ * renaming or removing them is a breaking change.
22
+ *
23
+ * # Wiring (consumer recipe)
24
+ *
25
+ * Each CLI's `main.ts` registers four global options and a `preAction`
26
+ * hook to read them into the shared state:
27
+ *
28
+ * ```ts
29
+ * import { Command } from "commander";
30
+ * import {
31
+ * setRawMode, setJqFilter, setFieldsFilter, setOutputFormat,
32
+ * } from "@enrichlayer/el-linear/output";
33
+ *
34
+ * const program = new Command()
35
+ * .option("--raw", "strip { data, meta } wrapper from list output")
36
+ * .option("--jq <filter>", "apply a jq filter to the JSON output")
37
+ * .option("--fields <fields>", "comma-separated field allow-list")
38
+ * .option("--format <fmt>", "json | summary", "json");
39
+ *
40
+ * program.hook("preAction", (_thisCommand, actionCommand) => {
41
+ * const o = actionCommand.optsWithGlobals();
42
+ * if (o.raw) setRawMode(true);
43
+ * if (o.jq) setJqFilter(o.jq);
44
+ * if (o.fields) {
45
+ * setFieldsFilter(o.fields.split(",").map((s: string) => s.trim()));
46
+ * }
47
+ * setOutputFormat(o.format === "summary" ? "summary" : "json");
48
+ * });
49
+ * ```
50
+ *
51
+ * Then in each subcommand handler emit results via `outputList(data)` /
52
+ * `outputSingle(data)` / `outputSuccess(payload)`, wrap async actions in
53
+ * `handleAsyncCommand`, and buffer informational messages via
54
+ * `outputWarning`. The shared state in this module takes care of `--jq`,
55
+ * `--fields`, `--raw`, and summary rendering uniformly.
56
+ *
57
+ * Adapt the option names and flags to your CLI's existing conventions —
58
+ * the contract is the four setter calls (`setRawMode`, `setJqFilter`,
59
+ * `setFieldsFilter`, `setOutputFormat`), not the literal `--jq` /
60
+ * `--fields` / `--raw` / `--format` names. A CLI that already exposes
61
+ * `--json` / `--summary` shorthands can keep them as long as the
62
+ * preAction hook ends up calling the same setters with equivalent values.
63
+ *
64
+ * # What is NOT exported
65
+ *
66
+ * - `--format summary` dispatch tables are linctl-specific (Linear
67
+ * resources). Other CLIs that want summary rendering should either
68
+ * ship `--format json` only or register their own per-command summary
69
+ * path that does not consume this module's `setOutputFormat("summary")`.
70
+ * - `outputError` is intentionally internal — it calls `process.exit(1)`
71
+ * directly. Consumers funnel errors through `handleAsyncCommand`, which
72
+ * in turn calls `outputError`. Don't re-export the bare function; the
73
+ * wrapper is the contract.
74
+ * - Token-sanitization (`sanitizeForLog`) is internal to the error path.
75
+ * Consumers that route errors through `handleAsyncCommand` automatically
76
+ * get token redaction on the error path — no separate redactor needed
77
+ * for that case. If a consumer wants sanitization for something OTHER
78
+ * than thrown errors (e.g. a custom debug log), it should pull a small
79
+ * redactor of its own; coupling token redaction to the output layer
80
+ * would make this API surface stickier than it needs to be.
81
+ */
82
+ export { getOutputFormat, handleAsyncCommand, outputList, outputSingle, outputSuccess, outputWarning, resetWarnings, setFieldsFilter, setJqFilter, setOutputFormat, setRawMode, warnIfTruncated, } from "./utils/output.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enrichlayer/el-linear",
3
- "version": "1.15.0",
3
+ "version": "1.16.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",
@@ -9,6 +9,11 @@
9
9
  ".": {
10
10
  "types": "./dist/main.d.ts",
11
11
  "import": "./dist/main.js"
12
+ },
13
+ "./output": {
14
+ "types": "./dist/output.d.ts",
15
+ "import": "./dist/output.js",
16
+ "default": "./dist/output.js"
12
17
  }
13
18
  },
14
19
  "sideEffects": false,