@depctdev/depct 0.2.0 → 0.3.0
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/package.json +3 -2
- package/src/index.cjs +266 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@depctdev/depct",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for running Node apps with Depct instrumentation",
|
|
5
5
|
"bin": {
|
|
6
6
|
"depct": "src/index.cjs"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"depct-loader": "0.1.0"
|
|
15
|
+
"depct-loader": "0.1.0",
|
|
16
|
+
"fast-glob": "^3.3.3"
|
|
16
17
|
}
|
|
17
18
|
}
|
package/src/index.cjs
CHANGED
|
@@ -2,20 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
|
+
const zlib = require("node:zlib");
|
|
5
6
|
const { spawn } = require("node:child_process");
|
|
6
7
|
|
|
7
8
|
const HELP = `depct CLI
|
|
8
9
|
|
|
9
10
|
Usage:
|
|
10
|
-
depct
|
|
11
|
-
depct run [options] <command> [args...]
|
|
11
|
+
depct init [options] Register a new project
|
|
12
|
+
depct run [options] -- <command> [args...] Run with instrumentation
|
|
13
|
+
depct generate [options] Generate documentation
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
depct
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Commands:
|
|
16
|
+
init Register project with the depct server and create .depctrc
|
|
17
|
+
run Run a command with depct instrumentation
|
|
18
|
+
generate Package source files, trigger doc generation, poll for completion
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
depct init options:
|
|
21
|
+
--name <name> Project name (default: from package.json)
|
|
22
|
+
--server-url <url> Server URL (default: DEPCT_SERVER_URL or http://localhost:3100)
|
|
23
|
+
|
|
24
|
+
depct run options:
|
|
19
25
|
--project-root <path> Set DEPCT_PROJECT_ROOT (default: current directory)
|
|
20
26
|
--project-id <id> Set DEPCT_PROJECT_ID
|
|
21
27
|
--server-url <url> Set DEPCT_SERVER_URL
|
|
@@ -26,6 +32,11 @@ Options:
|
|
|
26
32
|
--debug / --no-debug Set DEPCT_DEBUG (default: true)
|
|
27
33
|
--debug-events / --no-debug-events
|
|
28
34
|
Set DEPCT_DEBUG_EVENTS (default: true)
|
|
35
|
+
|
|
36
|
+
depct generate options:
|
|
37
|
+
--server-url <url> Server URL override
|
|
38
|
+
|
|
39
|
+
Global:
|
|
29
40
|
-h, --help Show help
|
|
30
41
|
`;
|
|
31
42
|
|
|
@@ -196,19 +207,229 @@ function applyEnvOptions(baseEnv, options) {
|
|
|
196
207
|
return env;
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
// ── .depctrc ──
|
|
211
|
+
|
|
212
|
+
const DEPCTRC_NAME = ".depctrc";
|
|
213
|
+
const DEFAULT_INCLUDE = ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.cjs", "**/*.mjs"];
|
|
214
|
+
const DEFAULT_EXCLUDE = ["node_modules/**", "dist/**", ".git/**", "coverage/**", ".next/**", "build/**"];
|
|
215
|
+
|
|
216
|
+
function readDepctrc() {
|
|
217
|
+
const rcPath = path.resolve(process.cwd(), DEPCTRC_NAME);
|
|
218
|
+
if (!fs.existsSync(rcPath)) {
|
|
219
|
+
fail(`No ${DEPCTRC_NAME} found. Run \`depct init\` first.`);
|
|
202
220
|
}
|
|
221
|
+
const raw = fs.readFileSync(rcPath, "utf8");
|
|
222
|
+
const config = JSON.parse(raw);
|
|
223
|
+
return {
|
|
224
|
+
project_id: config.project_id,
|
|
225
|
+
project_name: config.project_name || "",
|
|
226
|
+
server_url: config.server_url || process.env.DEPCT_SERVER_URL || "http://localhost:3100",
|
|
227
|
+
include: config.include || DEFAULT_INCLUDE,
|
|
228
|
+
exclude: config.exclude || DEFAULT_EXCLUDE,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
203
231
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
function writeDepctrc(config) {
|
|
233
|
+
const rcPath = path.resolve(process.cwd(), DEPCTRC_NAME);
|
|
234
|
+
fs.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── depct init ──
|
|
238
|
+
|
|
239
|
+
function parseInitArgs(argv) {
|
|
240
|
+
const options = { name: "", serverUrl: "" };
|
|
241
|
+
for (let i = 0; i < argv.length; i++) {
|
|
242
|
+
const arg = argv[i];
|
|
243
|
+
const next = argv[i + 1];
|
|
244
|
+
if (arg === "--name" && next) { options.name = next; i++; }
|
|
245
|
+
else if (arg === "--server-url" && next) { options.serverUrl = next; i++; }
|
|
246
|
+
else if (arg === "-h" || arg === "--help") printHelp(0);
|
|
247
|
+
}
|
|
248
|
+
return options;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function handleInit(argv) {
|
|
252
|
+
const options = parseInitArgs(argv);
|
|
253
|
+
const serverUrl = options.serverUrl || process.env.DEPCT_SERVER_URL || "http://localhost:3100";
|
|
254
|
+
const projectName = options.name || inferProjectIdFromPackageJson(process.cwd()) || path.basename(process.cwd());
|
|
255
|
+
|
|
256
|
+
process.stdout.write(`Registering project "${projectName}" with ${serverUrl}...\n`);
|
|
257
|
+
|
|
258
|
+
let response;
|
|
259
|
+
try {
|
|
260
|
+
response = await fetch(`${serverUrl}/v1/projects`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: { "content-type": "application/json" },
|
|
263
|
+
body: JSON.stringify({ project_name: projectName }),
|
|
264
|
+
});
|
|
265
|
+
} catch (err) {
|
|
266
|
+
fail(`Failed to connect to server at ${serverUrl}: ${err.message}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const result = await response.json();
|
|
270
|
+
if (!result.ok) {
|
|
271
|
+
fail(`Server error: ${result.error}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const config = {
|
|
275
|
+
project_id: result.project_id,
|
|
276
|
+
project_name: result.project_name,
|
|
277
|
+
server_url: serverUrl,
|
|
278
|
+
include: DEFAULT_INCLUDE,
|
|
279
|
+
exclude: DEFAULT_EXCLUDE,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
writeDepctrc(config);
|
|
283
|
+
|
|
284
|
+
process.stdout.write(`Project registered: ${result.project_id}\n`);
|
|
285
|
+
process.stdout.write(`Config written to ${DEPCTRC_NAME}\n`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── depct generate ──
|
|
289
|
+
|
|
290
|
+
function parseGenerateArgs(argv) {
|
|
291
|
+
const options = { serverUrl: "" };
|
|
292
|
+
for (let i = 0; i < argv.length; i++) {
|
|
293
|
+
const arg = argv[i];
|
|
294
|
+
const next = argv[i + 1];
|
|
295
|
+
if (arg === "--server-url" && next) { options.serverUrl = next; i++; }
|
|
296
|
+
else if (arg === "-h" || arg === "--help") printHelp(0);
|
|
297
|
+
}
|
|
298
|
+
return options;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function packageSourceFiles(include, exclude) {
|
|
302
|
+
const fg = require("fast-glob");
|
|
303
|
+
const files = await fg(include, {
|
|
304
|
+
cwd: process.cwd(),
|
|
305
|
+
ignore: exclude,
|
|
306
|
+
absolute: true,
|
|
307
|
+
onlyFiles: true,
|
|
308
|
+
followSymbolicLinks: false,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (files.length === 0) {
|
|
312
|
+
fail("No source files matched the include/exclude patterns in .depctrc");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const parts = [];
|
|
316
|
+
for (const filePath of files) {
|
|
317
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
318
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
319
|
+
parts.push(`===== ${relativePath} =====\n${content}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
process.stdout.write(`Packaged ${files.length} source files\n`);
|
|
323
|
+
return parts.join("\n\n");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function pollGenerationStatus(serverUrl, genId) {
|
|
327
|
+
const maxWaitMs = 300000;
|
|
328
|
+
const pollInterval = 3000;
|
|
329
|
+
const startTime = Date.now();
|
|
330
|
+
|
|
331
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
332
|
+
let response;
|
|
333
|
+
try {
|
|
334
|
+
response = await fetch(`${serverUrl}/v1/generate/${genId}/status`);
|
|
335
|
+
} catch {
|
|
336
|
+
process.stdout.write(".");
|
|
337
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = await response.json();
|
|
342
|
+
|
|
343
|
+
if (result.status === "complete") {
|
|
344
|
+
process.stdout.write("\nGeneration complete.\n");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (result.status === "failed") {
|
|
348
|
+
fail("Generation failed on the server.");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
process.stdout.write(".");
|
|
352
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
207
353
|
}
|
|
208
354
|
|
|
209
|
-
|
|
355
|
+
fail("Generation timed out after 5 minutes. Check server logs.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function handleGenerate(argv) {
|
|
359
|
+
const options = parseGenerateArgs(argv);
|
|
360
|
+
const config = readDepctrc();
|
|
361
|
+
const serverUrl = options.serverUrl || config.server_url;
|
|
362
|
+
|
|
363
|
+
process.stdout.write(`Packaging source files for project ${config.project_id}...\n`);
|
|
364
|
+
const sourceFiles = await packageSourceFiles(config.include, config.exclude);
|
|
365
|
+
|
|
366
|
+
const payload = JSON.stringify({
|
|
367
|
+
source_files: sourceFiles,
|
|
368
|
+
project_name: config.project_name,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const compressed = zlib.gzipSync(Buffer.from(payload, "utf8"));
|
|
372
|
+
|
|
373
|
+
process.stdout.write(`Sending ${(compressed.length / 1024).toFixed(1)} KB (gzip) to ${serverUrl}...\n`);
|
|
374
|
+
|
|
375
|
+
let response;
|
|
376
|
+
try {
|
|
377
|
+
response = await fetch(`${serverUrl}/v1/generate/${config.project_id}`, {
|
|
378
|
+
method: "POST",
|
|
379
|
+
headers: {
|
|
380
|
+
"content-type": "application/json",
|
|
381
|
+
"content-encoding": "gzip",
|
|
382
|
+
},
|
|
383
|
+
body: compressed,
|
|
384
|
+
});
|
|
385
|
+
} catch (err) {
|
|
386
|
+
fail(`Failed to connect to server: ${err.message}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const result = await response.json();
|
|
390
|
+
|
|
391
|
+
if (!result.ok) {
|
|
392
|
+
if (result.cached) {
|
|
393
|
+
process.stdout.write(`No new events since last generation. Using cached result (generation ${result.generation_id}).\n`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
fail(`Server error (${response.status}): ${result.error}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (result.cached) {
|
|
400
|
+
process.stdout.write(`No new events since last generation. Using cached result (generation ${result.generation_id}).\n`);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const genId = result.generation_id;
|
|
405
|
+
process.stdout.write(`Generation started (ID: ${genId}). Waiting for completion`);
|
|
406
|
+
|
|
407
|
+
await pollGenerationStatus(serverUrl, genId);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ── depct run ──
|
|
411
|
+
|
|
412
|
+
function handleRun(argv) {
|
|
413
|
+
const { options, command } = parseRunArgs(argv);
|
|
210
414
|
const [execPath, ...execArgs] = command;
|
|
211
415
|
const loaderEntry = resolveLoaderEntry();
|
|
416
|
+
|
|
417
|
+
// If .depctrc exists, use its project_id and server_url
|
|
418
|
+
const rcPath = path.resolve(process.cwd(), DEPCTRC_NAME);
|
|
419
|
+
if (fs.existsSync(rcPath)) {
|
|
420
|
+
try {
|
|
421
|
+
const config = JSON.parse(fs.readFileSync(rcPath, "utf8"));
|
|
422
|
+
if (config.project_id && !options.projectId) {
|
|
423
|
+
options.projectId = config.project_id;
|
|
424
|
+
}
|
|
425
|
+
if (config.server_url && !options.serverUrl) {
|
|
426
|
+
options.serverUrl = config.server_url;
|
|
427
|
+
}
|
|
428
|
+
} catch {
|
|
429
|
+
// ignore parse errors
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
212
433
|
const env = applyEnvOptions(process.env, options);
|
|
213
434
|
env.NODE_OPTIONS = withNodeOptions(env.NODE_OPTIONS, loaderEntry);
|
|
214
435
|
|
|
@@ -232,4 +453,35 @@ function run(argv) {
|
|
|
232
453
|
});
|
|
233
454
|
}
|
|
234
455
|
|
|
456
|
+
// ── main dispatch ──
|
|
457
|
+
|
|
458
|
+
function run(argv) {
|
|
459
|
+
if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
|
|
460
|
+
printHelp(0);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const commandName = argv[0];
|
|
464
|
+
|
|
465
|
+
if (commandName === "run") {
|
|
466
|
+
handleRun(argv.slice(1));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (commandName === "init") {
|
|
471
|
+
handleInit(argv.slice(1)).catch((err) => {
|
|
472
|
+
fail(err.message || String(err));
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (commandName === "generate") {
|
|
478
|
+
handleGenerate(argv.slice(1)).catch((err) => {
|
|
479
|
+
fail(err.message || String(err));
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
fail(`Unknown command: ${commandName}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
235
487
|
run(process.argv.slice(2));
|