@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.
Files changed (2) hide show
  1. package/package.json +3 -2
  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.2.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 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
 
@@ -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
- function run(argv) {
200
- if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
201
- printHelp(0);
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
- const commandName = argv[0];
205
- if (commandName !== "run") {
206
- fail(`Unknown command: ${commandName}`);
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 { options, command } = parseRunArgs(argv.slice(1));
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));