dsh-gitnexus-plus 0.0.0-stage → 0.1.1
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/LICENSE +38 -0
- package/README.md +483 -2
- package/cordis.patch.yml +18 -0
- package/lib/bin/gitnexus-coverage.d.ts +14 -0
- package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
- package/lib/bin/gitnexus-coverage.js +171 -0
- package/lib/bin/gitnexus-coverage.js.map +1 -0
- package/lib/client.js +1642 -0
- package/lib/gitnexus/analyze.d.ts +148 -0
- package/lib/gitnexus/analyze.d.ts.map +1 -0
- package/lib/gitnexus/analyze.js +283 -0
- package/lib/gitnexus/analyze.js.map +1 -0
- package/lib/gitnexus/commands.d.ts +96 -0
- package/lib/gitnexus/commands.d.ts.map +1 -0
- package/lib/gitnexus/commands.js +132 -0
- package/lib/gitnexus/commands.js.map +1 -0
- package/lib/gitnexus/coverage.d.ts +340 -0
- package/lib/gitnexus/coverage.d.ts.map +1 -0
- package/lib/gitnexus/coverage.js +552 -0
- package/lib/gitnexus/coverage.js.map +1 -0
- package/lib/gitnexus/format.d.ts +16 -0
- package/lib/gitnexus/format.d.ts.map +1 -0
- package/lib/gitnexus/format.js +178 -0
- package/lib/gitnexus/format.js.map +1 -0
- package/lib/gitnexus/git-activity.d.ts +136 -0
- package/lib/gitnexus/git-activity.d.ts.map +1 -0
- package/lib/gitnexus/git-activity.js +345 -0
- package/lib/gitnexus/git-activity.js.map +1 -0
- package/lib/gitnexus/git.d.ts +70 -0
- package/lib/gitnexus/git.d.ts.map +1 -0
- package/lib/gitnexus/git.js +121 -0
- package/lib/gitnexus/git.js.map +1 -0
- package/lib/gitnexus/index-meta.d.ts +150 -0
- package/lib/gitnexus/index-meta.d.ts.map +1 -0
- package/lib/gitnexus/index-meta.js +156 -0
- package/lib/gitnexus/index-meta.js.map +1 -0
- package/lib/gitnexus/install.d.ts +97 -0
- package/lib/gitnexus/install.d.ts.map +1 -0
- package/lib/gitnexus/install.js +228 -0
- package/lib/gitnexus/install.js.map +1 -0
- package/lib/gitnexus/languages.d.ts +73 -0
- package/lib/gitnexus/languages.d.ts.map +1 -0
- package/lib/gitnexus/languages.js +217 -0
- package/lib/gitnexus/languages.js.map +1 -0
- package/lib/gitnexus/root.d.ts +68 -0
- package/lib/gitnexus/root.d.ts.map +1 -0
- package/lib/gitnexus/root.js +162 -0
- package/lib/gitnexus/root.js.map +1 -0
- package/lib/gitnexus/scan.d.ts +67 -0
- package/lib/gitnexus/scan.d.ts.map +1 -0
- package/lib/gitnexus/scan.js +247 -0
- package/lib/gitnexus/scan.js.map +1 -0
- package/lib/gitnexus/toolchain.d.ts +136 -0
- package/lib/gitnexus/toolchain.d.ts.map +1 -0
- package/lib/gitnexus/toolchain.js +251 -0
- package/lib/gitnexus/toolchain.js.map +1 -0
- package/lib/index.d.ts +79 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +211 -0
- package/lib/index.js.map +1 -0
- package/lib/routes/analyze.d.ts +35 -0
- package/lib/routes/analyze.d.ts.map +1 -0
- package/lib/routes/analyze.js +71 -0
- package/lib/routes/analyze.js.map +1 -0
- package/lib/routes/coverage.d.ts +44 -0
- package/lib/routes/coverage.d.ts.map +1 -0
- package/lib/routes/coverage.js +68 -0
- package/lib/routes/coverage.js.map +1 -0
- package/lib/routes/guard.d.ts +92 -0
- package/lib/routes/guard.d.ts.map +1 -0
- package/lib/routes/guard.js +187 -0
- package/lib/routes/guard.js.map +1 -0
- package/lib/routes/install.d.ts +27 -0
- package/lib/routes/install.d.ts.map +1 -0
- package/lib/routes/install.js +46 -0
- package/lib/routes/install.js.map +1 -0
- package/package.json +87 -4
- package/scripts/link-runtime.mjs +146 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-gitnexus-plus 鈥?host half.
|
|
3
|
+
*
|
|
4
|
+
* Registers one read-only tool and one loopback HTTP route over the coverage
|
|
5
|
+
* engine in `./gitnexus/`. Both answer the same question 鈥?"what did this index
|
|
6
|
+
* leave out?" 鈥?for the two callers that need it: the model, and a browser half
|
|
7
|
+
* that will render it.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately read-only. Nothing here triggers `analyze`, writes to `.gitnexus/`,
|
|
10
|
+
* or takes the index lock; driving updates is a separate concern with its own
|
|
11
|
+
* safety story, and mixing the two would mean a status check could start a
|
|
12
|
+
* 30-second rebuild.
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { type MaintenanceCommand } from './gitnexus/commands.js';
|
|
16
|
+
export declare const name = "gitnexus-plus";
|
|
17
|
+
/**
|
|
18
|
+
* Only `tools` is required. `webServer` is reached through `ctx.inject` because a
|
|
19
|
+
* headless profile has no HTTP carrier at all 鈥?naming it here would leave the
|
|
20
|
+
* plugin permanently pending there, and the tool would never register.
|
|
21
|
+
*/
|
|
22
|
+
export declare const inject: readonly ["tools"];
|
|
23
|
+
/** The shape `Schema.object` infers below, named for use in signatures. */
|
|
24
|
+
export interface PluginConfig {
|
|
25
|
+
scanDefaultCapKb: number;
|
|
26
|
+
minifiedSampleLimit: number;
|
|
27
|
+
serveRoutes: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Shapes the maintenance commands the panel hands to the operator. The defaults
|
|
30
|
+
* are the values measured to work inside a confined DSH process; a deployment
|
|
31
|
+
* that is not sandboxed can turn them off to get keyword search and a
|
|
32
|
+
* self-managed heap.
|
|
33
|
+
*/
|
|
34
|
+
skipFts: boolean;
|
|
35
|
+
memoryOff: boolean;
|
|
36
|
+
analyzeHeapMb: number;
|
|
37
|
+
/**
|
|
38
|
+
* Wall-clock ceiling for a run this plugin starts.
|
|
39
|
+
*
|
|
40
|
+
* Ten minutes, because the escape hatch this panel offers is a full rebuild and
|
|
41
|
+
* those are measured in minutes on a large repository. Too short a ceiling turns
|
|
42
|
+
* the feature into "it always times out on the repos that need it most"; the run
|
|
43
|
+
* is cancellable, so the cost of it being generous is bounded by the operator.
|
|
44
|
+
*/
|
|
45
|
+
analyzeTimeoutMs: number;
|
|
46
|
+
/**
|
|
47
|
+
* Where the plugin installs GitNexus when asked. Empty means the default,
|
|
48
|
+
* `$DSH_HOME/gitnexus-plus/gitnexus`.
|
|
49
|
+
*/
|
|
50
|
+
gitnexusInstallDir: string;
|
|
51
|
+
/** Wall-clock ceiling for that install. Compiling native modules is slow. */
|
|
52
|
+
installTimeoutMs: number;
|
|
53
|
+
gitnexusHome: string;
|
|
54
|
+
}
|
|
55
|
+
export declare const Config: import("@deepseek-ai/schemastery").SchemaLike<{
|
|
56
|
+
scanDefaultCapKb: number;
|
|
57
|
+
minifiedSampleLimit: number;
|
|
58
|
+
serveRoutes: boolean;
|
|
59
|
+
skipFts: boolean;
|
|
60
|
+
memoryOff: boolean;
|
|
61
|
+
analyzeHeapMb: number;
|
|
62
|
+
analyzeTimeoutMs: number;
|
|
63
|
+
gitnexusInstallDir: string;
|
|
64
|
+
installTimeoutMs: number;
|
|
65
|
+
gitnexusHome: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** The maintenance payload the panel renders alongside a report. */
|
|
68
|
+
export interface MaintenanceInfo {
|
|
69
|
+
commands: MaintenanceCommand[];
|
|
70
|
+
rationale: string[];
|
|
71
|
+
}
|
|
72
|
+
export declare function apply(ctx: Context, config: PluginConfig): void;
|
|
73
|
+
/**
|
|
74
|
+
* Human-readable rendering lives in `./gitnexus/format.js` 鈥?free of DSH imports
|
|
75
|
+
* so it can be tested without a host. Re-exported here for callers that already
|
|
76
|
+
* hold the plugin module.
|
|
77
|
+
*/
|
|
78
|
+
export { formatReport } from './gitnexus/format.js';
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAqBxG,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,MAAM,oBAAqB,CAAC;AAEzC,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;EAWjB,CAAC;AAqBH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAuJ9D;AAED;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-gitnexus-plus 鈥?host half.
|
|
3
|
+
*
|
|
4
|
+
* Registers one read-only tool and one loopback HTTP route over the coverage
|
|
5
|
+
* engine in `./gitnexus/`. Both answer the same question 鈥?"what did this index
|
|
6
|
+
* leave out?" 鈥?for the two callers that need it: the model, and a browser half
|
|
7
|
+
* that will render it.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately read-only. Nothing here triggers `analyze`, writes to `.gitnexus/`,
|
|
10
|
+
* or takes the index lock; driving updates is a separate concern with its own
|
|
11
|
+
* safety story, and mixing the two would mean a status check could start a
|
|
12
|
+
* 30-second rebuild.
|
|
13
|
+
*/
|
|
14
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
15
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
16
|
+
import { analyzeCoverage } from './gitnexus/coverage.js';
|
|
17
|
+
import { formatReport } from './gitnexus/format.js';
|
|
18
|
+
import { maintenanceCommands, commandRationale } from './gitnexus/commands.js';
|
|
19
|
+
import { mountCoverageRoute } from './routes/coverage.js';
|
|
20
|
+
import { mountAnalyzeRoute } from './routes/analyze.js';
|
|
21
|
+
import { mountInstallRoute } from './routes/install.js';
|
|
22
|
+
import { createAnalyzeRunner } from './gitnexus/analyze.js';
|
|
23
|
+
import { createInstallRunner } from './gitnexus/install.js';
|
|
24
|
+
/**
|
|
25
|
+
* Whether a service resolved from the context has the shape the runner needs.
|
|
26
|
+
*
|
|
27
|
+
* `ctx.get` is untyped here 鈥?the DSH packages ship no declarations, so the service
|
|
28
|
+
* arrives as `unknown`. Checking the two methods the runner actually calls is the
|
|
29
|
+
* narrowest claim that can be made: asserting the full `ctx.shell` contract from this
|
|
30
|
+
* side would be a guess, and a wrong guess fails at the first click rather than here.
|
|
31
|
+
*/
|
|
32
|
+
function isShellLike(value) {
|
|
33
|
+
if (value === null || typeof value !== 'object')
|
|
34
|
+
return false;
|
|
35
|
+
const candidate = value;
|
|
36
|
+
return typeof candidate.resolve === 'function' && typeof candidate.execute === 'function';
|
|
37
|
+
}
|
|
38
|
+
export const name = 'gitnexus-plus';
|
|
39
|
+
/**
|
|
40
|
+
* Only `tools` is required. `webServer` is reached through `ctx.inject` because a
|
|
41
|
+
* headless profile has no HTTP carrier at all 鈥?naming it here would leave the
|
|
42
|
+
* plugin permanently pending there, and the tool would never register.
|
|
43
|
+
*/
|
|
44
|
+
export const inject = ['tools'];
|
|
45
|
+
export const Config = Schema.object({
|
|
46
|
+
scanDefaultCapKb: Schema.natural().default(512),
|
|
47
|
+
minifiedSampleLimit: Schema.natural().default(40),
|
|
48
|
+
serveRoutes: Schema.boolean().default(true),
|
|
49
|
+
skipFts: Schema.boolean().default(true),
|
|
50
|
+
memoryOff: Schema.boolean().default(true),
|
|
51
|
+
analyzeHeapMb: Schema.natural().default(8192),
|
|
52
|
+
analyzeTimeoutMs: Schema.natural().default(600000),
|
|
53
|
+
gitnexusInstallDir: Schema.string().default(''),
|
|
54
|
+
installTimeoutMs: Schema.natural().default(1200000),
|
|
55
|
+
gitnexusHome: Schema.string().default(''),
|
|
56
|
+
});
|
|
57
|
+
const configShapeMatches = true;
|
|
58
|
+
void configShapeMatches;
|
|
59
|
+
export function apply(ctx, config) {
|
|
60
|
+
const cache = new Map();
|
|
61
|
+
const CACHE_TTL_MS = 15_000;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a coverage report, from cache when it is recent enough.
|
|
64
|
+
*
|
|
65
|
+
* Scan results are pure functions of the working tree, so a short TTL removes the
|
|
66
|
+
* repeated cost of a 28k-file walk without ever showing a stale verdict to a caller
|
|
67
|
+
* who just changed something.
|
|
68
|
+
*
|
|
69
|
+
* `fresh` bypasses the TTL. A manual refresh is an explicit instruction from a
|
|
70
|
+
* person who believes the numbers may have moved, and answering it with a cached
|
|
71
|
+
* copy makes the control look broken 鈥?which is exactly how it was reported. A
|
|
72
|
+
* background poll is the case the TTL exists for; a click is not.
|
|
73
|
+
*/
|
|
74
|
+
const coverageFor = async (repoPath, options = {}) => {
|
|
75
|
+
const key = String(repoPath);
|
|
76
|
+
if (options.fresh !== true) {
|
|
77
|
+
const hit = cache.get(key);
|
|
78
|
+
if (hit !== undefined && Date.now() - hit.at < CACHE_TTL_MS)
|
|
79
|
+
return hit.report;
|
|
80
|
+
}
|
|
81
|
+
const report = await analyzeCoverage(key, {
|
|
82
|
+
minifiedSampleLimit: config.minifiedSampleLimit,
|
|
83
|
+
});
|
|
84
|
+
cache.set(key, { at: Date.now(), report });
|
|
85
|
+
return report;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* The maintenance commands, resolved at the boundary rather than inside
|
|
89
|
+
* `analyzeCoverage`: they depend on this plugin's config, and the report is a
|
|
90
|
+
* pure function of the repository.
|
|
91
|
+
*/
|
|
92
|
+
const maintenanceFor = (repoPath) => {
|
|
93
|
+
const options = {
|
|
94
|
+
skipFts: config.skipFts,
|
|
95
|
+
memoryOff: config.memoryOff,
|
|
96
|
+
gitnexusHome: config.gitnexusHome === '' ? null : config.gitnexusHome,
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
commands: maintenanceCommands(repoPath, options),
|
|
100
|
+
rationale: commandRationale(options),
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
ctx.tools.register(defineTool({
|
|
104
|
+
name: 'gitnexus_coverage',
|
|
105
|
+
description: 'Report what a GitNexus index omitted for a repository: files skipped for exceeding the ' +
|
|
106
|
+
'size cap, oversized generated/minified output that inflates a language share, and files ' +
|
|
107
|
+
'a fresh scan would select but the index does not cover. Read-only; never triggers a ' +
|
|
108
|
+
're-index. Use it to judge how much of a repository the index actually represents before ' +
|
|
109
|
+
'trusting graph queries.',
|
|
110
|
+
parameters: {
|
|
111
|
+
repo: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
required: true,
|
|
114
|
+
description: 'Absolute path to the repository root (the directory containing .gitnexus).',
|
|
115
|
+
},
|
|
116
|
+
includeLanguages: {
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
description: 'Include the per-language byte breakdown in the result. Defaults to true.',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
output: {
|
|
122
|
+
schema: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
additionalProperties: true,
|
|
125
|
+
properties: {
|
|
126
|
+
// `required: true` per property, never `required: [...]`. The DSL's
|
|
127
|
+
// compiler rejects the array form outright 鈥?it collects these marks
|
|
128
|
+
// into the JSON Schema `required` list itself.
|
|
129
|
+
text: { type: 'string', required: true },
|
|
130
|
+
indexed: { type: 'boolean', required: true },
|
|
131
|
+
largeFileCount: { type: 'integer', required: true },
|
|
132
|
+
largeBytes: { type: 'integer', required: true },
|
|
133
|
+
warnings: { type: 'array' },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
render: (_args, value) => [{ type: 'text', text: value.text }],
|
|
137
|
+
},
|
|
138
|
+
async execute(args) {
|
|
139
|
+
const report = await coverageFor(args.repo);
|
|
140
|
+
return {
|
|
141
|
+
text: formatReport(report, args.includeLanguages !== false),
|
|
142
|
+
indexed: report.repo.indexed,
|
|
143
|
+
largeFileCount: report.totals.large,
|
|
144
|
+
largeBytes: report.totals.largeBytes,
|
|
145
|
+
warnings: report.warnings.map((w) => ({ level: w.level, code: w.code, message: w.message })),
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
presentCall: (args) => ({
|
|
149
|
+
card: 'generic',
|
|
150
|
+
title: `GitNexus coverage 路 ${String(args.repo).split(/[\\/]/).pop()}`,
|
|
151
|
+
kind: 'read',
|
|
152
|
+
}),
|
|
153
|
+
}));
|
|
154
|
+
if (config.serveRoutes) {
|
|
155
|
+
// Optional carrier: absent on headless profiles, so the tool above still works.
|
|
156
|
+
ctx.inject(['webServer'], (svcCtx) => {
|
|
157
|
+
// Read through `ctx.get` rather than injecting, because `shell` is optional here
|
|
158
|
+
// and `inject` would make the whole route block wait for a service a headless
|
|
159
|
+
// deployment never provides. `null` is handed to the runner, which reports
|
|
160
|
+
// `unavailable` and tells the operator to run the command themselves 鈥?the
|
|
161
|
+
// read-only half of the panel keeps working either way.
|
|
162
|
+
const shellRaw = svcCtx.get('shell');
|
|
163
|
+
const analyze = createAnalyzeRunner({
|
|
164
|
+
shell: isShellLike(shellRaw) ? shellRaw : null,
|
|
165
|
+
timeoutMs: config.analyzeTimeoutMs,
|
|
166
|
+
});
|
|
167
|
+
// GitNexus is what produces the index this plugin reads, so a machine without it
|
|
168
|
+
// has nothing to display and no way to change that from here. This runner is the
|
|
169
|
+
// way out: it installs a plugin-owned copy so the operator does not have to work
|
|
170
|
+
// out a package manager, a registry, or a global prefix themselves.
|
|
171
|
+
const install = createInstallRunner({
|
|
172
|
+
shell: isShellLike(shellRaw) ? shellRaw : null,
|
|
173
|
+
prefix: config.gitnexusInstallDir === '' ? undefined : config.gitnexusInstallDir,
|
|
174
|
+
timeoutMs: config.installTimeoutMs,
|
|
175
|
+
});
|
|
176
|
+
const disposers = [
|
|
177
|
+
mountCoverageRoute(svcCtx.webServer, {
|
|
178
|
+
get: (repoPath, options) => coverageFor(repoPath, options),
|
|
179
|
+
maintenance: (repoPath) => maintenanceFor(repoPath),
|
|
180
|
+
}),
|
|
181
|
+
mountAnalyzeRoute(svcCtx.webServer, {
|
|
182
|
+
start: (repoPath) => analyze.start(repoPath),
|
|
183
|
+
status: (repoPath) => analyze.status(repoPath),
|
|
184
|
+
cancel: (repoPath) => analyze.cancel(repoPath),
|
|
185
|
+
}),
|
|
186
|
+
mountInstallRoute(svcCtx.webServer, {
|
|
187
|
+
start: () => install.start(),
|
|
188
|
+
status: () => install.status(),
|
|
189
|
+
cancel: () => install.cancel(),
|
|
190
|
+
cliPath: () => install.cliPath(),
|
|
191
|
+
}),
|
|
192
|
+
];
|
|
193
|
+
ctx.effect(() => () => {
|
|
194
|
+
for (const dispose of disposers)
|
|
195
|
+
dispose();
|
|
196
|
+
// Abort before clearing, so a run in flight is stopped rather than orphaned
|
|
197
|
+
// holding a handle onto a server that no longer exists.
|
|
198
|
+
analyze.dispose();
|
|
199
|
+
install.dispose();
|
|
200
|
+
cache.clear();
|
|
201
|
+
}, 'gitnexus-plus: HTTP routes');
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Human-readable rendering lives in `./gitnexus/format.js` 鈥?free of DSH imports
|
|
207
|
+
* so it can be tested without a host. Re-exported here for callers that already
|
|
208
|
+
* hold the plugin module.
|
|
209
|
+
*/
|
|
210
|
+
export { formatReport } from './gitnexus/format.js';
|
|
211
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAuB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAA2B,MAAM,wBAAwB,CAAC;AACxG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAkB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAiD,CAAC;IACpE,OAAO,OAAO,SAAS,CAAC,OAAO,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,UAAU,CAAC;AAC5F,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,eAAe,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAU,CAAC;AAmCzC,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IAC/C,mBAAmB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAClD,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,gBAAgB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IACnD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;CAC1C,CAAC,CAAC;AAkBH,MAAM,kBAAkB,GAAuB,IAAI,CAAC;AACpD,KAAK,kBAAkB,CAAC;AAuBxB,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAoB;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkD,CAAC;IACxE,MAAM,YAAY,GAAG,MAAM,CAAC;IAE5B;;;;;;;;;;;OAWG;IACH,MAAM,WAAW,GAAG,KAAK,EAAE,QAAgB,EAAE,UAA+B,EAAE,EAA2B,EAAE;QACzG,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,YAAY;gBAAE,OAAO,GAAG,CAAC,MAAM,CAAC;QACjF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE;YACxC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;SAChD,CAAC,CAAC;QACH,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF;;;;OAIG;IACH,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAmB,EAAE;QAC3D,MAAM,OAAO,GAAG;YACd,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY;SACtE,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC;YAChD,SAAS,EAAE,gBAAgB,CAAC,OAAO,CAAC;SACrC,CAAC;IACJ,CAAC,CAAC;IAEF,GAAG,CAAC,KAAK,CAAC,QAAQ,CAChB,UAAU,CAAuC;QAC/C,IAAI,EAAE,mBAAmB;QACzB,WAAW,EACT,yFAAyF;YACzF,0FAA0F;YAC1F,sFAAsF;YACtF,0FAA0F;YAC1F,yBAAyB;QAC3B,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,4EAA4E;aAC1F;YACD,gBAAgB,EAAE;gBAChB,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,0EAA0E;aACxF;SACF;QACD,MAAM,EAAE;YACN,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,IAAI;gBAC1B,UAAU,EAAE;oBACV,oEAAoE;oBACpE,qEAAqE;oBACrE,+CAA+C;oBAC/C,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACxC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;oBAC5C,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACnD,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE;oBAC/C,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;iBAC5B;aACF;YACD,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;SAC/D;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5C,OAAO;gBACL,IAAI,EAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,KAAK,KAAK,CAAC;gBAC3D,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;gBAC5B,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK;gBACnC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU;gBACpC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;aAC7F,CAAC;QACJ,CAAC;QACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACtB,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,uBAAuB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACtE,IAAI,EAAE,MAAM;SACb,CAAC;KACH,CAAC,CACH,CAAC;IAEF,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,gFAAgF;QAChF,GAAG,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE;YACnC,iFAAiF;YACjF,8EAA8E;YAC9E,2EAA2E;YAC3E,2EAA2E;YAC3E,wDAAwD;YACxD,MAAM,QAAQ,GAAY,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9C,MAAM,OAAO,GAAG,mBAAmB,CAAC;gBAClC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;gBAC9C,SAAS,EAAE,MAAM,CAAC,gBAAgB;aACnC,CAAC,CAAC;YACH,iFAAiF;YACjF,iFAAiF;YACjF,iFAAiF;YACjF,oEAAoE;YACpE,MAAM,OAAO,GAAG,mBAAmB,CAAC;gBAClC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;gBAC9C,MAAM,EAAE,MAAM,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB;gBAChF,SAAS,EAAE,MAAM,CAAC,gBAAgB;aACnC,CAAC,CAAC;YAEH,MAAM,SAAS,GAAG;gBAChB,kBAAkB,CAAC,MAAM,CAAC,SAAS,EAAE;oBACnC,GAAG,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC;oBAC1D,WAAW,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC;iBACpD,CAAC;gBACF,iBAAiB,CAAC,MAAM,CAAC,SAAS,EAAE;oBAClC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;oBAC5C,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;oBAC9C,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;iBAC/C,CAAC;gBACF,iBAAiB,CAAC,MAAM,CAAC,SAAS,EAAE;oBAClC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;oBAC5B,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE;oBAC9B,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE;oBAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;iBACjC,CAAC;aACH,CAAC;YACF,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;gBACpB,KAAK,MAAM,OAAO,IAAI,SAAS;oBAAE,OAAO,EAAE,CAAC;gBAC3C,4EAA4E;gBAC5E,wDAAwD;gBACxD,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClB,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,CAAC,EAAE,4BAA4B,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one endpoint that changes something.
|
|
3
|
+
*
|
|
4
|
+
* Everything else this plugin serves is a reading of the working tree; this route
|
|
5
|
+
* starts `gitnexus analyze --index-only` through the host's shell executor. It is a
|
|
6
|
+
* separate module from `coverage.ts` rather than another branch inside it, so the
|
|
7
|
+
* read-only half keeps a guard set that cannot mutate and this half can be reviewed
|
|
8
|
+
* on its own.
|
|
9
|
+
*
|
|
10
|
+
* THREE METHODS, THREE MEANINGS
|
|
11
|
+
* -----------------------------
|
|
12
|
+
* `POST` starts a run, `GET` reports where it got to, `DELETE` asks it to stop. They
|
|
13
|
+
* are one endpoint because the run is one resource and the panel polls the same URL it
|
|
14
|
+
* started, which removes the id bookkeeping a separate status route would need.
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS ROUTE DOES NOT DO
|
|
17
|
+
* ---------------------------
|
|
18
|
+
* It does not escalate. The runner never sets `sandboxPolicy`, so the process is
|
|
19
|
+
* confined exactly as the deployment confines every other command. A refusal comes
|
|
20
|
+
* back as `denied` and is not retried wider — see `gitnexus/analyze.ts`, which is
|
|
21
|
+
* where that decision lives and is tested.
|
|
22
|
+
*/
|
|
23
|
+
import type { WebServerService } from '@deepseek-ai/cordis';
|
|
24
|
+
import type { AnalyzeStatus } from '../gitnexus/analyze.js';
|
|
25
|
+
export interface AnalyzeRouteDeps {
|
|
26
|
+
/** Begin a run, or return the one already in flight for this repository. */
|
|
27
|
+
start: (repoPath: string) => AnalyzeStatus;
|
|
28
|
+
status: (repoPath: string) => AnalyzeStatus | null;
|
|
29
|
+
cancel: (repoPath: string) => boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @returns disposer that unregisters the route
|
|
33
|
+
*/
|
|
34
|
+
export declare function mountAnalyzeRoute(webServer: WebServerService, deps: AnalyzeRouteDeps): () => void;
|
|
35
|
+
//# sourceMappingURL=analyze.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/routes/analyze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAQ5D,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC;IAC3C,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACvC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAuCjG"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one endpoint that changes something.
|
|
3
|
+
*
|
|
4
|
+
* Everything else this plugin serves is a reading of the working tree; this route
|
|
5
|
+
* starts `gitnexus analyze --index-only` through the host's shell executor. It is a
|
|
6
|
+
* separate module from `coverage.ts` rather than another branch inside it, so the
|
|
7
|
+
* read-only half keeps a guard set that cannot mutate and this half can be reviewed
|
|
8
|
+
* on its own.
|
|
9
|
+
*
|
|
10
|
+
* THREE METHODS, THREE MEANINGS
|
|
11
|
+
* -----------------------------
|
|
12
|
+
* `POST` starts a run, `GET` reports where it got to, `DELETE` asks it to stop. They
|
|
13
|
+
* are one endpoint because the run is one resource and the panel polls the same URL it
|
|
14
|
+
* started, which removes the id bookkeeping a separate status route would need.
|
|
15
|
+
*
|
|
16
|
+
* WHAT THIS ROUTE DOES NOT DO
|
|
17
|
+
* ---------------------------
|
|
18
|
+
* It does not escalate. The runner never sets `sandboxPolicy`, so the process is
|
|
19
|
+
* confined exactly as the deployment confines every other command. A refusal comes
|
|
20
|
+
* back as `denied` and is not retried wider — see `gitnexus/analyze.ts`, which is
|
|
21
|
+
* where that decision lives and is tested.
|
|
22
|
+
*/
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { guardHandler, passGuard, passWriteGuard, answerHead, sendJson } from './guard.js';
|
|
25
|
+
/**
|
|
26
|
+
* The methods this route answers. A wider set than the coverage route's on purpose:
|
|
27
|
+
* the difference between the two routes *is* their method policy.
|
|
28
|
+
*/
|
|
29
|
+
const ANALYZE_METHODS = new Set(['GET', 'HEAD', 'POST', 'DELETE']);
|
|
30
|
+
/**
|
|
31
|
+
* @returns disposer that unregisters the route
|
|
32
|
+
*/
|
|
33
|
+
export function mountAnalyzeRoute(webServer, deps) {
|
|
34
|
+
const handler = guardHandler(async (req, res) => {
|
|
35
|
+
if (!passGuard(req, res, ANALYZE_METHODS))
|
|
36
|
+
return;
|
|
37
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
38
|
+
const repo = url.searchParams.get('repo');
|
|
39
|
+
if (repo === null || repo.trim() === '') {
|
|
40
|
+
sendJson(res, 400, { ok: false, code: 'missing-repo', message: 'repo query parameter is required' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const absRepo = path.resolve(repo);
|
|
44
|
+
if (answerHead(req, res))
|
|
45
|
+
return;
|
|
46
|
+
if (req.method === 'POST') {
|
|
47
|
+
// The only mutating verb here, so the only one that takes the write guard. A
|
|
48
|
+
// cross-origin page can reach loopback; `Origin` is what separates it from the
|
|
49
|
+
// desktop shell's own fetch.
|
|
50
|
+
if (!passWriteGuard(req, res))
|
|
51
|
+
return;
|
|
52
|
+
sendJson(res, 200, { ok: true, run: deps.start(absRepo) });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.method === 'DELETE') {
|
|
56
|
+
if (!passWriteGuard(req, res))
|
|
57
|
+
return;
|
|
58
|
+
const cancelled = deps.cancel(absRepo);
|
|
59
|
+
// Cancelling nothing is not an error worth a 404: the caller's intent — "this
|
|
60
|
+
// repository should not be analysing" — already holds.
|
|
61
|
+
sendJson(res, 200, { ok: true, cancelled, run: deps.status(absRepo) });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const run = deps.status(absRepo);
|
|
65
|
+
// `null` means nothing has run here in this host's lifetime, which is a state the
|
|
66
|
+
// panel renders ("尚未运行"), not a failure.
|
|
67
|
+
sendJson(res, 200, { ok: true, run });
|
|
68
|
+
});
|
|
69
|
+
return webServer.register({ kind: 'prefix', path: '/api/gitnexus/analyze', handler });
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/routes/analyze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3F;;;GAGG;AACH,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AASxF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAA2B,EAAE,IAAsB;IACnF,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,eAAe,CAAC;YAAE,OAAO;QAElD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAC;YACrG,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEnC,IAAI,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,6EAA6E;YAC7E,+EAA+E;YAC/E,6BAA6B;YAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;gBAAE,OAAO;YACtC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;gBAAE,OAAO;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvC,8EAA8E;YAC9E,uDAAuD;YACvD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,kFAAkF;QAClF,yCAAyC;QACzC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,CAAC,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/gitnexus/coverage?repo=<project>` — the panel's data source.
|
|
3
|
+
*
|
|
4
|
+
* Route shape follows the community convention (`/api/<plugin-slug>/…`) and the
|
|
5
|
+
* carrier contract in `@deepseek-ai/dsh-host-webserver`: bare `node:http`, no
|
|
6
|
+
* Express, exact-then-longest-prefix matching, and a disposer for teardown.
|
|
7
|
+
*
|
|
8
|
+
* THE BOUND IS "IS A GITNEXUS INDEX", NOT "IS IN THE REGISTRY"
|
|
9
|
+
* -----------------------------------------------------------
|
|
10
|
+
* An earlier revision accepted only paths present in `~/.gitnexus/registry.json`.
|
|
11
|
+
* That was wrong for the panel's actual job: it shows the project the user is
|
|
12
|
+
* *in*, and a working directory the global registry has never heard of — a fresh
|
|
13
|
+
* clone, an index built with `GITNEXUS_HOME` redirected — is exactly the case the
|
|
14
|
+
* panel exists to report on. Refusing it would have been a correctness bug wearing
|
|
15
|
+
* a security costume.
|
|
16
|
+
*
|
|
17
|
+
* The route still refuses to describe arbitrary directories: the path must already
|
|
18
|
+
* contain `.gitnexus/gitnexus.json` or `.gitnexus/meta.json`, so a caller cannot
|
|
19
|
+
* use it to enumerate a filesystem. Loopback and method guards live in `./guard.js`.
|
|
20
|
+
*/
|
|
21
|
+
import type { WebServerService } from '@deepseek-ai/cordis';
|
|
22
|
+
import type { CoverageReport } from '../gitnexus/coverage.js';
|
|
23
|
+
import type { MaintenanceInfo } from '../index.js';
|
|
24
|
+
/**
|
|
25
|
+
* What the route needs from the host half.
|
|
26
|
+
*
|
|
27
|
+
* Injected rather than imported so the route stays testable without a GitNexus
|
|
28
|
+
* install: the engine arrives as two functions, and a test supplies stubs.
|
|
29
|
+
*/
|
|
30
|
+
export interface CoverageRouteDeps {
|
|
31
|
+
/**
|
|
32
|
+
* `fresh` asks the engine to skip its short-lived cache. A manual refresh carries
|
|
33
|
+
* it; a background poll does not, which is the case the cache exists for.
|
|
34
|
+
*/
|
|
35
|
+
get: (repoPath: string, options?: {
|
|
36
|
+
fresh?: boolean;
|
|
37
|
+
}) => Promise<CoverageReport>;
|
|
38
|
+
maintenance?: (repoPath: string) => MaintenanceInfo;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @returns disposer that unregisters the route
|
|
42
|
+
*/
|
|
43
|
+
export declare function mountCoverageRoute(webServer: WebServerService, deps: CoverageRouteDeps): () => void;
|
|
44
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/routes/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClF,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,eAAe,CAAC;CACrD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CA2CnG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/gitnexus/coverage?repo=<project>` — the panel's data source.
|
|
3
|
+
*
|
|
4
|
+
* Route shape follows the community convention (`/api/<plugin-slug>/…`) and the
|
|
5
|
+
* carrier contract in `@deepseek-ai/dsh-host-webserver`: bare `node:http`, no
|
|
6
|
+
* Express, exact-then-longest-prefix matching, and a disposer for teardown.
|
|
7
|
+
*
|
|
8
|
+
* THE BOUND IS "IS A GITNEXUS INDEX", NOT "IS IN THE REGISTRY"
|
|
9
|
+
* -----------------------------------------------------------
|
|
10
|
+
* An earlier revision accepted only paths present in `~/.gitnexus/registry.json`.
|
|
11
|
+
* That was wrong for the panel's actual job: it shows the project the user is
|
|
12
|
+
* *in*, and a working directory the global registry has never heard of — a fresh
|
|
13
|
+
* clone, an index built with `GITNEXUS_HOME` redirected — is exactly the case the
|
|
14
|
+
* panel exists to report on. Refusing it would have been a correctness bug wearing
|
|
15
|
+
* a security costume.
|
|
16
|
+
*
|
|
17
|
+
* The route still refuses to describe arbitrary directories: the path must already
|
|
18
|
+
* contain `.gitnexus/gitnexus.json` or `.gitnexus/meta.json`, so a caller cannot
|
|
19
|
+
* use it to enumerate a filesystem. Loopback and method guards live in `./guard.js`.
|
|
20
|
+
*/
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { hasIndex } from '../gitnexus/index-meta.js';
|
|
23
|
+
import { guardHandler, passGuard, answerHead, sendJson } from './guard.js';
|
|
24
|
+
/**
|
|
25
|
+
* @returns disposer that unregisters the route
|
|
26
|
+
*/
|
|
27
|
+
export function mountCoverageRoute(webServer, deps) {
|
|
28
|
+
const handler = guardHandler(async (req, res) => {
|
|
29
|
+
if (!passGuard(req, res))
|
|
30
|
+
return;
|
|
31
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
32
|
+
const repo = url.searchParams.get('repo');
|
|
33
|
+
if (repo === null || repo.trim() === '') {
|
|
34
|
+
sendJson(res, 400, {
|
|
35
|
+
ok: false,
|
|
36
|
+
code: 'missing-repo',
|
|
37
|
+
message: 'repo query parameter is required',
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const absRepo = path.resolve(repo);
|
|
42
|
+
if (!(await hasIndex(absRepo))) {
|
|
43
|
+
// Not an error the caller should retry: this directory simply has no index
|
|
44
|
+
// yet, which is the state the panel's initialize action exists for.
|
|
45
|
+
sendJson(res, 404, {
|
|
46
|
+
ok: false,
|
|
47
|
+
code: 'not-indexed',
|
|
48
|
+
message: 'directory has no GitNexus index (.gitnexus/gitnexus.json or meta.json)',
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (answerHead(req, res))
|
|
53
|
+
return;
|
|
54
|
+
// `refresh=1` comes from the panel's manual refresh control. A background poll
|
|
55
|
+
// omits it and gets the cached copy, which is what the TTL is for.
|
|
56
|
+
const fresh = url.searchParams.get('refresh') === '1';
|
|
57
|
+
const report = await deps.get(absRepo, { fresh });
|
|
58
|
+
// The maintenance commands ride along with the report because they are
|
|
59
|
+
// config-derived and only meaningful next to the index they would rebuild.
|
|
60
|
+
const maintenance = typeof deps.maintenance === 'function' ? deps.maintenance(absRepo) : null;
|
|
61
|
+
// `readAt` is what makes a refresh visible: without it two reads of an unchanged
|
|
62
|
+
// repository are byte-identical on screen, so the control looks inert even when
|
|
63
|
+
// it did exactly what it was asked.
|
|
64
|
+
sendJson(res, 200, { ok: true, report, maintenance, readAt: new Date().toISOString() });
|
|
65
|
+
});
|
|
66
|
+
return webServer.register({ kind: 'prefix', path: '/api/gitnexus/coverage', handler });
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/routes/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoB3E;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAA2B,EAAE,IAAuB;IACrF,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;gBACjB,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,kCAAkC;aAC5C,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YAC/B,2EAA2E;YAC3E,oEAAoE;YACpE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;gBACjB,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,wEAAwE;aAClF,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,+EAA+E;QAC/E,mEAAmE;QACnE,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC;QACtD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,uEAAuE;QACvE,2EAA2E;QAC3E,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9F,iFAAiF;QACjF,gFAAgF;QAChF,oCAAoC;QACpC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,CAAC,CAAC;AACzF,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared guards for this plugin's HTTP routes.
|
|
3
|
+
*
|
|
4
|
+
* Extracted because two routes now need identical treatment, and a guard that is
|
|
5
|
+
* copy-pasted stops being a guard the first time one copy is edited.
|
|
6
|
+
*
|
|
7
|
+
* WHY THE GUARD EXISTS AT ALL
|
|
8
|
+
* ---------------------------
|
|
9
|
+
* On a bare `dsh web` profile a third-party named route is unauthenticated: the
|
|
10
|
+
* carrier owns no TLS, authentication, or Origin policy, and the Host/Origin
|
|
11
|
+
* checks that do exist belong to `client/connection` and cover only the routes it
|
|
12
|
+
* registers itself. The Desktop carrier adds its own gate (an
|
|
13
|
+
* `x-dsh-desktop-renderer` token; unauthenticated requests get 403), but a route
|
|
14
|
+
* that is safe only on one carrier is not safe.
|
|
15
|
+
*/
|
|
16
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
17
|
+
import type { RequestHandler } from '@deepseek-ai/cordis';
|
|
18
|
+
/** This plugin only ever answers reads. */
|
|
19
|
+
export declare const ALLOWED_METHODS: Set<string>;
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('node:http').IncomingMessage} req
|
|
22
|
+
* @returns {boolean} whether the peer is this machine
|
|
23
|
+
*/
|
|
24
|
+
export declare function isLoopback(req: IncomingMessage): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('node:http').ServerResponse} res
|
|
27
|
+
* @param {number} status
|
|
28
|
+
* @param {unknown} body
|
|
29
|
+
* @param {Record<string, string>} [headers]
|
|
30
|
+
*/
|
|
31
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown, headers?: Record<string, string>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Run the shared pre-checks, answering the request when one fails.
|
|
34
|
+
*
|
|
35
|
+
* `methods` exists because the default set is the read-only one: the coverage route
|
|
36
|
+
* answers GET and HEAD and nothing else, and giving it POST would widen it by accident.
|
|
37
|
+
* A route that legitimately mutates passes its own set, so the method policy is stated
|
|
38
|
+
* at the route rather than inferred from what the guard happens to allow.
|
|
39
|
+
*
|
|
40
|
+
* @param {import('node:http').IncomingMessage} req
|
|
41
|
+
* @param {import('node:http').ServerResponse} res
|
|
42
|
+
* @param methods the methods this route accepts
|
|
43
|
+
* @returns `true` when the caller may continue handling the request
|
|
44
|
+
*/
|
|
45
|
+
export declare function passGuard(req: IncomingMessage, res: ServerResponse, methods?: ReadonlySet<string>): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Answer a HEAD request with headers only, or report that the caller should
|
|
48
|
+
* continue and send the body.
|
|
49
|
+
*
|
|
50
|
+
* @returns `true` when the request was fully answered
|
|
51
|
+
*/
|
|
52
|
+
export declare function answerHead(req: IncomingMessage, res: ServerResponse): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Wrap a handler so no failure escapes as an unhandled rejection on the carrier.
|
|
55
|
+
*
|
|
56
|
+
* `Promise.resolve` around the call is not decoration: `RequestHandler` may return
|
|
57
|
+
* `void`, and calling `.catch` on `void` is a runtime `TypeError`. The shape the
|
|
58
|
+
* carrier actually uses is "sync throw or async rejection", and only the resolved
|
|
59
|
+
* form covers both.
|
|
60
|
+
*/
|
|
61
|
+
export declare function guardHandler(handler: RequestHandler): RequestHandler;
|
|
62
|
+
/**
|
|
63
|
+
* Extra guard for routes that *change* something.
|
|
64
|
+
*
|
|
65
|
+
* The loopback check alone is not enough for a mutation: any page the operator
|
|
66
|
+
* visits can issue a request to `127.0.0.1`, and a cross-origin `POST` needs no
|
|
67
|
+
* preflight when it uses a simple content type. `Origin` is the discriminator —
|
|
68
|
+
* a malicious page always sends its own origin, and it will not match this host.
|
|
69
|
+
*
|
|
70
|
+
* A request with NO `Origin` is allowed through, and that is deliberate rather
|
|
71
|
+
* than lazy: a same-origin `fetch` from the desktop shell, and any local tool
|
|
72
|
+
* (including this repository's own tests), legitimately send none. The remaining
|
|
73
|
+
* gates — loopback peer, and the Desktop carrier's renderer token — still apply,
|
|
74
|
+
* so an absent header is not by itself a way in.
|
|
75
|
+
*
|
|
76
|
+
* @param {import('node:http').IncomingMessage} req
|
|
77
|
+
* @param {import('node:http').ServerResponse} res
|
|
78
|
+
* @returns {boolean} `true` when the caller may proceed
|
|
79
|
+
*/
|
|
80
|
+
export declare function passWriteGuard(req: IncomingMessage, res: ServerResponse): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Read a JSON body with a hard byte cap.
|
|
83
|
+
*
|
|
84
|
+
* Bounded because the cap is the only thing standing between a malformed client
|
|
85
|
+
* and unbounded host memory — and this plugin's bodies are at most a few fields.
|
|
86
|
+
*
|
|
87
|
+
* @param {import('node:http').IncomingMessage} req
|
|
88
|
+
* @param {number} [maxBytes]
|
|
89
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
90
|
+
*/
|
|
91
|
+
export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
|
|
92
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/routes/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,2CAA2C;AAC3C,eAAO,MAAM,eAAe,aAA2B,CAAC;AAExD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CASxD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,QAShH;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,WAAW,CAAC,MAAM,CAAmB,GAC7C,OAAO,CAUT;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAM7E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAcpE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAmBjF;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,SAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA4B9F"}
|