@mestreyoda/fabrica 0.1.19 → 0.1.21

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.
@@ -442,6 +442,146 @@ generate_node_lockfile() {
442
442
  fi
443
443
  }
444
444
 
445
+ # --- Node.js CLI ---
446
+ scaffold_node_cli() {
447
+ scaffold_gitignore_node
448
+ scaffold_env_example "node-cli"
449
+
450
+ cat > package.json <<'EOF'
451
+ {
452
+ "name": "REPO_NAME_PLACEHOLDER",
453
+ "version": "0.1.0",
454
+ "private": true,
455
+ "type": "module",
456
+ "bin": {
457
+ "REPO_NAME_PLACEHOLDER": "dist/index.js"
458
+ },
459
+ "scripts": {
460
+ "dev": "tsx src/index.ts",
461
+ "build": "tsc",
462
+ "start": "node dist/index.js",
463
+ "lint": "eslint src/",
464
+ "test": "vitest run",
465
+ "test:watch": "vitest"
466
+ },
467
+ "dependencies": {
468
+ "commander": "^14.0.0"
469
+ },
470
+ "devDependencies": {
471
+ "@types/node": "^22.0.0",
472
+ "eslint": "^9.0.0",
473
+ "typescript": "^5.7.0",
474
+ "tsx": "^4.0.0",
475
+ "vitest": "^3.0.0",
476
+ "@vitest/coverage-v8": "^3.0.0"
477
+ }
478
+ }
479
+ EOF
480
+ jq --arg name "$REPO_NAME" '.name = $name | .bin = {($name): "dist/index.js"}' package.json > package.json.tmp && mv package.json.tmp package.json
481
+ FILES_CREATED+=("package.json")
482
+ generate_node_lockfile
483
+
484
+ cat > tsconfig.json <<'EOF'
485
+ {
486
+ "compilerOptions": {
487
+ "target": "ES2022",
488
+ "module": "ESNext",
489
+ "moduleResolution": "bundler",
490
+ "outDir": "dist",
491
+ "rootDir": "src",
492
+ "strict": true,
493
+ "esModuleInterop": true,
494
+ "skipLibCheck": true,
495
+ "resolveJsonModule": true,
496
+ "declaration": true
497
+ },
498
+ "include": ["src"],
499
+ "exclude": ["node_modules", "dist", "tests"]
500
+ }
501
+ EOF
502
+ FILES_CREATED+=("tsconfig.json")
503
+
504
+ mkdir -p src
505
+ cat > src/index.ts <<'EOF'
506
+ #!/usr/bin/env node
507
+ import { Command } from 'commander';
508
+
509
+ const program = new Command();
510
+
511
+ program
512
+ .name(process.env.npm_package_name ?? 'cli')
513
+ .version(process.env.npm_package_version ?? '0.1.0')
514
+ .description('CLI tool')
515
+ .argument('[args...]', 'arguments')
516
+ .action((args: string[]) => {
517
+ console.log('Hello from CLI', args.length ? args.join(' ') : '');
518
+ });
519
+
520
+ program.parse();
521
+ EOF
522
+ FILES_CREATED+=("src/index.ts")
523
+
524
+ mkdir -p tests
525
+ cat > tests/main.test.ts <<'EOF'
526
+ import { describe, it, expect } from 'vitest';
527
+ import { execFileSync } from 'node:child_process';
528
+ import { resolve } from 'node:path';
529
+
530
+ const cli = resolve(__dirname, '../src/index.ts');
531
+
532
+ function run(...args: string[]): string {
533
+ return execFileSync('npx', ['tsx', cli, ...args], {
534
+ encoding: 'utf-8',
535
+ env: { ...process.env, NODE_NO_WARNINGS: '1' },
536
+ }).trim();
537
+ }
538
+
539
+ describe('CLI', () => {
540
+ it('should print hello message', () => {
541
+ const output = run();
542
+ expect(output).toContain('Hello from CLI');
543
+ });
544
+
545
+ it('should show version with --version', () => {
546
+ const output = run('--version');
547
+ expect(output).toMatch(/\d+\.\d+\.\d+/);
548
+ });
549
+ });
550
+ EOF
551
+ FILES_CREATED+=("tests/main.test.ts")
552
+
553
+ mkdir -p scripts
554
+ cat > scripts/qa.sh <<'QAEOF'
555
+ #!/usr/bin/env bash
556
+ set -euo pipefail
557
+ echo "=== QA Gate ==="
558
+ FAIL=0
559
+
560
+ echo "--- Lint ---"
561
+ npx eslint src/ 2>&1 || { echo "LINT FAILED"; FAIL=1; }
562
+
563
+ echo "--- TypeScript ---"
564
+ npx tsc --noEmit 2>&1 || { echo "TSC FAILED"; FAIL=1; }
565
+
566
+ echo "--- Tests ---"
567
+ npx vitest run 2>&1 || { echo "TESTS FAILED"; FAIL=1; }
568
+
569
+ echo "--- Coverage (>=80%) ---"
570
+ npx vitest run --coverage --coverage.thresholds.lines=80 2>&1 || { echo "COVERAGE FAILED"; FAIL=1; }
571
+
572
+ echo "--- Secrets scan ---"
573
+ if grep -rn 'password\s*=\s*"[^"]\+"\|api_key\s*=\s*"[^"]\+"\|secret\s*=\s*"[^"]\+"' --include="*.ts" --include="*.js" src/ 2>/dev/null; then
574
+ echo "SECRETS FOUND — FAIL"; FAIL=1
575
+ else
576
+ echo "No hardcoded secrets found"
577
+ fi
578
+
579
+ exit $FAIL
580
+ QAEOF
581
+ chmod +x scripts/qa.sh
582
+ FILES_CREATED+=("scripts/qa.sh")
583
+ }
584
+
445
585
  # --- Next.js ---
446
586
  scaffold_nextjs() {
447
587
  scaffold_gitignore_node
@@ -1346,8 +1486,9 @@ if [[ -z "$OBJECTIVE" ]]; then
1346
1486
  fi
1347
1487
 
1348
1488
  case "$STACK" in
1349
- nextjs) scaffold_nextjs ;;
1350
- express) scaffold_express ;;
1489
+ nextjs) scaffold_nextjs ;;
1490
+ node-cli) scaffold_node_cli ;;
1491
+ express) scaffold_express ;;
1351
1492
  fastapi) scaffold_fastapi ;;
1352
1493
  flask) scaffold_flask ;;
1353
1494
  django) scaffold_django ;;
@@ -12,12 +12,12 @@ fi
12
12
  # genesis_detect_stack_from_hint <stack_hint>
13
13
  # Maps a raw stack hint (from GENESIS_STACK, scaffold.stack, etc.) to a
14
14
  # canonical stack name, or returns empty string if unknown.
15
- # Canonical stacks: nextjs, express, fastapi, flask, django, python-cli
15
+ # Canonical stacks: nextjs, node-cli, express, fastapi, flask, django, python-cli
16
16
  genesis_normalize_stack_hint() {
17
17
  local hint="${1:-}"
18
18
  hint="$(echo "$hint" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
19
19
  case "$hint" in
20
- nextjs|express|fastapi|flask|django|python-cli)
20
+ nextjs|node-cli|express|fastapi|flask|django|python-cli)
21
21
  printf '%s\n' "$hint"
22
22
  ;;
23
23
  *)
@@ -43,6 +43,8 @@ genesis_detect_stack_from_text() {
43
43
  echo "flask"
44
44
  elif echo "$text" | grep -qiE '\bdjango\b'; then
45
45
  echo "django"
46
+ elif echo "$text" | grep -qiE '\bnode\.?js\b.*\bcli\b|\bcli\b.*\bnode\.?js\b|\btypescript\b.*\bcli\b|\bcli\b.*\btypescript\b|\bcommander\b'; then
47
+ echo "node-cli"
46
48
  elif echo "$text" | grep -qiE '\bpython\b.*\bcli\b|\bcli\b.*\bpython\b|\bclick\b|\bargparse\b|\btyper\b'; then
47
49
  echo "python-cli"
48
50
  elif echo "$text" | grep -qiE '\bpython\b'; then
@@ -75,7 +77,7 @@ genesis_stack_flags() {
75
77
  fastapi|flask|django|python-cli|python)
76
78
  echo "true false false"
77
79
  ;;
78
- nextjs|express|node|javascript|typescript)
80
+ nextjs|express|node|node-cli|javascript|typescript)
79
81
  echo "false true false"
80
82
  ;;
81
83
  go|golang)
@@ -100,7 +102,7 @@ genesis_detect_stack_flags_from_context() {
100
102
  # Priority 1: explicit scaffold stack
101
103
  case "$stack_hint" in
102
104
  fastapi|flask|django|python|python-cli) IS_PY=true ;;
103
- nextjs|express|node|javascript|typescript) IS_JS=true ;;
105
+ nextjs|express|node|node-cli|javascript|typescript) IS_JS=true ;;
104
106
  go|golang) IS_GO=true ;;
105
107
  esac
106
108
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mestreyoda/fabrica",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",