@tenonhq/dovetail-core 0.0.110 → 0.0.111
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 +50 -2
- package/dist/commander.js +7 -0
- package/dist/commands.js +61 -1
- package/dist/tests/refreshTableFilter.test.js +204 -0
- package/dist/tests/syncManifestWhitelist.test.js +112 -0
- package/package.json +1 -1
- package/release-manifest.json +165 -199
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.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.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"));
|
|
@@ -272,6 +272,32 @@ const processManifest = async (manifest, forceWrite = false, sourcePath) => {
|
|
|
272
272
|
}
|
|
273
273
|
};
|
|
274
274
|
exports.processManifest = processManifest;
|
|
275
|
+
/**
|
|
276
|
+
* Narrow a manifest's table map to `tables`, for the FILE refresh only.
|
|
277
|
+
*
|
|
278
|
+
* Returns a NEW manifest and NEVER mutates its input — the caller must keep
|
|
279
|
+
* writing the original, full manifest to disk. Narrowing the persisted manifest
|
|
280
|
+
* would drop every other table from dove.manifest.<scope>.json and break
|
|
281
|
+
* push/watch. That non-mutation is the whole safety property of `--table`, so
|
|
282
|
+
* it lives in one testable function rather than inline in syncManifest.
|
|
283
|
+
*
|
|
284
|
+
* An empty table map means the scope holds none of the requested tables;
|
|
285
|
+
* refreshAllFiles no-ops on an empty map, so the scope is skipped untouched.
|
|
286
|
+
*/
|
|
287
|
+
const narrowManifestToTables = (manifest, tables) => {
|
|
288
|
+
if (!tables || tables.length === 0)
|
|
289
|
+
return manifest;
|
|
290
|
+
var wantedTables = {};
|
|
291
|
+
var presentTableNames = Object.keys(manifest.tables || {});
|
|
292
|
+
for (var w = 0; w < presentTableNames.length; w++) {
|
|
293
|
+
var wName = presentTableNames[w];
|
|
294
|
+
if (tables.indexOf(wName) !== -1) {
|
|
295
|
+
wantedTables[wName] = manifest.tables[wName];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return Object.assign({}, manifest, { tables: wantedTables });
|
|
299
|
+
};
|
|
300
|
+
exports.narrowManifestToTables = narrowManifestToTables;
|
|
275
301
|
const syncManifest = async (scope, options = {}) => {
|
|
276
302
|
// Top-level entry owns the collector lifecycle. Recursive calls (all-scopes
|
|
277
303
|
// → per-scope) inherit the collector via options._benchmarkCollector.
|
|
@@ -333,10 +359,31 @@ const syncManifest = async (scope, options = {}) => {
|
|
|
333
359
|
}
|
|
334
360
|
const refreshTableCount = Object.keys(newManifest.tables).length;
|
|
335
361
|
FileLogger_1.fileLogger.debug("Refreshed manifest for " + scope + ": " + refreshTableCount + " tables");
|
|
362
|
+
// The manifest is always written in FULL. Narrowing it to the --table
|
|
363
|
+
// subset would drop every other table from dove.manifest.<scope>.json and
|
|
364
|
+
// break push/watch, so the filter applies only to the file download below.
|
|
336
365
|
await fUtils.writeScopeManifest(scope, newManifest);
|
|
366
|
+
// --table gate: refresh file content for only the requested tables. Any
|
|
367
|
+
// table outside this scope's `_tables` whitelist was already dropped above,
|
|
368
|
+
// so intersecting the manifest is enough. `newManifest` is left intact —
|
|
369
|
+
// see narrowManifestToTables.
|
|
370
|
+
var refreshManifest = (0, exports.narrowManifestToTables)(newManifest, options.tables);
|
|
371
|
+
if (options.tables && options.tables.length > 0) {
|
|
372
|
+
var wantedNames = Object.keys(refreshManifest.tables);
|
|
373
|
+
if (wantedNames.length === 0) {
|
|
374
|
+
// Expected on every non-matching scope of an all-scopes filtered
|
|
375
|
+
// refresh, so this is debug — at info it would emit one noise line per
|
|
376
|
+
// scope that simply doesn't hold the table you asked for.
|
|
377
|
+
FileLogger_1.fileLogger.debug("syncManifest: scope " + scope + " holds none of the requested tables — skipping file refresh");
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
Logger_1.logger.info("Refreshing " + wantedNames.length + " of " + refreshTableCount + " tables in " +
|
|
381
|
+
scope + " (--table): " + wantedNames.join(", "));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
337
384
|
if (collector)
|
|
338
385
|
collector.startScope(scope);
|
|
339
|
-
await (0, exports.refreshAllFiles)(
|
|
386
|
+
await (0, exports.refreshAllFiles)(refreshManifest, scopeSourcePath, {
|
|
340
387
|
force: options.force,
|
|
341
388
|
benchmarkCollector: collector,
|
|
342
389
|
});
|
|
@@ -352,6 +399,7 @@ const syncManifest = async (scope, options = {}) => {
|
|
|
352
399
|
// scopes that leaked in before the whitelist gate existed.
|
|
353
400
|
var childOptions = {
|
|
354
401
|
force: options.force,
|
|
402
|
+
tables: options.tables,
|
|
355
403
|
_benchmarkCollector: collector,
|
|
356
404
|
};
|
|
357
405
|
if (declaredScopes.length > 0) {
|
package/dist/commander.js
CHANGED
|
@@ -79,6 +79,13 @@ async function initCommands() {
|
|
|
79
79
|
type: "string",
|
|
80
80
|
describe: "Refresh a single scope (default: all declared scopes)",
|
|
81
81
|
},
|
|
82
|
+
table: {
|
|
83
|
+
alias: "t",
|
|
84
|
+
type: "array",
|
|
85
|
+
describe: "Refresh file content for only these tables (repeatable or comma-separated). " +
|
|
86
|
+
"Must already be synced via includes._tables or includes._scopes.<scope>._tables. " +
|
|
87
|
+
"Default: every synced table.",
|
|
88
|
+
},
|
|
82
89
|
benchmark: {
|
|
83
90
|
alias: "b",
|
|
84
91
|
type: "boolean",
|
package/dist/commands.js
CHANGED
|
@@ -37,6 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.setLogLevel = setLogLevel;
|
|
40
|
+
exports.normalizeTableFilter = normalizeTableFilter;
|
|
41
|
+
exports.unknownTablesForScopes = unknownTablesForScopes;
|
|
40
42
|
exports.refreshCommand = refreshCommand;
|
|
41
43
|
exports.pushCommand = pushCommand;
|
|
42
44
|
exports.downloadCommand = downloadCommand;
|
|
@@ -61,15 +63,73 @@ const fs = __importStar(require("fs"));
|
|
|
61
63
|
function setLogLevel(args) {
|
|
62
64
|
Logger_1.logger.setLogLevel(args.logLevel);
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Normalize the --table flag into a clean list. yargs `type: "array"` already
|
|
68
|
+
* yields a string[], but accept comma-separated values too (`-t a,b`) so the
|
|
69
|
+
* flag behaves the way people reflexively type it.
|
|
70
|
+
*/
|
|
71
|
+
function normalizeTableFilter(raw) {
|
|
72
|
+
if (!raw)
|
|
73
|
+
return [];
|
|
74
|
+
const parts = Array.isArray(raw) ? raw : [raw];
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const part of parts) {
|
|
77
|
+
for (const piece of String(part).split(",")) {
|
|
78
|
+
const name = piece.trim();
|
|
79
|
+
if (name !== "" && out.indexOf(name) === -1)
|
|
80
|
+
out.push(name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Requested tables that no target scope actually syncs. A typo here would
|
|
87
|
+
* otherwise refresh nothing and look like a successful no-op, so the caller
|
|
88
|
+
* turns a non-empty result into a hard failure.
|
|
89
|
+
*/
|
|
90
|
+
function unknownTablesForScopes(tables, scope) {
|
|
91
|
+
const config = ConfigManager.getConfig();
|
|
92
|
+
const declaredScopes = scope ? [scope] : Object.keys(config.scopes || {});
|
|
93
|
+
// A project with no declared `scopes` (single-scope / legacy config) still
|
|
94
|
+
// refreshes — syncManifest falls back to the manifest's scopes. resolveConfigForScope
|
|
95
|
+
// on an unrecognized name yields the GLOBAL `_tables` whitelist, which is the
|
|
96
|
+
// right allow-list there. Without this fallback the validation below would be
|
|
97
|
+
// stricter than the refresh it guards and reject every table outright.
|
|
98
|
+
const scopeNames = declaredScopes.length > 0 ? declaredScopes : [""];
|
|
99
|
+
const allowed = [];
|
|
100
|
+
for (const scopeName of scopeNames) {
|
|
101
|
+
const resolved = ConfigManager.resolveConfigForScope(scopeName);
|
|
102
|
+
for (const table of resolved.tables || []) {
|
|
103
|
+
if (allowed.indexOf(table) === -1)
|
|
104
|
+
allowed.push(table);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return tables.filter((t) => allowed.indexOf(t) === -1);
|
|
108
|
+
}
|
|
64
109
|
async function refreshCommand(args, log = true) {
|
|
65
110
|
setLogLevel(args);
|
|
66
111
|
try {
|
|
67
112
|
if (!log)
|
|
68
113
|
setLogLevel({ logLevel: "warn" });
|
|
69
|
-
|
|
114
|
+
const tables = normalizeTableFilter(args.table);
|
|
115
|
+
if (tables.length > 0) {
|
|
116
|
+
const unknown = unknownTablesForScopes(tables, args.scope);
|
|
117
|
+
if (unknown.length > 0) {
|
|
118
|
+
const where = args.scope ? "scope '" + args.scope + "'" : "any configured scope";
|
|
119
|
+
const noun = unknown.length === 1 ? "table" : "tables";
|
|
120
|
+
const verb = unknown.length === 1 ? "is" : "are";
|
|
121
|
+
const pronoun = unknown.length === 1 ? "it" : "them";
|
|
122
|
+
throw new Error("--table: " + noun + " " + unknown.join(", ") + " " + verb + " not synced by " + where + ". " +
|
|
123
|
+
"Add " + pronoun + " to includes._tables (or includes._scopes.<scope>._tables) in dove.config.js first.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
FileLogger_1.fileLogger.debug("Syncing manifest from instance (force=" + !!args.force +
|
|
127
|
+
", benchmark=" + !!args.benchmark +
|
|
128
|
+
", tables=" + (tables.length > 0 ? tables.join(",") : "all") + ")");
|
|
70
129
|
await AppUtils.syncManifest(args.scope, {
|
|
71
130
|
force: !!args.force,
|
|
72
131
|
benchmark: !!args.benchmark,
|
|
132
|
+
tables: tables.length > 0 ? tables : undefined,
|
|
73
133
|
});
|
|
74
134
|
Logger_1.logger.success("Refresh complete!");
|
|
75
135
|
setLogLevel(args);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for `dove refresh --table` — narrow the file refresh to specific tables.
|
|
4
|
+
*
|
|
5
|
+
* Validates:
|
|
6
|
+
* - normalizeTableFilter accepts repeated flags AND comma-separated values, trimming + de-duping
|
|
7
|
+
* - unknownTablesForScopes resolves against the target scope, or the union of all scopes
|
|
8
|
+
* - refreshCommand HARD-FAILS on a table that no scope syncs (a typo must not look like a no-op)
|
|
9
|
+
* - refreshCommand forwards the table list to syncManifest, and forwards undefined when unfiltered
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const commands_1 = require("../commands");
|
|
46
|
+
const AppUtils = __importStar(require("../appUtils"));
|
|
47
|
+
const ConfigManager = __importStar(require("../config"));
|
|
48
|
+
// --- Mock setup (mirrors taskClear.test.ts) ---
|
|
49
|
+
var logMessages = [];
|
|
50
|
+
jest.mock("../Logger", function () {
|
|
51
|
+
return {
|
|
52
|
+
logger: {
|
|
53
|
+
setLogLevel: jest.fn(),
|
|
54
|
+
success: jest.fn(function (msg) {
|
|
55
|
+
logMessages.push({ level: "success", msg: msg });
|
|
56
|
+
}),
|
|
57
|
+
info: jest.fn(function (msg) {
|
|
58
|
+
logMessages.push({ level: "info", msg: msg });
|
|
59
|
+
}),
|
|
60
|
+
error: jest.fn(function (msg) {
|
|
61
|
+
logMessages.push({ level: "error", msg: msg });
|
|
62
|
+
}),
|
|
63
|
+
warn: jest.fn(function (msg) {
|
|
64
|
+
logMessages.push({ level: "warn", msg: msg });
|
|
65
|
+
}),
|
|
66
|
+
debug: jest.fn(),
|
|
67
|
+
getInternalLogger: jest.fn(function () {
|
|
68
|
+
return { error: jest.fn() };
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
jest.mock("../config", function () {
|
|
74
|
+
return {
|
|
75
|
+
getConfig: jest.fn(),
|
|
76
|
+
resolveConfigForScope: jest.fn(),
|
|
77
|
+
loadConfigs: jest.fn(),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
jest.mock("../appUtils", function () {
|
|
81
|
+
return { syncManifest: jest.fn() };
|
|
82
|
+
});
|
|
83
|
+
jest.mock("../snClient", function () {
|
|
84
|
+
return { defaultClient: jest.fn(), unwrapSNResponse: jest.fn() };
|
|
85
|
+
});
|
|
86
|
+
jest.mock("../FileLogger", function () {
|
|
87
|
+
return { fileLogger: { debug: jest.fn() } };
|
|
88
|
+
});
|
|
89
|
+
jest.mock("../logMessages", function () {
|
|
90
|
+
return { logPushResults: jest.fn(), logBuildResults: jest.fn() };
|
|
91
|
+
});
|
|
92
|
+
jest.mock("../gitUtils", function () {
|
|
93
|
+
return { gitDiffToEncodedPaths: jest.fn() };
|
|
94
|
+
});
|
|
95
|
+
jest.mock("../FileUtils", function () {
|
|
96
|
+
return { encodedPathsToFilePaths: jest.fn() };
|
|
97
|
+
});
|
|
98
|
+
jest.mock("inquirer", function () {
|
|
99
|
+
return { prompt: jest.fn() };
|
|
100
|
+
});
|
|
101
|
+
var mockConfig = ConfigManager;
|
|
102
|
+
var mockAppUtils = AppUtils;
|
|
103
|
+
// Two scopes, each syncing a different extra table — enough to prove the
|
|
104
|
+
// union-vs-single-scope resolution.
|
|
105
|
+
function stubTwoScopes() {
|
|
106
|
+
mockConfig.getConfig.mockReturnValue({
|
|
107
|
+
scopes: { x_cadso_journey: {}, x_cadso_automate: {} },
|
|
108
|
+
});
|
|
109
|
+
mockConfig.resolveConfigForScope.mockImplementation(function (scopeName) {
|
|
110
|
+
var extra = scopeName === "x_cadso_journey"
|
|
111
|
+
? "x_cadso_journey_action"
|
|
112
|
+
: "x_cadso_core_setting";
|
|
113
|
+
return { tables: ["sys_script", extra] };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
var defaultArgs = { logLevel: "info" };
|
|
117
|
+
describe("normalizeTableFilter", function () {
|
|
118
|
+
it("returns an empty list when the flag is absent", function () {
|
|
119
|
+
expect((0, commands_1.normalizeTableFilter)(undefined)).toEqual([]);
|
|
120
|
+
expect((0, commands_1.normalizeTableFilter)("")).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
it("accepts a single string", function () {
|
|
123
|
+
expect((0, commands_1.normalizeTableFilter)("sys_script")).toEqual(["sys_script"]);
|
|
124
|
+
});
|
|
125
|
+
it("accepts a repeated flag (yargs array)", function () {
|
|
126
|
+
expect((0, commands_1.normalizeTableFilter)(["sys_script", "sys_ui_action"])).toEqual([
|
|
127
|
+
"sys_script",
|
|
128
|
+
"sys_ui_action",
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
it("splits comma-separated values", function () {
|
|
132
|
+
expect((0, commands_1.normalizeTableFilter)("sys_script,sys_ui_action")).toEqual([
|
|
133
|
+
"sys_script",
|
|
134
|
+
"sys_ui_action",
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
it("trims whitespace and de-dupes", function () {
|
|
138
|
+
expect((0, commands_1.normalizeTableFilter)([" sys_script , sys_ui_action ", "sys_script"])).toEqual([
|
|
139
|
+
"sys_script",
|
|
140
|
+
"sys_ui_action",
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe("unknownTablesForScopes", function () {
|
|
145
|
+
beforeEach(function () {
|
|
146
|
+
logMessages = [];
|
|
147
|
+
jest.clearAllMocks();
|
|
148
|
+
stubTwoScopes();
|
|
149
|
+
});
|
|
150
|
+
it("accepts a table synced by the named scope", function () {
|
|
151
|
+
expect((0, commands_1.unknownTablesForScopes)(["x_cadso_journey_action"], "x_cadso_journey")).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
it("rejects a table that belongs to a DIFFERENT scope when one is named", function () {
|
|
154
|
+
expect((0, commands_1.unknownTablesForScopes)(["x_cadso_core_setting"], "x_cadso_journey")).toEqual(["x_cadso_core_setting"]);
|
|
155
|
+
});
|
|
156
|
+
it("accepts a table from any scope when no scope is named", function () {
|
|
157
|
+
expect((0, commands_1.unknownTablesForScopes)(["x_cadso_core_setting"])).toEqual([]);
|
|
158
|
+
});
|
|
159
|
+
it("reports tables no scope syncs", function () {
|
|
160
|
+
expect((0, commands_1.unknownTablesForScopes)(["sys_script", "nope_not_a_table"])).toEqual([
|
|
161
|
+
"nope_not_a_table",
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
// Regression: a project with no declared `scopes` (single-scope / legacy
|
|
165
|
+
// config) still refreshes — syncManifest falls back to the manifest's scopes.
|
|
166
|
+
// Validating against an empty scope list would make the allow-list empty and
|
|
167
|
+
// reject EVERY table, i.e. be stricter than the refresh it guards.
|
|
168
|
+
it("falls back to the global _tables when the config declares no scopes", function () {
|
|
169
|
+
mockConfig.getConfig.mockReturnValue({}); // no `scopes` key
|
|
170
|
+
mockConfig.resolveConfigForScope.mockImplementation(function () {
|
|
171
|
+
return { tables: ["sys_script", "sys_script_include"] };
|
|
172
|
+
});
|
|
173
|
+
expect((0, commands_1.unknownTablesForScopes)(["sys_script"])).toEqual([]);
|
|
174
|
+
expect((0, commands_1.unknownTablesForScopes)(["nope_not_a_table"])).toEqual(["nope_not_a_table"]);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe("refreshCommand --table", function () {
|
|
178
|
+
beforeEach(function () {
|
|
179
|
+
logMessages = [];
|
|
180
|
+
jest.clearAllMocks();
|
|
181
|
+
stubTwoScopes();
|
|
182
|
+
mockAppUtils.syncManifest.mockResolvedValue(undefined);
|
|
183
|
+
});
|
|
184
|
+
it("hard-fails on an unsynced table and does NOT refresh", async function () {
|
|
185
|
+
await expect((0, commands_1.refreshCommand)({
|
|
186
|
+
...defaultArgs,
|
|
187
|
+
scope: "x_cadso_journey",
|
|
188
|
+
table: ["nope_not_a_table"],
|
|
189
|
+
})).rejects.toThrow(/nope_not_a_table is not synced by scope 'x_cadso_journey'/);
|
|
190
|
+
expect(mockAppUtils.syncManifest).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
it("forwards the normalized table list to syncManifest", async function () {
|
|
193
|
+
await (0, commands_1.refreshCommand)({
|
|
194
|
+
...defaultArgs,
|
|
195
|
+
scope: "x_cadso_journey",
|
|
196
|
+
table: "x_cadso_journey_action",
|
|
197
|
+
});
|
|
198
|
+
expect(mockAppUtils.syncManifest).toHaveBeenCalledWith("x_cadso_journey", expect.objectContaining({ tables: ["x_cadso_journey_action"] }));
|
|
199
|
+
});
|
|
200
|
+
it("forwards undefined when no --table is given (unfiltered default)", async function () {
|
|
201
|
+
await (0, commands_1.refreshCommand)({ ...defaultArgs, scope: "x_cadso_journey" });
|
|
202
|
+
expect(mockAppUtils.syncManifest).toHaveBeenCalledWith("x_cadso_journey", expect.objectContaining({ tables: undefined }));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -67,6 +67,10 @@ var mockFileLogger = {
|
|
|
67
67
|
};
|
|
68
68
|
var mockClient = {
|
|
69
69
|
getManifest: jest.fn(),
|
|
70
|
+
// refreshAllFiles asks for file content per table. Returning {} means "no
|
|
71
|
+
// files to write", which keeps these tests off the plugin/write path while
|
|
72
|
+
// still recording WHICH tables were asked for.
|
|
73
|
+
getMissingFiles: jest.fn().mockResolvedValue({}),
|
|
70
74
|
};
|
|
71
75
|
var mockFUtils = {
|
|
72
76
|
writeScopeManifest: jest.fn().mockResolvedValue(undefined),
|
|
@@ -190,3 +194,111 @@ describe("syncManifest — scope + table whitelist gates", function () {
|
|
|
190
194
|
expect(refreshedScopes).not.toContain("x_cadso_ti_agile");
|
|
191
195
|
});
|
|
192
196
|
});
|
|
197
|
+
/**
|
|
198
|
+
* `dove refresh --table` narrows the FILE refresh only.
|
|
199
|
+
*
|
|
200
|
+
* The load-bearing invariant is that the PERSISTED manifest stays complete:
|
|
201
|
+
* narrowing dove.manifest.<scope>.json to the requested table would drop every
|
|
202
|
+
* other table from it and break push/watch. These tests exist so that a future
|
|
203
|
+
* "optimization" that narrows newManifest fails loudly instead of silently
|
|
204
|
+
* corrupting the manifest.
|
|
205
|
+
*/
|
|
206
|
+
describe("syncManifest — --table filter", function () {
|
|
207
|
+
function recordWithFile(name, sysId) {
|
|
208
|
+
return { records: { [name]: { name: name, sys_id: sysId, files: [{ name: "script", type: "js" }] } } };
|
|
209
|
+
}
|
|
210
|
+
beforeEach(function () {
|
|
211
|
+
jest.clearAllMocks();
|
|
212
|
+
mockClient.getMissingFiles.mockResolvedValue({});
|
|
213
|
+
mockConfig.isMultiScopeManifest.mockReturnValue(true);
|
|
214
|
+
mockConfig.resolveConfigForScope.mockImplementation(function (_scope) {
|
|
215
|
+
return {
|
|
216
|
+
tables: ["sys_script_include", "sys_script", "sys_ux_macroponent"],
|
|
217
|
+
fieldOverrides: {},
|
|
218
|
+
apiIncludes: {},
|
|
219
|
+
apiExcludes: {},
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
mockConfig.getConfig.mockReturnValue({ scopes: { x_cadso_core: {} } });
|
|
223
|
+
mockConfig.getManifest.mockResolvedValue({
|
|
224
|
+
x_cadso_core: { scope: "x_cadso_core", tables: {} },
|
|
225
|
+
});
|
|
226
|
+
mockClient.getManifest.mockResolvedValue({
|
|
227
|
+
scope: "x_cadso_core",
|
|
228
|
+
tables: {
|
|
229
|
+
sys_script_include: recordWithFile("FooInclude", "a1"),
|
|
230
|
+
sys_script: recordWithFile("BarBR", "a2"),
|
|
231
|
+
sys_ux_macroponent: recordWithFile("BazComp", "a3"),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
test("does NOT narrow the persisted manifest — all whitelisted tables still written", async function () {
|
|
236
|
+
await AppUtils.syncManifest("x_cadso_core", { tables: ["sys_script"] });
|
|
237
|
+
expect(mockFUtils.writeScopeManifest).toHaveBeenCalledTimes(1);
|
|
238
|
+
var writtenTables = Object.keys(mockFUtils.writeScopeManifest.mock.calls[0][1].tables);
|
|
239
|
+
// The manifest keeps every whitelisted table, not just the requested one.
|
|
240
|
+
expect(writtenTables.sort()).toEqual([
|
|
241
|
+
"sys_script",
|
|
242
|
+
"sys_script_include",
|
|
243
|
+
"sys_ux_macroponent",
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
test("narrows the file refresh to the requested tables only", async function () {
|
|
247
|
+
await AppUtils.syncManifest("x_cadso_core", { tables: ["sys_script"] });
|
|
248
|
+
expect(mockClient.getMissingFiles).toHaveBeenCalledTimes(1);
|
|
249
|
+
var askedFor = Object.keys(mockClient.getMissingFiles.mock.calls[0][0]);
|
|
250
|
+
expect(askedFor).toEqual(["sys_script"]);
|
|
251
|
+
});
|
|
252
|
+
test("refreshes every table when no --table filter is given", async function () {
|
|
253
|
+
await AppUtils.syncManifest("x_cadso_core", {});
|
|
254
|
+
var askedFor = Object.keys(mockClient.getMissingFiles.mock.calls[0][0]);
|
|
255
|
+
expect(askedFor.sort()).toEqual([
|
|
256
|
+
"sys_script",
|
|
257
|
+
"sys_script_include",
|
|
258
|
+
"sys_ux_macroponent",
|
|
259
|
+
]);
|
|
260
|
+
});
|
|
261
|
+
test("a scope holding none of the requested tables still writes its manifest, but refreshes no files", async function () {
|
|
262
|
+
await AppUtils.syncManifest("x_cadso_core", { tables: ["sys_ui_action"] });
|
|
263
|
+
// Manifest untouched-in-full …
|
|
264
|
+
expect(mockFUtils.writeScopeManifest).toHaveBeenCalledTimes(1);
|
|
265
|
+
expect(Object.keys(mockFUtils.writeScopeManifest.mock.calls[0][1].tables)).toHaveLength(3);
|
|
266
|
+
// … and not a single file downloaded.
|
|
267
|
+
expect(mockClient.getMissingFiles).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
describe("narrowManifestToTables", function () {
|
|
271
|
+
var manifest;
|
|
272
|
+
beforeEach(function () {
|
|
273
|
+
manifest = {
|
|
274
|
+
scope: "x_cadso_core",
|
|
275
|
+
tables: {
|
|
276
|
+
sys_script: { records: {} },
|
|
277
|
+
sys_script_include: { records: {} },
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
test("returns the SAME manifest when no tables are requested", function () {
|
|
282
|
+
expect(AppUtils.narrowManifestToTables(manifest, undefined)).toBe(manifest);
|
|
283
|
+
expect(AppUtils.narrowManifestToTables(manifest, [])).toBe(manifest);
|
|
284
|
+
});
|
|
285
|
+
test("returns a NEW manifest and never mutates the input", function () {
|
|
286
|
+
var narrowed = AppUtils.narrowManifestToTables(manifest, ["sys_script"]);
|
|
287
|
+
expect(narrowed).not.toBe(manifest);
|
|
288
|
+
expect(Object.keys(narrowed.tables)).toEqual(["sys_script"]);
|
|
289
|
+
// The corruption guard: the caller still writes `manifest` to disk in full.
|
|
290
|
+
expect(Object.keys(manifest.tables).sort()).toEqual([
|
|
291
|
+
"sys_script",
|
|
292
|
+
"sys_script_include",
|
|
293
|
+
]);
|
|
294
|
+
});
|
|
295
|
+
test("yields an empty table map when nothing matches", function () {
|
|
296
|
+
var narrowed = AppUtils.narrowManifestToTables(manifest, ["sys_ui_action"]);
|
|
297
|
+
expect(Object.keys(narrowed.tables)).toEqual([]);
|
|
298
|
+
expect(narrowed.scope).toBe("x_cadso_core");
|
|
299
|
+
});
|
|
300
|
+
test("ignores requested tables the scope does not hold", function () {
|
|
301
|
+
var narrowed = AppUtils.narrowManifestToTables(manifest, ["sys_script", "sys_ui_action"]);
|
|
302
|
+
expect(Object.keys(narrowed.tables)).toEqual(["sys_script"]);
|
|
303
|
+
});
|
|
304
|
+
});
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,7 +1,171 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "dovetail-manifest/v1",
|
|
3
|
-
"generated_at": "2026-
|
|
3
|
+
"generated_at": "2026-07-10T18:41:37.097Z",
|
|
4
4
|
"events": [
|
|
5
|
+
{
|
|
6
|
+
"$schema": "dovetail-release-event/v1",
|
|
7
|
+
"event_id": "dovetail-deploy@0.0.2",
|
|
8
|
+
"package": "@tenonhq/dovetail-deploy",
|
|
9
|
+
"version": "0.0.2",
|
|
10
|
+
"prev_version": "0.0.1",
|
|
11
|
+
"published_at": "2026-07-10T18:41:37.089Z",
|
|
12
|
+
"git": {
|
|
13
|
+
"sha": "58c416a421d7b9eaff3483468601cc1eda753b20",
|
|
14
|
+
"tag": "dovetail-deploy@0.0.2"
|
|
15
|
+
},
|
|
16
|
+
"semver_bump": "patch",
|
|
17
|
+
"commits": [
|
|
18
|
+
{
|
|
19
|
+
"sha": "58c416a42",
|
|
20
|
+
"subject": "feat(deploy): dynamic per-developer promotion source (#211)",
|
|
21
|
+
"pr": 211
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"exports_delta": null,
|
|
25
|
+
"narrative": null
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"$schema": "dovetail-release-event/v1",
|
|
29
|
+
"event_id": "dovetail-servicenow@0.0.26",
|
|
30
|
+
"package": "@tenonhq/dovetail-servicenow",
|
|
31
|
+
"version": "0.0.26",
|
|
32
|
+
"prev_version": "0.0.25",
|
|
33
|
+
"published_at": "2026-07-10T16:27:58.460Z",
|
|
34
|
+
"git": {
|
|
35
|
+
"sha": "9b3b58b8fb9aa53b32473c8c7711446f5a52a576",
|
|
36
|
+
"tag": "dovetail-servicenow@0.0.26"
|
|
37
|
+
},
|
|
38
|
+
"semver_bump": "patch",
|
|
39
|
+
"commits": [
|
|
40
|
+
{
|
|
41
|
+
"sha": "9b3b58b8f",
|
|
42
|
+
"subject": "feat: invoke arbitrary authenticated Scripted REST operations (invoke-rest) (#214)",
|
|
43
|
+
"pr": 214
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"exports_delta": null,
|
|
47
|
+
"narrative": null
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"$schema": "dovetail-release-event/v1",
|
|
51
|
+
"event_id": "dovetail-mcp@0.0.23",
|
|
52
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
53
|
+
"version": "0.0.23",
|
|
54
|
+
"prev_version": "0.0.22",
|
|
55
|
+
"published_at": "2026-07-10T16:27:58.460Z",
|
|
56
|
+
"git": {
|
|
57
|
+
"sha": "9b3b58b8fb9aa53b32473c8c7711446f5a52a576",
|
|
58
|
+
"tag": "dovetail-mcp@0.0.23"
|
|
59
|
+
},
|
|
60
|
+
"semver_bump": "patch",
|
|
61
|
+
"commits": [
|
|
62
|
+
{
|
|
63
|
+
"sha": "534f517e4",
|
|
64
|
+
"subject": "chore(release): publish 3 packages [skip ci]"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"exports_delta": null,
|
|
68
|
+
"narrative": null
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"$schema": "dovetail-release-event/v1",
|
|
72
|
+
"event_id": "dovetail-dashboard@0.0.48",
|
|
73
|
+
"package": "@tenonhq/dovetail-dashboard",
|
|
74
|
+
"version": "0.0.48",
|
|
75
|
+
"prev_version": "0.0.47",
|
|
76
|
+
"published_at": "2026-07-08T21:55:56.082Z",
|
|
77
|
+
"git": {
|
|
78
|
+
"sha": "7ef92071de6726ccfd751ae617a661f95fe685fc",
|
|
79
|
+
"tag": "dovetail-dashboard@0.0.48"
|
|
80
|
+
},
|
|
81
|
+
"semver_bump": "patch",
|
|
82
|
+
"commits": [
|
|
83
|
+
{
|
|
84
|
+
"sha": "7ef92071d",
|
|
85
|
+
"subject": "feat(ci): Slack alerts for publish success/failure + cut dashboard release (#209)",
|
|
86
|
+
"pr": 209
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"exports_delta": null,
|
|
90
|
+
"narrative": null
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"$schema": "dovetail-release-event/v1",
|
|
94
|
+
"event_id": "dovetail-clickup@0.0.13",
|
|
95
|
+
"package": "@tenonhq/dovetail-clickup",
|
|
96
|
+
"version": "0.0.13",
|
|
97
|
+
"prev_version": "0.0.12",
|
|
98
|
+
"published_at": "2026-07-08T18:19:16.768Z",
|
|
99
|
+
"git": {
|
|
100
|
+
"sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
|
|
101
|
+
"tag": "dovetail-clickup@0.0.13"
|
|
102
|
+
},
|
|
103
|
+
"semver_bump": "patch",
|
|
104
|
+
"commits": [
|
|
105
|
+
{
|
|
106
|
+
"sha": "84fc7a200",
|
|
107
|
+
"subject": "fix: clickup get_task resolves custom ids instead of a 401 (#208)",
|
|
108
|
+
"pr": 208
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"sha": "4bc78bc61",
|
|
112
|
+
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"sha": "c82439b30",
|
|
116
|
+
"subject": "chore(deps): bump js-yaml, jest and ts-jest (#183)",
|
|
117
|
+
"pr": 183
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"sha": "9332fdf7a",
|
|
121
|
+
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"sha": "9046da774",
|
|
125
|
+
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"exports_delta": null,
|
|
129
|
+
"narrative": null
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"$schema": "dovetail-release-event/v1",
|
|
133
|
+
"event_id": "dovetail-core@0.0.110",
|
|
134
|
+
"package": "@tenonhq/dovetail-core",
|
|
135
|
+
"version": "0.0.110",
|
|
136
|
+
"prev_version": "0.0.109",
|
|
137
|
+
"published_at": "2026-07-08T18:19:16.768Z",
|
|
138
|
+
"git": {
|
|
139
|
+
"sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
|
|
140
|
+
"tag": "dovetail-core@0.0.110"
|
|
141
|
+
},
|
|
142
|
+
"semver_bump": "patch",
|
|
143
|
+
"commits": [],
|
|
144
|
+
"exports_delta": null,
|
|
145
|
+
"narrative": null
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"$schema": "dovetail-release-event/v1",
|
|
149
|
+
"event_id": "dovetail-mcp@0.0.22",
|
|
150
|
+
"package": "@tenonhq/dovetail-mcp",
|
|
151
|
+
"version": "0.0.22",
|
|
152
|
+
"prev_version": "0.0.21",
|
|
153
|
+
"published_at": "2026-07-08T18:19:16.768Z",
|
|
154
|
+
"git": {
|
|
155
|
+
"sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
|
|
156
|
+
"tag": "dovetail-mcp@0.0.22"
|
|
157
|
+
},
|
|
158
|
+
"semver_bump": "patch",
|
|
159
|
+
"commits": [
|
|
160
|
+
{
|
|
161
|
+
"sha": "84fc7a200",
|
|
162
|
+
"subject": "fix: clickup get_task resolves custom ids instead of a 401 (#208)",
|
|
163
|
+
"pr": 208
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"exports_delta": null,
|
|
167
|
+
"narrative": null
|
|
168
|
+
},
|
|
5
169
|
{
|
|
6
170
|
"$schema": "dovetail-release-event/v1",
|
|
7
171
|
"event_id": "dovetail-dashboard@0.0.44",
|
|
@@ -1105,204 +1269,6 @@
|
|
|
1105
1269
|
],
|
|
1106
1270
|
"exports_delta": null,
|
|
1107
1271
|
"narrative": null
|
|
1108
|
-
},
|
|
1109
|
-
{
|
|
1110
|
-
"$schema": "dovetail-release-event/v1",
|
|
1111
|
-
"event_id": "dovetail-sass-plugin@0.0.11",
|
|
1112
|
-
"package": "@tenonhq/dovetail-sass-plugin",
|
|
1113
|
-
"version": "0.0.11",
|
|
1114
|
-
"prev_version": "0.0.10",
|
|
1115
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1116
|
-
"git": {
|
|
1117
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1118
|
-
"tag": "dovetail-sass-plugin@0.0.11"
|
|
1119
|
-
},
|
|
1120
|
-
"semver_bump": "patch",
|
|
1121
|
-
"commits": [
|
|
1122
|
-
{
|
|
1123
|
-
"sha": "71bdf9bba",
|
|
1124
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1125
|
-
"pr": 148
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
"sha": "9332fdf7a",
|
|
1129
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1130
|
-
},
|
|
1131
|
-
{
|
|
1132
|
-
"sha": "9046da774",
|
|
1133
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1134
|
-
}
|
|
1135
|
-
],
|
|
1136
|
-
"exports_delta": null,
|
|
1137
|
-
"narrative": null
|
|
1138
|
-
},
|
|
1139
|
-
{
|
|
1140
|
-
"$schema": "dovetail-release-event/v1",
|
|
1141
|
-
"event_id": "dovetail-typescript-plugin@0.0.11",
|
|
1142
|
-
"package": "@tenonhq/dovetail-typescript-plugin",
|
|
1143
|
-
"version": "0.0.11",
|
|
1144
|
-
"prev_version": "0.0.10",
|
|
1145
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1146
|
-
"git": {
|
|
1147
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1148
|
-
"tag": "dovetail-typescript-plugin@0.0.11"
|
|
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": "9332fdf7a",
|
|
1159
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1160
|
-
},
|
|
1161
|
-
{
|
|
1162
|
-
"sha": "9046da774",
|
|
1163
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1164
|
-
}
|
|
1165
|
-
],
|
|
1166
|
-
"exports_delta": null,
|
|
1167
|
-
"narrative": null
|
|
1168
|
-
},
|
|
1169
|
-
{
|
|
1170
|
-
"$schema": "dovetail-release-event/v1",
|
|
1171
|
-
"event_id": "dovetail-webpack-plugin@0.0.10",
|
|
1172
|
-
"package": "@tenonhq/dovetail-webpack-plugin",
|
|
1173
|
-
"version": "0.0.10",
|
|
1174
|
-
"prev_version": "0.0.9",
|
|
1175
|
-
"published_at": "2026-06-05T06:43:18.252Z",
|
|
1176
|
-
"git": {
|
|
1177
|
-
"sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
|
|
1178
|
-
"tag": "dovetail-webpack-plugin@0.0.10"
|
|
1179
|
-
},
|
|
1180
|
-
"semver_bump": "patch",
|
|
1181
|
-
"commits": [
|
|
1182
|
-
{
|
|
1183
|
-
"sha": "71bdf9bba",
|
|
1184
|
-
"subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
|
|
1185
|
-
"pr": 148
|
|
1186
|
-
},
|
|
1187
|
-
{
|
|
1188
|
-
"sha": "9332fdf7a",
|
|
1189
|
-
"subject": "chore(release): publish 20 packages [skip ci]"
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
"sha": "9046da774",
|
|
1193
|
-
"subject": "chore(release): publish 19 packages [skip ci]"
|
|
1194
|
-
}
|
|
1195
|
-
],
|
|
1196
|
-
"exports_delta": null,
|
|
1197
|
-
"narrative": null
|
|
1198
|
-
},
|
|
1199
|
-
{
|
|
1200
|
-
"$schema": "dovetail-release-event/v1",
|
|
1201
|
-
"event_id": "dovetail-servicenow@0.0.15",
|
|
1202
|
-
"package": "@tenonhq/dovetail-servicenow",
|
|
1203
|
-
"version": "0.0.15",
|
|
1204
|
-
"prev_version": "0.0.14",
|
|
1205
|
-
"published_at": "2026-06-04T22:16:51.160Z",
|
|
1206
|
-
"git": {
|
|
1207
|
-
"sha": "d46b5bd2dce0fb3b13f0d1d5933f88d1d421b550",
|
|
1208
|
-
"tag": "dovetail-servicenow@0.0.15"
|
|
1209
|
-
},
|
|
1210
|
-
"semver_bump": "patch",
|
|
1211
|
-
"commits": [
|
|
1212
|
-
{
|
|
1213
|
-
"sha": "d46b5bd2d",
|
|
1214
|
-
"subject": "feat(servicenow): dove-sn edit-action — headless action-type script/output edit (#147)",
|
|
1215
|
-
"pr": 147
|
|
1216
|
-
},
|
|
1217
|
-
{
|
|
1218
|
-
"sha": "af1417796",
|
|
1219
|
-
"subject": "chore(release): publish 3 packages [skip ci]"
|
|
1220
|
-
}
|
|
1221
|
-
],
|
|
1222
|
-
"exports_delta": null,
|
|
1223
|
-
"narrative": null
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
"$schema": "dovetail-release-event/v1",
|
|
1227
|
-
"event_id": "dovetail-claude-plans@0.0.21",
|
|
1228
|
-
"package": "@tenonhq/dovetail-claude-plans",
|
|
1229
|
-
"version": "0.0.21",
|
|
1230
|
-
"prev_version": "0.0.20",
|
|
1231
|
-
"published_at": "2026-06-04T08:56:53.804Z",
|
|
1232
|
-
"git": {
|
|
1233
|
-
"sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
|
|
1234
|
-
"tag": "dovetail-claude-plans@0.0.21"
|
|
1235
|
-
},
|
|
1236
|
-
"semver_bump": "patch",
|
|
1237
|
-
"commits": [
|
|
1238
|
-
{
|
|
1239
|
-
"sha": "44e5c84b8",
|
|
1240
|
-
"subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
|
|
1241
|
-
"pr": 145
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
"sha": "92213e1f8",
|
|
1245
|
-
"subject": "chore(release): publish 2 packages [skip ci]"
|
|
1246
|
-
}
|
|
1247
|
-
],
|
|
1248
|
-
"exports_delta": null,
|
|
1249
|
-
"narrative": null
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
"$schema": "dovetail-release-event/v1",
|
|
1253
|
-
"event_id": "dovetail-servicenow@0.0.14",
|
|
1254
|
-
"package": "@tenonhq/dovetail-servicenow",
|
|
1255
|
-
"version": "0.0.14",
|
|
1256
|
-
"prev_version": "0.0.13",
|
|
1257
|
-
"published_at": "2026-06-04T08:56:53.804Z",
|
|
1258
|
-
"git": {
|
|
1259
|
-
"sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
|
|
1260
|
-
"tag": "dovetail-servicenow@0.0.14"
|
|
1261
|
-
},
|
|
1262
|
-
"semver_bump": "patch",
|
|
1263
|
-
"commits": [
|
|
1264
|
-
{
|
|
1265
|
-
"sha": "44e5c84b8",
|
|
1266
|
-
"subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
|
|
1267
|
-
"pr": 145
|
|
1268
|
-
},
|
|
1269
|
-
{
|
|
1270
|
-
"sha": "e0cf83a8c",
|
|
1271
|
-
"subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.13 [skip ci]"
|
|
1272
|
-
}
|
|
1273
|
-
],
|
|
1274
|
-
"exports_delta": null,
|
|
1275
|
-
"narrative": null
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
"$schema": "dovetail-release-event/v1",
|
|
1279
|
-
"event_id": "dovetail-mcp@0.0.8",
|
|
1280
|
-
"package": "@tenonhq/dovetail-mcp",
|
|
1281
|
-
"version": "0.0.8",
|
|
1282
|
-
"prev_version": "0.0.7",
|
|
1283
|
-
"published_at": "2026-06-04T08:56:53.804Z",
|
|
1284
|
-
"git": {
|
|
1285
|
-
"sha": "44e5c84b85007cf6e9223b279ff79afd5319c5ab",
|
|
1286
|
-
"tag": "dovetail-mcp@0.0.8"
|
|
1287
|
-
},
|
|
1288
|
-
"semver_bump": "patch",
|
|
1289
|
-
"commits": [
|
|
1290
|
-
{
|
|
1291
|
-
"sha": "44e5c84b8",
|
|
1292
|
-
"subject": "chore(docs): neutralize upstream operating-repo references in package docs (#145)",
|
|
1293
|
-
"pr": 145
|
|
1294
|
-
},
|
|
1295
|
-
{
|
|
1296
|
-
"sha": "bd127c726",
|
|
1297
|
-
"subject": "chore(release): @tenonhq/dovetail-servicenow@0.0.12 [skip ci]"
|
|
1298
|
-
},
|
|
1299
|
-
{
|
|
1300
|
-
"sha": "448696846",
|
|
1301
|
-
"subject": "chore(release): publish 3 packages [skip ci]"
|
|
1302
|
-
}
|
|
1303
|
-
],
|
|
1304
|
-
"exports_delta": null,
|
|
1305
|
-
"narrative": null
|
|
1306
1272
|
}
|
|
1307
1273
|
]
|
|
1308
1274
|
}
|