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.
- package/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +42 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/release.yml +68 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +1 -1
- package/CHANGELOG.md +99 -0
- package/README.md +16 -0
- package/bin/odac.js +196 -38
- package/client/odac.js +238 -171
- package/docs/backend/01-overview/03-development-server.md +38 -45
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +2 -2
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +14 -1
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/03-controller-classes.md +40 -20
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +2 -2
- package/docs/backend/10-authentication/03-register.md +1 -1
- package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
- package/docs/backend/10-authentication/05-session-management.md +15 -1
- package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
- package/docs/backend/10-authentication/07-magic-links.md +1 -1
- package/docs/index.json +5 -1
- package/jest.config.js +1 -1
- package/package.json +18 -14
- package/src/Auth.js +58 -23
- package/src/Config.js +7 -7
- package/src/Database.js +1 -1
- package/src/Env.js +3 -1
- package/src/Ipc.js +7 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +19 -9
- package/src/Odac.js +56 -44
- package/src/Request.js +6 -2
- package/src/Route/Cron.js +58 -17
- package/src/Route/Internal.js +1 -1
- package/src/Route.js +375 -125
- package/src/Server.js +40 -3
- package/src/Storage.js +4 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +24 -15
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +17 -11
- package/src/View.js +68 -12
- package/src/WebSocket.js +45 -12
- package/template/package.json +3 -1
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +88 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +105 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/WebSocket.test.js +238 -0
- package/test/scripts/check-coverage.js +4 -4
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Odac.test.js +0 -234
- package/test/core/Process.test.js +0 -156
- package/test/framework/WebSocket.test.js +0 -100
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
- /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 {
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
77
|
+
if (command === 'init') {
|
|
78
|
+
const projectName = args[0] || '.'
|
|
79
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
22
80
|
|
|
23
|
-
|
|
81
|
+
fs.mkdirSync(targetDir, {recursive: true})
|
|
24
82
|
|
|
25
|
-
|
|
26
|
-
|
|
83
|
+
console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
|
|
84
|
+
const templateDir = path.resolve(__dirname, '../template')
|
|
27
85
|
|
|
28
|
-
|
|
29
|
-
|
|
86
|
+
try {
|
|
87
|
+
fs.cpSync(templateDir, targetDir, {recursive: true})
|
|
30
88
|
|
|
31
|
-
|
|
32
|
-
|
|
89
|
+
const pkgPath = path.join(targetDir, 'package.json')
|
|
90
|
+
const frameworkPkg = require('../package.json')
|
|
33
91
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
96
|
+
if (!pkg.dependencies) pkg.dependencies = {}
|
|
97
|
+
pkg.dependencies[frameworkPkg.name] = `^${frameworkPkg.version}`
|
|
40
98
|
|
|
41
|
-
|
|
99
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2))
|
|
42
100
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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()
|