odac 1.1.0 → 1.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 (117) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +42 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/release.yml +68 -1
  6. package/.github/workflows/test-coverage.yml +6 -5
  7. package/.husky/pre-commit +10 -0
  8. package/.husky/pre-push +13 -0
  9. package/.releaserc.js +1 -1
  10. package/CHANGELOG.md +99 -0
  11. package/README.md +16 -0
  12. package/bin/odac.js +196 -38
  13. package/client/odac.js +238 -171
  14. package/docs/backend/01-overview/03-development-server.md +38 -45
  15. package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
  16. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  17. package/docs/backend/03-config/01-database-connection.md +2 -2
  18. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  19. package/docs/backend/03-config/03-request-timeout.md +1 -1
  20. package/docs/backend/03-config/04-environment-variables.md +4 -4
  21. package/docs/backend/03-config/05-early-hints.md +2 -2
  22. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  23. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  24. package/docs/backend/04-routing/09-websocket.md +14 -1
  25. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  26. package/docs/backend/05-controllers/03-controller-classes.md +40 -20
  27. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  28. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  29. package/docs/backend/08-database/01-getting-started.md +2 -2
  30. package/docs/backend/10-authentication/03-register.md +1 -1
  31. package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
  32. package/docs/backend/10-authentication/05-session-management.md +15 -1
  33. package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
  34. package/docs/backend/10-authentication/07-magic-links.md +1 -1
  35. package/docs/index.json +5 -1
  36. package/jest.config.js +1 -1
  37. package/package.json +18 -14
  38. package/src/Auth.js +58 -23
  39. package/src/Config.js +7 -7
  40. package/src/Database.js +1 -1
  41. package/src/Env.js +3 -1
  42. package/src/Ipc.js +7 -0
  43. package/src/Lang.js +9 -2
  44. package/src/Mail.js +19 -9
  45. package/src/Odac.js +56 -44
  46. package/src/Request.js +6 -2
  47. package/src/Route/Cron.js +58 -17
  48. package/src/Route/Internal.js +1 -1
  49. package/src/Route.js +375 -125
  50. package/src/Server.js +40 -3
  51. package/src/Storage.js +4 -0
  52. package/src/Token.js +6 -4
  53. package/src/Validator.js +24 -15
  54. package/src/Var.js +22 -6
  55. package/src/View/EarlyHints.js +43 -33
  56. package/src/View/Form.js +17 -11
  57. package/src/View.js +68 -12
  58. package/src/WebSocket.js +45 -12
  59. package/template/package.json +3 -1
  60. package/template/view/content/home.html +3 -3
  61. package/template/view/head/main.html +2 -2
  62. package/test/Client.test.js +168 -0
  63. package/test/Config.test.js +112 -0
  64. package/test/Lang.test.js +92 -0
  65. package/test/Odac.test.js +88 -0
  66. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  67. package/test/{framework/Route.test.js → Route.test.js} +105 -1
  68. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  69. package/test/WebSocket.test.js +238 -0
  70. package/test/scripts/check-coverage.js +4 -4
  71. package/test/cli/Cli.test.js +0 -36
  72. package/test/core/Commands.test.js +0 -538
  73. package/test/core/Config.test.js +0 -1432
  74. package/test/core/Lang.test.js +0 -250
  75. package/test/core/Odac.test.js +0 -234
  76. package/test/core/Process.test.js +0 -156
  77. package/test/framework/WebSocket.test.js +0 -100
  78. package/test/server/Api.test.js +0 -647
  79. package/test/server/DNS.test.js +0 -2050
  80. package/test/server/DNS.test.js.bak +0 -2084
  81. package/test/server/Hub.test.js +0 -497
  82. package/test/server/Log.test.js +0 -73
  83. package/test/server/Mail.account.test_.js +0 -460
  84. package/test/server/Mail.init.test_.js +0 -411
  85. package/test/server/Mail.test_.js +0 -1340
  86. package/test/server/SSL.test_.js +0 -1491
  87. package/test/server/Server.test.js +0 -765
  88. package/test/server/Service.test_.js +0 -1127
  89. package/test/server/Subdomain.test.js +0 -440
  90. package/test/server/Web/Firewall.test.js +0 -175
  91. package/test/server/Web/Proxy.test.js +0 -397
  92. package/test/server/Web.test.js +0 -1494
  93. package/test/server/__mocks__/acme-client.js +0 -17
  94. package/test/server/__mocks__/bcrypt.js +0 -50
  95. package/test/server/__mocks__/child_process.js +0 -389
  96. package/test/server/__mocks__/crypto.js +0 -432
  97. package/test/server/__mocks__/fs.js +0 -450
  98. package/test/server/__mocks__/globalOdac.js +0 -227
  99. package/test/server/__mocks__/http.js +0 -575
  100. package/test/server/__mocks__/https.js +0 -272
  101. package/test/server/__mocks__/index.js +0 -249
  102. package/test/server/__mocks__/mail/server.js +0 -100
  103. package/test/server/__mocks__/mail/smtp.js +0 -31
  104. package/test/server/__mocks__/mailparser.js +0 -81
  105. package/test/server/__mocks__/net.js +0 -369
  106. package/test/server/__mocks__/node-forge.js +0 -328
  107. package/test/server/__mocks__/os.js +0 -320
  108. package/test/server/__mocks__/path.js +0 -291
  109. package/test/server/__mocks__/selfsigned.js +0 -8
  110. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  111. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  112. package/test/server/__mocks__/smtp-server.js +0 -106
  113. package/test/server/__mocks__/sqlite3.js +0 -394
  114. package/test/server/__mocks__/testFactories.js +0 -299
  115. package/test/server/__mocks__/testHelpers.js +0 -363
  116. package/test/server/__mocks__/tls.js +0 -229
  117. /package/template/{config.json → odac.json} +0 -0
package/bin/odac.js CHANGED
@@ -3,69 +3,227 @@
3
3
  const fs = require('node:fs')
4
4
  const path = require('node:path')
5
5
  const readline = require('node:readline')
6
- const { execSync } = require('node:child_process')
6
+ const {execSync, spawn} = require('node:child_process')
7
+ const cluster = require('node:cluster')
7
8
 
8
9
  const command = process.argv[2]
9
10
  const args = process.argv.slice(3)
10
11
 
11
12
  const rl = readline.createInterface({
12
- input: process.stdin,
13
- output: process.stdout
13
+ input: process.stdin,
14
+ output: process.stdout
14
15
  })
15
16
 
16
17
  const ask = question => new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
17
18
 
19
+ /**
20
+ * Resolves Tailwind CSS paths and ensures required directories/files exist.
21
+ * Supports multiple CSS entry points from 'view/css'.
22
+ * @returns {Array<{ input: string, cssOutput: string, isCustom: boolean, name: string }>}
23
+ */
24
+ function getTailwindConfigs() {
25
+ const cssDir = path.join(process.cwd(), 'view/css')
26
+ const cacheDir = path.join(process.cwd(), 'storage/.cache')
27
+ const defaultCssInput = path.join(cacheDir, 'tailwind.css')
28
+ const defaultCssOutput = path.join(process.cwd(), 'public/assets/css/app.css')
29
+
30
+ const configs = []
31
+
32
+ // Scan for custom CSS files
33
+ if (fs.existsSync(cssDir) && fs.lstatSync(cssDir).isDirectory()) {
34
+ const files = fs.readdirSync(cssDir).filter(file => file.endsWith('.css'))
35
+
36
+ files.forEach(file => {
37
+ const input = path.join(cssDir, file)
38
+ const cssOutput = path.join(process.cwd(), 'public/assets/css', file)
39
+
40
+ // Ensure output directory exists
41
+ const cssOutputDir = path.dirname(cssOutput)
42
+ fs.mkdirSync(cssOutputDir, {recursive: true})
43
+
44
+ configs.push({
45
+ input,
46
+ cssOutput,
47
+ isCustom: true,
48
+ name: file
49
+ })
50
+ })
51
+ }
52
+
53
+ // Fallback to default if no custom files found
54
+ if (configs.length === 0) {
55
+ fs.mkdirSync(cacheDir, {recursive: true})
56
+ try {
57
+ fs.writeFileSync(defaultCssInput, '@import "tailwindcss";', {flag: 'wx'})
58
+ } catch (e) {
59
+ if (e.code !== 'EEXIST') throw e
60
+ }
61
+
62
+ const cssOutputDir = path.dirname(defaultCssOutput)
63
+ fs.mkdirSync(cssOutputDir, {recursive: true})
64
+
65
+ configs.push({
66
+ input: defaultCssInput,
67
+ cssOutput: defaultCssOutput,
68
+ isCustom: false,
69
+ name: 'Default'
70
+ })
71
+ }
72
+
73
+ return configs
74
+ }
75
+
18
76
  async function run() {
19
- if (command === 'init') {
20
- const projectName = args[0] || '.'
21
- const targetDir = path.resolve(process.cwd(), projectName)
77
+ if (command === 'init') {
78
+ const projectName = args[0] || '.'
79
+ const targetDir = path.resolve(process.cwd(), projectName)
22
80
 
23
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
81
+ fs.mkdirSync(targetDir, {recursive: true})
24
82
 
25
- console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
26
- const templateDir = path.resolve(__dirname, '../template')
83
+ console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
84
+ const templateDir = path.resolve(__dirname, '../template')
27
85
 
28
- try {
29
- fs.cpSync(templateDir, targetDir, { recursive: true })
86
+ try {
87
+ fs.cpSync(templateDir, targetDir, {recursive: true})
30
88
 
31
- const pkgPath = path.join(targetDir, 'package.json')
32
- const frameworkPkg = require('../package.json')
89
+ const pkgPath = path.join(targetDir, 'package.json')
90
+ const frameworkPkg = require('../package.json')
33
91
 
34
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
35
- pkg.name = projectName === '.' ? path.basename(targetDir) : projectName
36
- pkg.version = '0.0.1'
92
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
93
+ pkg.name = projectName === '.' ? path.basename(targetDir) : projectName
94
+ pkg.version = '0.0.1'
37
95
 
38
- if (!pkg.dependencies) pkg.dependencies = {}
39
- pkg.dependencies[frameworkPkg.name] = `^${frameworkPkg.version}`
96
+ if (!pkg.dependencies) pkg.dependencies = {}
97
+ pkg.dependencies[frameworkPkg.name] = `^${frameworkPkg.version}`
40
98
 
41
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
99
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
42
100
 
43
- console.log('\n📦 Installing dependencies...')
44
- try {
45
- execSync('npm install', {
46
- stdio: 'inherit',
47
- cwd: targetDir
48
- })
49
- } catch (err) {
50
- console.warn('⚠️ npm install failed. You might need to run it manually.')
51
- process.exit(1)
101
+ console.log('\n📦 Installing dependencies...')
102
+ try {
103
+ execSync('npm install', {
104
+ stdio: 'inherit',
105
+ cwd: targetDir
106
+ })
107
+ } catch (err) {
108
+ console.warn('⚠️ npm install failed. You might need to run it manually.')
109
+ process.exit(1)
110
+ }
111
+
112
+ console.log('\n✨ Project initialized successfully!')
113
+ } catch (error) {
114
+ console.error('❌ Error initializing project:', error.message)
115
+ }
116
+ } else if (command === 'dev') {
117
+ if (cluster.isPrimary) {
118
+ const configs = getTailwindConfigs()
119
+ const tails = []
120
+ const names = configs.map(c => c.name).join(', ')
121
+
122
+ console.log(`🎨 \x1b[36mODAC Styles:\x1b[0m Watching for changes (${names})`)
123
+
124
+ configs.forEach(({input, cssOutput, name, isCustom}) => {
125
+ let tailwindProcess = null
126
+
127
+ const startWatcher = () => {
128
+ const localCli = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss')
129
+ const useLocal = fs.existsSync(localCli)
130
+ const cmd = useLocal ? localCli : 'npx'
131
+ const args = useLocal
132
+ ? ['-i', input, '-o', cssOutput, '--watch=always']
133
+ : ['@tailwindcss/cli', '-i', input, '-o', cssOutput, '--watch=always']
134
+
135
+ tailwindProcess = spawn(cmd, args, {
136
+ stdio: ['pipe', 'ignore', 'pipe'],
137
+ shell: !useLocal, // Valid for npm/npx compatibility if local not found
138
+ cwd: process.cwd()
139
+ })
140
+
141
+ // Filter stderr: suppress status noise, forward only real errors
142
+ tailwindProcess.stderr.on('data', chunk => {
143
+ const raw = chunk.toString()
144
+ const lines = raw.split('\n')
145
+ for (const line of lines) {
146
+ // Strip ANSI escape codes to ensure reliable filtering
147
+ const clean = line.replace(/\x1B\[[0-9;]*[JKmsu]/g, '').trim()
148
+
149
+ if (!clean || clean.startsWith('Done in') || clean.startsWith('≈')) continue
150
+
151
+ process.stderr.write(`\x1b[31m[ODAC Style Error]\x1b[0m ${line}\n`)
52
152
  }
153
+ })
53
154
 
54
- console.log('\n✨ Project initialized successfully!')
155
+ tailwindProcess.on('error', err => {
156
+ console.error(`❌ \x1b[31m[ODAC Style Error]\x1b[0m Failed to start watcher for ${name}:`, err.message)
157
+ })
55
158
 
56
- } catch (error) {
57
- console.error('❌ Error initializing project:', error.message)
159
+ tailwindProcess.on('exit', code => {
160
+ if (code !== 0 && code !== null) {
161
+ console.warn(`⚠️ Tailwind watcher for ${name} exited unexpectedly (code ${code}). Restarting in 1s...`)
162
+ setTimeout(startWatcher, 1000)
163
+ }
164
+ })
58
165
  }
59
166
 
60
- } else if (command === 'dev') {
61
- require('../index.js')
62
- } else {
63
- console.log('Usage:')
64
- console.log(' npx odac init (Interactive mode)')
65
- console.log(' npx odac init <project> (Quick mode)')
167
+ startWatcher()
168
+
169
+ // Push a wrapper compatible with the cleanup function
170
+ tails.push({
171
+ kill: () => {
172
+ if (tailwindProcess) tailwindProcess.kill()
173
+ }
174
+ })
175
+ })
176
+
177
+ const cleanup = () => {
178
+ tails.forEach(t => {
179
+ try {
180
+ t.kill()
181
+ } catch (e) {}
182
+ })
183
+ }
184
+ process.on('SIGINT', cleanup)
185
+ process.on('SIGTERM', cleanup)
186
+ process.on('exit', cleanup)
66
187
  }
67
188
 
68
- rl.close()
189
+ require('../index.js')
190
+ } else if (command === 'build') {
191
+ console.log('🏗️ Building for production...')
192
+
193
+ const configs = getTailwindConfigs()
194
+ let hasError = false
195
+
196
+ configs.forEach(({input, cssOutput, name, isCustom}) => {
197
+ console.log(`🎨 Compiling ${name} (${isCustom ? 'Custom' : 'Default'}) CSS...`)
198
+ try {
199
+ execSync(`npx @tailwindcss/cli -i "${input}" -o "${cssOutput}" --minify`, {
200
+ stdio: 'inherit',
201
+ cwd: process.cwd()
202
+ })
203
+ } catch (error) {
204
+ console.error(`❌ Build failed for ${name}:`, error.message)
205
+ hasError = true
206
+ }
207
+ })
208
+
209
+ if (hasError) {
210
+ process.exit(1)
211
+ } else {
212
+ console.log('✅ All builds completed successfully!')
213
+ }
214
+ } else if (command === 'start') {
215
+ process.env.NODE_ENV = 'production'
216
+ require('../index.js')
217
+ } else {
218
+ console.log('Usage:')
219
+ console.log(' npx odac init (Interactive mode)')
220
+ console.log(' npx odac init <project> (Quick mode)')
221
+ console.log(' npx odac dev (Development mode)')
222
+ console.log(' npx odac build (Production build)')
223
+ console.log(' npx odac start (Start server)')
224
+ }
225
+
226
+ rl.close()
69
227
  }
70
228
 
71
229
  run()