@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.
Files changed (2) hide show
  1. package/package.json +3 -2
  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.2.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 run [options] -- <command> [args...]
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
- Examples:
14
- depct run -- node server.js
15
- depct run --project-id my-api --server-url http://localhost:3100 -- node server.js
16
- depct run --no-debug-events -- node server.js
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
- Options:
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
- function run(argv) {
200
- if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
201
- printHelp(0);
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
- const commandName = argv[0];
205
- if (commandName !== "run") {
206
- fail(`Unknown command: ${commandName}`);
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
- const { options, command } = parseRunArgs(argv.slice(1));
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));