vonosan-cli 0.1.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 (121) hide show
  1. package/dist/__tests__/env-parity.pbt.test.d.ts +15 -0
  2. package/dist/__tests__/env-parity.pbt.test.d.ts.map +1 -0
  3. package/dist/__tests__/env-parity.pbt.test.js +125 -0
  4. package/dist/__tests__/env-parity.pbt.test.js.map +1 -0
  5. package/dist/__tests__/module-generator.pbt.test.d.ts +15 -0
  6. package/dist/__tests__/module-generator.pbt.test.d.ts.map +1 -0
  7. package/dist/__tests__/module-generator.pbt.test.js +130 -0
  8. package/dist/__tests__/module-generator.pbt.test.js.map +1 -0
  9. package/dist/commands/add.d.ts +21 -0
  10. package/dist/commands/add.d.ts.map +1 -0
  11. package/dist/commands/add.js +128 -0
  12. package/dist/commands/add.js.map +1 -0
  13. package/dist/commands/audit.d.ts +18 -0
  14. package/dist/commands/audit.d.ts.map +1 -0
  15. package/dist/commands/audit.js +73 -0
  16. package/dist/commands/audit.js.map +1 -0
  17. package/dist/commands/branch-finish.d.ts +19 -0
  18. package/dist/commands/branch-finish.d.ts.map +1 -0
  19. package/dist/commands/branch-finish.js +80 -0
  20. package/dist/commands/branch-finish.js.map +1 -0
  21. package/dist/commands/branch-new.d.ts +17 -0
  22. package/dist/commands/branch-new.d.ts.map +1 -0
  23. package/dist/commands/branch-new.js +70 -0
  24. package/dist/commands/branch-new.js.map +1 -0
  25. package/dist/commands/commit.d.ts +21 -0
  26. package/dist/commands/commit.d.ts.map +1 -0
  27. package/dist/commands/commit.js +79 -0
  28. package/dist/commands/commit.js.map +1 -0
  29. package/dist/commands/db.d.ts +28 -0
  30. package/dist/commands/db.d.ts.map +1 -0
  31. package/dist/commands/db.js +83 -0
  32. package/dist/commands/db.js.map +1 -0
  33. package/dist/commands/env-add.d.ts +17 -0
  34. package/dist/commands/env-add.d.ts.map +1 -0
  35. package/dist/commands/env-add.js +73 -0
  36. package/dist/commands/env-add.js.map +1 -0
  37. package/dist/commands/fix-headers.d.ts +15 -0
  38. package/dist/commands/fix-headers.d.ts.map +1 -0
  39. package/dist/commands/fix-headers.js +37 -0
  40. package/dist/commands/fix-headers.js.map +1 -0
  41. package/dist/commands/fix-logs.d.ts +15 -0
  42. package/dist/commands/fix-logs.d.ts.map +1 -0
  43. package/dist/commands/fix-logs.js +87 -0
  44. package/dist/commands/fix-logs.js.map +1 -0
  45. package/dist/commands/jobs.d.ts +17 -0
  46. package/dist/commands/jobs.d.ts.map +1 -0
  47. package/dist/commands/jobs.js +107 -0
  48. package/dist/commands/jobs.js.map +1 -0
  49. package/dist/commands/lint.d.ts +15 -0
  50. package/dist/commands/lint.d.ts.map +1 -0
  51. package/dist/commands/lint.js +49 -0
  52. package/dist/commands/lint.js.map +1 -0
  53. package/dist/commands/make.d.ts +31 -0
  54. package/dist/commands/make.d.ts.map +1 -0
  55. package/dist/commands/make.js +176 -0
  56. package/dist/commands/make.js.map +1 -0
  57. package/dist/commands/migrate.d.ts +43 -0
  58. package/dist/commands/migrate.d.ts.map +1 -0
  59. package/dist/commands/migrate.js +120 -0
  60. package/dist/commands/migrate.js.map +1 -0
  61. package/dist/commands/schema-sync.d.ts +17 -0
  62. package/dist/commands/schema-sync.d.ts.map +1 -0
  63. package/dist/commands/schema-sync.js +48 -0
  64. package/dist/commands/schema-sync.js.map +1 -0
  65. package/dist/commands/test.d.ts +19 -0
  66. package/dist/commands/test.d.ts.map +1 -0
  67. package/dist/commands/test.js +113 -0
  68. package/dist/commands/test.js.map +1 -0
  69. package/dist/commands/upgrade.d.ts +24 -0
  70. package/dist/commands/upgrade.d.ts.map +1 -0
  71. package/dist/commands/upgrade.js +177 -0
  72. package/dist/commands/upgrade.js.map +1 -0
  73. package/dist/generators/deployment/docker-compose.d.ts +20 -0
  74. package/dist/generators/deployment/docker-compose.d.ts.map +1 -0
  75. package/dist/generators/deployment/docker-compose.js +95 -0
  76. package/dist/generators/deployment/docker-compose.js.map +1 -0
  77. package/dist/generators/deployment/dockerfile.d.ts +18 -0
  78. package/dist/generators/deployment/dockerfile.d.ts.map +1 -0
  79. package/dist/generators/deployment/dockerfile.js +99 -0
  80. package/dist/generators/deployment/dockerfile.js.map +1 -0
  81. package/dist/generators/deployment/pm2.d.ts +17 -0
  82. package/dist/generators/deployment/pm2.d.ts.map +1 -0
  83. package/dist/generators/deployment/pm2.js +59 -0
  84. package/dist/generators/deployment/pm2.js.map +1 -0
  85. package/dist/generators/deployment/secrets.d.ts +19 -0
  86. package/dist/generators/deployment/secrets.d.ts.map +1 -0
  87. package/dist/generators/deployment/secrets.js +41 -0
  88. package/dist/generators/deployment/secrets.js.map +1 -0
  89. package/dist/generators/deployment/shutdown.d.ts +17 -0
  90. package/dist/generators/deployment/shutdown.d.ts.map +1 -0
  91. package/dist/generators/deployment/shutdown.js +61 -0
  92. package/dist/generators/deployment/shutdown.js.map +1 -0
  93. package/dist/generators/deployment/wrangler.d.ts +25 -0
  94. package/dist/generators/deployment/wrangler.d.ts.map +1 -0
  95. package/dist/generators/deployment/wrangler.js +93 -0
  96. package/dist/generators/deployment/wrangler.js.map +1 -0
  97. package/dist/generators/file-generators.d.ts +38 -0
  98. package/dist/generators/file-generators.d.ts.map +1 -0
  99. package/dist/generators/file-generators.js +546 -0
  100. package/dist/generators/file-generators.js.map +1 -0
  101. package/dist/generators/module-generator.d.ts +33 -0
  102. package/dist/generators/module-generator.d.ts.map +1 -0
  103. package/dist/generators/module-generator.js +61 -0
  104. package/dist/generators/module-generator.js.map +1 -0
  105. package/dist/header-generator.d.ts +34 -0
  106. package/dist/header-generator.d.ts.map +1 -0
  107. package/dist/header-generator.js +77 -0
  108. package/dist/header-generator.js.map +1 -0
  109. package/dist/index.d.ts +12 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +177 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/linter/env-parity.d.ts +28 -0
  114. package/dist/linter/env-parity.d.ts.map +1 -0
  115. package/dist/linter/env-parity.js +59 -0
  116. package/dist/linter/env-parity.js.map +1 -0
  117. package/dist/linter.d.ts +23 -0
  118. package/dist/linter.d.ts.map +1 -0
  119. package/dist/linter.js +207 -0
  120. package/dist/linter.js.map +1 -0
  121. package/package.json +32 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ /**
11
+ * Generates the Bonifade Technologies file header comment string.
12
+ *
13
+ * @param _filePath - reserved for future per-file metadata (unused today)
14
+ */
15
+ export declare function generateHeader(_filePath: string): string;
16
+ /**
17
+ * Returns `true` when the file content already contains the Bonifade header.
18
+ */
19
+ export declare function hasHeader(content: string): boolean;
20
+ /**
21
+ * Injects the Bonifade header at the top of `content` if it is missing.
22
+ * Preserves all existing content unchanged.
23
+ *
24
+ * @param filePath - path used to generate the header (passed to generateHeader)
25
+ * @param content - current file content
26
+ * @returns the (possibly modified) file content
27
+ */
28
+ export declare function injectHeader(filePath: string, content: string): string;
29
+ /**
30
+ * Reads a file from disk, injects the header if missing, and writes it back.
31
+ * Returns `true` when the file was modified, `false` when it already had a header.
32
+ */
33
+ export declare function fixHeaderInFile(filePath: string): boolean;
34
+ //# sourceMappingURL=header-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-generator.d.ts","sourceRoot":"","sources":["../src/header-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAaxD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWtE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMzD"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ import { readFileSync, writeFileSync } from 'node:fs';
11
+ /** The canonical header marker — used to detect existing headers */
12
+ const HEADER_MARKER = '🏢 Company Name: Bonifade Technologies';
13
+ /**
14
+ * Returns today's date as YYYY-MM-DD.
15
+ */
16
+ function today() {
17
+ return new Date().toISOString().slice(0, 10);
18
+ }
19
+ /**
20
+ * Generates the Bonifade Technologies file header comment string.
21
+ *
22
+ * @param _filePath - reserved for future per-file metadata (unused today)
23
+ */
24
+ export function generateHeader(_filePath) {
25
+ const date = today();
26
+ return [
27
+ '/**',
28
+ ' * ──────────────────────────────────────────────────────────────────',
29
+ ' * 🏢 Company Name: Bonifade Technologies',
30
+ ' * 👨‍💻 Developer: Bowofade Oyerinde',
31
+ ' * 🐙 GitHub: oyenet1',
32
+ ` * 📅 Created Date: ${date}`,
33
+ ` * 🔄 Updated Date: ${date}`,
34
+ ' * ──────────────────────────────────────────────────────────────────',
35
+ ' */',
36
+ ].join('\n');
37
+ }
38
+ /**
39
+ * Returns `true` when the file content already contains the Bonifade header.
40
+ */
41
+ export function hasHeader(content) {
42
+ return content.includes(HEADER_MARKER);
43
+ }
44
+ /**
45
+ * Injects the Bonifade header at the top of `content` if it is missing.
46
+ * Preserves all existing content unchanged.
47
+ *
48
+ * @param filePath - path used to generate the header (passed to generateHeader)
49
+ * @param content - current file content
50
+ * @returns the (possibly modified) file content
51
+ */
52
+ export function injectHeader(filePath, content) {
53
+ if (hasHeader(content))
54
+ return content;
55
+ const header = generateHeader(filePath);
56
+ // Preserve a leading shebang line if present
57
+ if (content.startsWith('#!')) {
58
+ const newlineIdx = content.indexOf('\n');
59
+ const shebang = content.slice(0, newlineIdx + 1);
60
+ const rest = content.slice(newlineIdx + 1);
61
+ return `${shebang}${header}\n\n${rest}`;
62
+ }
63
+ return `${header}\n\n${content}`;
64
+ }
65
+ /**
66
+ * Reads a file from disk, injects the header if missing, and writes it back.
67
+ * Returns `true` when the file was modified, `false` when it already had a header.
68
+ */
69
+ export function fixHeaderInFile(filePath) {
70
+ const original = readFileSync(filePath, 'utf8');
71
+ const updated = injectHeader(filePath, original);
72
+ if (updated === original)
73
+ return false;
74
+ writeFileSync(filePath, updated, 'utf8');
75
+ return true;
76
+ }
77
+ //# sourceMappingURL=header-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-generator.js","sourceRoot":"","sources":["../src/header-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAErD,oEAAoE;AACpE,MAAM,aAAa,GAAG,wCAAwC,CAAA;AAE9D;;GAEG;AACH,SAAS,KAAK;IACZ,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,IAAI,GAAG,KAAK,EAAE,CAAA;IACpB,OAAO;QACL,KAAK;QACL,uEAAuE;QACvE,2CAA2C;QAC3C,uCAAuC;QACvC,uBAAuB;QACvB,uBAAuB,IAAI,EAAE;QAC7B,uBAAuB,IAAI,EAAE;QAC7B,uEAAuE;QACvE,KAAK;KACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,OAAe;IAC5D,IAAI,SAAS,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IACtC,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAA;IACvC,6CAA6C;IAC7C,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAA;QAChD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;QAC1C,OAAO,GAAG,OAAO,GAAG,MAAM,OAAO,IAAI,EAAE,CAAA;IACzC,CAAC;IACD,OAAO,GAAG,MAAM,OAAO,OAAO,EAAE,CAAA;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAChD,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACtC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;IACxC,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ──────────────────────────────────────────────────────────────────
4
+ * 🏢 Company Name: Bonifade Technologies
5
+ * 👨‍💻 Developer: Bowofade Oyerinde
6
+ * 🐙 GitHub: oyenet1
7
+ * 📅 Created Date: 2026-04-05
8
+ * 🔄 Updated Date: 2026-04-05
9
+ * ──────────────────────────────────────────────────────────────────
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ──────────────────────────────────────────────────────────────────
4
+ * 🏢 Company Name: Bonifade Technologies
5
+ * 👨‍💻 Developer: Bowofade Oyerinde
6
+ * 🐙 GitHub: oyenet1
7
+ * 📅 Created Date: 2026-04-05
8
+ * 🔄 Updated Date: 2026-04-05
9
+ * ──────────────────────────────────────────────────────────────────
10
+ */
11
+ /**
12
+ * @vonosan/cli — Artisan-style CLI for the Vonosan framework.
13
+ *
14
+ * Command router: maps `vonosan <command>` to the appropriate handler.
15
+ */
16
+ import { runLint } from './commands/lint.js';
17
+ import { runFixHeaders } from './commands/fix-headers.js';
18
+ import { runFixLogs } from './commands/fix-logs.js';
19
+ import { runAudit } from './commands/audit.js';
20
+ import { runEnvAdd } from './commands/env-add.js';
21
+ import { runBranchNew } from './commands/branch-new.js';
22
+ import { runBranchFinish } from './commands/branch-finish.js';
23
+ import { runCommit } from './commands/commit.js';
24
+ import { runMigrateRun, runMigrateRollback, runMigrateStatus, runMigrateReset, runMigrateFresh, runMigrateMake, } from './commands/migrate.js';
25
+ import { runDbPush, runDbStudio, runDbSeed } from './commands/db.js';
26
+ import { runSchemaSync } from './commands/schema-sync.js';
27
+ import { runMakeModule, runMakeVersion, runMakeService, runMakeController, runMakeDto, runMakeRoutes, runMakeSchema, runMakeMiddleware, runMakePage, runMakeComponent, runMakeComposable, runMakeStore, runMakeMigration, runMakeSeed, runMakeTest, runMakeNotification, runMakeResource, runMakePolicy, runMakeJob, runMakeEmail, runMakeHelper, } from './commands/make.js';
28
+ import { runAdd } from './commands/add.js';
29
+ import { runJobsRun } from './commands/jobs.js';
30
+ import { runUpgradeCheck, runUpgradeApply } from './commands/upgrade.js';
31
+ import { runTest, runTestClean } from './commands/test.js';
32
+ const COMMANDS = {
33
+ // Linting & auditing
34
+ lint: runLint,
35
+ 'fix:headers': runFixHeaders,
36
+ 'fix:logs': runFixLogs,
37
+ audit: runAudit,
38
+ // Environment
39
+ 'env:add': runEnvAdd,
40
+ // Git automation
41
+ 'branch:new': runBranchNew,
42
+ 'branch:finish': runBranchFinish,
43
+ commit: runCommit,
44
+ // Migrations
45
+ 'migrate:run': runMigrateRun,
46
+ 'migrate:rollback': runMigrateRollback,
47
+ 'migrate:status': runMigrateStatus,
48
+ 'migrate:reset': runMigrateReset,
49
+ 'migrate:fresh': runMigrateFresh,
50
+ 'migrate:make': runMigrateMake,
51
+ // Database
52
+ 'db:push': runDbPush,
53
+ 'db:studio': runDbStudio,
54
+ 'db:seed': runDbSeed,
55
+ // Schema
56
+ 'schema:sync': runSchemaSync,
57
+ // Code generators
58
+ 'make:module': runMakeModule,
59
+ 'make:version': runMakeVersion,
60
+ 'make:service': runMakeService,
61
+ 'make:controller': runMakeController,
62
+ 'make:dto': runMakeDto,
63
+ 'make:routes': runMakeRoutes,
64
+ 'make:schema': runMakeSchema,
65
+ 'make:middleware': runMakeMiddleware,
66
+ 'make:page': runMakePage,
67
+ 'make:component': runMakeComponent,
68
+ 'make:composable': runMakeComposable,
69
+ 'make:store': runMakeStore,
70
+ 'make:migration': runMakeMigration,
71
+ 'make:seed': runMakeSeed,
72
+ 'make:test': runMakeTest,
73
+ 'make:notification': runMakeNotification,
74
+ 'make:resource': runMakeResource,
75
+ 'make:policy': runMakePolicy,
76
+ 'make:job': runMakeJob,
77
+ 'make:email': runMakeEmail,
78
+ 'make:helper': runMakeHelper,
79
+ // Module installer
80
+ add: runAdd,
81
+ // Jobs
82
+ 'jobs:run': runJobsRun,
83
+ // Upgrade
84
+ 'upgrade:check': runUpgradeCheck,
85
+ 'upgrade:apply-codemods': runUpgradeApply,
86
+ // Test
87
+ test: runTest,
88
+ 'test:clean': runTestClean,
89
+ };
90
+ // ─── Help text ────────────────────────────────────────────────────────────────
91
+ function printHelp() {
92
+ process.stdout.write(`
93
+ \x1b[1mVono CLI\x1b[0m — Artisan-style tooling for the Vonosan framework
94
+
95
+ \x1b[33mUsage:\x1b[0m vonosan <command> [args] [options]
96
+
97
+ \x1b[33mLinting & Auditing:\x1b[0m
98
+ vonosan lint [src] Scan for header, log, naming, versioning, DRY violations
99
+ vonosan fix:headers [src] Inject missing Bonifade headers
100
+ vonosan fix:logs [src] Replace console.* with Logger.*
101
+ vonosan audit [--fix] Full audit; exits 0 (clean) or 1 (violations)
102
+
103
+ \x1b[33mEnvironment:\x1b[0m
104
+ vonosan env:add <KEY> [desc] Append key to .env and .env.example
105
+
106
+ \x1b[33mGit Automation:\x1b[0m
107
+ vonosan branch:new <name> Create feature/<name> branch
108
+ vonosan branch:finish Merge feature branch into parent
109
+ vonosan commit "<message>" Validate Conventional Commits and commit
110
+
111
+ \x1b[33mMigrations:\x1b[0m
112
+ vonosan migrate:run Apply pending migrations
113
+ vonosan migrate:rollback Roll back last migration
114
+ vonosan migrate:status Show migration status
115
+ vonosan migrate:reset Rollback all, then re-run
116
+ vonosan migrate:fresh [--seed] Drop all, re-run, optionally seed
117
+ vonosan migrate:make <name> Sync schema barrel + generate migration
118
+
119
+ \x1b[33mDatabase:\x1b[0m
120
+ vonosan db:push Push schema to DB (no migration file)
121
+ vonosan db:studio Open Drizzle Studio
122
+ vonosan db:seed [name] Run seed files
123
+
124
+ \x1b[33mSchema:\x1b[0m
125
+ vonosan schema:sync Regenerate src/db/schema.ts barrel
126
+
127
+ \x1b[33mCode Generators:\x1b[0m
128
+ vonosan make:module <name> Generate full module scaffold
129
+ vonosan make:version <v> Generate new API version namespace
130
+ vonosan make:service <name> Generate service file
131
+ vonosan make:controller <name> Generate controller file
132
+ vonosan make:dto <name> Generate DTO + Zod schema
133
+ vonosan make:routes <name> Generate routes file
134
+ vonosan make:schema <name> Generate Drizzle schema
135
+ vonosan make:middleware <name> Generate middleware
136
+ vonosan make:page <m/Page> Generate .page.vue
137
+ vonosan make:component <m/C> Generate Vue component
138
+ vonosan make:composable <m/use> Generate composable
139
+ vonosan make:store <name> Generate Pinia store
140
+ vonosan make:migration <name> Generate SQL migration file
141
+ vonosan make:seed <name> Generate seed file
142
+ vonosan make:test <name> Generate test file
143
+ vonosan make:notification <n> Generate notification
144
+ vonosan make:resource <name> Generate resource transformer
145
+ vonosan make:policy <name> Generate policy class
146
+ vonosan make:job <name> Generate cron job
147
+ vonosan make:email <name> Generate email template
148
+ vonosan make:helper <name> Generate shared helper
149
+
150
+ \x1b[33mModule Installer:\x1b[0m
151
+ vonosan add <module> Install @vonosan/<module> and update config
152
+ vonosan add <module> --eject Copy module source into src/modules/<module>/
153
+
154
+ \x1b[33mJobs:\x1b[0m
155
+ vonosan jobs:run <name> Execute a named cron job immediately
156
+ `);
157
+ }
158
+ // ─── Entry point ─────────────────────────────────────────────────────────────
159
+ async function main() {
160
+ const [, , command, ...args] = process.argv;
161
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
162
+ printHelp();
163
+ return;
164
+ }
165
+ const handler = COMMANDS[command];
166
+ if (!handler) {
167
+ process.stderr.write(`\x1b[31mUnknown command: "${command}"\x1b[0m\n`);
168
+ process.stderr.write(`Run \x1b[1mvono --help\x1b[0m to see available commands.\n`);
169
+ process.exit(1);
170
+ }
171
+ await handler(args);
172
+ }
173
+ main().catch((err) => {
174
+ process.stderr.write(`\x1b[31mFatal error: ${String(err)}\x1b[0m\n`);
175
+ process.exit(1);
176
+ });
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ export interface EnvParityResult {
11
+ /** Keys present in .env but missing from .env.example */
12
+ missingInExample: string[];
13
+ /** Keys present in .env.example but missing from .env */
14
+ missingInEnv: string[];
15
+ }
16
+ /**
17
+ * Parses an env file and returns the set of defined keys.
18
+ * Ignores blank lines and comment lines (starting with #).
19
+ */
20
+ export declare function parseEnvKeys(content: string): Set<string>;
21
+ /**
22
+ * Compares `.env` and `.env.example` keys in `projectRoot`.
23
+ *
24
+ * Returns lists of keys missing from each file.
25
+ * If `.env` does not exist, returns empty lists (no error — per Requirement 8.4).
26
+ */
27
+ export declare function checkEnvParity(projectRoot: string): EnvParityResult;
28
+ //# sourceMappingURL=env-parity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-parity.d.ts","sourceRoot":"","sources":["../../src/linter/env-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,yDAAyD;IACzD,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAWzD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAwBnE"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ /**
13
+ * Parses an env file and returns the set of defined keys.
14
+ * Ignores blank lines and comment lines (starting with #).
15
+ */
16
+ export function parseEnvKeys(content) {
17
+ const keys = new Set();
18
+ for (const line of content.split('\n')) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed || trimmed.startsWith('#'))
21
+ continue;
22
+ const eqIdx = trimmed.indexOf('=');
23
+ if (eqIdx === -1)
24
+ continue;
25
+ const key = trimmed.slice(0, eqIdx).trim();
26
+ if (key)
27
+ keys.add(key);
28
+ }
29
+ return keys;
30
+ }
31
+ /**
32
+ * Compares `.env` and `.env.example` keys in `projectRoot`.
33
+ *
34
+ * Returns lists of keys missing from each file.
35
+ * If `.env` does not exist, returns empty lists (no error — per Requirement 8.4).
36
+ */
37
+ export function checkEnvParity(projectRoot) {
38
+ const envPath = join(projectRoot, '.env');
39
+ const examplePath = join(projectRoot, '.env.example');
40
+ if (!existsSync(envPath)) {
41
+ return { missingInExample: [], missingInEnv: [] };
42
+ }
43
+ const envKeys = parseEnvKeys(readFileSync(envPath, 'utf8'));
44
+ const exampleKeys = existsSync(examplePath)
45
+ ? parseEnvKeys(readFileSync(examplePath, 'utf8'))
46
+ : new Set();
47
+ const missingInExample = [];
48
+ const missingInEnv = [];
49
+ for (const key of envKeys) {
50
+ if (!exampleKeys.has(key))
51
+ missingInExample.push(key);
52
+ }
53
+ for (const key of exampleKeys) {
54
+ if (!envKeys.has(key))
55
+ missingInEnv.push(key);
56
+ }
57
+ return { missingInExample, missingInEnv };
58
+ }
59
+ //# sourceMappingURL=env-parity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-parity.js","sourceRoot":"","sources":["../../src/linter/env-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAShC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,SAAQ;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAC1C,IAAI,GAAG;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IACzC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;IAErD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;IACnD,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;IAC3D,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC;QACzC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACjD,CAAC,CAAC,IAAI,GAAG,EAAU,CAAA;IAErB,MAAM,gBAAgB,GAAa,EAAE,CAAA;IACrC,MAAM,YAAY,GAAa,EAAE,CAAA;IAEjC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACvD,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/C,CAAC;IAED,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ export type LintRule = 'header-missing' | 'console-log' | 'naming-snake-case-response' | 'versioning-missing-prefix' | 'dry-violation';
11
+ export interface LintResult {
12
+ file: string;
13
+ line: number;
14
+ rule: LintRule;
15
+ message: string;
16
+ }
17
+ /**
18
+ * Scans all `.ts` and `.vue` files under `srcDir` and returns all lint violations.
19
+ *
20
+ * @param srcDir - absolute or relative path to the project's `src/` directory
21
+ */
22
+ export declare function lintProject(srcDir: string): Promise<LintResult[]>;
23
+ //# sourceMappingURL=linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,MAAM,MAAM,QAAQ,GAChB,gBAAgB,GAChB,aAAa,GACb,4BAA4B,GAC5B,2BAA2B,GAC3B,eAAe,CAAA;AAEnB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAsLD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAwBvE"}
package/dist/linter.js ADDED
@@ -0,0 +1,207 @@
1
+ /**
2
+ * ──────────────────────────────────────────────────────────────────
3
+ * 🏢 Company Name: Bonifade Technologies
4
+ * 👨‍💻 Developer: Bowofade Oyerinde
5
+ * 🐙 GitHub: oyenet1
6
+ * 📅 Created Date: 2026-04-05
7
+ * 🔄 Updated Date: 2026-04-05
8
+ * ──────────────────────────────────────────────────────────────────
9
+ */
10
+ import { readFileSync } from 'node:fs';
11
+ import { join, relative } from 'node:path';
12
+ import fg from 'fast-glob';
13
+ import { hasHeader } from './header-generator.js';
14
+ // ─── Individual rule checkers ─────────────────────────────────────────────────
15
+ /**
16
+ * Rule: every .ts / .vue file must start with the Bonifade header.
17
+ */
18
+ function checkHeader(filePath, content) {
19
+ if (hasHeader(content))
20
+ return [];
21
+ return [
22
+ {
23
+ file: filePath,
24
+ line: 1,
25
+ rule: 'header-missing',
26
+ message: `Missing Bonifade Technologies file header. Run \`vonosan fix:headers\` to auto-fix.`,
27
+ },
28
+ ];
29
+ }
30
+ /**
31
+ * Rule: no raw console.log / console.warn / console.error / console.debug.
32
+ * Files containing `// @vonosan-ignore-logs` are skipped entirely.
33
+ */
34
+ function checkConsoleLogs(filePath, content) {
35
+ if (content.includes('// @vonosan-ignore-logs'))
36
+ return [];
37
+ const results = [];
38
+ const lines = content.split('\n');
39
+ const consoleRe = /\bconsole\.(log|warn|error|debug|info)\s*\(/;
40
+ for (let i = 0; i < lines.length; i++) {
41
+ const line = lines[i];
42
+ // Skip the logger.ts file itself — it is the ONE allowed place
43
+ if (filePath.includes('logger.ts'))
44
+ continue;
45
+ if (consoleRe.test(line)) {
46
+ const match = line.match(consoleRe);
47
+ results.push({
48
+ file: filePath,
49
+ line: i + 1,
50
+ rule: 'console-log',
51
+ message: `Raw \`console.${match[1]}()\` detected. Use \`Logger.${match[1]}()\` from vonosan/server instead.`,
52
+ });
53
+ }
54
+ }
55
+ return results;
56
+ }
57
+ /**
58
+ * Rule: API response objects must not contain snake_case keys.
59
+ * Heuristic: looks for `{ some_key:` patterns inside return statements in
60
+ * controller / service files.
61
+ */
62
+ function checkNamingConventions(filePath, content) {
63
+ // Only check controller / service files
64
+ if (!filePath.match(/\.(controller|service)\.ts$/))
65
+ return [];
66
+ const results = [];
67
+ const lines = content.split('\n');
68
+ // Matches object literal keys that are snake_case (contain underscore)
69
+ const snakeCaseKeyRe = /\b([a-z][a-z0-9]*(?:_[a-z0-9]+)+)\s*:/;
70
+ for (let i = 0; i < lines.length; i++) {
71
+ const line = lines[i];
72
+ const match = line.match(snakeCaseKeyRe);
73
+ if (match) {
74
+ results.push({
75
+ file: filePath,
76
+ line: i + 1,
77
+ rule: 'naming-snake-case-response',
78
+ message: `API response key \`${match[1]}\` uses snake_case. Convert to camelCase using \`toCamel()\`.`,
79
+ });
80
+ }
81
+ }
82
+ return results;
83
+ }
84
+ /**
85
+ * Rule: all routes must be nested under a versioned prefix like /api/v1/.
86
+ * Only checks *.routes.ts files.
87
+ */
88
+ function checkVersioning(filePath, content) {
89
+ if (!filePath.endsWith('.routes.ts'))
90
+ return [];
91
+ const results = [];
92
+ const lines = content.split('\n');
93
+ // Matches route definitions that start with a path not under /api/v<n>
94
+ // e.g. app.get('/users', ...) — missing /api/v1 prefix
95
+ const routeDefRe = /\.(get|post|put|patch|delete|all)\s*\(\s*['"`](\/[^'"`]*)/;
96
+ for (let i = 0; i < lines.length; i++) {
97
+ const line = lines[i];
98
+ const match = line.match(routeDefRe);
99
+ if (match) {
100
+ const path = match[2];
101
+ if (!path.match(/^\/api\/v\d+/)) {
102
+ results.push({
103
+ file: filePath,
104
+ line: i + 1,
105
+ rule: 'versioning-missing-prefix',
106
+ message: `Route \`${path}\` is not nested under a versioned prefix (e.g. /api/v1/). All routes must be versioned.`,
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return results;
112
+ }
113
+ // ─── DRY violation detector ───────────────────────────────────────────────────
114
+ /**
115
+ * Extracts function bodies from a TypeScript file.
116
+ * Returns a map of normalised body → list of { file, line }.
117
+ */
118
+ function extractFunctionBodies(filePath, content) {
119
+ const bodies = new Map();
120
+ const lines = content.split('\n');
121
+ // Simple heuristic: find `function` or arrow function declarations and
122
+ // capture the block body between matching braces.
123
+ for (let i = 0; i < lines.length; i++) {
124
+ const line = lines[i];
125
+ if (/\bfunction\b|\=\s*\(.*\)\s*=>/.test(line) && line.includes('{')) {
126
+ // Collect body until matching closing brace
127
+ let depth = 0;
128
+ let body = '';
129
+ for (let j = i; j < lines.length; j++) {
130
+ for (const ch of lines[j]) {
131
+ if (ch === '{')
132
+ depth++;
133
+ else if (ch === '}')
134
+ depth--;
135
+ }
136
+ body += lines[j] + '\n';
137
+ if (depth === 0 && j > i)
138
+ break;
139
+ }
140
+ // Normalise whitespace for comparison
141
+ const normalised = body.replace(/\s+/g, ' ').trim();
142
+ if (normalised.length > 40) {
143
+ // Only flag non-trivial bodies
144
+ bodies.set(normalised, i + 1);
145
+ }
146
+ }
147
+ }
148
+ return bodies;
149
+ }
150
+ /**
151
+ * Rule: identical function bodies in 2+ module files = DRY violation.
152
+ */
153
+ function checkDryViolations(files) {
154
+ // Map: normalised body → list of { file, line }
155
+ const bodyMap = new Map();
156
+ for (const { path: filePath, content } of files) {
157
+ // Only check module files
158
+ if (!filePath.includes('/modules/'))
159
+ continue;
160
+ const bodies = extractFunctionBodies(filePath, content);
161
+ for (const [body, line] of bodies) {
162
+ const existing = bodyMap.get(body) ?? [];
163
+ existing.push({ file: filePath, line });
164
+ bodyMap.set(body, existing);
165
+ }
166
+ }
167
+ const results = [];
168
+ for (const [, occurrences] of bodyMap) {
169
+ if (occurrences.length >= 2) {
170
+ for (const { file, line } of occurrences) {
171
+ results.push({
172
+ file,
173
+ line,
174
+ rule: 'dry-violation',
175
+ message: `Identical function body found in ${occurrences.length} files. Extract to a shared utility in src/shared/utils/.`,
176
+ });
177
+ }
178
+ }
179
+ }
180
+ return results;
181
+ }
182
+ // ─── Main linter entry point ──────────────────────────────────────────────────
183
+ /**
184
+ * Scans all `.ts` and `.vue` files under `srcDir` and returns all lint violations.
185
+ *
186
+ * @param srcDir - absolute or relative path to the project's `src/` directory
187
+ */
188
+ export async function lintProject(srcDir) {
189
+ const pattern = join(srcDir, '**/*.{ts,vue}').replace(/\\/g, '/');
190
+ const filePaths = await fg(pattern, { absolute: true, ignore: ['**/node_modules/**', '**/dist/**'] });
191
+ const fileContents = [];
192
+ for (const filePath of filePaths) {
193
+ const content = readFileSync(filePath, 'utf8');
194
+ fileContents.push({ path: relative(process.cwd(), filePath), content });
195
+ }
196
+ const results = [];
197
+ for (const { path: filePath, content } of fileContents) {
198
+ results.push(...checkHeader(filePath, content));
199
+ results.push(...checkConsoleLogs(filePath, content));
200
+ results.push(...checkNamingConventions(filePath, content));
201
+ results.push(...checkVersioning(filePath, content));
202
+ }
203
+ // DRY check needs all files at once
204
+ results.push(...checkDryViolations(fileContents));
205
+ return results;
206
+ }
207
+ //# sourceMappingURL=linter.js.map