@tenonhq/dovetail-core 0.0.112 → 0.0.114
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/appUtils.js +56 -1
- package/dist/commander.js +5 -0
- package/dist/createRecordCommand.js +26 -13
- package/dist/tests/dovetailUtilsMSCollision.test.js +232 -0
- package/dist/tests/syncManifestRecord.test.js +234 -0
- package/package.json +1 -1
- package/release-manifest.json +241 -293
package/dist/appUtils.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.refreshAllFiles = exports.findMissingFiles = exports.syncManifest = exports.narrowManifestToTables = exports.processManifest = exports.normalizeManifestKeys = exports.toSafeFolderName = exports.stampMetadataContent = void 0;
|
|
39
|
+
exports.createAndAssignUpdateSet = exports.swapScope = exports.buildFiles = exports.summarizeRecord = exports.pushFiles = exports.getAppFileList = exports.groupAppFiles = exports.processMissingFiles = exports.refreshAllFiles = exports.findMissingFiles = exports.syncManifest = exports.narrowManifestToRecord = exports.narrowManifestToTables = exports.processManifest = exports.normalizeManifestKeys = exports.toSafeFolderName = exports.stampMetadataContent = void 0;
|
|
40
40
|
const path_1 = __importDefault(require("path"));
|
|
41
41
|
const progress_1 = __importDefault(require("progress"));
|
|
42
42
|
const fUtils = __importStar(require("./FileUtils"));
|
|
@@ -298,6 +298,44 @@ const narrowManifestToTables = (manifest, tables) => {
|
|
|
298
298
|
return Object.assign({}, manifest, { tables: wantedTables });
|
|
299
299
|
};
|
|
300
300
|
exports.narrowManifestToTables = narrowManifestToTables;
|
|
301
|
+
/**
|
|
302
|
+
* Narrow a manifest's table map to a SINGLE record (matched by `table` +
|
|
303
|
+
* `sysId`), for the FILE refresh only.
|
|
304
|
+
*
|
|
305
|
+
* Returns a NEW manifest and NEVER mutates its input — same safety contract as
|
|
306
|
+
* `narrowManifestToTables`: the caller keeps writing the original, full manifest
|
|
307
|
+
* to disk, so the on-disk `dove.manifest.<scope>.json` never loses its other
|
|
308
|
+
* tables/records. Only the file download is scoped to this one record.
|
|
309
|
+
*
|
|
310
|
+
* If the table or a record with that sys_id is absent from the manifest, the
|
|
311
|
+
* returned manifest has an empty table map — refreshAllFiles no-ops on it, so no
|
|
312
|
+
* files are written (the create flow warns and points the user at `dove refresh`).
|
|
313
|
+
*/
|
|
314
|
+
const narrowManifestToRecord = (manifest, table, sysId) => {
|
|
315
|
+
var empty = Object.assign({}, manifest, { tables: {} });
|
|
316
|
+
var tables = manifest.tables || {};
|
|
317
|
+
var tableEntry = tables[table];
|
|
318
|
+
if (!tableEntry || !tableEntry.records)
|
|
319
|
+
return empty;
|
|
320
|
+
var records = tableEntry.records;
|
|
321
|
+
var recKeys = Object.keys(records);
|
|
322
|
+
var wantedRecords = {};
|
|
323
|
+
// sys_id is unique per table, so stop at the first match — the record set for
|
|
324
|
+
// a large table can be sizable and this is the exact scenario the feature targets.
|
|
325
|
+
for (var i = 0; i < recKeys.length; i++) {
|
|
326
|
+
var rec = records[recKeys[i]];
|
|
327
|
+
if (rec && rec.sys_id === sysId) {
|
|
328
|
+
wantedRecords[recKeys[i]] = rec;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (Object.keys(wantedRecords).length === 0)
|
|
333
|
+
return empty;
|
|
334
|
+
var narrowedTables = {};
|
|
335
|
+
narrowedTables[table] = Object.assign({}, tableEntry, { records: wantedRecords });
|
|
336
|
+
return Object.assign({}, manifest, { tables: narrowedTables });
|
|
337
|
+
};
|
|
338
|
+
exports.narrowManifestToRecord = narrowManifestToRecord;
|
|
301
339
|
const syncManifest = async (scope, options = {}) => {
|
|
302
340
|
// Top-level entry owns the collector lifecycle. Recursive calls (all-scopes
|
|
303
341
|
// → per-scope) inherit the collector via options._benchmarkCollector.
|
|
@@ -368,6 +406,23 @@ const syncManifest = async (scope, options = {}) => {
|
|
|
368
406
|
// so intersecting the manifest is enough. `newManifest` is left intact —
|
|
369
407
|
// see narrowManifestToTables.
|
|
370
408
|
var refreshManifest = (0, exports.narrowManifestToTables)(newManifest, options.tables);
|
|
409
|
+
// Single-record narrowing (dove create round-trip). Applied after the
|
|
410
|
+
// --table narrowing so the two compose; the full manifest was already
|
|
411
|
+
// written above, so only the file download is scoped to the one record.
|
|
412
|
+
if (options.record) {
|
|
413
|
+
refreshManifest = (0, exports.narrowManifestToRecord)(refreshManifest, options.record.table, options.record.sysId);
|
|
414
|
+
if (Object.keys(refreshManifest.tables).length === 0) {
|
|
415
|
+
Logger_1.logger.warn("Created record " + options.record.sysId + " (" + options.record.table +
|
|
416
|
+
") not found in the refreshed scope manifest — local files not written. " +
|
|
417
|
+
"If '" + options.record.table + "' is missing from this scope's `_tables` " +
|
|
418
|
+
"whitelist in dove.config.js, add it there first; otherwise run " +
|
|
419
|
+
"'npx dove refresh' to pull it.");
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
FileLogger_1.fileLogger.debug("syncManifest: narrowed file refresh to single record " +
|
|
423
|
+
options.record.sysId + " (" + options.record.table + ")");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
371
426
|
if (options.tables && options.tables.length > 0) {
|
|
372
427
|
var wantedNames = Object.keys(refreshManifest.tables);
|
|
373
428
|
if (wantedNames.length === 0) {
|
package/dist/commander.js
CHANGED
|
@@ -228,6 +228,11 @@ function configureCli(cli) {
|
|
|
228
228
|
default: false,
|
|
229
229
|
describe: "Skip interactive prompts",
|
|
230
230
|
},
|
|
231
|
+
refresh: {
|
|
232
|
+
type: "boolean",
|
|
233
|
+
default: true,
|
|
234
|
+
describe: "Pull the created record's files to disk afterward (default: true; --no-refresh skips the local sync)",
|
|
235
|
+
},
|
|
231
236
|
});
|
|
232
237
|
return cmdArgs;
|
|
233
238
|
}, createRecordCommand_1.createRecordCommand)
|
|
@@ -281,20 +281,33 @@ async function createRecordCommand(args) {
|
|
|
281
281
|
" (" +
|
|
282
282
|
newSysId +
|
|
283
283
|
")");
|
|
284
|
-
// 7.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
var sourcePath = ConfigManager.getSourcePath();
|
|
290
|
-
var localPath = path_1.default.join(sourcePath, table, recordName);
|
|
291
|
-
Logger_1.logger.success(chalk_1.default.green("Local files created at: ") + localPath);
|
|
284
|
+
// 7. Round-trip: pull ONLY the new record's files back locally. Scoped to
|
|
285
|
+
// { table, sysId } so creating one record no longer refreshes the entire
|
|
286
|
+
// scope (the whole-scope-churn footgun). --no-refresh skips it entirely.
|
|
287
|
+
if (typedArgs.refresh === false) {
|
|
288
|
+
Logger_1.logger.info("Skipping local sync (--no-refresh). Run 'npx dove refresh' to pull the record locally.");
|
|
292
289
|
}
|
|
293
|
-
|
|
294
|
-
Logger_1.logger.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
else {
|
|
291
|
+
Logger_1.logger.info("Syncing record to local files...");
|
|
292
|
+
FileLogger_1.fileLogger.debug("Starting single-record sync for scope:", scope);
|
|
293
|
+
try {
|
|
294
|
+
await AppUtils.syncManifest(scope, {
|
|
295
|
+
record: { table: table, sysId: newSysId },
|
|
296
|
+
});
|
|
297
|
+
// Resolve the scope's own source directory — syncManifest writes into
|
|
298
|
+
// getSourcePathForScope(scope), which a scope config can override. Using
|
|
299
|
+
// the top-level getSourcePath() here would print a wrong path in
|
|
300
|
+
// multi-scope setups even though the files landed correctly.
|
|
301
|
+
var sourcePath = ConfigManager.getSourcePathForScope(scope);
|
|
302
|
+
var localPath = path_1.default.join(sourcePath, table, recordName);
|
|
303
|
+
Logger_1.logger.success(chalk_1.default.green("Local files created at: ") + localPath);
|
|
304
|
+
}
|
|
305
|
+
catch (syncErr) {
|
|
306
|
+
Logger_1.logger.warn("Record created on instance but local sync failed.");
|
|
307
|
+
Logger_1.logger.warn("Run 'npx dove refresh' to pull the record locally.");
|
|
308
|
+
if (syncErr instanceof Error) {
|
|
309
|
+
FileLogger_1.fileLogger.error("Sync error:", syncErr.message);
|
|
310
|
+
}
|
|
298
311
|
}
|
|
299
312
|
}
|
|
300
313
|
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests the SERVER half of the sync engine — servicenow/dovetail/sys_script_include/
|
|
4
|
+
* DovetailUtilsMS.js — against the record-name collision that silently dropped records.
|
|
5
|
+
*
|
|
6
|
+
* Before the fix, `records` was keyed by display name, so two records sharing a name
|
|
7
|
+
* collapsed into one entry: one vanished from the manifest with no warning, and because
|
|
8
|
+
* the survivor kept the shared folder, a later push to that folder wrote to the WRONG
|
|
9
|
+
* record.
|
|
10
|
+
*
|
|
11
|
+
* This loads the real script include into a sandbox with minimal ServiceNow stubs
|
|
12
|
+
* (Class / GlideRecord / gs) rather than re-implementing the logic, so the test fails if
|
|
13
|
+
* the deployed source regresses. There is no ATF/SN harness in this repo, and this code
|
|
14
|
+
* is deployed to a shared instance — so this is the only gate it gets before deploy.
|
|
15
|
+
*
|
|
16
|
+
* getFileMap() short-circuits when `includes[table]` carries explicit field overrides,
|
|
17
|
+
* which is why no sys_dictionary / TableUtils stub is needed here.
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
const fs_1 = __importDefault(require("fs"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const vm_1 = __importDefault(require("vm"));
|
|
26
|
+
const SOURCE = path_1.default.join(__dirname, "../../../../servicenow/dovetail/sys_script_include/DovetailUtilsMS.js");
|
|
27
|
+
/** Minimal GlideRecord over a fixture list. Only what buildTableMap and processMissingFiles touch. */
|
|
28
|
+
function makeGlideRecord(rows) {
|
|
29
|
+
return function GlideRecordStub(_table) {
|
|
30
|
+
var i = -1;
|
|
31
|
+
this.get = function (sysId) {
|
|
32
|
+
i = rows.findIndex(function (r) {
|
|
33
|
+
return r.sys_id === sysId;
|
|
34
|
+
});
|
|
35
|
+
return i !== -1;
|
|
36
|
+
};
|
|
37
|
+
this.addQuery = function () {
|
|
38
|
+
return { addOrCondition: function () { } };
|
|
39
|
+
};
|
|
40
|
+
this.addEncodedQuery = function () { };
|
|
41
|
+
this.query = function () {
|
|
42
|
+
i = -1;
|
|
43
|
+
};
|
|
44
|
+
this.next = function () {
|
|
45
|
+
i += 1;
|
|
46
|
+
return i < rows.length;
|
|
47
|
+
};
|
|
48
|
+
this.getValue = function (field) {
|
|
49
|
+
return rows[i][field];
|
|
50
|
+
};
|
|
51
|
+
this.getDisplayValue = function (field) {
|
|
52
|
+
// No arg → the record's display value (the name). This is what
|
|
53
|
+
// generateRecordName() uses, and it is where the collision comes from.
|
|
54
|
+
return field ? rows[i][field] : rows[i].name;
|
|
55
|
+
};
|
|
56
|
+
this.getElements = function () {
|
|
57
|
+
return null; // metaData capture is skipped; getContents is false below
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function loadUtils(rows) {
|
|
62
|
+
const warnings = [];
|
|
63
|
+
const sandbox = {
|
|
64
|
+
Class: {
|
|
65
|
+
create: function () {
|
|
66
|
+
return function () {
|
|
67
|
+
if (this.initialize)
|
|
68
|
+
this.initialize.apply(this, arguments);
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
GlideRecord: makeGlideRecord(rows),
|
|
73
|
+
gs: {
|
|
74
|
+
warn: function (msg) {
|
|
75
|
+
warnings.push(msg);
|
|
76
|
+
},
|
|
77
|
+
getProperty: function () {
|
|
78
|
+
return "https://example.service-now.com/";
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
vm_1.default.createContext(sandbox);
|
|
83
|
+
vm_1.default.runInContext(fs_1.default.readFileSync(SOURCE, "utf8"), sandbox);
|
|
84
|
+
return { utils: new sandbox.DovetailUtilsMS(), warnings: warnings };
|
|
85
|
+
}
|
|
86
|
+
function buildMap(rows) {
|
|
87
|
+
const loaded = loadUtils(rows);
|
|
88
|
+
const result = loaded.utils.buildTableMap({
|
|
89
|
+
tableName: "x_cadso_journey_action",
|
|
90
|
+
scopeId: "scope1",
|
|
91
|
+
getContents: false,
|
|
92
|
+
// Explicit field override → getFileMap short-circuits (no sys_dictionary stub).
|
|
93
|
+
includes: { x_cadso_journey_action: { script: { type: "js" } } },
|
|
94
|
+
excludes: {},
|
|
95
|
+
tableOptions: {},
|
|
96
|
+
});
|
|
97
|
+
return { records: result.records, warnings: loaded.warnings };
|
|
98
|
+
}
|
|
99
|
+
describe("DovetailUtilsMS.buildTableMap — duplicate display names", () => {
|
|
100
|
+
it("keeps BOTH records when two share a display name (regression: one was silently dropped)", () => {
|
|
101
|
+
const out = buildMap([
|
|
102
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint" },
|
|
103
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint" },
|
|
104
|
+
]);
|
|
105
|
+
const keys = Object.keys(out.records);
|
|
106
|
+
expect(keys).toHaveLength(2); // was 1 — the second overwrote the first
|
|
107
|
+
const sysIds = keys.map((k) => out.records[k].sys_id).sort();
|
|
108
|
+
expect(sysIds).toEqual([
|
|
109
|
+
"1607d7f0c373f2d0d4ddf1db05013101",
|
|
110
|
+
"46ae2decc33f32d0d4ddf1db050131d9",
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
it("suffixes EVERY member of a colliding set, so no record arbitrarily keeps the bare name", () => {
|
|
114
|
+
// If only the later duplicate were suffixed, which record kept "Blueprint" would
|
|
115
|
+
// depend on query order — and folder names would churn between refreshes.
|
|
116
|
+
const out = buildMap([
|
|
117
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint" },
|
|
118
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint" },
|
|
119
|
+
]);
|
|
120
|
+
expect(Object.keys(out.records).sort()).toEqual([
|
|
121
|
+
"Blueprint (1607d7f0)",
|
|
122
|
+
"Blueprint (46ae2dec)",
|
|
123
|
+
]);
|
|
124
|
+
expect(out.records["Blueprint"]).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
it("keeps record.name identical to the map key (writers build the folder path from it)", () => {
|
|
127
|
+
const out = buildMap([
|
|
128
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint" },
|
|
129
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint" },
|
|
130
|
+
]);
|
|
131
|
+
Object.keys(out.records).forEach((key) => {
|
|
132
|
+
expect(out.records[key].name).toBe(key);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
it("warns on a collision instead of dropping silently", () => {
|
|
136
|
+
const out = buildMap([
|
|
137
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint" },
|
|
138
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint" },
|
|
139
|
+
]);
|
|
140
|
+
expect(out.warnings).toHaveLength(2);
|
|
141
|
+
expect(out.warnings[0]).toContain("duplicate display name");
|
|
142
|
+
expect(out.warnings[0]).toContain("Blueprint");
|
|
143
|
+
});
|
|
144
|
+
it("leaves unique names completely untouched (no suffix, no warning)", () => {
|
|
145
|
+
const out = buildMap([
|
|
146
|
+
{ sys_id: "dc2ef56133a0e2507b18bc534d5c7bf5", name: "Send Email" },
|
|
147
|
+
{ sys_id: "0d4626f933e826507b18bc534d5c7b37", name: "Send Text" },
|
|
148
|
+
]);
|
|
149
|
+
expect(Object.keys(out.records).sort()).toEqual(["Send Email", "Send Text"]);
|
|
150
|
+
expect(out.warnings).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
it("handles three-way collisions", () => {
|
|
153
|
+
const out = buildMap([
|
|
154
|
+
{ sys_id: "aaaaaaaa11111111", name: "Dup" },
|
|
155
|
+
{ sys_id: "bbbbbbbb22222222", name: "Dup" },
|
|
156
|
+
{ sys_id: "cccccccc33333333", name: "Dup" },
|
|
157
|
+
]);
|
|
158
|
+
expect(Object.keys(out.records).sort()).toEqual([
|
|
159
|
+
"Dup (aaaaaaaa)",
|
|
160
|
+
"Dup (bbbbbbbb)",
|
|
161
|
+
"Dup (cccccccc)",
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
it("disambiguates only the colliding set, not the whole table", () => {
|
|
165
|
+
const out = buildMap([
|
|
166
|
+
{ sys_id: "aaaaaaaa11111111", name: "Blueprint" },
|
|
167
|
+
{ sys_id: "bbbbbbbb22222222", name: "Blueprint" },
|
|
168
|
+
{ sys_id: "cccccccc33333333", name: "Send Email" },
|
|
169
|
+
]);
|
|
170
|
+
expect(Object.keys(out.records).sort()).toEqual([
|
|
171
|
+
"Blueprint (aaaaaaaa)",
|
|
172
|
+
"Blueprint (bbbbbbbb)",
|
|
173
|
+
"Send Email", // untouched — this is why differentiatorField was the wrong tool
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* bulkDownload keys its response by display name too — the same collapse dropped the
|
|
179
|
+
* file CONTENT for one of a colliding pair even once the manifest listed both.
|
|
180
|
+
*/
|
|
181
|
+
function processMissing(rows) {
|
|
182
|
+
const loaded = loadUtils(rows);
|
|
183
|
+
const recordMap = {};
|
|
184
|
+
rows.forEach((r) => {
|
|
185
|
+
recordMap[r.sys_id] = [{ name: "script", type: "js" }];
|
|
186
|
+
});
|
|
187
|
+
const result = loaded.utils.processMissingFiles({ x_cadso_journey_action: recordMap }, {});
|
|
188
|
+
return {
|
|
189
|
+
records: result.x_cadso_journey_action.records,
|
|
190
|
+
warnings: loaded.warnings,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
describe("DovetailUtilsMS.processMissingFiles — duplicate display names", () => {
|
|
194
|
+
it("keeps BOTH records' content when two share a display name (regression: one payload was silently dropped)", () => {
|
|
195
|
+
const out = processMissing([
|
|
196
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint", script: "var a = 1;" },
|
|
197
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint", script: "var b = 2;" },
|
|
198
|
+
]);
|
|
199
|
+
const keys = Object.keys(out.records).sort();
|
|
200
|
+
expect(keys).toEqual(["Blueprint (1607d7f0)", "Blueprint (46ae2dec)"]);
|
|
201
|
+
expect(out.records["Blueprint"]).toBeUndefined();
|
|
202
|
+
// The actual bulkDownload payload — each record keeps its OWN file content.
|
|
203
|
+
expect(out.records["Blueprint (1607d7f0)"].files[0].content).toBe("var a = 1;");
|
|
204
|
+
expect(out.records["Blueprint (46ae2dec)"].files[0].content).toBe("var b = 2;");
|
|
205
|
+
});
|
|
206
|
+
it("keeps record.name identical to the map key (writers build the folder path from it)", () => {
|
|
207
|
+
const out = processMissing([
|
|
208
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint", script: "" },
|
|
209
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint", script: "" },
|
|
210
|
+
]);
|
|
211
|
+
Object.keys(out.records).forEach((key) => {
|
|
212
|
+
expect(out.records[key].name).toBe(key);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it("warns on a collision instead of staying silent (parity with buildTableMap)", () => {
|
|
216
|
+
const out = processMissing([
|
|
217
|
+
{ sys_id: "1607d7f0c373f2d0d4ddf1db05013101", name: "Blueprint", script: "" },
|
|
218
|
+
{ sys_id: "46ae2decc33f32d0d4ddf1db050131d9", name: "Blueprint", script: "" },
|
|
219
|
+
]);
|
|
220
|
+
expect(out.warnings).toHaveLength(2);
|
|
221
|
+
expect(out.warnings[0]).toContain("duplicate display name");
|
|
222
|
+
expect(out.warnings[0]).toContain("bulkDownload");
|
|
223
|
+
});
|
|
224
|
+
it("leaves unique names completely untouched (no suffix, no warning)", () => {
|
|
225
|
+
const out = processMissing([
|
|
226
|
+
{ sys_id: "dc2ef56133a0e2507b18bc534d5c7bf5", name: "Send Email", script: "" },
|
|
227
|
+
{ sys_id: "0d4626f933e826507b18bc534d5c7b37", name: "Send Text", script: "" },
|
|
228
|
+
]);
|
|
229
|
+
expect(Object.keys(out.records).sort()).toEqual(["Send Email", "Send Text"]);
|
|
230
|
+
expect(out.warnings).toHaveLength(0);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the single-record narrowing of syncManifest — the `record` option
|
|
4
|
+
* used by `dove create`'s post-create round-trip.
|
|
5
|
+
*
|
|
6
|
+
* Regression target: `dove create <table>` previously called syncManifest(scope)
|
|
7
|
+
* with no narrowing, which re-downloaded and rewrote EVERY record in the scope.
|
|
8
|
+
* Creating one record churned ~1,892 files in a scope like x_cadso_text_spoke.
|
|
9
|
+
* The fix scopes the file refresh to just the created record.
|
|
10
|
+
*
|
|
11
|
+
* These tests assert:
|
|
12
|
+
* 1. narrowManifestToRecord returns only the matching record, never mutating input
|
|
13
|
+
* 2. narrowManifestToRecord returns an empty table map when the record is absent
|
|
14
|
+
* 3. syncManifest({ record }) fetches + writes ONLY the target record's files
|
|
15
|
+
* 4. sibling records already on disk are left untouched (never re-fetched)
|
|
16
|
+
* 5. a record absent from the fetched manifest → warn, no fetch, no throw
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const os = __importStar(require("os"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
// ---------- mocks (must come before importing the module under test) ----------
|
|
56
|
+
var mockLogger = {
|
|
57
|
+
info: jest.fn(),
|
|
58
|
+
debug: jest.fn(),
|
|
59
|
+
warn: jest.fn(),
|
|
60
|
+
error: jest.fn(),
|
|
61
|
+
success: jest.fn(),
|
|
62
|
+
getLogLevel: function () { return "warn"; },
|
|
63
|
+
};
|
|
64
|
+
var mockFileLogger = {
|
|
65
|
+
debug: jest.fn(),
|
|
66
|
+
info: jest.fn(),
|
|
67
|
+
warn: jest.fn(),
|
|
68
|
+
error: jest.fn(),
|
|
69
|
+
};
|
|
70
|
+
var mockClient = {
|
|
71
|
+
getManifest: jest.fn(),
|
|
72
|
+
getMissingFiles: jest.fn(),
|
|
73
|
+
};
|
|
74
|
+
jest.mock("../Logger", function () { return { logger: mockLogger }; });
|
|
75
|
+
jest.mock("../FileLogger", function () { return { fileLogger: mockFileLogger }; });
|
|
76
|
+
jest.mock("../snClient", function () {
|
|
77
|
+
return {
|
|
78
|
+
defaultClient: function () { return mockClient; },
|
|
79
|
+
unwrapSNResponse: function (p) { return Promise.resolve(p).then(function (r) { return r; }); },
|
|
80
|
+
processPushResponse: jest.fn(),
|
|
81
|
+
retryOnErr: jest.fn(),
|
|
82
|
+
retryOnHttpErr: jest.fn(),
|
|
83
|
+
unwrapTableAPIFirstItem: jest.fn(),
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
var mockConfig = {
|
|
87
|
+
getConfig: jest.fn().mockReturnValue({
|
|
88
|
+
scopes: { x_cadso_core: {} },
|
|
89
|
+
tableOptions: {},
|
|
90
|
+
}),
|
|
91
|
+
getManifest: jest.fn().mockResolvedValue({
|
|
92
|
+
x_cadso_core: { scope: "x_cadso_core", tables: {} },
|
|
93
|
+
}),
|
|
94
|
+
getSourcePathForScope: jest.fn(),
|
|
95
|
+
getSourcePath: jest.fn(),
|
|
96
|
+
getManifestPath: jest.fn().mockReturnValue("/tmp/dove.manifest.json"),
|
|
97
|
+
resolveConfigForScope: jest.fn().mockImplementation(function () {
|
|
98
|
+
return {
|
|
99
|
+
tables: ["sys_script_include"],
|
|
100
|
+
fieldOverrides: {},
|
|
101
|
+
apiIncludes: {},
|
|
102
|
+
apiExcludes: {},
|
|
103
|
+
};
|
|
104
|
+
}),
|
|
105
|
+
isMultiScopeManifest: jest.fn().mockReturnValue(true),
|
|
106
|
+
updateManifest: jest.fn(),
|
|
107
|
+
};
|
|
108
|
+
jest.mock("../config", function () { return mockConfig; });
|
|
109
|
+
jest.mock("progress", function () {
|
|
110
|
+
return jest.fn().mockImplementation(function () {
|
|
111
|
+
return { tick: jest.fn() };
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// Stub writeScopeManifest so the test never writes a manifest file. Leave the
|
|
115
|
+
// other FileUtils exports real so we can assert against the file system.
|
|
116
|
+
jest.mock("../FileUtils", function () {
|
|
117
|
+
var actual = jest.requireActual("../FileUtils");
|
|
118
|
+
return Object.assign({}, actual, {
|
|
119
|
+
writeScopeManifest: jest.fn().mockResolvedValue(undefined),
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
const AppUtils = __importStar(require("../appUtils"));
|
|
123
|
+
// ---------- fixtures ----------
|
|
124
|
+
// A three-record scope manifest, all in sys_script_include, matching the shape
|
|
125
|
+
// client.getManifest returns (files carry name+type only; no content).
|
|
126
|
+
function threeRecordManifest() {
|
|
127
|
+
return {
|
|
128
|
+
scope: "x_cadso_core",
|
|
129
|
+
tables: {
|
|
130
|
+
sys_script_include: {
|
|
131
|
+
records: {
|
|
132
|
+
New: { name: "New", sys_id: "sysid_New", files: [{ name: "script", type: "js" }] },
|
|
133
|
+
Sib1: { name: "Sib1", sys_id: "sysid_Sib1", files: [{ name: "script", type: "js" }] },
|
|
134
|
+
Sib2: { name: "Sib2", sys_id: "sysid_Sib2", files: [{ name: "script", type: "js" }] },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var tmpRoot;
|
|
141
|
+
function writeLocal(record, name, type, content) {
|
|
142
|
+
var recDir = path.join(tmpRoot, "sys_script_include", record);
|
|
143
|
+
fs.mkdirSync(recDir, { recursive: true });
|
|
144
|
+
fs.writeFileSync(path.join(recDir, name + "." + type), content);
|
|
145
|
+
}
|
|
146
|
+
function readLocal(record, name, type) {
|
|
147
|
+
var p = path.join(tmpRoot, "sys_script_include", record, name + "." + type);
|
|
148
|
+
try {
|
|
149
|
+
return fs.readFileSync(p, "utf8");
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ---------- pure helper ----------
|
|
156
|
+
describe("narrowManifestToRecord", function () {
|
|
157
|
+
test("returns only the matching record and does NOT mutate the input", function () {
|
|
158
|
+
var manifest = threeRecordManifest();
|
|
159
|
+
var out = AppUtils.narrowManifestToRecord(manifest, "sys_script_include", "sysid_New");
|
|
160
|
+
// input untouched — all three records still present
|
|
161
|
+
expect(Object.keys(manifest.tables.sys_script_include.records).sort())
|
|
162
|
+
.toEqual(["New", "Sib1", "Sib2"]);
|
|
163
|
+
// output holds exactly the one record
|
|
164
|
+
expect(Object.keys(out.tables)).toEqual(["sys_script_include"]);
|
|
165
|
+
expect(Object.keys(out.tables.sys_script_include.records)).toEqual(["New"]);
|
|
166
|
+
expect(out.tables.sys_script_include.records.New.sys_id).toBe("sysid_New");
|
|
167
|
+
});
|
|
168
|
+
test("returns an empty table map when the sys_id is absent", function () {
|
|
169
|
+
var manifest = threeRecordManifest();
|
|
170
|
+
var out = AppUtils.narrowManifestToRecord(manifest, "sys_script_include", "sysid_Missing");
|
|
171
|
+
expect(Object.keys(out.tables)).toEqual([]);
|
|
172
|
+
});
|
|
173
|
+
test("returns an empty table map when the table is absent", function () {
|
|
174
|
+
var manifest = threeRecordManifest();
|
|
175
|
+
var out = AppUtils.narrowManifestToRecord(manifest, "sys_ui_action", "sysid_New");
|
|
176
|
+
expect(Object.keys(out.tables)).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// ---------- syncManifest({ record }) ----------
|
|
180
|
+
describe("syncManifest — single-record round-trip (dove create)", function () {
|
|
181
|
+
beforeEach(function () {
|
|
182
|
+
jest.clearAllMocks();
|
|
183
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sinc-record-test-"));
|
|
184
|
+
mockConfig.getSourcePathForScope.mockReturnValue(tmpRoot);
|
|
185
|
+
mockConfig.getSourcePath.mockReturnValue(tmpRoot);
|
|
186
|
+
});
|
|
187
|
+
afterEach(function () {
|
|
188
|
+
try {
|
|
189
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
catch (e) { }
|
|
192
|
+
});
|
|
193
|
+
test("fetches + writes ONLY the target record; siblings are never re-fetched", async function () {
|
|
194
|
+
// A stale sibling file already on disk — it must be left exactly as-is.
|
|
195
|
+
writeLocal("Sib1", "script", "js", "STALE SIBLING — must not change");
|
|
196
|
+
mockClient.getManifest.mockResolvedValue(threeRecordManifest());
|
|
197
|
+
// getMissingFiles should be asked for the new record ONLY; return its content.
|
|
198
|
+
mockClient.getMissingFiles.mockResolvedValue({
|
|
199
|
+
sys_script_include: {
|
|
200
|
+
records: {
|
|
201
|
+
New: {
|
|
202
|
+
name: "New", sys_id: "sysid_New",
|
|
203
|
+
files: [{ name: "script", type: "js", content: "// fresh created record" }],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
await AppUtils.syncManifest("x_cadso_core", {
|
|
209
|
+
record: { table: "sys_script_include", sysId: "sysid_New" },
|
|
210
|
+
});
|
|
211
|
+
// exactly one content fetch, scoped to the one record
|
|
212
|
+
expect(mockClient.getMissingFiles).toHaveBeenCalledTimes(1);
|
|
213
|
+
var missingArg = mockClient.getMissingFiles.mock.calls[0][0];
|
|
214
|
+
expect(Object.keys(missingArg.sys_script_include)).toEqual(["sysid_New"]);
|
|
215
|
+
// the new record landed
|
|
216
|
+
expect(readLocal("New", "script", "js")).toBe("// fresh created record");
|
|
217
|
+
// siblings untouched
|
|
218
|
+
expect(readLocal("Sib1", "script", "js")).toBe("STALE SIBLING — must not change");
|
|
219
|
+
expect(readLocal("Sib2", "script", "js")).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
test("record absent from the fetched manifest → warn, no fetch, no throw", async function () {
|
|
222
|
+
mockClient.getManifest.mockResolvedValue(threeRecordManifest());
|
|
223
|
+
await AppUtils.syncManifest("x_cadso_core", {
|
|
224
|
+
record: { table: "sys_script_include", sysId: "sysid_DoesNotExist" },
|
|
225
|
+
});
|
|
226
|
+
// empty narrowed manifest → refreshAllFiles no-ops, so no content fetch
|
|
227
|
+
expect(mockClient.getMissingFiles).not.toHaveBeenCalled();
|
|
228
|
+
// and the user is told to run a full refresh
|
|
229
|
+
var warned = mockLogger.warn.mock.calls.some(function (c) {
|
|
230
|
+
return typeof c[0] === "string" && c[0].indexOf("sysid_DoesNotExist") !== -1;
|
|
231
|
+
});
|
|
232
|
+
expect(warned).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
});
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,7 +1,247 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "dovetail-manifest/v1",
|
|
3
|
-
"generated_at": "2026-07-
|
|
3
|
+
"generated_at": "2026-07-15T02:38:50.750Z",
|
|
4
4
|
"events": [
|
|
5
|
+
{
|
|
6
|
+
"$schema": "dovetail-release-event/v1",
|
|
7
|
+
"event_id": "dovetail-servicenow@0.0.30",
|
|
8
|
+
"package": "@tenonhq/dovetail-servicenow",
|
|
9
|
+
"version": "0.0.30",
|
|
10
|
+
"prev_version": "0.0.29",
|
|
11
|
+
"published_at": "2026-07-15T02:38:50.737Z",
|
|
12
|
+
"git": {
|
|
13
|
+
"sha": "2977ae7b00bf62482f12d7a6ccf7b87babdbae21",
|
|
14
|
+
"tag": "dovetail-servicenow@0.0.30"
|
|
15
|
+
},
|
|
16
|
+
"semver_bump": "patch",
|
|
17
|
+
"commits": [
|
|
18
|
+
{
|
|
19
|
+
"sha": "2977ae7b0",
|
|
20
|
+
"subject": "feat(servicenow): set-field/create-record accept --from-json for large fields (#224)",
|
|
21
|
+
"pr": 224
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"sha": "e0c350259",
|
|
25
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"exports_delta": null,
|
|
29
|
+
"narrative": null
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"$schema": "dovetail-release-event/v1",
|
|
33
|
+
"event_id": "dovetail-mcp@0.0.27",
|
|
34
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
35
|
+
"version": "0.0.27",
|
|
36
|
+
"prev_version": "0.0.26",
|
|
37
|
+
"published_at": "2026-07-15T02:38:50.737Z",
|
|
38
|
+
"git": {
|
|
39
|
+
"sha": "2977ae7b00bf62482f12d7a6ccf7b87babdbae21",
|
|
40
|
+
"tag": "dovetail-mcp@0.0.27"
|
|
41
|
+
},
|
|
42
|
+
"semver_bump": "patch",
|
|
43
|
+
"commits": [
|
|
44
|
+
{
|
|
45
|
+
"sha": "e0c350259",
|
|
46
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"exports_delta": null,
|
|
50
|
+
"narrative": null
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"$schema": "dovetail-release-event/v1",
|
|
54
|
+
"event_id": "dovetail-servicenow@0.0.29",
|
|
55
|
+
"package": "@tenonhq/dovetail-servicenow",
|
|
56
|
+
"version": "0.0.29",
|
|
57
|
+
"prev_version": "0.0.28",
|
|
58
|
+
"published_at": "2026-07-15T01:16:10.133Z",
|
|
59
|
+
"git": {
|
|
60
|
+
"sha": "6d7309949cc82ece927f9a4f61614ef4a23cc5ee",
|
|
61
|
+
"tag": "dovetail-servicenow@0.0.29"
|
|
62
|
+
},
|
|
63
|
+
"semver_bump": "patch",
|
|
64
|
+
"commits": [
|
|
65
|
+
{
|
|
66
|
+
"sha": "6d7309949",
|
|
67
|
+
"subject": "feat(servicenow): set-column verb + set_column MCP tool (DEV-509) (#217)",
|
|
68
|
+
"pr": 217
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"sha": "6a4081d25",
|
|
72
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"exports_delta": null,
|
|
76
|
+
"narrative": null
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"$schema": "dovetail-release-event/v1",
|
|
80
|
+
"event_id": "dovetail-mcp@0.0.26",
|
|
81
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
82
|
+
"version": "0.0.26",
|
|
83
|
+
"prev_version": "0.0.25",
|
|
84
|
+
"published_at": "2026-07-15T01:16:10.133Z",
|
|
85
|
+
"git": {
|
|
86
|
+
"sha": "6d7309949cc82ece927f9a4f61614ef4a23cc5ee",
|
|
87
|
+
"tag": "dovetail-mcp@0.0.26"
|
|
88
|
+
},
|
|
89
|
+
"semver_bump": "patch",
|
|
90
|
+
"commits": [
|
|
91
|
+
{
|
|
92
|
+
"sha": "6a4081d25",
|
|
93
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"exports_delta": null,
|
|
97
|
+
"narrative": null
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"$schema": "dovetail-release-event/v1",
|
|
101
|
+
"event_id": "dovetail-servicenow@0.0.28",
|
|
102
|
+
"package": "@tenonhq/dovetail-servicenow",
|
|
103
|
+
"version": "0.0.28",
|
|
104
|
+
"prev_version": "0.0.27",
|
|
105
|
+
"published_at": "2026-07-14T20:25:03.138Z",
|
|
106
|
+
"git": {
|
|
107
|
+
"sha": "a5aeaa3c3f56c0bb24de80f12808689c5987e457",
|
|
108
|
+
"tag": "dovetail-servicenow@0.0.28"
|
|
109
|
+
},
|
|
110
|
+
"semver_bump": "patch",
|
|
111
|
+
"commits": [
|
|
112
|
+
{
|
|
113
|
+
"sha": "a5aeaa3c3",
|
|
114
|
+
"subject": "feat(servicenow): edit a published action type's steps in one publish (#216) (#223)",
|
|
115
|
+
"pr": 216
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"sha": "b8967c79d",
|
|
119
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
"exports_delta": null,
|
|
123
|
+
"narrative": null
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"$schema": "dovetail-release-event/v1",
|
|
127
|
+
"event_id": "dovetail-mcp@0.0.25",
|
|
128
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
129
|
+
"version": "0.0.25",
|
|
130
|
+
"prev_version": "0.0.24",
|
|
131
|
+
"published_at": "2026-07-14T20:25:03.138Z",
|
|
132
|
+
"git": {
|
|
133
|
+
"sha": "a5aeaa3c3f56c0bb24de80f12808689c5987e457",
|
|
134
|
+
"tag": "dovetail-mcp@0.0.25"
|
|
135
|
+
},
|
|
136
|
+
"semver_bump": "patch",
|
|
137
|
+
"commits": [
|
|
138
|
+
{
|
|
139
|
+
"sha": "b8967c79d",
|
|
140
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
"exports_delta": null,
|
|
144
|
+
"narrative": null
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"$schema": "dovetail-release-event/v1",
|
|
148
|
+
"event_id": "dovetail-servicenow@0.0.27",
|
|
149
|
+
"package": "@tenonhq/dovetail-servicenow",
|
|
150
|
+
"version": "0.0.27",
|
|
151
|
+
"prev_version": "0.0.26",
|
|
152
|
+
"published_at": "2026-07-14T20:15:42.598Z",
|
|
153
|
+
"git": {
|
|
154
|
+
"sha": "43d6c5124bec7e79537d8ad6364749f36c601d9c",
|
|
155
|
+
"tag": "dovetail-servicenow@0.0.27"
|
|
156
|
+
},
|
|
157
|
+
"semver_bump": "patch",
|
|
158
|
+
"commits": [
|
|
159
|
+
{
|
|
160
|
+
"sha": "43d6c5124",
|
|
161
|
+
"subject": "feat(servicenow): add-column mandatory/default via scope-aware insert (#213)",
|
|
162
|
+
"pr": 213
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"sha": "ef9e73505",
|
|
166
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"exports_delta": null,
|
|
170
|
+
"narrative": null
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"$schema": "dovetail-release-event/v1",
|
|
174
|
+
"event_id": "dovetail-mcp@0.0.24",
|
|
175
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
176
|
+
"version": "0.0.24",
|
|
177
|
+
"prev_version": "0.0.23",
|
|
178
|
+
"published_at": "2026-07-14T20:15:42.598Z",
|
|
179
|
+
"git": {
|
|
180
|
+
"sha": "43d6c5124bec7e79537d8ad6364749f36c601d9c",
|
|
181
|
+
"tag": "dovetail-mcp@0.0.24"
|
|
182
|
+
},
|
|
183
|
+
"semver_bump": "patch",
|
|
184
|
+
"commits": [
|
|
185
|
+
{
|
|
186
|
+
"sha": "ef9e73505",
|
|
187
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"exports_delta": null,
|
|
191
|
+
"narrative": null
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"$schema": "dovetail-release-event/v1",
|
|
195
|
+
"event_id": "dovetail-core@0.0.113",
|
|
196
|
+
"package": "@tenonhq/dovetail-core",
|
|
197
|
+
"version": "0.0.113",
|
|
198
|
+
"prev_version": "0.0.112",
|
|
199
|
+
"published_at": "2026-07-14T20:02:38.069Z",
|
|
200
|
+
"git": {
|
|
201
|
+
"sha": "99f064bf76f0fa4a2e049debff52be8303ebed24",
|
|
202
|
+
"tag": "dovetail-core@0.0.113"
|
|
203
|
+
},
|
|
204
|
+
"semver_bump": "patch",
|
|
205
|
+
"commits": [
|
|
206
|
+
{
|
|
207
|
+
"sha": "99f064bf7",
|
|
208
|
+
"subject": "fix(servicenow): stop getManifest silently dropping same-named records (#222)",
|
|
209
|
+
"pr": 222
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"sha": "38ffe1e27",
|
|
213
|
+
"subject": "chore(release): @tenonhq/dovetail-core@0.0.112 [skip ci]"
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"exports_delta": null,
|
|
217
|
+
"narrative": null
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"$schema": "dovetail-release-event/v1",
|
|
221
|
+
"event_id": "dovetail-core@0.0.112",
|
|
222
|
+
"package": "@tenonhq/dovetail-core",
|
|
223
|
+
"version": "0.0.112",
|
|
224
|
+
"prev_version": "0.0.111",
|
|
225
|
+
"published_at": "2026-07-14T19:40:43.702Z",
|
|
226
|
+
"git": {
|
|
227
|
+
"sha": "a2777edcbd46ee69de13e73173132f9cd18aa618",
|
|
228
|
+
"tag": "dovetail-core@0.0.112"
|
|
229
|
+
},
|
|
230
|
+
"semver_bump": "patch",
|
|
231
|
+
"commits": [
|
|
232
|
+
{
|
|
233
|
+
"sha": "a2777edcb",
|
|
234
|
+
"subject": "fix: reject unknown dove commands and alias pull to refresh (#219) (#221)",
|
|
235
|
+
"pr": 219
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"sha": "6a42ef546",
|
|
239
|
+
"subject": "chore(release): @tenonhq/dovetail-core@0.0.111 [skip ci]"
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
"exports_delta": null,
|
|
243
|
+
"narrative": null
|
|
244
|
+
},
|
|
5
245
|
{
|
|
6
246
|
"$schema": "dovetail-release-event/v1",
|
|
7
247
|
"event_id": "dovetail-core@0.0.111",
|
|
@@ -985,298 +1225,6 @@
|
|
|
985
1225
|
],
|
|
986
1226
|
"exports_delta": null,
|
|
987
1227
|
"narrative": null
|
|
988
|
-
},
|
|
989
|
-
{
|
|
990
|
-
"$schema": "dovetail-release-event/v1",
|
|
991
|
-
"event_id": "dovetail-babel-plugin@0.0.10",
|
|
992
|
-
"package": "@tenonhq/dovetail-babel-plugin",
|
|
993
|
-
"version": "0.0.10",
|
|
994
|
-
"prev_version": "0.0.9",
|
|
995
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
996
|
-
"git": {
|
|
997
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
998
|
-
"tag": "dovetail-babel-plugin@0.0.10"
|
|
999
|
-
},
|
|
1000
|
-
"semver_bump": "patch",
|
|
1001
|
-
"commits": [
|
|
1002
|
-
{
|
|
1003
|
-
"sha": "71bdf9bba",
|
|
1004
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1005
|
-
"pr": 148
|
|
1006
|
-
},
|
|
1007
|
-
{
|
|
1008
|
-
"sha": "9332fdf7a",
|
|
1009
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1010
|
-
},
|
|
1011
|
-
{
|
|
1012
|
-
"sha": "9046da774",
|
|
1013
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1014
|
-
}
|
|
1015
|
-
],
|
|
1016
|
-
"exports_delta": null,
|
|
1017
|
-
"narrative": null
|
|
1018
|
-
},
|
|
1019
|
-
{
|
|
1020
|
-
"$schema": "dovetail-release-event/v1",
|
|
1021
|
-
"event_id": "dovetail-babel-plugin-remove-modules@0.0.10",
|
|
1022
|
-
"package": "@tenonhq/dovetail-babel-plugin-remove-modules",
|
|
1023
|
-
"version": "0.0.10",
|
|
1024
|
-
"prev_version": "0.0.9",
|
|
1025
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1026
|
-
"git": {
|
|
1027
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1028
|
-
"tag": "dovetail-babel-plugin-remove-modules@0.0.10"
|
|
1029
|
-
},
|
|
1030
|
-
"semver_bump": "patch",
|
|
1031
|
-
"commits": [
|
|
1032
|
-
{
|
|
1033
|
-
"sha": "71bdf9bba",
|
|
1034
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1035
|
-
"pr": 148
|
|
1036
|
-
},
|
|
1037
|
-
{
|
|
1038
|
-
"sha": "9332fdf7a",
|
|
1039
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1040
|
-
},
|
|
1041
|
-
{
|
|
1042
|
-
"sha": "9046da774",
|
|
1043
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1044
|
-
}
|
|
1045
|
-
],
|
|
1046
|
-
"exports_delta": null,
|
|
1047
|
-
"narrative": null
|
|
1048
|
-
},
|
|
1049
|
-
{
|
|
1050
|
-
"$schema": "dovetail-release-event/v1",
|
|
1051
|
-
"event_id": "dovetail-babel-preset-servicenow@0.0.10",
|
|
1052
|
-
"package": "@tenonhq/dovetail-babel-preset-servicenow",
|
|
1053
|
-
"version": "0.0.10",
|
|
1054
|
-
"prev_version": "0.0.9",
|
|
1055
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1056
|
-
"git": {
|
|
1057
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1058
|
-
"tag": "dovetail-babel-preset-servicenow@0.0.10"
|
|
1059
|
-
},
|
|
1060
|
-
"semver_bump": "patch",
|
|
1061
|
-
"commits": [
|
|
1062
|
-
{
|
|
1063
|
-
"sha": "71bdf9bba",
|
|
1064
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1065
|
-
"pr": 148
|
|
1066
|
-
},
|
|
1067
|
-
{
|
|
1068
|
-
"sha": "9332fdf7a",
|
|
1069
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1070
|
-
},
|
|
1071
|
-
{
|
|
1072
|
-
"sha": "9046da774",
|
|
1073
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1074
|
-
}
|
|
1075
|
-
],
|
|
1076
|
-
"exports_delta": null,
|
|
1077
|
-
"narrative": null
|
|
1078
|
-
},
|
|
1079
|
-
{
|
|
1080
|
-
"$schema": "dovetail-release-event/v1",
|
|
1081
|
-
"event_id": "dovetail-schema@0.0.11",
|
|
1082
|
-
"package": "@tenonhq/dovetail-schema",
|
|
1083
|
-
"version": "0.0.11",
|
|
1084
|
-
"prev_version": "0.0.10",
|
|
1085
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1086
|
-
"git": {
|
|
1087
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1088
|
-
"tag": "dovetail-schema@0.0.11"
|
|
1089
|
-
},
|
|
1090
|
-
"semver_bump": "patch",
|
|
1091
|
-
"commits": [
|
|
1092
|
-
{
|
|
1093
|
-
"sha": "71bdf9bba",
|
|
1094
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1095
|
-
"pr": 148
|
|
1096
|
-
},
|
|
1097
|
-
{
|
|
1098
|
-
"sha": "a8922d9d5",
|
|
1099
|
-
"subject": "chore(release): @tenonhq/dovetail-schema@0.0.10 [skip ci]"
|
|
1100
|
-
}
|
|
1101
|
-
],
|
|
1102
|
-
"exports_delta": null,
|
|
1103
|
-
"narrative": null
|
|
1104
|
-
},
|
|
1105
|
-
{
|
|
1106
|
-
"$schema": "dovetail-release-event/v1",
|
|
1107
|
-
"event_id": "dovetail-core@0.0.103",
|
|
1108
|
-
"package": "@tenonhq/dovetail-core",
|
|
1109
|
-
"version": "0.0.103",
|
|
1110
|
-
"prev_version": "0.0.102",
|
|
1111
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1112
|
-
"git": {
|
|
1113
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1114
|
-
"tag": "dovetail-core@0.0.103"
|
|
1115
|
-
},
|
|
1116
|
-
"semver_bump": "patch",
|
|
1117
|
-
"commits": [
|
|
1118
|
-
{
|
|
1119
|
-
"sha": "71bdf9bba",
|
|
1120
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1121
|
-
"pr": 148
|
|
1122
|
-
},
|
|
1123
|
-
{
|
|
1124
|
-
"sha": "d5e064760",
|
|
1125
|
-
"subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.15 [skip ci]"
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
"sha": "af1417796",
|
|
1129
|
-
"subject": "chore(release): publish 3 packages [skip ci]"
|
|
1130
|
-
},
|
|
1131
|
-
{
|
|
1132
|
-
"sha": "e25ae74c2",
|
|
1133
|
-
"subject": "chore(release): @tenonhq/dovetail-core@0.0.102 [skip ci]"
|
|
1134
|
-
}
|
|
1135
|
-
],
|
|
1136
|
-
"exports_delta": null,
|
|
1137
|
-
"narrative": null
|
|
1138
|
-
},
|
|
1139
|
-
{
|
|
1140
|
-
"$schema": "dovetail-release-event/v1",
|
|
1141
|
-
"event_id": "dovetail-dashboard@0.0.37",
|
|
1142
|
-
"package": "@tenonhq/dovetail-dashboard",
|
|
1143
|
-
"version": "0.0.37",
|
|
1144
|
-
"prev_version": "0.0.36",
|
|
1145
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1146
|
-
"git": {
|
|
1147
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1148
|
-
"tag": "dovetail-dashboard@0.0.37"
|
|
1149
|
-
},
|
|
1150
|
-
"semver_bump": "patch",
|
|
1151
|
-
"commits": [
|
|
1152
|
-
{
|
|
1153
|
-
"sha": "71bdf9bba",
|
|
1154
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1155
|
-
"pr": 148
|
|
1156
|
-
},
|
|
1157
|
-
{
|
|
1158
|
-
"sha": "ba1c67775",
|
|
1159
|
-
"subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.36 [skip ci]"
|
|
1160
|
-
}
|
|
1161
|
-
],
|
|
1162
|
-
"exports_delta": null,
|
|
1163
|
-
"narrative": null
|
|
1164
|
-
},
|
|
1165
|
-
{
|
|
1166
|
-
"$schema": "dovetail-release-event/v1",
|
|
1167
|
-
"event_id": "dovetail-eslint-plugin@0.0.10",
|
|
1168
|
-
"package": "@tenonhq/dovetail-eslint-plugin",
|
|
1169
|
-
"version": "0.0.10",
|
|
1170
|
-
"prev_version": "0.0.9",
|
|
1171
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1172
|
-
"git": {
|
|
1173
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1174
|
-
"tag": "dovetail-eslint-plugin@0.0.10"
|
|
1175
|
-
},
|
|
1176
|
-
"semver_bump": "patch",
|
|
1177
|
-
"commits": [
|
|
1178
|
-
{
|
|
1179
|
-
"sha": "71bdf9bba",
|
|
1180
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1181
|
-
"pr": 148
|
|
1182
|
-
},
|
|
1183
|
-
{
|
|
1184
|
-
"sha": "9332fdf7a",
|
|
1185
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1186
|
-
},
|
|
1187
|
-
{
|
|
1188
|
-
"sha": "9046da774",
|
|
1189
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1190
|
-
}
|
|
1191
|
-
],
|
|
1192
|
-
"exports_delta": null,
|
|
1193
|
-
"narrative": null
|
|
1194
|
-
},
|
|
1195
|
-
{
|
|
1196
|
-
"$schema": "dovetail-release-event/v1",
|
|
1197
|
-
"event_id": "dovetail-gmail@0.0.11",
|
|
1198
|
-
"package": "@tenonhq/dovetail-gmail",
|
|
1199
|
-
"version": "0.0.11",
|
|
1200
|
-
"prev_version": "0.0.10",
|
|
1201
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1202
|
-
"git": {
|
|
1203
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1204
|
-
"tag": "dovetail-gmail@0.0.11"
|
|
1205
|
-
},
|
|
1206
|
-
"semver_bump": "patch",
|
|
1207
|
-
"commits": [
|
|
1208
|
-
{
|
|
1209
|
-
"sha": "71bdf9bba",
|
|
1210
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1211
|
-
"pr": 148
|
|
1212
|
-
},
|
|
1213
|
-
{
|
|
1214
|
-
"sha": "9332fdf7a",
|
|
1215
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1216
|
-
},
|
|
1217
|
-
{
|
|
1218
|
-
"sha": "9046da774",
|
|
1219
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1220
|
-
}
|
|
1221
|
-
],
|
|
1222
|
-
"exports_delta": null,
|
|
1223
|
-
"narrative": null
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
"$schema": "dovetail-release-event/v1",
|
|
1227
|
-
"event_id": "dovetail-google-calendar@0.0.11",
|
|
1228
|
-
"package": "@tenonhq/dovetail-google-calendar",
|
|
1229
|
-
"version": "0.0.11",
|
|
1230
|
-
"prev_version": "0.0.10",
|
|
1231
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1232
|
-
"git": {
|
|
1233
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1234
|
-
"tag": "dovetail-google-calendar@0.0.11"
|
|
1235
|
-
},
|
|
1236
|
-
"semver_bump": "patch",
|
|
1237
|
-
"commits": [
|
|
1238
|
-
{
|
|
1239
|
-
"sha": "71bdf9bba",
|
|
1240
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1241
|
-
"pr": 148
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
"sha": "9332fdf7a",
|
|
1245
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
"sha": "9046da774",
|
|
1249
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1250
|
-
}
|
|
1251
|
-
],
|
|
1252
|
-
"exports_delta": null,
|
|
1253
|
-
"narrative": null
|
|
1254
|
-
},
|
|
1255
|
-
{
|
|
1256
|
-
"$schema": "dovetail-release-event/v1",
|
|
1257
|
-
"event_id": "dovetail-mcp@0.0.9",
|
|
1258
|
-
"package": "@tenonhq/dovetail-mcp",
|
|
1259
|
-
"version": "0.0.9",
|
|
1260
|
-
"prev_version": "0.0.8",
|
|
1261
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1262
|
-
"git": {
|
|
1263
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1264
|
-
"tag": "dovetail-mcp@0.0.9"
|
|
1265
|
-
},
|
|
1266
|
-
"semver_bump": "patch",
|
|
1267
|
-
"commits": [
|
|
1268
|
-
{
|
|
1269
|
-
"sha": "71bdf9bba",
|
|
1270
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1271
|
-
"pr": 148
|
|
1272
|
-
},
|
|
1273
|
-
{
|
|
1274
|
-
"sha": "af1417796",
|
|
1275
|
-
"subject": "chore(release): publish 3 packages [skip ci]"
|
|
1276
|
-
}
|
|
1277
|
-
],
|
|
1278
|
-
"exports_delta": null,
|
|
1279
|
-
"narrative": null
|
|
1280
1228
|
}
|
|
1281
1229
|
]
|
|
1282
1230
|
}
|