@tenonhq/dovetail-core 0.0.110 → 0.0.112

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/README.md CHANGED
@@ -26,7 +26,7 @@ Quick reference:
26
26
  ```bash
27
27
  npx dove watch # multi-scope watch + dashboard
28
28
  npx dove push # build + push current files
29
- npx dove refresh # pull manifest + new files
29
+ npx dove refresh # pull manifest + new files (aliases: pull, r)
30
30
  npx dove build # local build only
31
31
  npx dove deploy # deploy built artifacts
32
32
  npx dove dashboard # update-set dashboard web UI
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)(newManifest, scopeSourcePath, {
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
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configureCli = configureCli;
6
7
  exports.initCommands = initCommands;
7
8
  const commands_1 = require("./commands");
8
9
  const allScopesCommands_1 = require("./allScopesCommands");
@@ -18,15 +19,26 @@ const clickupCommands_1 = require("./clickupCommands");
18
19
  const loginCommand_1 = require("./loginCommand");
19
20
  const knowledgeDiffCommand_1 = require("./knowledgeDiffCommand");
20
21
  const yargs_1 = __importDefault(require("yargs"));
21
- async function initCommands() {
22
+ /**
23
+ * @description Registers every `dove` command, the global options, and the
24
+ * parser-strictness rules on a yargs instance. Split out from initCommands so
25
+ * tests can drive a fresh, non-exiting parser (see tests/commanderStrict.test.ts).
26
+ * @param {Argv} cli - A yargs instance to configure.
27
+ * @returns {Argv} The same instance, with all commands and options registered.
28
+ */
29
+ function configureCli(cli) {
22
30
  const sharedOptions = {
23
31
  logLevel: {
24
32
  default: "info",
25
33
  },
26
34
  };
27
- yargs_1.default
35
+ return cli
36
+ .scriptName("dove")
28
37
  .option("env", {
29
- alias: "e",
38
+ // `--env-file` / `--envFile` are long-documented spellings and are parsed
39
+ // from raw argv in envArg.ts. Declaring them as aliases keeps `--help` and
40
+ // the parser honest about what is actually accepted.
41
+ alias: ["e", "env-file", "envFile"],
30
42
  type: "string",
31
43
  describe: "Path to a .env file to load for this command (default: .env in the project root). Lets one checkout target multiple instances.",
32
44
  })
@@ -65,7 +77,10 @@ async function initCommands() {
65
77
  }, async (args) => {
66
78
  await (0, allScopesCommands_1.watchAllScopesCommand)(args);
67
79
  })
68
- .command(["refresh", "r"], "Pull latest manifest and file contents from the ServiceNow instance", (cmdArgs) => {
80
+ .command(
81
+ // `pull` is the verb people reach for ("pull before push") and it appears
82
+ // in downstream docs and scripts — alias it rather than silently no-op.
83
+ ["refresh", "r", "pull"], "Pull latest manifest and file contents from the ServiceNow instance", (cmdArgs) => {
69
84
  cmdArgs.options({
70
85
  ...sharedOptions,
71
86
  force: {
@@ -79,6 +94,13 @@ async function initCommands() {
79
94
  type: "string",
80
95
  describe: "Refresh a single scope (default: all declared scopes)",
81
96
  },
97
+ table: {
98
+ alias: "t",
99
+ type: "array",
100
+ describe: "Refresh file content for only these tables (repeatable or comma-separated). " +
101
+ "Must already be synced via includes._tables or includes._scopes.<scope>._tables. " +
102
+ "Default: every synced table.",
103
+ },
82
104
  benchmark: {
83
105
  alias: "b",
84
106
  type: "boolean",
@@ -561,5 +583,17 @@ async function initCommands() {
561
583
  }, async function (args) {
562
584
  await (0, knowledgeDiffCommand_1.knowledgeDiffCommand)(args);
563
585
  })
564
- .help().argv;
586
+ // An unrecognized command used to fall through the parser and exit 0, doing
587
+ // nothing — so `dove pull` (and any typo) looked like it had succeeded, which
588
+ // is the exact false confidence "pull before push" exists to prevent.
589
+ // strictCommands (not strict) is deliberate: it rejects unknown *commands*
590
+ // while still tolerating undeclared flags, so documented option spellings
591
+ // keep working. demandCommand turns a bare `dove` into help + exit 1.
592
+ .demandCommand(1, "Please specify a command (run `dove --help` to see them all)")
593
+ .strictCommands()
594
+ .recommendCommands()
595
+ .help();
596
+ }
597
+ async function initCommands() {
598
+ configureCli(yargs_1.default).argv;
565
599
  }
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
- FileLogger_1.fileLogger.debug("Syncing manifest from instance (force=" + !!args.force + ", benchmark=" + !!args.benchmark + ")");
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,152 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // --- Mock setup (must be before imports) ---
7
+ //
8
+ // Every command module is mocked: these tests exercise the parser (routing +
9
+ // strictness), not the commands themselves, and the real handlers would hit
10
+ // ServiceNow.
11
+ jest.mock("../commands", () => ({
12
+ refreshCommand: jest.fn(),
13
+ pushCommand: jest.fn(),
14
+ downloadCommand: jest.fn(),
15
+ initCommand: jest.fn(),
16
+ buildCommand: jest.fn(),
17
+ deployCommand: jest.fn(),
18
+ statusCommand: jest.fn(),
19
+ taskClearCommand: jest.fn(),
20
+ }));
21
+ jest.mock("../allScopesCommands", () => ({
22
+ initScopesCommand: jest.fn(),
23
+ watchAllScopesCommand: jest.fn(),
24
+ }));
25
+ jest.mock("../updateSetCommands", () => ({
26
+ createUpdateSetCommand: jest.fn(),
27
+ switchUpdateSetCommand: jest.fn(),
28
+ listUpdateSetsCommand: jest.fn(),
29
+ showCurrentUpdateSetCommand: jest.fn(),
30
+ changeScopeCommand: jest.fn(),
31
+ showCurrentScopeCommand: jest.fn(),
32
+ }));
33
+ jest.mock("../dashboardCommand", () => ({ dashboardCommand: jest.fn() }));
34
+ jest.mock("../schemaCommand", () => ({
35
+ schemaPullCommand: jest.fn(),
36
+ schemaDiffCommand: jest.fn(),
37
+ schemaSnapshotsCommand: jest.fn(),
38
+ }));
39
+ jest.mock("../claudeCommand", () => ({ initClaudeCommand: jest.fn() }));
40
+ jest.mock("../createRecordCommand", () => ({ createRecordCommand: jest.fn() }));
41
+ jest.mock("../deleteRecordCommand", () => ({ deleteRecordCommand: jest.fn() }));
42
+ jest.mock("../reconcileCommand", () => ({ reconcileCommand: jest.fn() }));
43
+ jest.mock("../migrateCommand", () => ({ migrateCommand: jest.fn() }));
44
+ jest.mock("../loginCommand", () => ({ loginCommand: jest.fn() }));
45
+ jest.mock("../knowledgeDiffCommand", () => ({ knowledgeDiffCommand: jest.fn() }));
46
+ jest.mock("../clickupCommands", () => ({
47
+ clickupTasksCommand: jest.fn(),
48
+ clickupTaskCommand: jest.fn(),
49
+ clickupCreateCommand: jest.fn(),
50
+ clickupUpdateCommand: jest.fn(),
51
+ clickupCommentCommand: jest.fn(),
52
+ clickupSetupCommand: jest.fn(),
53
+ clickupTeamsCommand: jest.fn(),
54
+ clickupSpacesCommand: jest.fn(),
55
+ clickupListsCommand: jest.fn(),
56
+ }));
57
+ // --- Imports (after mocks) ---
58
+ const yargs_1 = __importDefault(require("yargs/yargs"));
59
+ const commander_1 = require("../commander");
60
+ const commands_1 = require("../commands");
61
+ /**
62
+ * @description Runs a fake `dove` invocation through a fresh, non-exiting parser.
63
+ * @param {string[]} args - Argv as the user would type it, minus the binary name.
64
+ * @returns {Promise<ParseResult>} The parse error (if any), yargs' captured output, and the parsed argv.
65
+ */
66
+ async function run(args) {
67
+ const cli = (0, commander_1.configureCli)((0, yargs_1.default)([])).exitProcess(false);
68
+ const result = await new Promise(function (resolve) {
69
+ cli.parse(args, {}, function (error, argv, output) {
70
+ resolve({
71
+ error: error === null ? undefined : error,
72
+ output: output || "",
73
+ argv: (argv || {}),
74
+ });
75
+ });
76
+ });
77
+ // Command handlers are async; let their microtasks flush before asserting.
78
+ await new Promise(function (resolve) {
79
+ setImmediate(resolve);
80
+ });
81
+ return result;
82
+ }
83
+ beforeEach(function () {
84
+ jest.clearAllMocks();
85
+ });
86
+ describe("dove pull", function () {
87
+ it("routes `pull` to the refresh command", async function () {
88
+ const { error } = await run(["pull"]);
89
+ expect(error).toBeUndefined();
90
+ expect(commands_1.refreshCommand).toHaveBeenCalledTimes(1);
91
+ });
92
+ it("still routes the canonical `refresh` and its `r` alias", async function () {
93
+ await run(["refresh"]);
94
+ expect(commands_1.refreshCommand).toHaveBeenCalledTimes(1);
95
+ await run(["r"]);
96
+ expect(commands_1.refreshCommand).toHaveBeenCalledTimes(2);
97
+ });
98
+ it("passes refresh flags through the pull alias", async function () {
99
+ const { error, argv } = await run(["pull", "--scope", "x_cadso_core", "--force"]);
100
+ expect(error).toBeUndefined();
101
+ expect(argv.scope).toBe("x_cadso_core");
102
+ expect(argv.force).toBe(true);
103
+ expect(commands_1.refreshCommand).toHaveBeenCalledTimes(1);
104
+ });
105
+ });
106
+ describe("unknown commands", function () {
107
+ it("errors instead of silently succeeding", async function () {
108
+ const { error } = await run(["psuh"]);
109
+ expect(error).toBeDefined();
110
+ expect(String(error)).toContain("Unknown command");
111
+ expect(commands_1.pushCommand).not.toHaveBeenCalled();
112
+ });
113
+ it("suggests the closest real command on a typo", async function () {
114
+ const { error, output } = await run(["refesh"]);
115
+ expect(error).toBeDefined();
116
+ expect(output + String(error)).toContain("Did you mean refresh?");
117
+ expect(commands_1.refreshCommand).not.toHaveBeenCalled();
118
+ });
119
+ it("errors when no command is given at all", async function () {
120
+ const { error } = await run([]);
121
+ expect(error).toBeDefined();
122
+ expect(String(error)).toContain("Please specify a command");
123
+ });
124
+ });
125
+ describe("existing invocations keep working", function () {
126
+ it("accepts a known command", async function () {
127
+ const { error } = await run(["status"]);
128
+ expect(error).toBeUndefined();
129
+ expect(commands_1.statusCommand).toHaveBeenCalledTimes(1);
130
+ });
131
+ it("accepts push with its --diff flag and optional target positional", async function () {
132
+ const { error, argv } = await run(["push", "--diff", "main"]);
133
+ expect(error).toBeUndefined();
134
+ expect(argv.diff).toBe("main");
135
+ expect(commands_1.pushCommand).toHaveBeenCalledTimes(1);
136
+ });
137
+ it("accepts the documented --env-file spelling (not just --env)", async function () {
138
+ // strictCommands, not strict: undeclared flags stay tolerated and the
139
+ // documented env aliases all resolve to `env`. See envArg.ts.
140
+ const { error, argv } = await run(["status", "--env-file", ".env.prod"]);
141
+ expect(error).toBeUndefined();
142
+ expect(argv.env).toBe(".env.prod");
143
+ });
144
+ it("still routes nested subcommands", async function () {
145
+ const { error } = await run(["schema", "pull"]);
146
+ expect(error).toBeUndefined();
147
+ });
148
+ it("rejects an unknown nested subcommand", async function () {
149
+ const { error } = await run(["clickup", "taks"]);
150
+ expect(error).toBeDefined();
151
+ });
152
+ });
@@ -3,6 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ // --- Mock setup (must be before imports) ---
7
+ //
8
+ // These tests assert on credential loading only. init() also boots the CLI
9
+ // parser, which under Jest sees jest's own argv, finds no `dove` command, and
10
+ // now exits 1 (the parser rejects unknown commands instead of silently
11
+ // succeeding). Stub the parser so the credential assertions are what's tested.
12
+ jest.mock("../commander", () => ({ initCommands: jest.fn() }));
13
+ // --- Imports (after mocks) ---
6
14
  const fs_1 = __importDefault(require("fs"));
7
15
  const path_1 = __importDefault(require("path"));
8
16
  const bootstrap_1 = require("../bootstrap");
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-core",
3
- "version": "0.0.110",
3
+ "version": "0.0.112",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",
@@ -1,7 +1,209 @@
1
1
  {
2
2
  "$schema": "dovetail-manifest/v1",
3
- "generated_at": "2026-06-23T19:16:43.134Z",
3
+ "generated_at": "2026-07-14T18:00:31.248Z",
4
4
  "events": [
5
+ {
6
+ "$schema": "dovetail-release-event/v1",
7
+ "event_id": "dovetail-core@0.0.111",
8
+ "package": "@tenonhq/dovetail-core",
9
+ "version": "0.0.111",
10
+ "prev_version": "0.0.110",
11
+ "published_at": "2026-07-14T18:00:31.240Z",
12
+ "git": {
13
+ "sha": "c017d194ec18d0293a0a512f835dafcf49f16560",
14
+ "tag": "dovetail-core@0.0.111"
15
+ },
16
+ "semver_bump": "patch",
17
+ "commits": [
18
+ {
19
+ "sha": "c017d194e",
20
+ "subject": "feat(core): add --table filter to dove refresh (#218)",
21
+ "pr": 218
22
+ },
23
+ {
24
+ "sha": "34c5ae1ba",
25
+ "subject": "chore(release): @tenonhq/dovetail-deploy@0.0.2 [skip ci]"
26
+ },
27
+ {
28
+ "sha": "ef9e73505",
29
+ "subject": "chore(release): publish 2 packages [skip ci]"
30
+ },
31
+ {
32
+ "sha": "619cf397b",
33
+ "subject": "chore(release): @tenonhq/dovetail-dashboard@0.0.48 [skip ci]"
34
+ },
35
+ {
36
+ "sha": "534f517e4",
37
+ "subject": "chore(release): publish 3 packages [skip ci]"
38
+ }
39
+ ],
40
+ "exports_delta": null,
41
+ "narrative": null
42
+ },
43
+ {
44
+ "$schema": "dovetail-release-event/v1",
45
+ "event_id": "dovetail-deploy@0.0.2",
46
+ "package": "@tenonhq/dovetail-deploy",
47
+ "version": "0.0.2",
48
+ "prev_version": "0.0.1",
49
+ "published_at": "2026-07-10T18:41:37.089Z",
50
+ "git": {
51
+ "sha": "58c416a421d7b9eaff3483468601cc1eda753b20",
52
+ "tag": "dovetail-deploy@0.0.2"
53
+ },
54
+ "semver_bump": "patch",
55
+ "commits": [
56
+ {
57
+ "sha": "58c416a42",
58
+ "subject": "feat(deploy): dynamic per-developer promotion source (#211)",
59
+ "pr": 211
60
+ }
61
+ ],
62
+ "exports_delta": null,
63
+ "narrative": null
64
+ },
65
+ {
66
+ "$schema": "dovetail-release-event/v1",
67
+ "event_id": "dovetail-servicenow@0.0.26",
68
+ "package": "@tenonhq/dovetail-servicenow",
69
+ "version": "0.0.26",
70
+ "prev_version": "0.0.25",
71
+ "published_at": "2026-07-10T16:27:58.460Z",
72
+ "git": {
73
+ "sha": "9b3b58b8fb9aa53b32473c8c7711446f5a52a576",
74
+ "tag": "dovetail-servicenow@0.0.26"
75
+ },
76
+ "semver_bump": "patch",
77
+ "commits": [
78
+ {
79
+ "sha": "9b3b58b8f",
80
+ "subject": "feat: invoke arbitrary authenticated Scripted REST operations (invoke-rest) (#214)",
81
+ "pr": 214
82
+ }
83
+ ],
84
+ "exports_delta": null,
85
+ "narrative": null
86
+ },
87
+ {
88
+ "$schema": "dovetail-release-event/v1",
89
+ "event_id": "dovetail-mcp@0.0.23",
90
+ "package": "@tenonhq/dovetail-mcp",
91
+ "version": "0.0.23",
92
+ "prev_version": "0.0.22",
93
+ "published_at": "2026-07-10T16:27:58.460Z",
94
+ "git": {
95
+ "sha": "9b3b58b8fb9aa53b32473c8c7711446f5a52a576",
96
+ "tag": "dovetail-mcp@0.0.23"
97
+ },
98
+ "semver_bump": "patch",
99
+ "commits": [
100
+ {
101
+ "sha": "534f517e4",
102
+ "subject": "chore(release): publish 3 packages [skip ci]"
103
+ }
104
+ ],
105
+ "exports_delta": null,
106
+ "narrative": null
107
+ },
108
+ {
109
+ "$schema": "dovetail-release-event/v1",
110
+ "event_id": "dovetail-dashboard@0.0.48",
111
+ "package": "@tenonhq/dovetail-dashboard",
112
+ "version": "0.0.48",
113
+ "prev_version": "0.0.47",
114
+ "published_at": "2026-07-08T21:55:56.082Z",
115
+ "git": {
116
+ "sha": "7ef92071de6726ccfd751ae617a661f95fe685fc",
117
+ "tag": "dovetail-dashboard@0.0.48"
118
+ },
119
+ "semver_bump": "patch",
120
+ "commits": [
121
+ {
122
+ "sha": "7ef92071d",
123
+ "subject": "feat(ci): Slack alerts for publish success/failure + cut dashboard release (#209)",
124
+ "pr": 209
125
+ }
126
+ ],
127
+ "exports_delta": null,
128
+ "narrative": null
129
+ },
130
+ {
131
+ "$schema": "dovetail-release-event/v1",
132
+ "event_id": "dovetail-clickup@0.0.13",
133
+ "package": "@tenonhq/dovetail-clickup",
134
+ "version": "0.0.13",
135
+ "prev_version": "0.0.12",
136
+ "published_at": "2026-07-08T18:19:16.768Z",
137
+ "git": {
138
+ "sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
139
+ "tag": "dovetail-clickup@0.0.13"
140
+ },
141
+ "semver_bump": "patch",
142
+ "commits": [
143
+ {
144
+ "sha": "84fc7a200",
145
+ "subject": "fix: clickup get_task resolves custom ids instead of a 401 (#208)",
146
+ "pr": 208
147
+ },
148
+ {
149
+ "sha": "4bc78bc61",
150
+ "subject": "chore(release): publish 2 packages [skip ci]"
151
+ },
152
+ {
153
+ "sha": "c82439b30",
154
+ "subject": "chore(deps): bump js-yaml, jest and ts-jest (#183)",
155
+ "pr": 183
156
+ },
157
+ {
158
+ "sha": "9332fdf7a",
159
+ "subject": "chore(release): publish 20 packages [skip ci]"
160
+ },
161
+ {
162
+ "sha": "9046da774",
163
+ "subject": "chore(release): publish 19 packages [skip ci]"
164
+ }
165
+ ],
166
+ "exports_delta": null,
167
+ "narrative": null
168
+ },
169
+ {
170
+ "$schema": "dovetail-release-event/v1",
171
+ "event_id": "dovetail-core@0.0.110",
172
+ "package": "@tenonhq/dovetail-core",
173
+ "version": "0.0.110",
174
+ "prev_version": "0.0.109",
175
+ "published_at": "2026-07-08T18:19:16.768Z",
176
+ "git": {
177
+ "sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
178
+ "tag": "dovetail-core@0.0.110"
179
+ },
180
+ "semver_bump": "patch",
181
+ "commits": [],
182
+ "exports_delta": null,
183
+ "narrative": null
184
+ },
185
+ {
186
+ "$schema": "dovetail-release-event/v1",
187
+ "event_id": "dovetail-mcp@0.0.22",
188
+ "package": "@tenonhq/dovetail-mcp",
189
+ "version": "0.0.22",
190
+ "prev_version": "0.0.21",
191
+ "published_at": "2026-07-08T18:19:16.768Z",
192
+ "git": {
193
+ "sha": "84fc7a200e0840aaa4b2c93d6764dce959bb6536",
194
+ "tag": "dovetail-mcp@0.0.22"
195
+ },
196
+ "semver_bump": "patch",
197
+ "commits": [
198
+ {
199
+ "sha": "84fc7a200",
200
+ "subject": "fix: clickup get_task resolves custom ids instead of a 401 (#208)",
201
+ "pr": 208
202
+ }
203
+ ],
204
+ "exports_delta": null,
205
+ "narrative": null
206
+ },
5
207
  {
6
208
  "$schema": "dovetail-release-event/v1",
7
209
  "event_id": "dovetail-dashboard@0.0.44",
@@ -1075,234 +1277,6 @@
1075
1277
  ],
1076
1278
  "exports_delta": null,
1077
1279
  "narrative": null
1078
- },
1079
- {
1080
- "$schema": "dovetail-release-event/v1",
1081
- "event_id": "dovetail-prettier-plugin@0.0.11",
1082
- "package": "@tenonhq/dovetail-prettier-plugin",
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-prettier-plugin@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": "9332fdf7a",
1099
- "subject": "chore(release): publish 20 packages [skip ci]"
1100
- },
1101
- {
1102
- "sha": "9046da774",
1103
- "subject": "chore(release): publish 19 packages [skip ci]"
1104
- }
1105
- ],
1106
- "exports_delta": null,
1107
- "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
1280
  }
1307
1281
  ]
1308
1282
  }