@tenonhq/dovetail-core 0.0.111 → 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/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: {
@@ -568,5 +583,17 @@ async function initCommands() {
568
583
  }, async function (args) {
569
584
  await (0, knowledgeDiffCommand_1.knowledgeDiffCommand)(args);
570
585
  })
571
- .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;
572
599
  }
@@ -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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/dovetail-core",
3
- "version": "0.0.111",
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,45 @@
1
1
  {
2
2
  "$schema": "dovetail-manifest/v1",
3
- "generated_at": "2026-07-10T18:41:37.097Z",
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
+ },
5
43
  {
6
44
  "$schema": "dovetail-release-event/v1",
7
45
  "event_id": "dovetail-deploy@0.0.2",
@@ -1239,36 +1277,6 @@
1239
1277
  ],
1240
1278
  "exports_delta": null,
1241
1279
  "narrative": null
1242
- },
1243
- {
1244
- "$schema": "dovetail-release-event/v1",
1245
- "event_id": "dovetail-prettier-plugin@0.0.11",
1246
- "package": "@tenonhq/dovetail-prettier-plugin",
1247
- "version": "0.0.11",
1248
- "prev_version": "0.0.10",
1249
- "published_at": "2026-06-05T06:43:18.252Z",
1250
- "git": {
1251
- "sha": "71bdf9bba8bd5e3849a212a18d9ebc9241232b32",
1252
- "tag": "dovetail-prettier-plugin@0.0.11"
1253
- },
1254
- "semver_bump": "patch",
1255
- "commits": [
1256
- {
1257
- "sha": "71bdf9bba",
1258
- "subject": "fix: float internal dep ranges to ~0.0.x so consumers need no overrides (#148)",
1259
- "pr": 148
1260
- },
1261
- {
1262
- "sha": "9332fdf7a",
1263
- "subject": "chore(release): publish 20 packages [skip ci]"
1264
- },
1265
- {
1266
- "sha": "9046da774",
1267
- "subject": "chore(release): publish 19 packages [skip ci]"
1268
- }
1269
- ],
1270
- "exports_delta": null,
1271
- "narrative": null
1272
1280
  }
1273
1281
  ]
1274
1282
  }