odac 1.2.0 ā 1.4.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/memory.md +16 -1
- package/.github/workflows/release.yml +27 -5
- package/.husky/pre-push +3 -3
- package/.releaserc.js +2 -2
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +64 -0
- package/README.md +1 -1
- package/bin/odac.js +187 -8
- package/client/odac.js +243 -178
- package/docs/ai/README.md +49 -0
- package/docs/ai/skills/SKILL.md +39 -0
- package/docs/ai/skills/backend/authentication.md +67 -0
- package/docs/ai/skills/backend/config.md +32 -0
- package/docs/ai/skills/backend/controllers.md +62 -0
- package/docs/ai/skills/backend/cron.md +50 -0
- package/docs/ai/skills/backend/database.md +21 -0
- package/docs/ai/skills/backend/forms.md +19 -0
- package/docs/ai/skills/backend/ipc.md +55 -0
- package/docs/ai/skills/backend/mail.md +34 -0
- package/docs/ai/skills/backend/request_response.md +35 -0
- package/docs/ai/skills/backend/routing.md +51 -0
- package/docs/ai/skills/backend/storage.md +43 -0
- package/docs/ai/skills/backend/streaming.md +34 -0
- package/docs/ai/skills/backend/structure.md +57 -0
- package/docs/ai/skills/backend/translations.md +42 -0
- package/docs/ai/skills/backend/utilities.md +24 -0
- package/docs/ai/skills/backend/validation.md +53 -0
- package/docs/ai/skills/backend/views.md +61 -0
- package/docs/ai/skills/frontend/core.md +66 -0
- package/docs/ai/skills/frontend/forms.md +21 -0
- package/docs/ai/skills/frontend/navigation.md +20 -0
- package/docs/ai/skills/frontend/realtime.md +47 -0
- package/docs/backend/04-routing/09-websocket.md +14 -1
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +2 -0
- package/docs/backend/10-authentication/05-session-management.md +25 -3
- package/package.json +13 -13
- package/src/Auth.js +100 -15
- package/src/Database.js +1 -1
- package/src/Mail.js +19 -9
- package/src/Odac.js +17 -14
- package/src/Request.js +5 -1
- package/src/Route/Internal.js +21 -18
- package/src/Route/MimeTypes.js +56 -0
- package/src/Route.js +136 -92
- package/src/Validator.js +23 -14
- package/src/View/Form.js +91 -51
- package/src/View.js +15 -10
- package/src/WebSocket.js +45 -12
- package/test/Auth.test.js +249 -0
- package/test/Client.test.js +29 -0
- package/test/Odac.test.js +4 -2
- package/test/Route.test.js +104 -0
- package/test/View/Form.test.js +37 -0
- package/test/WebSocket.test.js +141 -3
- package/.github/workflows/test-publish.yml +0 -36
package/.agent/rules/memory.md
CHANGED
|
@@ -28,6 +28,21 @@ trigger: always_on
|
|
|
28
28
|
- **Loop Optimization:** Use labeled loops (`label: for`) for efficient control flow in nested structures. Eliminate intermediate "flag" variables (`isMatch`, `found`) by using direct `return` or `continue label`.
|
|
29
29
|
- **Direct Returns:** Return a value as soon as it is determined. Avoid assigning to a temporary variable (e.g. `matchedUser`) and breaking the loop, unless post-loop processing is strictly necessary.
|
|
30
30
|
- **Async State Safety:** When an async function depends on mutable class state (like `pendingMiddlewares`), capture that state into a local `const` *synchronously* before triggering any async operations. This prevents race conditions where the state changes before the async task consumes it.
|
|
31
|
+
- **Async I/O Preference:** Prefer asynchronous file system operations (`fs.promises` or `await fs.promises.*`) over synchronous methods (`fs.readFileSync`, `fs.writeFileSync`) to prevent blocking the event loop and ensure high concurrency, especially in request handling paths.
|
|
31
32
|
|
|
32
33
|
## Dependency Management
|
|
33
|
-
- **Prefer Native Fetch:** Use the native `fetch` API for network requests in both Node.js (18+) and browser environments to reduce dependencies and bundle size.
|
|
34
|
+
- **Prefer Native Fetch:** Use the native `fetch` API for network requests in both Node.js (18+) and browser environments to reduce dependencies and bundle size.
|
|
35
|
+
|
|
36
|
+
## Naming & Text Conventions
|
|
37
|
+
- **ODAC Casing:** Always write "ODAC" in uppercase letters when referring to the framework name in strings, comments, log messages, or user-facing text. **EXCEPTION:** The class name itself (`class Odac`) and variable references to it should remain `Odac` (PascalCase) as per code conventions.
|
|
38
|
+
|
|
39
|
+
## Testing & Validation
|
|
40
|
+
- **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
|
|
41
|
+
- **Verify Correctness:** do not assume code works; prove it with a test that covers both success and failure scenarios (e.g., edge cases, error conditions).
|
|
42
|
+
- **Update Existing Tests:** If a feature modifies existing behavior, update the relevant tests to reflect the new logic and ensure they pass.
|
|
43
|
+
|
|
44
|
+
## Client Library (odac.js)
|
|
45
|
+
- **Automatic JSON Parsing:** The `#ajax` method (and by extension `odac.get`) must automatically parse the response if the `Content-Type` header contains `application/json`, even if `dataType` is not explicitly set to `json`.
|
|
46
|
+
|
|
47
|
+
## Security Logic & Authentication
|
|
48
|
+
- **Enterprise Token Rotation:** The `Auth.js` system utilizes a non-blocking refresh token rotation mechanism for cookies (`odac_x`/`odac_y`). To prevent race conditions during concurrent requests in high-throughput SPAs, rotated tokens are **not** immediately deleted. Instead, their `active` timestamp is set to naturally expire in 60 seconds (Grace Period), and their `date` timestamp is set to the Unix Epoch (`new Date(0)`) as an identifier mark. Never delete rotated tokens immediately.
|
|
@@ -7,7 +7,7 @@ on:
|
|
|
7
7
|
paths-ignore:
|
|
8
8
|
- 'CHANGELOG.md'
|
|
9
9
|
- 'package.json'
|
|
10
|
-
- '.github/workflows
|
|
10
|
+
- '.github/workflows/**'
|
|
11
11
|
workflow_dispatch:
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
@@ -41,10 +41,6 @@ jobs:
|
|
|
41
41
|
env:
|
|
42
42
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
43
43
|
run: npx semantic-release
|
|
44
|
-
|
|
45
|
-
- name: Publish to npm
|
|
46
|
-
run: npm publish --provenance --access public
|
|
47
|
-
|
|
48
44
|
- name: Get version
|
|
49
45
|
id: version
|
|
50
46
|
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
|
@@ -56,6 +52,8 @@ jobs:
|
|
|
56
52
|
repository: odac-run/actions
|
|
57
53
|
token: ${{ secrets.GH_PAT }}
|
|
58
54
|
path: .github/odac-actions
|
|
55
|
+
ref: main
|
|
56
|
+
persist-credentials: false
|
|
59
57
|
|
|
60
58
|
- name: Generate Release Notes
|
|
61
59
|
id: ai_notes
|
|
@@ -70,11 +68,35 @@ jobs:
|
|
|
70
68
|
- name: Cleanup Actions
|
|
71
69
|
if: always()
|
|
72
70
|
run: rm -rf .github/odac-actions
|
|
71
|
+
|
|
72
|
+
- name: Update PR Title
|
|
73
|
+
uses: actions/github-script@v7
|
|
74
|
+
if: steps.ai_notes.outcome == 'success'
|
|
75
|
+
env:
|
|
76
|
+
AI_TITLE: ${{ steps.ai_notes.outputs.release-title }}
|
|
77
|
+
with:
|
|
78
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
79
|
+
script: |
|
|
80
|
+
const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
81
|
+
owner: context.repo.owner,
|
|
82
|
+
repo: context.repo.repo,
|
|
83
|
+
commit_sha: context.sha,
|
|
84
|
+
});
|
|
85
|
+
if (prs.data.length > 0) {
|
|
86
|
+
await github.rest.pulls.update({
|
|
87
|
+
owner: context.repo.owner,
|
|
88
|
+
repo: context.repo.repo,
|
|
89
|
+
pull_number: prs.data[0].number,
|
|
90
|
+
title: process.env.AI_TITLE
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
- name: Update Release
|
|
74
95
|
uses: softprops/action-gh-release@v1
|
|
75
96
|
if: steps.ai_notes.outcome == 'success' && steps.version.outputs.version != ''
|
|
76
97
|
with:
|
|
77
98
|
tag_name: v${{ steps.version.outputs.version }}
|
|
99
|
+
name: ${{ steps.ai_notes.outputs.release-title }}
|
|
78
100
|
body_path: RELEASE_NOTE.md
|
|
79
101
|
draft: false
|
|
80
102
|
prerelease: false
|
package/.husky/pre-push
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
2
|
. "$(dirname -- "$0")/_/husky.sh"
|
|
3
3
|
|
|
4
|
-
# Prevent pushing code with High/Critical vulnerabilities
|
|
5
|
-
echo "š”ļø Running Security Gate (npm audit)..."
|
|
4
|
+
# Prevent pushing code with High/Critical vulnerabilities in production dependencies
|
|
5
|
+
echo "š”ļø Running Production Security Gate (npm audit)..."
|
|
6
6
|
|
|
7
|
-
npm audit --audit-level=high
|
|
7
|
+
npm audit --audit-level=high --omit=dev
|
|
8
8
|
|
|
9
9
|
if [ $? -ne 0 ]; then
|
|
10
10
|
echo "ā Security Check Failed! High vulnerabilities detected."
|
package/.releaserc.js
CHANGED
|
@@ -122,7 +122,7 @@ Powered by [ā” ODAC](https://odac.run)
|
|
|
122
122
|
[
|
|
123
123
|
'@semantic-release/npm',
|
|
124
124
|
{
|
|
125
|
-
|
|
125
|
+
provenance: true
|
|
126
126
|
}
|
|
127
127
|
],
|
|
128
128
|
[
|
|
@@ -134,4 +134,4 @@ Powered by [ā” ODAC](https://odac.run)
|
|
|
134
134
|
],
|
|
135
135
|
'@semantic-release/github'
|
|
136
136
|
]
|
|
137
|
-
}
|
|
137
|
+
}
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ODAC Agent Instructions
|
|
2
|
+
|
|
3
|
+
You are an AI Agent operating within the **ODAC Framework** repository. This document outlines the core principles, architectural standards, and operational guidelines you MUST follow to maintain the integrity and performance of this enterprise-grade system.
|
|
4
|
+
|
|
5
|
+
## 1. Project Identity & Philosophy
|
|
6
|
+
- **Name:** ODAC (Always uppercase in strings/docs/logs).
|
|
7
|
+
- **Core Goal:** To provide a robust, zero-config, high-performance Node.js framework for distributed cloud applications.
|
|
8
|
+
- **The "Big 3" Priorities:**
|
|
9
|
+
1. **Enterprise-Level Security:** Security is foundational. Default to secure, validate all inputs, sanitize all outputs.
|
|
10
|
+
2. **Zero-Config:** Works out-of-the-box. Convention over configuration.
|
|
11
|
+
3. **High Performance:** Optimize for throughput and low latency (Sub-millisecond targets).
|
|
12
|
+
|
|
13
|
+
## 2. Architectural Principles
|
|
14
|
+
- **Asynchronous & Non-Blocking:** Exclusively use non-blocking I/O. Use `fs.promises` instead of sync methods.
|
|
15
|
+
- **Dependency Injection (DI):** Build components with DI for maximum testability.
|
|
16
|
+
- **Single Responsibility Principle (SRP):** Keep classes and functions focused and small.
|
|
17
|
+
- **Memory Management:** Be paranoid about leaks. Clean up listeners, streams, and connections.
|
|
18
|
+
- **O(n log n) Bound:** Prioritize O(1) or O(n log n) algorithms. Justify any O(n²) operations.
|
|
19
|
+
|
|
20
|
+
## 3. Coding Standards & Integrity
|
|
21
|
+
- **Modern JavaScript:** Use ES6+ features, ES Modules (import/export).
|
|
22
|
+
- **Strictly Prohibited:** **No usage of `var`**. Use `const` (preferred) or `let`.
|
|
23
|
+
- **Fail-Fast Pattern:** Implement early returns for negative cases. Avoid deeply nested `if/else`.
|
|
24
|
+
- **Anti-Spaghetti Rules:**
|
|
25
|
+
- Resolve Promises upfront (e.g., `Promise.all`) before loops.
|
|
26
|
+
- Avoid mixing `await` inside deep logic.
|
|
27
|
+
- Capture mutable state synchronously before async operations.
|
|
28
|
+
- **No Quick/Lazy Fixes:** Implement correctly from the start. Refactor if necessary; no "band-aid" patches.
|
|
29
|
+
|
|
30
|
+
## 4. Technical Constraints (Strict Compliance)
|
|
31
|
+
- **Session Safety:** `Odac.Request.setSession()` MUST be called before accessing `Odac.Request.session()`.
|
|
32
|
+
- **Structured Logging:** No `console.log`. Use the internal JSON logger with appropriate levels.
|
|
33
|
+
- **Native APIs:** Prefer native Node.js/Browser APIs (like `fetch`) over external libraries to minimize overhead.
|
|
34
|
+
- **Token Rotation:** In `Auth.js`, use the 60-second grace period for rotated tokens. Never delete them immediately.
|
|
35
|
+
- **Ajax Parsing:** `odac.js` must automatically parse JSON responses if headers allow.
|
|
36
|
+
|
|
37
|
+
## 5. Testing & Documentation
|
|
38
|
+
- **TDD Requirement:** No feature is complete without unit/integration tests covering both success and edge cases.
|
|
39
|
+
- **Documentation:** Every exported member must have JSDoc explaining *Why* it exists, not just *What* it does.
|
|
40
|
+
- **No User Dialogues in Code:** Do not include assistant-user interaction in comments or code files.
|
|
41
|
+
|
|
42
|
+
## 6. Communication Style
|
|
43
|
+
- **Authoritative & Precise:** Be the expert. Do not explain basic concepts.
|
|
44
|
+
- **Proactive Correction:** If the user suggests a sub-optimal or insecure pattern (e.g., synchronous reads), refuse and implement the correct async version, explaining the trade-off.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
*Note: This file is a living document. Updates should be reflected in `memory.md` and subsequent AI interactions.*
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
### āļø Engine Tuning
|
|
2
|
+
|
|
3
|
+
- Extract MIME type definitions into a dedicated module.
|
|
4
|
+
|
|
5
|
+
### ā”ļø Performance Upgrades
|
|
6
|
+
|
|
7
|
+
- prevent redundant database table migration calls by introducing a static cache to track completed migrations.
|
|
8
|
+
|
|
9
|
+
### ⨠What's New
|
|
10
|
+
|
|
11
|
+
- add comprehensive ODAC Agent instructions and guidelines
|
|
12
|
+
- **auth:** implement enterprise refresh token rotation with grace period and session persistence
|
|
13
|
+
- Automatically parse JSON responses in client-side AJAX requests based on the `Content-Type` header.
|
|
14
|
+
- implement comprehensive AI agent skills system and automated CLI setup
|
|
15
|
+
|
|
16
|
+
### š ļø Fixes & Improvements
|
|
17
|
+
|
|
18
|
+
- add HTML escaping functionality to Form class and corresponding tests
|
|
19
|
+
- **ai:** correct target path for syncing AI skills
|
|
20
|
+
- **auth:** replace magic number with constant for rotated token threshold
|
|
21
|
+
- **auth:** replace magic number with constant for token rotation grace period
|
|
22
|
+
- enhance odac:form parser with nested quotes and dynamic binding support
|
|
23
|
+
- **route:** support nested property paths in actions and resolve App class conflict
|
|
24
|
+
- **view:** ensure odac:for with 'in' attribute parses correctly as javascript
|
|
25
|
+
- **view:** implement clear attribute in odac:form to control auto-clearing
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Powered by [ā” ODAC](https://odac.run)
|
|
32
|
+
|
|
33
|
+
### āļø Engine Tuning
|
|
34
|
+
|
|
35
|
+
- Improve disposable domain cache management by relocating the cache path, ensuring directory existence, and standardizing error logging.
|
|
36
|
+
- Migrate file system operations in Mail and View to use async `fs.promises` for non-blocking I/O, aligning with new memory.md guidelines.
|
|
37
|
+
- remove unused `WebSocketClient` variable assignments in `WebSocket.test.js`
|
|
38
|
+
- streamline cache file reading by removing redundant access check.
|
|
39
|
+
- **validator:** Migrate file operations to `fs.promises` for asynchronous I/O and enhance security with explicit content sanitization.
|
|
40
|
+
|
|
41
|
+
### ā”ļø Performance Upgrades
|
|
42
|
+
|
|
43
|
+
- Optimize parametric route matching by implementing a two-phase, pre-indexed lookup strategy grouped by segment count.
|
|
44
|
+
|
|
45
|
+
### ⨠What's New
|
|
46
|
+
|
|
47
|
+
- Add configurable max payload size and message rate limiting options to WebSocket routes.
|
|
48
|
+
- Refined pre-push security audit to omit dev dependencies, optimized variable initializations, enhanced test mocks with `writeHead` and `finished` properties, introduced conditional `setTimeout` for request handling, and improved code formatting in client-side WebSocket and AJAX logic.
|
|
49
|
+
- update `.gitignore` to no longer ignore package manager lock files and to ignore the `storage/` directory
|
|
50
|
+
|
|
51
|
+
### š ļø Fixes & Improvements
|
|
52
|
+
|
|
53
|
+
- Enhance route directory not found error logging and add ODAC casing convention to memory rules.
|
|
54
|
+
- Set owner-only read/write permissions (0o600) for temporary cache files created during validation.
|
|
55
|
+
- suppress tailwind process stdout output
|
|
56
|
+
- Update default Unix socket path and enhance socket connection error handling with specific guidance for `ENOENT` errors.
|
|
57
|
+
- Update Tailwind CSS watch flag to '--watch=always' and refine child process stdio configuration.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
Powered by [ā” ODAC](https://odac.run)
|
|
64
|
+
|
|
1
65
|
### agent
|
|
2
66
|
|
|
3
67
|
- Clarify logging strategies for development and production environments
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* š **Powerful Routing:** Create clean, custom URLs and manage infinite pages with a flexible routing system.
|
|
11
11
|
* ⨠**Seamless SPA Experience:** Automatic AJAX handling for forms and page transitions eliminates the need for complex client-side code.
|
|
12
12
|
* š”ļø **Built-in Security:** Automatic CSRF protection and secure default headers keep your application safe.
|
|
13
|
-
* š **Authentication:** Ready-to-use session management
|
|
13
|
+
* š **Authentication:** Ready-to-use session management with enterprise-grade **Refresh Token Rotation**, secure password hashing, and authentication helpers.
|
|
14
14
|
* šļø **Database Agnostic:** Integrated support for major databases (PostgreSQL, MySQL, SQLite) and Redis via Knex.js.
|
|
15
15
|
* š **i18n Support:** Native multi-language support to help you reach a global audience.
|
|
16
16
|
* ā° **Task Scheduling:** Built-in Cron job system for handling background tasks and recurring operations.
|
package/bin/odac.js
CHANGED
|
@@ -16,6 +16,72 @@ const rl = readline.createInterface({
|
|
|
16
16
|
|
|
17
17
|
const ask = question => new Promise(resolve => rl.question(question, answer => resolve(answer.trim())))
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Interactive selection menu for CLI.
|
|
21
|
+
* @param {string} title Menu title
|
|
22
|
+
* @param {string[]} options List of choice strings
|
|
23
|
+
* @returns {Promise<number>} Selected index
|
|
24
|
+
*/
|
|
25
|
+
const select = async (title, options) => {
|
|
26
|
+
if (!process.stdout.isTTY) return 0
|
|
27
|
+
|
|
28
|
+
return new Promise(resolve => {
|
|
29
|
+
let current = 0
|
|
30
|
+
const hideCursor = '\u001B[?25l'
|
|
31
|
+
const showCursor = '\u001B[?25h'
|
|
32
|
+
|
|
33
|
+
// Calculate total lines the title occupies
|
|
34
|
+
const titleLines = title.split('\n')
|
|
35
|
+
const totalLines = titleLines.length + options.length
|
|
36
|
+
|
|
37
|
+
const render = () => {
|
|
38
|
+
// Clear all lines we previously wrote
|
|
39
|
+
titleLines.forEach(line => {
|
|
40
|
+
process.stdout.write('\r\x1b[K' + line + '\n')
|
|
41
|
+
})
|
|
42
|
+
options.forEach((opt, i) => {
|
|
43
|
+
const line = i === current ? `\x1b[36m ⯠${opt}\x1b[0m` : ` ${opt}`
|
|
44
|
+
process.stdout.write('\r\x1b[K' + line + '\n')
|
|
45
|
+
})
|
|
46
|
+
process.stdout.write(`\x1b[${totalLines}A`) // Move back to the very start
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
process.stdout.write(hideCursor)
|
|
50
|
+
if (!process.stdin.isRaw) {
|
|
51
|
+
process.stdin.setRawMode(true)
|
|
52
|
+
process.stdin.resume()
|
|
53
|
+
}
|
|
54
|
+
readline.emitKeypressEvents(process.stdin)
|
|
55
|
+
|
|
56
|
+
render()
|
|
57
|
+
|
|
58
|
+
const onKey = (str, key) => {
|
|
59
|
+
if (key.name === 'up') {
|
|
60
|
+
current = current > 0 ? current - 1 : options.length - 1
|
|
61
|
+
render()
|
|
62
|
+
} else if (key.name === 'down') {
|
|
63
|
+
current = current < options.length - 1 ? current + 1 : 0
|
|
64
|
+
render()
|
|
65
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
66
|
+
cleanup()
|
|
67
|
+
process.stdout.write(`\x1b[${totalLines}B\n`) // Move down past everything
|
|
68
|
+
resolve(current)
|
|
69
|
+
} else if (key.ctrl && key.name === 'c') {
|
|
70
|
+
cleanup()
|
|
71
|
+
process.exit()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const cleanup = () => {
|
|
76
|
+
process.stdin.removeListener('keypress', onKey)
|
|
77
|
+
if (process.stdin.isRaw) process.stdin.setRawMode(false)
|
|
78
|
+
process.stdout.write(showCursor)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.stdin.on('keypress', onKey)
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
19
85
|
/**
|
|
20
86
|
* Resolves Tailwind CSS paths and ensures required directories/files exist.
|
|
21
87
|
* Supports multiple CSS entry points from 'view/css'.
|
|
@@ -73,6 +139,94 @@ function getTailwindConfigs() {
|
|
|
73
139
|
return configs
|
|
74
140
|
}
|
|
75
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Manages the AI Agent skills synchronization.
|
|
144
|
+
* @param {string} targetDir The directory to sync skills into.
|
|
145
|
+
*/
|
|
146
|
+
async function manageSkills(targetDir = process.cwd()) {
|
|
147
|
+
const aiSourceDir = path.resolve(__dirname, '../docs/ai')
|
|
148
|
+
|
|
149
|
+
if (!fs.existsSync(aiSourceDir)) {
|
|
150
|
+
console.error('ā AI components not found in framework.')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const options = [
|
|
155
|
+
'Antigravity / Cascade (.agent/skills)',
|
|
156
|
+
'Claude / Projects (.claude/skills)',
|
|
157
|
+
'Continue (.continue/skills)',
|
|
158
|
+
'Cursor (.cursor/skills)',
|
|
159
|
+
'Kilo Code (.kilocode/skills)',
|
|
160
|
+
'Kiro CLI (.kiro/skills)',
|
|
161
|
+
'Qwen Code (.qwen/skills)',
|
|
162
|
+
'Windsurf (.windsurf/skills)',
|
|
163
|
+
'Custom Path',
|
|
164
|
+
'Skip / Cancel'
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
const choiceIndex = await select('\nš¤ \x1b[36mODAC AI Agent Skills Manager\x1b[0m\nSelect your AI Agent / IDE for setup:', options)
|
|
168
|
+
|
|
169
|
+
let targetSubDir = ''
|
|
170
|
+
let copySkillsOnly = true
|
|
171
|
+
|
|
172
|
+
const SKIP_INDEX = 9
|
|
173
|
+
const CUSTOM_INDEX = 8
|
|
174
|
+
|
|
175
|
+
if (choiceIndex === SKIP_INDEX) return // Skip / Cancel
|
|
176
|
+
|
|
177
|
+
switch (choiceIndex) {
|
|
178
|
+
case 0:
|
|
179
|
+
targetSubDir = '.agent/skills'
|
|
180
|
+
break
|
|
181
|
+
case 1:
|
|
182
|
+
targetSubDir = '.claude/skills'
|
|
183
|
+
break
|
|
184
|
+
case 2:
|
|
185
|
+
targetSubDir = '.continue/skills'
|
|
186
|
+
break
|
|
187
|
+
case 3:
|
|
188
|
+
targetSubDir = '.cursor/skills'
|
|
189
|
+
break
|
|
190
|
+
case 4:
|
|
191
|
+
targetSubDir = '.kilocode/skills'
|
|
192
|
+
break
|
|
193
|
+
case 5:
|
|
194
|
+
targetSubDir = '.kiro/skills'
|
|
195
|
+
break
|
|
196
|
+
case 6:
|
|
197
|
+
targetSubDir = '.qwen/skills'
|
|
198
|
+
break
|
|
199
|
+
case 7:
|
|
200
|
+
targetSubDir = '.windsurf/skills'
|
|
201
|
+
break
|
|
202
|
+
case CUSTOM_INDEX:
|
|
203
|
+
targetSubDir = await ask('Enter custom path: ')
|
|
204
|
+
copySkillsOnly = false
|
|
205
|
+
break
|
|
206
|
+
default:
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const targetBase = path.resolve(targetDir, targetSubDir)
|
|
211
|
+
const targetPath = targetBase
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
fs.mkdirSync(targetPath, {recursive: true})
|
|
215
|
+
|
|
216
|
+
if (copySkillsOnly) {
|
|
217
|
+
const skillsSource = path.join(aiSourceDir, 'skills')
|
|
218
|
+
fs.cpSync(skillsSource, targetPath, {recursive: true})
|
|
219
|
+
} else {
|
|
220
|
+
fs.cpSync(aiSourceDir, targetPath, {recursive: true})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(`\n⨠AI skills successfully synced to: \x1b[32m${targetSubDir}\x1b[0m`)
|
|
224
|
+
console.log('Your AI Agent now has full knowledge of the ODAC Framework. š')
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error('ā Failed to sync AI skills:', err.message)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
76
230
|
async function run() {
|
|
77
231
|
if (command === 'init') {
|
|
78
232
|
const projectName = args[0] || '.'
|
|
@@ -110,13 +264,27 @@ async function run() {
|
|
|
110
264
|
}
|
|
111
265
|
|
|
112
266
|
console.log('\n⨠Project initialized successfully!')
|
|
267
|
+
|
|
268
|
+
// Interactive AI Skills setup
|
|
269
|
+
if (process.stdout.isTTY) {
|
|
270
|
+
const setupAIIndex = await select('\nš¤ Should we setup AI Agent skills for your IDE?', ['Yes', 'No'])
|
|
271
|
+
if (setupAIIndex === 0) {
|
|
272
|
+
await manageSkills(targetDir)
|
|
273
|
+
} else {
|
|
274
|
+
console.log('\nš” \x1b[33mTip:\x1b[0m You can always run \x1b[36mnpx odac skills\x1b[0m later.')
|
|
275
|
+
}
|
|
276
|
+
}
|
|
113
277
|
} catch (error) {
|
|
114
278
|
console.error('ā Error initializing project:', error.message)
|
|
115
279
|
}
|
|
116
280
|
} else if (command === 'dev') {
|
|
281
|
+
// ... existing dev logic ...
|
|
117
282
|
if (cluster.isPrimary) {
|
|
118
283
|
const configs = getTailwindConfigs()
|
|
119
284
|
const tails = []
|
|
285
|
+
const names = configs.map(c => c.name).join(', ')
|
|
286
|
+
|
|
287
|
+
console.log(`šØ \x1b[36mODAC Styles:\x1b[0m Watching for changes (${names})`)
|
|
120
288
|
|
|
121
289
|
configs.forEach(({input, cssOutput, name, isCustom}) => {
|
|
122
290
|
let tailwindProcess = null
|
|
@@ -125,19 +293,28 @@ async function run() {
|
|
|
125
293
|
const localCli = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss')
|
|
126
294
|
const useLocal = fs.existsSync(localCli)
|
|
127
295
|
const cmd = useLocal ? localCli : 'npx'
|
|
128
|
-
const args = useLocal
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log(`š Watching directory: ${process.cwd()}`)
|
|
296
|
+
const args = useLocal
|
|
297
|
+
? ['-i', input, '-o', cssOutput, '--watch=always']
|
|
298
|
+
: ['@tailwindcss/cli', '-i', input, '-o', cssOutput, '--watch=always']
|
|
132
299
|
|
|
133
300
|
tailwindProcess = spawn(cmd, args, {
|
|
134
|
-
stdio: '
|
|
135
|
-
shell: !useLocal,
|
|
301
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
302
|
+
shell: !useLocal,
|
|
136
303
|
cwd: process.cwd()
|
|
137
304
|
})
|
|
138
305
|
|
|
306
|
+
tailwindProcess.stderr.on('data', chunk => {
|
|
307
|
+
const raw = chunk.toString()
|
|
308
|
+
const lines = raw.split('\n')
|
|
309
|
+
for (const line of lines) {
|
|
310
|
+
const clean = line.replace(/\x1B\[[0-9;]*[JKmsu]/g, '').trim()
|
|
311
|
+
if (!clean || clean.startsWith('Done in') || clean.startsWith('ā')) continue
|
|
312
|
+
process.stderr.write(`\x1b[31m[ODAC Style Error]\x1b[0m ${line}\n`)
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
|
|
139
316
|
tailwindProcess.on('error', err => {
|
|
140
|
-
console.error(`ā
|
|
317
|
+
console.error(`ā \x1b[31m[ODAC Style Error]\x1b[0m Failed to start watcher for ${name}:`, err.message)
|
|
141
318
|
})
|
|
142
319
|
|
|
143
320
|
tailwindProcess.on('exit', code => {
|
|
@@ -150,7 +327,6 @@ async function run() {
|
|
|
150
327
|
|
|
151
328
|
startWatcher()
|
|
152
329
|
|
|
153
|
-
// Push a wrapper compatible with the cleanup function
|
|
154
330
|
tails.push({
|
|
155
331
|
kill: () => {
|
|
156
332
|
if (tailwindProcess) tailwindProcess.kill()
|
|
@@ -198,6 +374,8 @@ async function run() {
|
|
|
198
374
|
} else if (command === 'start') {
|
|
199
375
|
process.env.NODE_ENV = 'production'
|
|
200
376
|
require('../index.js')
|
|
377
|
+
} else if (command === 'skills') {
|
|
378
|
+
await manageSkills()
|
|
201
379
|
} else {
|
|
202
380
|
console.log('Usage:')
|
|
203
381
|
console.log(' npx odac init (Interactive mode)')
|
|
@@ -205,6 +383,7 @@ async function run() {
|
|
|
205
383
|
console.log(' npx odac dev (Development mode)')
|
|
206
384
|
console.log(' npx odac build (Production build)')
|
|
207
385
|
console.log(' npx odac start (Start server)')
|
|
386
|
+
console.log(' npx odac skills (Sync AI Agent skills)')
|
|
208
387
|
}
|
|
209
388
|
|
|
210
389
|
rl.close()
|