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