@tenonhq/dovetail-core 0.0.91 → 0.0.93
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/commander.js +30 -0
- package/dist/knowledgeDiff.js +64 -0
- package/dist/knowledgeDiffCommand.js +121 -0
- package/dist/snClient.js +46 -3
- package/dist/tests/knowledgeDiff.test.js +83 -0
- package/dist/tests/snClient.fallback.test.js +82 -2
- package/package.json +3 -2
- package/release-manifest.json +5 -0
package/dist/commander.js
CHANGED
|
@@ -15,6 +15,7 @@ const deleteRecordCommand_1 = require("./deleteRecordCommand");
|
|
|
15
15
|
const migrateCommand_1 = require("./migrateCommand");
|
|
16
16
|
const clickupCommands_1 = require("./clickupCommands");
|
|
17
17
|
const loginCommand_1 = require("./loginCommand");
|
|
18
|
+
const knowledgeDiffCommand_1 = require("./knowledgeDiffCommand");
|
|
18
19
|
const yargs_1 = __importDefault(require("yargs"));
|
|
19
20
|
async function initCommands() {
|
|
20
21
|
const sharedOptions = {
|
|
@@ -461,6 +462,35 @@ async function initCommands() {
|
|
|
461
462
|
.demandCommand(1, "Please specify a task subcommand (e.g., clear)");
|
|
462
463
|
}, function () {
|
|
463
464
|
/* subcommands handle execution */
|
|
465
|
+
})
|
|
466
|
+
.command(["knowledge-diff"], "Report Dovetail releases this repo has not documented yet (stages pending events for /dovetail-features-sync)", function (cmdArgs) {
|
|
467
|
+
cmdArgs.options({
|
|
468
|
+
...sharedOptions,
|
|
469
|
+
manifest: {
|
|
470
|
+
type: "string",
|
|
471
|
+
describe: "Path to the Dovetail release manifest (defaults to the bundled one)",
|
|
472
|
+
},
|
|
473
|
+
config: {
|
|
474
|
+
type: "string",
|
|
475
|
+
describe: "Path to the consumer knowledge-sync config.json",
|
|
476
|
+
},
|
|
477
|
+
ledger: {
|
|
478
|
+
type: "string",
|
|
479
|
+
describe: "Path to the documented-state ledger.json",
|
|
480
|
+
},
|
|
481
|
+
out: {
|
|
482
|
+
type: "string",
|
|
483
|
+
describe: "Directory to write pending release-event files",
|
|
484
|
+
},
|
|
485
|
+
json: {
|
|
486
|
+
type: "boolean",
|
|
487
|
+
default: false,
|
|
488
|
+
describe: "Print the diff as JSON instead of writing pending files",
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
return cmdArgs;
|
|
492
|
+
}, async function (args) {
|
|
493
|
+
await (0, knowledgeDiffCommand_1.knowledgeDiffCommand)(args);
|
|
464
494
|
})
|
|
465
495
|
.help().argv;
|
|
466
496
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pure, deterministic core for `dove knowledge-diff`. No fs, no network — unit-testable.
|
|
3
|
+
// Given a Dovetail release manifest (the publish feed) and a consumer repo's
|
|
4
|
+
// documented-state ledger + watch config, returns the release-events the
|
|
5
|
+
// consumer has not documented yet. knowledgeDiffCommand wraps this with I/O to
|
|
6
|
+
// stage pending events for the in-session /dovetail-features-sync skill.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.semverGt = semverGt;
|
|
9
|
+
exports.eventFilename = eventFilename;
|
|
10
|
+
exports.computeDrift = computeDrift;
|
|
11
|
+
// Numeric semver compare for the x.y.z range Dovetail uses (no prerelease tags).
|
|
12
|
+
function semverGt(a, b) {
|
|
13
|
+
const pa = String(a)
|
|
14
|
+
.split(".")
|
|
15
|
+
.map(function (n) {
|
|
16
|
+
return parseInt(n, 10) || 0;
|
|
17
|
+
});
|
|
18
|
+
const pb = String(b)
|
|
19
|
+
.split(".")
|
|
20
|
+
.map(function (n) {
|
|
21
|
+
return parseInt(n, 10) || 0;
|
|
22
|
+
});
|
|
23
|
+
for (let i = 0; i < 3; i++) {
|
|
24
|
+
const da = pa[i] || 0;
|
|
25
|
+
const db = pb[i] || 0;
|
|
26
|
+
if (da > db)
|
|
27
|
+
return true;
|
|
28
|
+
if (da < db)
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
// Filesystem-safe pending filename: unscoped package name + version.
|
|
34
|
+
function eventFilename(ev) {
|
|
35
|
+
const unscoped = String(ev.package || "").replace(/^@[^/]+\//, "");
|
|
36
|
+
return unscoped + "@" + ev.version + ".json";
|
|
37
|
+
}
|
|
38
|
+
// Events newer than the ledger's documented_version, limited to watch_packages
|
|
39
|
+
// (when set) and excluding ignore_packages. Deterministically sorted so callers
|
|
40
|
+
// and tests get a stable order.
|
|
41
|
+
function computeDrift(manifest, ledger, config) {
|
|
42
|
+
const events = (manifest && manifest.events) || [];
|
|
43
|
+
const watch = (config && config.watch_packages) || [];
|
|
44
|
+
const ignore = (config && config.ignore_packages) || [];
|
|
45
|
+
const ledgerPackages = (ledger && ledger.packages) || {};
|
|
46
|
+
const drift = [];
|
|
47
|
+
for (const ev of events) {
|
|
48
|
+
if (!ev || !ev.package || !ev.version)
|
|
49
|
+
continue;
|
|
50
|
+
if (ignore.indexOf(ev.package) !== -1)
|
|
51
|
+
continue;
|
|
52
|
+
if (watch.length > 0 && watch.indexOf(ev.package) === -1)
|
|
53
|
+
continue;
|
|
54
|
+
const entry = ledgerPackages[ev.package];
|
|
55
|
+
const documented = entry && entry.documented_version;
|
|
56
|
+
if (documented && !semverGt(ev.version, documented))
|
|
57
|
+
continue;
|
|
58
|
+
drift.push(ev);
|
|
59
|
+
}
|
|
60
|
+
drift.sort(function (a, b) {
|
|
61
|
+
return (a.package + a.version).localeCompare(b.package + b.version);
|
|
62
|
+
});
|
|
63
|
+
return drift;
|
|
64
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.knowledgeDiffCommand = knowledgeDiffCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const commands_1 = require("./commands");
|
|
40
|
+
const Logger_1 = require("./Logger");
|
|
41
|
+
const knowledgeDiff_1 = require("./knowledgeDiff");
|
|
42
|
+
function readJson(filePath) {
|
|
43
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
44
|
+
}
|
|
45
|
+
function resolveManifestPath(explicit) {
|
|
46
|
+
if (explicit)
|
|
47
|
+
return path.resolve(explicit);
|
|
48
|
+
// Bundled inside @tenonhq/dovetail-core at publish time (see publish-on-merge.js).
|
|
49
|
+
// __dirname is <pkg>/dist at runtime, so ".." is the installed package root.
|
|
50
|
+
return path.resolve(__dirname, "..", "release-manifest.json");
|
|
51
|
+
}
|
|
52
|
+
// `dove knowledge-diff` — pure, deterministic detector. Compares the Dovetail
|
|
53
|
+
// release manifest against this repo's documented-state ledger and stages the
|
|
54
|
+
// undocumented release-events for the in-session /dovetail-features-sync skill.
|
|
55
|
+
// No network, no LLM — safe to run at install or in CI.
|
|
56
|
+
async function knowledgeDiffCommand(args) {
|
|
57
|
+
(0, commands_1.setLogLevel)(args);
|
|
58
|
+
const cwd = process.cwd();
|
|
59
|
+
const configPath = args.config
|
|
60
|
+
? path.resolve(args.config)
|
|
61
|
+
: path.join(cwd, "context", "dovetail-releases", "config.json");
|
|
62
|
+
let config = {};
|
|
63
|
+
if (fs.existsSync(configPath)) {
|
|
64
|
+
config = readJson(configPath);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
Logger_1.logger.warn("knowledge-diff: no config at " + configPath + "; watching all packages.");
|
|
68
|
+
}
|
|
69
|
+
const manifestPath = resolveManifestPath(args.manifest);
|
|
70
|
+
if (!fs.existsSync(manifestPath)) {
|
|
71
|
+
Logger_1.logger.warn("knowledge-diff: no release manifest at " +
|
|
72
|
+
manifestPath +
|
|
73
|
+
" — nothing to diff (Dovetail publish writes it).");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const manifest = readJson(manifestPath);
|
|
77
|
+
let ledgerPath;
|
|
78
|
+
if (args.ledger) {
|
|
79
|
+
ledgerPath = path.resolve(args.ledger);
|
|
80
|
+
}
|
|
81
|
+
else if (config.ledger) {
|
|
82
|
+
ledgerPath = path.resolve(cwd, config.ledger);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
ledgerPath = path.join(cwd, "context", "dovetail-releases", "ledger.json");
|
|
86
|
+
}
|
|
87
|
+
const ledger = fs.existsSync(ledgerPath)
|
|
88
|
+
? readJson(ledgerPath)
|
|
89
|
+
: { packages: {} };
|
|
90
|
+
const drift = (0, knowledgeDiff_1.computeDrift)(manifest, ledger, config);
|
|
91
|
+
if (args.json) {
|
|
92
|
+
process.stdout.write(JSON.stringify({ count: drift.length, events: drift }, null, 2) + "\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (drift.length === 0) {
|
|
96
|
+
Logger_1.logger.info("knowledge-diff: no new Dovetail releases to document.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let outDir;
|
|
100
|
+
if (args.out) {
|
|
101
|
+
outDir = path.resolve(args.out);
|
|
102
|
+
}
|
|
103
|
+
else if (config.pending_dir) {
|
|
104
|
+
outDir = path.resolve(cwd, config.pending_dir);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
outDir = path.join(cwd, "context", "dovetail-releases", "pending");
|
|
108
|
+
}
|
|
109
|
+
if (!fs.existsSync(outDir)) {
|
|
110
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
for (const ev of drift) {
|
|
113
|
+
const dest = path.join(outDir, (0, knowledgeDiff_1.eventFilename)(ev));
|
|
114
|
+
fs.writeFileSync(dest, JSON.stringify(ev, null, 2) + "\n");
|
|
115
|
+
}
|
|
116
|
+
Logger_1.logger.info("knowledge-diff: wrote " +
|
|
117
|
+
drift.length +
|
|
118
|
+
" pending release-event(s) to " +
|
|
119
|
+
path.relative(cwd, outDir) +
|
|
120
|
+
". Run /dovetail-features-sync to document them.");
|
|
121
|
+
}
|
package/dist/snClient.js
CHANGED
|
@@ -75,9 +75,46 @@ function _resetDovetailApiFallback() {
|
|
|
75
75
|
function _isUsingLegacyClaudePath() {
|
|
76
76
|
return _dovetailApiUseLegacyClaudePath;
|
|
77
77
|
}
|
|
78
|
+
// SN returns 400 with this body when a Scripted REST endpoint does not exist
|
|
79
|
+
// at the requested URI. The /dovetail -> /claude fallback above only fires on
|
|
80
|
+
// 404, so the same "endpoint missing" condition presents as an opaque 400 to
|
|
81
|
+
// callers. Detect it and surface a clear hint.
|
|
82
|
+
var _SN_MISSING_ENDPOINT_BODY = "Requested URI does not represent any resource";
|
|
83
|
+
function _summarizeResponseBody(data) {
|
|
84
|
+
if (data === undefined || data === null)
|
|
85
|
+
return "";
|
|
86
|
+
try {
|
|
87
|
+
var asJson = typeof data === "string" ? data : JSON.stringify(data);
|
|
88
|
+
return asJson.length > 500 ? asJson.slice(0, 500) + "…" : asJson;
|
|
89
|
+
}
|
|
90
|
+
catch (_) {
|
|
91
|
+
return String(data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Augment an axios error in place with a human-readable message describing
|
|
95
|
+
// the attempted Dovetail REST call. Preserves .response/.code/.config so
|
|
96
|
+
// existing callers and tests that inspect those fields keep working.
|
|
97
|
+
function _enrichDovetailApiError(e, attemptedEndpoint) {
|
|
98
|
+
if (!axios_1.default.isAxiosError(e))
|
|
99
|
+
return e;
|
|
100
|
+
var status = e.response ? e.response.status : undefined;
|
|
101
|
+
var body = e.response ? _summarizeResponseBody(e.response.data) : "";
|
|
102
|
+
var missingEndpoint = status === 400 && body.indexOf(_SN_MISSING_ENDPOINT_BODY) !== -1;
|
|
103
|
+
var hint = missingEndpoint
|
|
104
|
+
? " — Scripted REST endpoint not found on this instance. Re-import the Dovetail Scripted REST API XML (see Dovetail/docs/dovetail-servicenow-migration.md)."
|
|
105
|
+
: "";
|
|
106
|
+
e.message =
|
|
107
|
+
"Dovetail REST call failed: " +
|
|
108
|
+
(status ? "HTTP " + status : "no response") +
|
|
109
|
+
" " + attemptedEndpoint +
|
|
110
|
+
(body ? " — " + body : "") +
|
|
111
|
+
hint;
|
|
112
|
+
return e;
|
|
113
|
+
}
|
|
78
114
|
async function _callDovetailApi(op, call) {
|
|
115
|
+
var endpoint = _dovetailEndpoint(op);
|
|
79
116
|
try {
|
|
80
|
-
return await call(
|
|
117
|
+
return await call(endpoint);
|
|
81
118
|
}
|
|
82
119
|
catch (e) {
|
|
83
120
|
if (!_dovetailApiUseLegacyClaudePath && _getHttpStatus(e) === 404) {
|
|
@@ -85,9 +122,15 @@ async function _callDovetailApi(op, call) {
|
|
|
85
122
|
" returned 404. Falling back to legacy /api/cadso/claude/" + op +
|
|
86
123
|
". Re-import the Dovetail Scripted REST API XML on your ServiceNow instance to silence this warning.");
|
|
87
124
|
_dovetailApiUseLegacyClaudePath = true;
|
|
88
|
-
|
|
125
|
+
var legacyEndpoint = _dovetailEndpoint(op);
|
|
126
|
+
try {
|
|
127
|
+
return await call(legacyEndpoint);
|
|
128
|
+
}
|
|
129
|
+
catch (e2) {
|
|
130
|
+
throw _enrichDovetailApiError(e2, legacyEndpoint);
|
|
131
|
+
}
|
|
89
132
|
}
|
|
90
|
-
throw e;
|
|
133
|
+
throw _enrichDovetailApiError(e, endpoint);
|
|
91
134
|
}
|
|
92
135
|
}
|
|
93
136
|
function _getRetryAfterMs(e) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const knowledgeDiff_1 = require("../knowledgeDiff");
|
|
4
|
+
describe("semverGt", function () {
|
|
5
|
+
it("compares x.y.z numerically (not lexically)", function () {
|
|
6
|
+
expect((0, knowledgeDiff_1.semverGt)("0.0.13", "0.0.12")).toBe(true);
|
|
7
|
+
expect((0, knowledgeDiff_1.semverGt)("0.0.12", "0.0.12")).toBe(false);
|
|
8
|
+
expect((0, knowledgeDiff_1.semverGt)("0.1.0", "0.0.99")).toBe(true);
|
|
9
|
+
expect((0, knowledgeDiff_1.semverGt)("1.0.0", "0.9.9")).toBe(true);
|
|
10
|
+
expect((0, knowledgeDiff_1.semverGt)("0.0.2", "0.0.10")).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe("eventFilename", function () {
|
|
14
|
+
it("strips the npm scope and appends the version", function () {
|
|
15
|
+
expect((0, knowledgeDiff_1.eventFilename)({
|
|
16
|
+
event_id: "x",
|
|
17
|
+
package: "@tenonhq/dovetail-clickup",
|
|
18
|
+
version: "0.0.13",
|
|
19
|
+
prev_version: "0.0.12",
|
|
20
|
+
})).toBe("dovetail-clickup@0.0.13.json");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("computeDrift", function () {
|
|
24
|
+
const manifest = {
|
|
25
|
+
events: [
|
|
26
|
+
{
|
|
27
|
+
event_id: "dovetail-clickup@0.0.13",
|
|
28
|
+
package: "@tenonhq/dovetail-clickup",
|
|
29
|
+
version: "0.0.13",
|
|
30
|
+
prev_version: "0.0.12",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
event_id: "dovetail-core@0.0.93",
|
|
34
|
+
package: "@tenonhq/dovetail-core",
|
|
35
|
+
version: "0.0.93",
|
|
36
|
+
prev_version: "0.0.92",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
event_id: "dovetail-gmail@0.0.11",
|
|
40
|
+
package: "@tenonhq/dovetail-gmail",
|
|
41
|
+
version: "0.0.11",
|
|
42
|
+
prev_version: "0.0.10",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
const ledger = {
|
|
47
|
+
packages: {
|
|
48
|
+
"@tenonhq/dovetail-clickup": { documented_version: "0.0.12" },
|
|
49
|
+
"@tenonhq/dovetail-core": { documented_version: "0.0.93" },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
it("returns events newer than the ledger; undocumented packages count as drift", function () {
|
|
53
|
+
const ids = (0, knowledgeDiff_1.computeDrift)(manifest, ledger, { watch_packages: [] }).map(function (e) {
|
|
54
|
+
return e.event_id;
|
|
55
|
+
});
|
|
56
|
+
expect(ids).toContain("dovetail-clickup@0.0.13"); // 0.0.13 > 0.0.12
|
|
57
|
+
expect(ids).toContain("dovetail-gmail@0.0.11"); // not in ledger yet
|
|
58
|
+
expect(ids).not.toContain("dovetail-core@0.0.93"); // equal to documented
|
|
59
|
+
});
|
|
60
|
+
it("limits to watch_packages when the set is non-empty", function () {
|
|
61
|
+
const drift = (0, knowledgeDiff_1.computeDrift)(manifest, ledger, {
|
|
62
|
+
watch_packages: ["@tenonhq/dovetail-clickup"],
|
|
63
|
+
});
|
|
64
|
+
expect(drift.length).toBe(1);
|
|
65
|
+
expect(drift[0].package).toBe("@tenonhq/dovetail-clickup");
|
|
66
|
+
});
|
|
67
|
+
it("excludes ignore_packages", function () {
|
|
68
|
+
const ids = (0, knowledgeDiff_1.computeDrift)(manifest, ledger, {
|
|
69
|
+
watch_packages: [],
|
|
70
|
+
ignore_packages: ["@tenonhq/dovetail-gmail"],
|
|
71
|
+
}).map(function (e) {
|
|
72
|
+
return e.event_id;
|
|
73
|
+
});
|
|
74
|
+
expect(ids).not.toContain("dovetail-gmail@0.0.11");
|
|
75
|
+
expect(ids).toContain("dovetail-clickup@0.0.13");
|
|
76
|
+
});
|
|
77
|
+
it("is deterministically sorted by package+version", function () {
|
|
78
|
+
const ids = (0, knowledgeDiff_1.computeDrift)(manifest, ledger, {}).map(function (e) {
|
|
79
|
+
return e.event_id;
|
|
80
|
+
});
|
|
81
|
+
expect(ids).toEqual(["dovetail-clickup@0.0.13", "dovetail-gmail@0.0.11"]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -21,10 +21,10 @@ jest.mock("../genericUtils", function () {
|
|
|
21
21
|
return { wait: jest.fn().mockResolvedValue(undefined) };
|
|
22
22
|
});
|
|
23
23
|
const snClient_1 = require("../snClient");
|
|
24
|
-
function makeAxiosError(status) {
|
|
24
|
+
function makeAxiosError(status, data) {
|
|
25
25
|
var error = new Error("Request failed with status " + status);
|
|
26
26
|
error.isAxiosError = true;
|
|
27
|
-
error.response = { status: status, headers: {}, data: {} };
|
|
27
|
+
error.response = { status: status, headers: {}, data: data === undefined ? {} : data };
|
|
28
28
|
return error;
|
|
29
29
|
}
|
|
30
30
|
function makeAxiosResponse(data) {
|
|
@@ -117,4 +117,84 @@ describe("_callDovetailApi 404 fallback", function () {
|
|
|
117
117
|
expect((0, snClient_1._isUsingLegacyClaudePath)()).toBe(false);
|
|
118
118
|
expect(mockLogger.warn).not.toHaveBeenCalled();
|
|
119
119
|
});
|
|
120
|
+
describe("descriptive error enrichment", function () {
|
|
121
|
+
it("includes status, endpoint, and response body on a 401", async function () {
|
|
122
|
+
var call = jest.fn(async function (_endpoint) {
|
|
123
|
+
throw makeAxiosError(401, { error: { message: "User Not Authenticated" } });
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
await (0, snClient_1._callDovetailApi)("changeScope", call);
|
|
127
|
+
throw new Error("expected rejection");
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
expect(e.response.status).toBe(401);
|
|
131
|
+
expect(e.message).toContain("HTTP 401");
|
|
132
|
+
expect(e.message).toContain("api/cadso/dovetail/changeScope");
|
|
133
|
+
expect(e.message).toContain("User Not Authenticated");
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
it("adds the missing-endpoint hint when SN returns its 400 'Requested URI does not represent any resource'", async function () {
|
|
137
|
+
var snBody = {
|
|
138
|
+
error: { message: "Requested URI does not represent any resource", detail: null },
|
|
139
|
+
status: "failure",
|
|
140
|
+
};
|
|
141
|
+
var call = jest.fn(async function (_endpoint) {
|
|
142
|
+
throw makeAxiosError(400, snBody);
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
await (0, snClient_1._callDovetailApi)("changeScope", call);
|
|
146
|
+
throw new Error("expected rejection");
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
expect(e.response.status).toBe(400);
|
|
150
|
+
expect(e.message).toContain("HTTP 400");
|
|
151
|
+
expect(e.message).toContain("api/cadso/dovetail/changeScope");
|
|
152
|
+
expect(e.message).toContain("Re-import the Dovetail Scripted REST API XML");
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
it("does NOT add the missing-endpoint hint for unrelated 400s", async function () {
|
|
156
|
+
var call = jest.fn(async function (_endpoint) {
|
|
157
|
+
throw makeAxiosError(400, { error: { message: "Invalid scope name" } });
|
|
158
|
+
});
|
|
159
|
+
try {
|
|
160
|
+
await (0, snClient_1._callDovetailApi)("changeScope", call);
|
|
161
|
+
throw new Error("expected rejection");
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
expect(e.message).toContain("HTTP 400");
|
|
165
|
+
expect(e.message).toContain("Invalid scope name");
|
|
166
|
+
expect(e.message).not.toContain("Re-import the Dovetail Scripted REST API XML");
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
it("after fallback latches, errors from the legacy path are also enriched", async function () {
|
|
170
|
+
var calls = [];
|
|
171
|
+
var call = jest.fn(async function (endpoint) {
|
|
172
|
+
calls.push(endpoint);
|
|
173
|
+
if (endpoint.indexOf("api/cadso/dovetail/") === 0) {
|
|
174
|
+
throw makeAxiosError(404, {});
|
|
175
|
+
}
|
|
176
|
+
throw makeAxiosError(500, { error: { message: "boom" } });
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
await (0, snClient_1._callDovetailApi)("changeScope", call);
|
|
180
|
+
throw new Error("expected rejection");
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
expect(calls).toEqual([
|
|
184
|
+
"api/cadso/dovetail/changeScope",
|
|
185
|
+
"api/cadso/claude/changeScope",
|
|
186
|
+
]);
|
|
187
|
+
expect(e.response.status).toBe(500);
|
|
188
|
+
expect(e.message).toContain("HTTP 500");
|
|
189
|
+
expect(e.message).toContain("api/cadso/claude/changeScope");
|
|
190
|
+
expect(e.message).toContain("boom");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
it("leaves non-axios errors untouched", async function () {
|
|
194
|
+
var call = jest.fn(async function (_endpoint) {
|
|
195
|
+
throw new Error("ECONNREFUSED");
|
|
196
|
+
});
|
|
197
|
+
await expect((0, snClient_1._callDovetailApi)("changeScope", call)).rejects.toThrow("ECONNREFUSED");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
120
200
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenonhq/dovetail-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.93",
|
|
4
4
|
"description": "Next-gen file syncer",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"files": [
|
|
62
62
|
"dist/**/*",
|
|
63
63
|
"skills/**/*",
|
|
64
|
-
"README.md"
|
|
64
|
+
"README.md",
|
|
65
|
+
"release-manifest.json"
|
|
65
66
|
],
|
|
66
67
|
"publishConfig": {
|
|
67
68
|
"access": "public"
|