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.
- package/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/auto-pr-description.yml +3 -1
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +184 -0
- package/README.md +53 -34
- package/bin/odac.js +181 -49
- package/client/odac.js +878 -995
- package/docs/backend/01-overview/03-development-server.md +39 -46
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
- package/docs/backend/03-config/00-configuration-overview.md +15 -6
- package/docs/backend/03-config/01-database-connection.md +3 -3
- 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/02-controller-less-view-routes.md +9 -3
- 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 +29 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
- package/docs/backend/05-controllers/03-controller-classes.md +61 -55
- package/docs/backend/05-forms/01-custom-forms.md +103 -95
- package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
- 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/02-rendering-a-view.md +1 -1
- package/docs/backend/07-views/03-variables.md +5 -5
- package/docs/backend/07-views/04-request-data.md +1 -1
- package/docs/backend/07-views/08-backend-javascript.md +1 -1
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +100 -0
- package/docs/backend/08-database/02-basics.md +136 -0
- package/docs/backend/08-database/03-advanced.md +84 -0
- package/docs/backend/08-database/04-migrations.md +48 -0
- package/docs/backend/09-validation/01-the-validator-service.md +1 -0
- package/docs/backend/10-authentication/03-register.md +9 -2
- package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
- package/docs/backend/10-authentication/05-session-management.md +16 -2
- package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
- package/docs/backend/10-authentication/07-magic-links.md +134 -0
- package/docs/backend/11-mail/01-the-mail-service.md +118 -28
- package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
- package/docs/backend/13-utilities/01-odac-var.md +7 -7
- package/docs/backend/13-utilities/02-ipc.md +73 -0
- package/docs/frontend/01-overview/01-introduction.md +5 -1
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
- package/docs/index.json +21 -125
- package/eslint.config.mjs +5 -47
- package/jest.config.js +1 -1
- package/package.json +16 -7
- package/src/Auth.js +414 -121
- package/src/Config.js +12 -7
- package/src/Database.js +188 -0
- package/src/Env.js +3 -1
- package/src/Ipc.js +337 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +408 -37
- package/src/Odac.js +105 -40
- package/src/Request.js +71 -49
- package/src/Route/Cron.js +62 -18
- package/src/Route/Internal.js +215 -12
- package/src/Route/Middleware.js +7 -2
- package/src/Route.js +372 -109
- package/src/Server.js +118 -12
- package/src/Storage.js +169 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +95 -3
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +210 -28
- package/src/View.js +108 -7
- package/src/WebSocket.js +18 -3
- package/template/odac.json +5 -0
- package/template/package.json +3 -1
- package/template/route/www.js +12 -10
- 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 +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/docs/backend/08-database/01-database-connection.md +0 -99
- package/docs/backend/08-database/02-using-mysql.md +0 -322
- package/src/Mysql.js +0 -575
- package/template/config.json +0 -5
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Candy.test.js +0 -234
- 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/Process.test.js +0 -156
- 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/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 {
|
|
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
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
62
|
+
const cssOutputDir = path.dirname(defaultCssOutput)
|
|
63
|
+
fs.mkdirSync(cssOutputDir, {recursive: true})
|
|
24
64
|
|
|
25
|
-
|
|
26
|
-
|
|
65
|
+
configs.push({
|
|
66
|
+
input: defaultCssInput,
|
|
67
|
+
cssOutput: defaultCssOutput,
|
|
68
|
+
isCustom: false,
|
|
69
|
+
name: 'Default'
|
|
70
|
+
})
|
|
71
|
+
}
|
|
27
72
|
|
|
28
|
-
|
|
29
|
-
|
|
73
|
+
return configs
|
|
74
|
+
}
|
|
30
75
|
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
async function run() {
|
|
77
|
+
if (command === 'init') {
|
|
78
|
+
const projectName = args[0] || '.'
|
|
79
|
+
const targetDir = path.resolve(process.cwd(), projectName)
|
|
33
80
|
|
|
34
|
-
|
|
35
|
-
pkg.name = projectName === '.' ? path.basename(targetDir) : projectName
|
|
36
|
-
pkg.version = '0.0.1'
|
|
81
|
+
fs.mkdirSync(targetDir, {recursive: true})
|
|
37
82
|
|
|
38
|
-
|
|
39
|
-
|
|
83
|
+
console.log(`🚀 Initializing new Odac project in: ${targetDir}`)
|
|
84
|
+
const templateDir = path.resolve(__dirname, '../template')
|
|
40
85
|
|
|
41
|
-
|
|
86
|
+
try {
|
|
87
|
+
fs.cpSync(templateDir, targetDir, {recursive: true})
|
|
42
88
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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()
|