odac 1.0.1 → 1.2.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 (143) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +33 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/auto-pr-description.yml +3 -1
  6. package/.github/workflows/release.yml +42 -1
  7. package/.github/workflows/test-coverage.yml +6 -5
  8. package/.github/workflows/test-publish.yml +36 -0
  9. package/.husky/pre-commit +10 -0
  10. package/.husky/pre-push +13 -0
  11. package/.releaserc.js +3 -3
  12. package/CHANGELOG.md +184 -0
  13. package/README.md +53 -34
  14. package/bin/odac.js +181 -49
  15. package/client/odac.js +878 -995
  16. package/docs/backend/01-overview/03-development-server.md +39 -46
  17. package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
  18. package/docs/backend/03-config/00-configuration-overview.md +15 -6
  19. package/docs/backend/03-config/01-database-connection.md +3 -3
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  21. package/docs/backend/03-config/03-request-timeout.md +1 -1
  22. package/docs/backend/03-config/04-environment-variables.md +4 -4
  23. package/docs/backend/03-config/05-early-hints.md +2 -2
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  26. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  27. package/docs/backend/04-routing/09-websocket.md +29 -0
  28. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  29. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
  30. package/docs/backend/05-controllers/03-controller-classes.md +61 -55
  31. package/docs/backend/05-forms/01-custom-forms.md +103 -95
  32. package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
  33. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  34. package/docs/backend/07-views/02-rendering-a-view.md +1 -1
  35. package/docs/backend/07-views/03-variables.md +5 -5
  36. package/docs/backend/07-views/04-request-data.md +1 -1
  37. package/docs/backend/07-views/08-backend-javascript.md +1 -1
  38. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  39. package/docs/backend/08-database/01-getting-started.md +100 -0
  40. package/docs/backend/08-database/02-basics.md +136 -0
  41. package/docs/backend/08-database/03-advanced.md +84 -0
  42. package/docs/backend/08-database/04-migrations.md +48 -0
  43. package/docs/backend/09-validation/01-the-validator-service.md +1 -0
  44. package/docs/backend/10-authentication/03-register.md +9 -2
  45. package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
  46. package/docs/backend/10-authentication/05-session-management.md +16 -2
  47. package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
  48. package/docs/backend/10-authentication/07-magic-links.md +134 -0
  49. package/docs/backend/11-mail/01-the-mail-service.md +118 -28
  50. package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
  51. package/docs/backend/13-utilities/01-odac-var.md +7 -7
  52. package/docs/backend/13-utilities/02-ipc.md +73 -0
  53. package/docs/frontend/01-overview/01-introduction.md +5 -1
  54. package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
  55. package/docs/index.json +21 -125
  56. package/eslint.config.mjs +5 -47
  57. package/jest.config.js +1 -1
  58. package/package.json +16 -7
  59. package/src/Auth.js +414 -121
  60. package/src/Config.js +12 -7
  61. package/src/Database.js +188 -0
  62. package/src/Env.js +3 -1
  63. package/src/Ipc.js +337 -0
  64. package/src/Lang.js +9 -2
  65. package/src/Mail.js +408 -37
  66. package/src/Odac.js +105 -40
  67. package/src/Request.js +71 -49
  68. package/src/Route/Cron.js +62 -18
  69. package/src/Route/Internal.js +215 -12
  70. package/src/Route/Middleware.js +7 -2
  71. package/src/Route.js +372 -109
  72. package/src/Server.js +118 -12
  73. package/src/Storage.js +169 -0
  74. package/src/Token.js +6 -4
  75. package/src/Validator.js +95 -3
  76. package/src/Var.js +22 -6
  77. package/src/View/EarlyHints.js +43 -33
  78. package/src/View/Form.js +210 -28
  79. package/src/View.js +108 -7
  80. package/src/WebSocket.js +18 -3
  81. package/template/odac.json +5 -0
  82. package/template/package.json +3 -1
  83. package/template/route/www.js +12 -10
  84. package/template/view/content/home.html +3 -3
  85. package/template/view/head/main.html +2 -2
  86. package/test/Client.test.js +168 -0
  87. package/test/Config.test.js +112 -0
  88. package/test/Lang.test.js +92 -0
  89. package/test/Odac.test.js +86 -0
  90. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  91. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  92. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  93. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  94. package/test/scripts/check-coverage.js +4 -4
  95. package/docs/backend/08-database/01-database-connection.md +0 -99
  96. package/docs/backend/08-database/02-using-mysql.md +0 -322
  97. package/src/Mysql.js +0 -575
  98. package/template/config.json +0 -5
  99. package/test/cli/Cli.test.js +0 -36
  100. package/test/core/Candy.test.js +0 -234
  101. package/test/core/Commands.test.js +0 -538
  102. package/test/core/Config.test.js +0 -1432
  103. package/test/core/Lang.test.js +0 -250
  104. package/test/core/Process.test.js +0 -156
  105. package/test/server/Api.test.js +0 -647
  106. package/test/server/DNS.test.js +0 -2050
  107. package/test/server/DNS.test.js.bak +0 -2084
  108. package/test/server/Hub.test.js +0 -497
  109. package/test/server/Log.test.js +0 -73
  110. package/test/server/Mail.account.test_.js +0 -460
  111. package/test/server/Mail.init.test_.js +0 -411
  112. package/test/server/Mail.test_.js +0 -1340
  113. package/test/server/SSL.test_.js +0 -1491
  114. package/test/server/Server.test.js +0 -765
  115. package/test/server/Service.test_.js +0 -1127
  116. package/test/server/Subdomain.test.js +0 -440
  117. package/test/server/Web/Firewall.test.js +0 -175
  118. package/test/server/Web/Proxy.test.js +0 -397
  119. package/test/server/Web.test.js +0 -1494
  120. package/test/server/__mocks__/acme-client.js +0 -17
  121. package/test/server/__mocks__/bcrypt.js +0 -50
  122. package/test/server/__mocks__/child_process.js +0 -389
  123. package/test/server/__mocks__/crypto.js +0 -432
  124. package/test/server/__mocks__/fs.js +0 -450
  125. package/test/server/__mocks__/globalOdac.js +0 -227
  126. package/test/server/__mocks__/http.js +0 -575
  127. package/test/server/__mocks__/https.js +0 -272
  128. package/test/server/__mocks__/index.js +0 -249
  129. package/test/server/__mocks__/mail/server.js +0 -100
  130. package/test/server/__mocks__/mail/smtp.js +0 -31
  131. package/test/server/__mocks__/mailparser.js +0 -81
  132. package/test/server/__mocks__/net.js +0 -369
  133. package/test/server/__mocks__/node-forge.js +0 -328
  134. package/test/server/__mocks__/os.js +0 -320
  135. package/test/server/__mocks__/path.js +0 -291
  136. package/test/server/__mocks__/selfsigned.js +0 -8
  137. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  138. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  139. package/test/server/__mocks__/smtp-server.js +0 -106
  140. package/test/server/__mocks__/sqlite3.js +0 -394
  141. package/test/server/__mocks__/testFactories.js +0 -299
  142. package/test/server/__mocks__/testHelpers.js +0 -363
  143. package/test/server/__mocks__/tls.js +0 -229
package/bin/odac.js CHANGED
@@ -3,79 +3,211 @@
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
 
18
- async function run() {
19
- if (command === 'init') {
20
- const projectName = args[0] || '.'
21
- const targetDir = path.resolve(process.cwd(), projectName)
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
+ }
22
61
 
23
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
62
+ const cssOutputDir = path.dirname(defaultCssOutput)
63
+ fs.mkdirSync(cssOutputDir, {recursive: true})
24
64
 
25
- console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
26
- const templateDir = path.resolve(__dirname, '../template')
65
+ configs.push({
66
+ input: defaultCssInput,
67
+ cssOutput: defaultCssOutput,
68
+ isCustom: false,
69
+ name: 'Default'
70
+ })
71
+ }
27
72
 
28
- try {
29
- fs.cpSync(templateDir, targetDir, { recursive: true })
73
+ return configs
74
+ }
30
75
 
31
- const pkgPath = path.join(targetDir, 'package.json')
32
- const frameworkPkg = require('../package.json')
76
+ async function run() {
77
+ if (command === 'init') {
78
+ const projectName = args[0] || '.'
79
+ const targetDir = path.resolve(process.cwd(), projectName)
33
80
 
34
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
35
- pkg.name = projectName === '.' ? path.basename(targetDir) : projectName
36
- pkg.version = '0.0.1'
81
+ fs.mkdirSync(targetDir, {recursive: true})
37
82
 
38
- if (!pkg.dependencies) pkg.dependencies = {}
39
- pkg.dependencies[frameworkPkg.name] = `^${frameworkPkg.version}`
83
+ console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
84
+ const templateDir = path.resolve(__dirname, '../template')
40
85
 
41
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
86
+ try {
87
+ fs.cpSync(templateDir, targetDir, {recursive: true})
42
88
 
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)
52
- }
89
+ const pkgPath = path.join(targetDir, 'package.json')
90
+ const frameworkPkg = require('../package.json')
53
91
 
54
- console.log('\n✨ Project initialized successfully!')
55
- console.log('\n🚀 Starting server...')
92
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
93
+ pkg.name = projectName === '.' ? path.basename(targetDir) : projectName
94
+ pkg.version = '0.0.1'
56
95
 
57
- try {
58
- execSync('npm run dev', {
59
- stdio: 'inherit',
60
- cwd: targetDir
61
- })
62
- } catch (err) {
63
- console.log('\n👋 Server stopped.')
64
- }
96
+ if (!pkg.dependencies) pkg.dependencies = {}
97
+ pkg.dependencies[frameworkPkg.name] = `^${frameworkPkg.version}`
98
+
99
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
65
100
 
66
- } catch (error) {
67
- console.error('❌ Error initializing project:', error.message)
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
+
121
+ configs.forEach(({input, cssOutput, name, isCustom}) => {
122
+ let tailwindProcess = null
123
+
124
+ const startWatcher = () => {
125
+ const localCli = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss')
126
+ const useLocal = fs.existsSync(localCli)
127
+ const cmd = useLocal ? localCli : 'npx'
128
+ const args = useLocal ? ['-i', input, '-o', cssOutput, '--watch'] : ['@tailwindcss/cli', '-i', input, '-o', cssOutput, '--watch']
129
+
130
+ console.log(`🎨 Starting Tailwind CSS for ${name} (${isCustom ? 'Custom' : 'Default'})...`)
131
+ console.log(`📂 Watching directory: ${process.cwd()}`)
132
+
133
+ tailwindProcess = spawn(cmd, args, {
134
+ stdio: 'inherit',
135
+ shell: !useLocal, // Valid for npm/npx compatibility if local not found
136
+ cwd: process.cwd()
137
+ })
138
+
139
+ tailwindProcess.on('error', err => {
140
+ console.error(`❌ Tailwind watcher failed to start for ${name}:`, err.message)
141
+ })
142
+
143
+ tailwindProcess.on('exit', code => {
144
+ if (code !== 0 && code !== null) {
145
+ console.warn(`⚠️ Tailwind watcher for ${name} exited unexpectedly (code ${code}). Restarting in 1s...`)
146
+ setTimeout(startWatcher, 1000)
147
+ }
148
+ })
68
149
  }
69
150
 
70
- } else if (command === 'dev') {
71
- require('../index.js')
72
- } else {
73
- console.log('Usage:')
74
- console.log(' npx odac init (Interactive mode)')
75
- console.log(' npx odac init <project> (Quick mode)')
151
+ startWatcher()
152
+
153
+ // Push a wrapper compatible with the cleanup function
154
+ tails.push({
155
+ kill: () => {
156
+ if (tailwindProcess) tailwindProcess.kill()
157
+ }
158
+ })
159
+ })
160
+
161
+ const cleanup = () => {
162
+ tails.forEach(t => {
163
+ try {
164
+ t.kill()
165
+ } catch (e) {}
166
+ })
167
+ }
168
+ process.on('SIGINT', cleanup)
169
+ process.on('SIGTERM', cleanup)
170
+ process.on('exit', cleanup)
76
171
  }
77
172
 
78
- rl.close()
173
+ require('../index.js')
174
+ } else if (command === 'build') {
175
+ console.log('🏗️ Building for production...')
176
+
177
+ const configs = getTailwindConfigs()
178
+ let hasError = false
179
+
180
+ configs.forEach(({input, cssOutput, name, isCustom}) => {
181
+ console.log(`🎨 Compiling ${name} (${isCustom ? 'Custom' : 'Default'}) CSS...`)
182
+ try {
183
+ execSync(`npx @tailwindcss/cli -i "${input}" -o "${cssOutput}" --minify`, {
184
+ stdio: 'inherit',
185
+ cwd: process.cwd()
186
+ })
187
+ } catch (error) {
188
+ console.error(`❌ Build failed for ${name}:`, error.message)
189
+ hasError = true
190
+ }
191
+ })
192
+
193
+ if (hasError) {
194
+ process.exit(1)
195
+ } else {
196
+ console.log('✅ All builds completed successfully!')
197
+ }
198
+ } else if (command === 'start') {
199
+ process.env.NODE_ENV = 'production'
200
+ require('../index.js')
201
+ } else {
202
+ console.log('Usage:')
203
+ console.log(' npx odac init (Interactive mode)')
204
+ console.log(' npx odac init <project> (Quick mode)')
205
+ console.log(' npx odac dev (Development mode)')
206
+ console.log(' npx odac build (Production build)')
207
+ console.log(' npx odac start (Start server)')
208
+ }
209
+
210
+ rl.close()
79
211
  }
80
212
 
81
213
  run()