@tenonhq/dovetail-servicenow 0.0.32 → 0.0.33

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/cli.js CHANGED
@@ -906,7 +906,8 @@ function printHelp() {
906
906
  " invoke-rest Invoke an arbitrary authenticated REST operation (Scripted REST incl.)\n" +
907
907
  " DRY-RUN BY DEFAULT — nothing is sent without --confirm\n" +
908
908
  " (--method <GET|POST|PUT|DELETE> --path /api/<scope>/<service>/<resource>\n" +
909
- " [--body '<json>' | --body-json <path>] [--confirm] [--dry-run] [--json])\n" +
909
+ " [--body '<json>' | --body-json <path>] [--confirm] [--dry-run] [--json]\n" +
910
+ " [--out <file> full JSON result to a file (atomic; overwrites); for large bodies])\n" +
910
911
  " set-field Set field value(s) on an EXISTING record, into an update set, then verify\n" +
911
912
  " (--table <t> --sys-id <id>|--query <q> --update-set <sys_id>\n" +
912
913
  ' (--fields "k=v,k2=v2" | --from-json <path>) [--dry-run] [--json])\n' +
@@ -1516,6 +1517,11 @@ async function runHostAssets(flags) {
1516
1517
  * --confirm Send for real. WITHOUT it the command is a DRY-RUN.
1517
1518
  * --dry-run Force a dry-run even with --confirm.
1518
1519
  * --json Emit the structured InvokeRestResult.
1520
+ * --out <file> Also write the full structured result to a file
1521
+ * (pretty JSON, atomic temp+rename, OVERWRITES an
1522
+ * existing file; parent dir must exist). The reliable
1523
+ * channel for large response bodies - piped stdout is
1524
+ * flush-guarded but a file needs no downstream reader.
1519
1525
  *
1520
1526
  * Invoke an arbitrary authenticated REST operation (Scripted REST included).
1521
1527
  * Dry-run by default; --confirm sends and returns { httpStatus, ok, body } with
@@ -1562,6 +1568,17 @@ async function runInvokeRest(flags) {
1562
1568
  params.body = body;
1563
1569
  }
1564
1570
  var result = await (0, invokeRest_1.invokeRest)(params);
1571
+ if (flags.out) {
1572
+ try {
1573
+ (0, invokeRest_1.writeInvokeRestResultFile)(flags.out, result);
1574
+ }
1575
+ catch (err) {
1576
+ process.stderr.write("invoke-rest: --out write failed: " +
1577
+ (err instanceof Error ? err.message : String(err)) +
1578
+ "\n");
1579
+ return 1;
1580
+ }
1581
+ }
1565
1582
  if (flags.json === "true") {
1566
1583
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1567
1584
  }
@@ -1783,13 +1800,57 @@ async function main() {
1783
1800
  }
1784
1801
  throw new Error("Unknown command: " + parsed.command);
1785
1802
  }
1803
+ // A closed downstream pipe (e.g. `dove-sn ... --json | head`) surfaces as an
1804
+ // EPIPE on stdout. Exit quietly with the code already set instead of crashing
1805
+ // with an unhandled stream error.
1806
+ process.stdout.on("error", function (err) {
1807
+ if (err && err.code === "EPIPE") {
1808
+ process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
1809
+ }
1810
+ throw err;
1811
+ });
1812
+ /**
1813
+ * process.exit() discards buffered stdout/stderr - when stdout is a PIPE,
1814
+ * anything past the OS pipe buffer (~64KB) is silently dropped, which is how
1815
+ * `invoke-rest --json` used to truncate large bodies mid-string. Queue an
1816
+ * empty chunk behind any pending data on each stream and exit only when both
1817
+ * callbacks confirm the flush. The barrier is queued UNCONDITIONALLY (not
1818
+ * gated on writableLength) so the exit never races stream internals about
1819
+ * whether a prior write is still in flight.
1820
+ */
1821
+ function exitAfterFlush(code) {
1822
+ process.exitCode = code;
1823
+ var pending = 0;
1824
+ var finish = function () {
1825
+ pending -= 1;
1826
+ if (pending <= 0) {
1827
+ process.exit(code);
1828
+ }
1829
+ };
1830
+ [process.stdout, process.stderr].forEach(function (stream) {
1831
+ if (stream.destroyed || !stream.writable) {
1832
+ return;
1833
+ }
1834
+ pending += 1;
1835
+ try {
1836
+ stream.write("", finish);
1837
+ }
1838
+ catch (writeErr) {
1839
+ // A stream that rejects the barrier write has nothing left to flush.
1840
+ pending -= 1;
1841
+ }
1842
+ });
1843
+ if (pending === 0) {
1844
+ process.exit(code);
1845
+ }
1846
+ }
1786
1847
  main()
1787
1848
  .then(function (code) {
1788
- process.exit(code);
1849
+ exitAfterFlush(code);
1789
1850
  })
1790
1851
  .catch(function (err) {
1791
1852
  process.stderr.write("dove-sn error: " +
1792
1853
  (err && err.message ? err.message : String(err)) +
1793
1854
  "\n");
1794
- process.exit(1);
1855
+ exitAfterFlush(1);
1795
1856
  });
@@ -48,3 +48,15 @@ export interface InvokeRestResult {
48
48
  note: string;
49
49
  }
50
50
  export declare function invokeRest(params: InvokeRestParams): Promise<InvokeRestResult>;
51
+ /**
52
+ * Write an invoke-rest result to a file as pretty-printed JSON — the
53
+ * first-class landing spot for large responses (piped stdout truncates at the
54
+ * OS pipe buffer if the process exits before draining; see the CLI's
55
+ * flush-aware exit). ATOMIC: writes to a same-directory temp file then
56
+ * renames, so a killed process leaves either the old file or the new one —
57
+ * never a silently-truncated hybrid. Overwrites an existing file by design
58
+ * (documented in --help). The parent directory must already exist; a missing
59
+ * or unwritable directory throws with the underlying error so the CLI can
60
+ * exit 1 loudly instead of "succeeding" with no file.
61
+ */
62
+ export declare function writeInvokeRestResultFile(outPath: string, result: unknown): void;
@@ -17,9 +17,46 @@
17
17
  * only in the returned result. Any log line about an invocation elsewhere must
18
18
  * carry method, path and status ONLY.
19
19
  */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
20
53
  Object.defineProperty(exports, "__esModule", { value: true });
21
54
  exports.INVOKE_REST_METHODS = void 0;
22
55
  exports.invokeRest = invokeRest;
56
+ exports.writeInvokeRestResultFile = writeInvokeRestResultFile;
57
+ const fs = __importStar(require("fs"));
58
+ // Aliased: `path` is the instance-relative REST path throughout this module.
59
+ const nodePath = __importStar(require("path"));
23
60
  const client_1 = require("./client");
24
61
  exports.INVOKE_REST_METHODS = ["GET", "POST", "PUT", "DELETE"];
25
62
  function normalizeMethod(method) {
@@ -91,3 +128,33 @@ async function invokeRest(params) {
91
128
  }
92
129
  return result;
93
130
  }
131
+ /**
132
+ * Write an invoke-rest result to a file as pretty-printed JSON — the
133
+ * first-class landing spot for large responses (piped stdout truncates at the
134
+ * OS pipe buffer if the process exits before draining; see the CLI's
135
+ * flush-aware exit). ATOMIC: writes to a same-directory temp file then
136
+ * renames, so a killed process leaves either the old file or the new one —
137
+ * never a silently-truncated hybrid. Overwrites an existing file by design
138
+ * (documented in --help). The parent directory must already exist; a missing
139
+ * or unwritable directory throws with the underlying error so the CLI can
140
+ * exit 1 loudly instead of "succeeding" with no file.
141
+ */
142
+ function writeInvokeRestResultFile(outPath, result) {
143
+ var dir = nodePath.dirname(outPath);
144
+ var tempPath = nodePath.join(dir, "." + nodePath.basename(outPath) + ".tmp-" + process.pid + "-" + Date.now());
145
+ var payload = JSON.stringify(result, null, 2) + "\n";
146
+ try {
147
+ fs.writeFileSync(tempPath, payload, { encoding: "utf8" });
148
+ fs.renameSync(tempPath, outPath);
149
+ }
150
+ catch (err) {
151
+ // Best-effort temp cleanup so a failed write leaves no residue.
152
+ try {
153
+ fs.unlinkSync(tempPath);
154
+ }
155
+ catch (cleanupErr) {
156
+ // The temp file never landed — nothing to clean.
157
+ }
158
+ throw err;
159
+ }
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-servicenow",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },