@tenonhq/dovetail-core 0.0.101 → 0.0.103

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/snClient.js CHANGED
@@ -111,6 +111,22 @@ function _enrichDovetailApiError(e, attemptedEndpoint) {
111
111
  hint;
112
112
  return e;
113
113
  }
114
+ // True when an error indicates the Dovetail Scripted REST endpoint does not
115
+ // exist on this instance (404, or the 400 "Requested URI does not represent any
116
+ // resource" body). Used to degrade createUpdateSet to the Table API path on
117
+ // instances that do not have the createUpdateSet op deployed yet.
118
+ function _isMissingDovetailEndpoint(e) {
119
+ if (!axios_1.default.isAxiosError(e))
120
+ return false;
121
+ var status = e.response ? e.response.status : undefined;
122
+ if (status === 404)
123
+ return true;
124
+ if (status === 400) {
125
+ var body = e.response ? _summarizeResponseBody(e.response.data) : "";
126
+ return body.indexOf(_SN_MISSING_ENDPOINT_BODY) !== -1;
127
+ }
128
+ return false;
129
+ }
114
130
  async function _callDovetailApi(op, call) {
115
131
  var endpoint = _dovetailEndpoint(op);
116
132
  try {
@@ -353,8 +369,12 @@ const snClient = (baseURL, username, password) => {
353
369
  const endpoint = "api/cadso/dovetail_sync/getCurrentScope";
354
370
  return client.get(endpoint);
355
371
  };
356
- const createUpdateSet = (updateSetName, scopeSysId, description) => {
357
- const endpoint = `api/now/table/sys_update_set`;
372
+ const createUpdateSet = async (updateSetName, scopeSysId, description) => {
373
+ // Prefer the scope-safe Dovetail server op. It switches the current
374
+ // application and inserts the set in one server call, so the set always
375
+ // lands in `scopeSysId`. A raw Table API POST ignores `application` (the
376
+ // field defaults to the session's current app), and the old
377
+ // changeScope-then-POST path raced and mis-scoped sets created back to back.
358
378
  const data = {
359
379
  name: updateSetName,
360
380
  state: "in progress",
@@ -365,7 +385,39 @@ const snClient = (baseURL, username, password) => {
365
385
  if (description) {
366
386
  data.description = description;
367
387
  }
368
- return client.post(endpoint, data);
388
+ try {
389
+ const resp = await _callDovetailApi("createUpdateSet", (endpoint) => client.post(endpoint, data));
390
+ const payload = resp.data || {};
391
+ const sysId = payload.sys_id || (payload.result && payload.result.sys_id);
392
+ if (!sysId) {
393
+ throw new Error("createUpdateSet endpoint returned no sys_id");
394
+ }
395
+ // Normalize to the Table-API shape that unwrapSNResponse expects
396
+ // (resp.data.result), so callers stay unchanged.
397
+ return {
398
+ ...resp,
399
+ data: {
400
+ result: {
401
+ sys_id: sysId,
402
+ name: payload.name || updateSetName,
403
+ application: payload.application,
404
+ },
405
+ },
406
+ };
407
+ }
408
+ catch (e) {
409
+ if (!_isMissingDovetailEndpoint(e)) {
410
+ throw e;
411
+ }
412
+ // Instance without the createUpdateSet op yet: degrade to the legacy
413
+ // Table API path so existing flows keep working during rollout. Sets
414
+ // created this way may be mis-scoped until the op is deployed.
415
+ Logger_1.logger.warn("Dovetail createUpdateSet endpoint not found on this instance; falling " +
416
+ "back to the Table API. Update sets may be mis-scoped until the " +
417
+ "Dovetail createUpdateSet REST op is installed on this instance.");
418
+ const endpoint = `api/now/table/sys_update_set`;
419
+ return client.post(endpoint, data);
420
+ }
369
421
  };
370
422
  const getCurrentUpdateSetUserPref = (userSysId) => {
371
423
  const endpoint = `api/now/table/sys_user_preference`;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ // Tests for snClient.createUpdateSet: it must prefer the scope-safe Dovetail
3
+ // `createUpdateSet` server op (normalizing its {sys_id,...} body to the
4
+ // {result:{...}} shape unwrapSNResponse expects), and fall back to the raw
5
+ // Table API ONLY when the endpoint is missing (404 / 400 "Requested URI ...").
6
+ // A 500 or an unrelated 400 must NOT trigger the fallback.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ var mockLogger = {
9
+ info: jest.fn(),
10
+ debug: jest.fn(),
11
+ warn: jest.fn(),
12
+ error: jest.fn(),
13
+ getLogLevel: function () { return "debug"; },
14
+ };
15
+ jest.mock("../Logger", function () {
16
+ return { logger: mockLogger };
17
+ });
18
+ jest.mock("../FileLogger", function () {
19
+ return { fileLogger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() } };
20
+ });
21
+ jest.mock("../genericUtils", function () {
22
+ return { wait: jest.fn().mockResolvedValue(undefined) };
23
+ });
24
+ const snClient_1 = require("../snClient");
25
+ function makeAxiosError(status, data) {
26
+ var error = new Error("Request failed with status " + status);
27
+ error.isAxiosError = true;
28
+ error.response = { status: status, headers: {}, data: data === undefined ? {} : data };
29
+ return error;
30
+ }
31
+ function makeAxiosResponse(data) {
32
+ return { status: 200, data: data, headers: {}, statusText: "OK", config: {} };
33
+ }
34
+ var MISSING_ENDPOINT_BODY = {
35
+ error: { message: "Requested URI does not represent any resource", detail: null },
36
+ status: "failure",
37
+ };
38
+ describe("snClient.createUpdateSet", function () {
39
+ beforeEach(function () {
40
+ (0, snClient_1._resetDovetailApiFallback)();
41
+ jest.clearAllMocks();
42
+ });
43
+ it("calls the createUpdateSet endpoint and normalizes the response to {result}", async function () {
44
+ var c = (0, snClient_1.snClient)("https://x.service-now.com/", "u", "p");
45
+ var post = jest.spyOn(c.client, "post").mockImplementation(async function () {
46
+ return makeAxiosResponse({ success: true, sys_id: "abc123", name: "My Set", application: "scopeSys" });
47
+ });
48
+ var resp = await c.createUpdateSet("My Set", "scopeSys", "a description");
49
+ expect(post).toHaveBeenCalledTimes(1);
50
+ expect(post.mock.calls[0][0]).toContain("dovetail_core/createUpdateSet");
51
+ expect(post.mock.calls[0][1]).toMatchObject({
52
+ name: "My Set",
53
+ application: "scopeSys",
54
+ description: "a description",
55
+ state: "in progress",
56
+ });
57
+ expect(resp.data.result.sys_id).toBe("abc123");
58
+ expect(resp.data.result.application).toBe("scopeSys");
59
+ });
60
+ it("falls back to the Table API when the endpoint 404s", async function () {
61
+ var c = (0, snClient_1.snClient)("https://x.service-now.com/", "u", "p");
62
+ var urls = [];
63
+ jest.spyOn(c.client, "post").mockImplementation(async function (url) {
64
+ urls.push(url);
65
+ if (url.indexOf("dovetail") !== -1) {
66
+ throw makeAxiosError(404, {});
67
+ }
68
+ return makeAxiosResponse({ result: { sys_id: "tbl1", name: "My Set" } });
69
+ });
70
+ var resp = await c.createUpdateSet("My Set", "scopeSys");
71
+ // dovetail_core -> legacy dovetail (404 fallback) -> Table API
72
+ expect(urls[urls.length - 1]).toBe("api/now/table/sys_update_set");
73
+ expect(resp.data.result.sys_id).toBe("tbl1");
74
+ expect(mockLogger.warn).toHaveBeenCalled();
75
+ });
76
+ it("falls back to the Table API on the 400 missing-endpoint body", async function () {
77
+ var c = (0, snClient_1.snClient)("https://x.service-now.com/", "u", "p");
78
+ var urls = [];
79
+ jest.spyOn(c.client, "post").mockImplementation(async function (url) {
80
+ urls.push(url);
81
+ if (url.indexOf("dovetail") !== -1) {
82
+ throw makeAxiosError(400, MISSING_ENDPOINT_BODY);
83
+ }
84
+ return makeAxiosResponse({ result: { sys_id: "tbl2", name: "My Set" } });
85
+ });
86
+ var resp = await c.createUpdateSet("My Set", "scopeSys");
87
+ expect(urls[urls.length - 1]).toBe("api/now/table/sys_update_set");
88
+ expect(resp.data.result.sys_id).toBe("tbl2");
89
+ });
90
+ it("does NOT fall back on an unrelated 400 (rethrows)", async function () {
91
+ var c = (0, snClient_1.snClient)("https://x.service-now.com/", "u", "p");
92
+ var tableHit = false;
93
+ jest.spyOn(c.client, "post").mockImplementation(async function (url) {
94
+ if (url.indexOf("dovetail") !== -1) {
95
+ throw makeAxiosError(400, { error: { message: "Invalid update set name" } });
96
+ }
97
+ tableHit = true;
98
+ return makeAxiosResponse({ result: { sys_id: "should-not-happen" } });
99
+ });
100
+ await expect(c.createUpdateSet("My Set", "scopeSys")).rejects.toBeDefined();
101
+ expect(tableHit).toBe(false);
102
+ });
103
+ it("does NOT fall back on a 500 (rethrows)", async function () {
104
+ var c = (0, snClient_1.snClient)("https://x.service-now.com/", "u", "p");
105
+ var tableHit = false;
106
+ jest.spyOn(c.client, "post").mockImplementation(async function (url) {
107
+ if (url.indexOf("dovetail") !== -1) {
108
+ throw makeAxiosError(500, { error: { message: "boom" } });
109
+ }
110
+ tableHit = true;
111
+ return makeAxiosResponse({ result: { sys_id: "should-not-happen" } });
112
+ });
113
+ await expect(c.createUpdateSet("My Set", "scopeSys")).rejects.toBeDefined();
114
+ expect(tableHit).toBe(false);
115
+ });
116
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-core",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "node": ">=22"
23
23
  },
24
24
  "devDependencies": {
25
- "@tenonhq/dovetail-types": "^0.0.17",
25
+ "@tenonhq/dovetail-types": "~0.0.19",
26
26
  "@types/dotenv": "^6.1.1",
27
27
  "@types/inquirer": "^8.2.10",
28
28
  "@types/jest": "^29.5.5",
@@ -38,8 +38,8 @@
38
38
  "typescript": "^5.2.2"
39
39
  },
40
40
  "dependencies": {
41
- "@tenonhq/dovetail-clickup": "^0.0.10",
42
- "@tenonhq/dovetail-schema": "^0.0.10",
41
+ "@tenonhq/dovetail-clickup": "~0.0.12",
42
+ "@tenonhq/dovetail-schema": "~0.0.11",
43
43
  "axios": "^1.5.1",
44
44
  "axios-cookiejar-support": "^4.0.7",
45
45
  "axios-rate-limit": "^1.3.0",
@@ -1,7 +1,167 @@
1
1
  {
2
2
  "$schema": "dovetail-manifest/v1",
3
- "generated_at": "2026-06-02T21:39:46.696Z",
3
+ "generated_at": "2026-06-04T22:16:51.168Z",
4
4
  "events": [
5
+ {
6
+ "$schema": "dovetail-release-event/v1",
7
+ "event_id": "dovetail-servicenow@0.0.15",
8
+ "package": "@tenonhq/dovetail-servicenow",
9
+ "version": "0.0.15",
10
+ "prev_version": "0.0.14",
11
+ "published_at": "2026-06-04T22:16:51.160Z",
12
+ "git": {
13
+ "sha": "d46b5bd2dce0fb3b13f0d1d5933f88d1d421b550",
14
+ "tag": "dovetail-servicenow@0.0.15"
15
+ },
16
+ "semver_bump": "patch",
17
+ "commits": [
18
+ {
19
+ "sha": "d46b5bd2d",
20
+ "subject": "feat(servicenow): dove-sn edit-action — headless action-type script/output edit (#147)",
21
+ "pr": 147
22
+ },
23
+ {
24
+ "sha": "af1417796",
25
+ "subject": "chore(release): publish 3 packages [skip ci]"
26
+ }
27
+ ],
28
+ "exports_delta": null,
29
+ "narrative": null
30
+ },
31
+ {
32
+ "$schema": "dovetail-release-event/v1",
33
+ "event_id": "dovetail-claude-plans@0.0.21",
34
+ "package": "@tenonhq/dovetail-claude-plans",
35
+ "version": "0.0.21",
36
+ "prev_version": "0.0.20",
37
+ "published_at": "2026-06-04T08:56:53.804Z",
38
+ "git": {
39
+ "sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
40
+ "tag": "dovetail-claude-plans@0.0.21"
41
+ },
42
+ "semver_bump": "patch",
43
+ "commits": [
44
+ {
45
+ "sha": "44e5c84b8",
46
+ "subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
47
+ "pr": 145
48
+ },
49
+ {
50
+ "sha": "92213e1f8",
51
+ "subject": "chore(release): publish 2 packages [skip ci]"
52
+ }
53
+ ],
54
+ "exports_delta": null,
55
+ "narrative": null
56
+ },
57
+ {
58
+ "$schema": "dovetail-release-event/v1",
59
+ "event_id": "dovetail-servicenow@0.0.14",
60
+ "package": "@tenonhq/dovetail-servicenow",
61
+ "version": "0.0.14",
62
+ "prev_version": "0.0.13",
63
+ "published_at": "2026-06-04T08:56:53.804Z",
64
+ "git": {
65
+ "sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
66
+ "tag": "dovetail-servicenow@0.0.14"
67
+ },
68
+ "semver_bump": "patch",
69
+ "commits": [
70
+ {
71
+ "sha": "44e5c84b8",
72
+ "subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
73
+ "pr": 145
74
+ },
75
+ {
76
+ "sha": "e0cf83a8c",
77
+ "subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.13 [skip ci]"
78
+ }
79
+ ],
80
+ "exports_delta": null,
81
+ "narrative": null
82
+ },
83
+ {
84
+ "$schema": "dovetail-release-event/v1",
85
+ "event_id": "dovetail-mcp@0.0.8",
86
+ "package": "@tenonhq/dovetail-mcp",
87
+ "version": "0.0.8",
88
+ "prev_version": "0.0.7",
89
+ "published_at": "2026-06-04T08:56:53.804Z",
90
+ "git": {
91
+ "sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
92
+ "tag": "dovetail-mcp@0.0.8"
93
+ },
94
+ "semver_bump": "patch",
95
+ "commits": [
96
+ {
97
+ "sha": "44e5c84b8",
98
+ "subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
99
+ "pr": 145
100
+ },
101
+ {
102
+ "sha": "bd127c726",
103
+ "subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.12 [skip ci]"
104
+ },
105
+ {
106
+ "sha": "448696846",
107
+ "subject": "chore(release): publish 3 packages [skip ci]"
108
+ }
109
+ ],
110
+ "exports_delta": null,
111
+ "narrative": null
112
+ },
113
+ {
114
+ "$schema": "dovetail-release-event/v1",
115
+ "event_id": "dovetail-core@0.0.102",
116
+ "package": "@tenonhq/dovetail-core",
117
+ "version": "0.0.102",
118
+ "prev_version": "0.0.101",
119
+ "published_at": "2026-06-04T08:12:34.612Z",
120
+ "git": {
121
+ "sha": "1b4b8aa7bf4e8d22a9b0dae4ce0ffa624ea49da3",
122
+ "tag": "dovetail-core@0.0.102"
123
+ },
124
+ "semver_bump": "patch",
125
+ "commits": [
126
+ {
127
+ "sha": "1b4b8aa7b",
128
+ "subject": "fix(core): create update sets via the scope-safe createUpdateSet op (#146)",
129
+ "pr": 146
130
+ },
131
+ {
132
+ "sha": "c098f8782",
133
+ "subject": "chore(release): @tenonhq/dovetail-core@0.0.101 [skip ci]"
134
+ }
135
+ ],
136
+ "exports_delta": null,
137
+ "narrative": null
138
+ },
139
+ {
140
+ "$schema": "dovetail-release-event/v1",
141
+ "event_id": "dovetail-core@0.0.101",
142
+ "package": "@tenonhq/dovetail-core",
143
+ "version": "0.0.101",
144
+ "prev_version": "0.0.100",
145
+ "published_at": "2026-06-04T07:05:38.971Z",
146
+ "git": {
147
+ "sha": "7c78a10933956739a7a234e48362cd8dc0b7ad71",
148
+ "tag": "dovetail-core@0.0.101"
149
+ },
150
+ "semver_bump": "patch",
151
+ "commits": [
152
+ {
153
+ "sha": "7c78a1093",
154
+ "subject": "fix(core): bump dovetail-schema pin ^0.0.9 -> ^0.0.10 (#142)",
155
+ "pr": 142
156
+ },
157
+ {
158
+ "sha": "6d7b59a14",
159
+ "subject": "chore(release): @tenonhq/dovetail-core@0.0.100 [skip ci]"
160
+ }
161
+ ],
162
+ "exports_delta": null,
163
+ "narrative": null
164
+ },
5
165
  {
6
166
  "$schema": "dovetail-release-event/v1",
7
167
  "event_id": "dovetail-core@0.0.100",