juxscript 1.0.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/README.md +292 -0
- package/bin/cli.js +149 -0
- package/lib/adapters/base-adapter.js +35 -0
- package/lib/adapters/index.js +33 -0
- package/lib/adapters/mysql-adapter.js +65 -0
- package/lib/adapters/postgres-adapter.js +70 -0
- package/lib/adapters/sqlite-adapter.js +56 -0
- package/lib/components/app.ts +124 -0
- package/lib/components/button.ts +136 -0
- package/lib/components/card.ts +205 -0
- package/lib/components/chart.ts +125 -0
- package/lib/components/code.ts +242 -0
- package/lib/components/container.ts +282 -0
- package/lib/components/data.ts +105 -0
- package/lib/components/docs-data.json +1211 -0
- package/lib/components/error-handler.ts +285 -0
- package/lib/components/footer.ts +146 -0
- package/lib/components/header.ts +167 -0
- package/lib/components/hero.ts +170 -0
- package/lib/components/import.ts +430 -0
- package/lib/components/input.ts +175 -0
- package/lib/components/layout.ts +113 -0
- package/lib/components/list.ts +392 -0
- package/lib/components/main.ts +111 -0
- package/lib/components/menu.ts +170 -0
- package/lib/components/modal.ts +216 -0
- package/lib/components/nav.ts +136 -0
- package/lib/components/node.ts +200 -0
- package/lib/components/reactivity.js +104 -0
- package/lib/components/script.ts +152 -0
- package/lib/components/sidebar.ts +168 -0
- package/lib/components/style.ts +129 -0
- package/lib/components/table.ts +279 -0
- package/lib/components/tabs.ts +191 -0
- package/lib/components/theme.ts +97 -0
- package/lib/components/view.ts +174 -0
- package/lib/jux.ts +203 -0
- package/lib/layouts/default.css +260 -0
- package/lib/layouts/default.jux +8 -0
- package/lib/layouts/figma.css +334 -0
- package/lib/layouts/figma.jux +0 -0
- package/lib/layouts/notion.css +258 -0
- package/lib/styles/base-theme.css +186 -0
- package/lib/styles/dark-theme.css +144 -0
- package/lib/styles/global.css +1131 -0
- package/lib/styles/light-theme.css +144 -0
- package/lib/styles/tokens/dark.css +86 -0
- package/lib/styles/tokens/light.css +86 -0
- package/lib/themes/dark.css +86 -0
- package/lib/themes/light.css +86 -0
- package/lib/utils/path-resolver.js +23 -0
- package/machinery/compiler.js +262 -0
- package/machinery/doc-generator.js +160 -0
- package/machinery/generators/css.js +128 -0
- package/machinery/generators/html.js +108 -0
- package/machinery/imports.js +155 -0
- package/machinery/server.js +185 -0
- package/machinery/validators/file-validator.js +123 -0
- package/machinery/watcher.js +148 -0
- package/package.json +58 -0
- package/types/globals.d.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# *JUX* Authoring UI's in pure javascript.
|
|
2
|
+
|
|
3
|
+
> Build beautiful, reactive UI's using only javascript. **No markup required.**
|
|
4
|
+
> A clean authorship layer capable of being taught to non-developers, strong enough to be used by real developers.
|
|
5
|
+
|
|
6
|
+
>> **jux** challenges the idea that markup based systems are the most efficient way to author web applications.
|
|
7
|
+
## AI friendly - **use less tokens!**
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
- Markup is expensive!
|
|
12
|
+
Have you ever considered the energy requirements to ship chunks of HTML markup like Vue or React components?
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- [ ] Build a code calculator (token calculator)
|
|
16
|
+
> As a user, I want to be efficient with the LLM's. I want my code to be clean. I want to avoid repeating myself. I ultimately, want to write less code. Less code is better code.
|
|
17
|
+
|
|
18
|
+
## Ecosystem friendly (NPM)
|
|
19
|
+
|
|
20
|
+
> Because it is just javascript, .jux files work with any npm package you want to throw at it with standard ESM syntax.
|
|
21
|
+
|
|
22
|
+
## GETTING STARTED
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
> install
|
|
26
|
+
`npm i juxscript`
|
|
27
|
+
|
|
28
|
+
>> Your IDE must be setup to read .jux files as .js files. In VS Code, this is do in the `.vscode/settings.json`. Here is an example.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
//.vscode/settings.json
|
|
32
|
+
{
|
|
33
|
+
"files.associations": {
|
|
34
|
+
"*.jux": "javascript",
|
|
35
|
+
"*.juxt": "javascript"
|
|
36
|
+
},
|
|
37
|
+
"javascript.validate.enable": false,
|
|
38
|
+
"[javascript]": {
|
|
39
|
+
"editor.defaultFormatter": "vscode.typescript-language-features"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> building and serving the `examples` project directory.
|
|
45
|
+
`npm run dev`
|
|
46
|
+
|
|
47
|
+
> building only (the `examples` project directory)
|
|
48
|
+
`npm run build:examples`
|
|
49
|
+
|
|
50
|
+
# GOAL: Eliminating the Markup System of building UI's.
|
|
51
|
+
> HTML markup is the equivalent of still writing in `C` ro `C++` despite the availability of more functional levels of abstraction like `python`.
|
|
52
|
+
> To remove the requirement for markup, **jux** must address the utility of markup.
|
|
53
|
+
|
|
54
|
+
## Layouts
|
|
55
|
+
|
|
56
|
+
### Grid System
|
|
57
|
+
- [ ] Laying out JUX files in a rational way.
|
|
58
|
+
> Develop a higher-level layout javascript grammar for building layouts and palettes of style using *ZERO markup*.
|
|
59
|
+
|
|
60
|
+
- [ ] A Partial View Strategy
|
|
61
|
+
>>> As a dev I may need to load partial pages/components into other pages. This should avoid noisy wrappers and ideally be just javascript imports of jux objects.
|
|
62
|
+
|
|
63
|
+
### Components
|
|
64
|
+
|
|
65
|
+
We have constructed the most helpful components for building 99% of core business/saas web applications, that present beautifully on the page and are created purely with javascript.
|
|
66
|
+
> https://www.shadcn-vue.com/docs/components
|
|
67
|
+
> Run time slots.. Example, `table.slot(columnName -> object);`
|
|
68
|
+
|
|
69
|
+
# Backend
|
|
70
|
+
|
|
71
|
+
### Routing & Views
|
|
72
|
+
- [X] Subviews architecture
|
|
73
|
+
- [X] Nested routing support
|
|
74
|
+
- [X] View composition
|
|
75
|
+
- [X] Layout system
|
|
76
|
+
- [X] User views
|
|
77
|
+
- [X] `profile.jux` - User
|
|
78
|
+
|
|
79
|
+
### Server Docs/Documentation.
|
|
80
|
+
|
|
81
|
+
- [ ] Should *jux* ship with a server at all? Or merely for demos.
|
|
82
|
+
|
|
83
|
+
> What is the server even doing? Is it merely a matter of understanding that JUX builder/compilation gives you a mirror of how you setup your project. It can be as flat or deep as you would like.
|
|
84
|
+
|
|
85
|
+
> *At this point*, we are speaking almost 100% towards UI interfaces.
|
|
86
|
+
> Servers should be 'loosely coupled.'. This allows for a standard.
|
|
87
|
+
>>> In the future, we could introduce an *auto api* feature, similar to *next.js* api heuristic for folders and files.
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
[ **@jesseallenrxtrail -> Discuss.** ]
|
|
92
|
+
|
|
93
|
+
### Chore: Commit to building more in package directory.
|
|
94
|
+
|
|
95
|
+
> We can experiment with our example project, using a simple `express` server, running. What third-party integration do we need?
|
|
96
|
+
- [ ] Chore: Cleanup our `machinery/server.js` to separate concerns and build our actual endpoints we need to build software here.
|
|
97
|
+
|
|
98
|
+
#### Alternative Chore: package this for `npmjs.org`, combine with our existing project, re-author frontend in *jux*.
|
|
99
|
+
|
|
100
|
+
- [ ] Alternative Chore: package this for `npmjs.org` and ship it.
|
|
101
|
+
- [ ] Start a brand new project.
|
|
102
|
+
- [ ] Build the server aspect in `flask` (already done).
|
|
103
|
+
- [ ] Author the frontend in **jux** instead of **vue**.
|
|
104
|
+
|
|
105
|
+
> This may require us to move our 'complilation error module', `lib/components/error-handler.ts` to the front-end for display without a 'server call' at all?
|
|
106
|
+
- [ ] Chore for this? This takes more analysis, discussion.
|
|
107
|
+
|
|
108
|
+
> How and what will we wire for backend and what **jux** components will we support for working with backends.
|
|
109
|
+
- [ X ] SQL Database (query component)
|
|
110
|
+
- [ ] Chore: This is currently supported. Needs revisiting and testing
|
|
111
|
+
> API's
|
|
112
|
+
- [ ] Base payload creater and consumer as a **jux** component. *jux.api*??
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# Jux Configs
|
|
117
|
+
|
|
118
|
+
> Jux configs.. `jux.config.js` what should this entail?
|
|
119
|
+
Currently it looks like this:
|
|
120
|
+
- [ ] Chore: **WHAT IS ACTUALLY BEING USED AND WHERE?**
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
export default {
|
|
124
|
+
projectRoot: __dirname,
|
|
125
|
+
distDir: path.join(__dirname, 'dist'),
|
|
126
|
+
build: {
|
|
127
|
+
minify: false
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
database: {
|
|
131
|
+
// Default connection
|
|
132
|
+
default: 'sqlite',
|
|
133
|
+
|
|
134
|
+
// Named connections
|
|
135
|
+
connections: {
|
|
136
|
+
sqlite: {
|
|
137
|
+
driver: 'sqlite',
|
|
138
|
+
database: path.join(__dirname, 'db', 'jux.db')
|
|
139
|
+
},
|
|
140
|
+
... // props to feed drivers for mysql/postgres
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
server: {
|
|
145
|
+
port: process.env.PORT || 3000,
|
|
146
|
+
host: process.env.HOST || 'localhost'
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
**As a user, I will want to control where JUX sends queries.**
|
|
152
|
+
**As a user, I may want to specify a default proxy for backend. I may also want to specify, explicitly, URL's to hit other service endpoints**
|
|
153
|
+
- [ ] Escape hatch (backend API proxy vs default)
|
|
154
|
+
- [ ] As a user, I may want to run my own server for backend requests/proxies.
|
|
155
|
+
- [ ] As a user, I may want to hit multiple endpoints in the same JUX file.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
# CORE
|
|
160
|
+
|
|
161
|
+
### STATIC SERVING (your local file system, s3 bucket static sites, any host that allows serving etc...)
|
|
162
|
+
**Should JUX function, as a 100% client side app (STATIC WEBSITES)**
|
|
163
|
+
- [ ] Chore: Discovery... I think the answer is yes. I think what might be missing is bundling.
|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
## REACTIVITY LAYER.
|
|
168
|
+
### Goal for Reactivity
|
|
169
|
+
> We are shooting for better than nasty amounts of javascript, not as sophisticated as vue/react.
|
|
170
|
+
|
|
171
|
+
> As a user, I want changes in state of my objects to be consumable by other objects on the page so they can paint changes and interface with each other seamlessly.
|
|
172
|
+
- [ ] Build a basic `v-model` strategy in pure javascript. Different than jquery, javascript etc.
|
|
173
|
+
- [ ] Build standard eventing systems, listeners and emitters on components where they make sense.
|
|
174
|
+
`
|
|
175
|
+
# ROADMAP
|
|
176
|
+
|
|
177
|
+
### KEY UI PAGES
|
|
178
|
+
- [ ] JWT Authentication (`authJWT`) (demo `examples` only)
|
|
179
|
+
- [ ] Token generation (demo `examples` only)
|
|
180
|
+
- [ ] Token validation middleware (demo `examples` only)
|
|
181
|
+
- [ ] Refresh token logic (demo `examples` only)
|
|
182
|
+
- [ ] *Login system PAGES! Ships with `vendor` assets like lib files/layouts etc.*
|
|
183
|
+
- [ ] Login
|
|
184
|
+
- [ ] Logout
|
|
185
|
+
- [ ] Profile
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### Additional Features
|
|
189
|
+
- [ ] API documentation
|
|
190
|
+
- [ ] Testing setup
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
### LAYOUT NOTES...
|
|
197
|
+
# Jux Standard Layout Regions
|
|
198
|
+
|
|
199
|
+
- [ ] CHORE : REVISIT! I think maybe we ship with our figma, notion, default etc. But then document how to build and reuse a .jux page as a layout using the `jux.layout` class.
|
|
200
|
+
|
|
201
|
+
## The Opinionated Structure
|
|
202
|
+
|
|
203
|
+
**jux** layout pages provide a **strongly opinionated** layout structure that covers 99% of web applications. Every page includes these regions by default, even if not all are used. This eliminates layout decisions and provides consistent target containers across your entire application.
|
|
204
|
+
|
|
205
|
+
These are free to use, but you can roll your own just as easily.
|
|
206
|
+
You can find the documentation for each of these layouts here:
|
|
207
|
+
|
|
208
|
+
- Notion
|
|
209
|
+
- Jux
|
|
210
|
+
- Figma
|
|
211
|
+
- Other?
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
### Sample Documenation
|
|
215
|
+
```markdown
|
|
216
|
+
#app
|
|
217
|
+
├── #appheader // Top navigation bar - your app's primary navigation
|
|
218
|
+
│ ├── #appheader-logo // Brand identity - logo, app name
|
|
219
|
+
│ ├── #appheader-nav // Primary navigation menu
|
|
220
|
+
│ └── #appheader-actions // User actions - profile, notifications, settings
|
|
221
|
+
│
|
|
222
|
+
├── #appsubheader // Contextual navigation - changes based on current page
|
|
223
|
+
│ ├── #appsubheader-breadcrumbs // Where am I? - navigation path
|
|
224
|
+
│ ├── #appsubheader-tabs // Page-level tabs - views within the same context
|
|
225
|
+
│ └── #appsubheader-actions // Context-specific actions - filters, search, "New Item"
|
|
226
|
+
│
|
|
227
|
+
├── #appsidebar // Left sidebar - persistent navigation or filters
|
|
228
|
+
│ ├── #appsidebar-header // Sidebar title or search
|
|
229
|
+
│ ├── #appsidebar-content // Main sidebar content - nav tree, filters
|
|
230
|
+
│ └── #appsidebar-footer // Sidebar actions or status
|
|
231
|
+
│
|
|
232
|
+
├── #appmain // Your content lives here - the star of the show
|
|
233
|
+
│
|
|
234
|
+
├── #appaside // Right sidebar - contextual help, metadata
|
|
235
|
+
│ ├── #appaside-header // Aside title
|
|
236
|
+
│ ├── #appaside-content // Properties panel, help docs, related items
|
|
237
|
+
│ └── #appaside-footer // Aside actions
|
|
238
|
+
│
|
|
239
|
+
├── #appfooter // Bottom of the page
|
|
240
|
+
│ ├── #appfooter-content // Footer navigation, social links
|
|
241
|
+
│ └── #appfooter-legal // Copyright, terms, privacy
|
|
242
|
+
│
|
|
243
|
+
└── #appmodal // Overlays and dialogs
|
|
244
|
+
├── #appmodal-backdrop // Click-to-close background
|
|
245
|
+
└── #appmodal-container
|
|
246
|
+
├── #appmodal-header // Dialog title and close button
|
|
247
|
+
├── #appmodal-content // Dialog body
|
|
248
|
+
└── #appmodal-footer // Dialog actions - Cancel, Save, etc.
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Why consider a Jux layout?
|
|
253
|
+
|
|
254
|
+
**Predictability**: Every page has the same structure. Developers know exactly where to render components.
|
|
255
|
+
|
|
256
|
+
**Flexibility**: Don't need a sidebar? Leave it empty. CSS handles visibility automatically.
|
|
257
|
+
|
|
258
|
+
**Composability**: Components can render to any region with `.render('#target-id')`.
|
|
259
|
+
|
|
260
|
+
**Convention over Configuration**: No layout decisions needed. Just start building.
|
|
261
|
+
|
|
262
|
+
## Common Patterns
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
// Dashboard with sidebar navigation
|
|
266
|
+
const sideNav = jux.nav('side-nav', { /* config */ });
|
|
267
|
+
await sideNav.render('#appsidebar-content');
|
|
268
|
+
|
|
269
|
+
const dashboard = jux.dashboard('main-dashboard', { /* config */ });
|
|
270
|
+
await dashboard.render('#appmain');
|
|
271
|
+
|
|
272
|
+
// Settings page with tabs
|
|
273
|
+
const tabs = jux.tabs('settings-tabs', { /* config */ });
|
|
274
|
+
await tabs.render('#appsubheader-tabs');
|
|
275
|
+
|
|
276
|
+
const content = jux.container('settings-content', { /* config */ });
|
|
277
|
+
await content.render('#appmain');
|
|
278
|
+
|
|
279
|
+
// Detail view with metadata sidebar
|
|
280
|
+
const detail = jux.detail('item-detail', { /* config */ });
|
|
281
|
+
await detail.render('#appmain');
|
|
282
|
+
|
|
283
|
+
const metadata = jux.metadata('item-meta', { /* config */ });
|
|
284
|
+
await metadata.render('#appaside-content');
|
|
285
|
+
|
|
286
|
+
// Modal dialog
|
|
287
|
+
const dialog = jux.dialog('confirm-dialog', { /* config */ });
|
|
288
|
+
await dialog.render('#appmodal-content');
|
|
289
|
+
|
|
290
|
+
// Auto-render to default container (component chooses based on type)
|
|
291
|
+
const hero = jux.hero('page-hero', { /* config */ });
|
|
292
|
+
await hero.render(); // Automatically renders to #appmain
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { compileJuxFile, copyLibToOutput, copyProjectAssets } from '../machinery/compiler.js';
|
|
4
|
+
import { generateDocs } from '../machinery/doc-generator.js';
|
|
5
|
+
import { start } from '../machinery/server.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const command = process.argv[2];
|
|
14
|
+
const projectRoot = process.cwd();
|
|
15
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
16
|
+
|
|
17
|
+
function findJuxFiles(dir, fileList = []) {
|
|
18
|
+
const files = fs.readdirSync(dir);
|
|
19
|
+
|
|
20
|
+
files.forEach(file => {
|
|
21
|
+
const filePath = path.join(dir, file);
|
|
22
|
+
const stat = fs.statSync(filePath);
|
|
23
|
+
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
if (file !== 'node_modules' && file !== 'dist' && file !== '.git') {
|
|
26
|
+
findJuxFiles(filePath, fileList);
|
|
27
|
+
}
|
|
28
|
+
} else if (file.endsWith('.jux')) {
|
|
29
|
+
fileList.push(filePath);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return fileList;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function loadConfig() {
|
|
37
|
+
const configPath = path.join(projectRoot, 'jux.config.js');
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(configPath)) {
|
|
40
|
+
try {
|
|
41
|
+
const configModule = await import(configPath);
|
|
42
|
+
return configModule.default || {};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.warn('⚠️ Could not load jux.config.js:', err.message);
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function buildProject(isServe = false) {
|
|
53
|
+
console.log('🔨 Building JUX project...\n');
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(distDir)) {
|
|
57
|
+
fs.rmSync(distDir, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
60
|
+
|
|
61
|
+
// Step 1: Generate documentation FIRST
|
|
62
|
+
console.log('📚 Generating documentation...');
|
|
63
|
+
try {
|
|
64
|
+
await generateDocs(projectRoot);
|
|
65
|
+
console.log('✅ Documentation generated\n');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn('⚠️ Failed to generate docs:', error.message);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Step 2: Copy lib/ to dist/lib/ (includes docs-data.json)
|
|
71
|
+
await copyLibToOutput(projectRoot, distDir);
|
|
72
|
+
|
|
73
|
+
// Step 3: Copy project assets (CSS, JS) from project root
|
|
74
|
+
await copyProjectAssets(projectRoot, distDir);
|
|
75
|
+
|
|
76
|
+
// Step 4: Find and compile project .jux files
|
|
77
|
+
const projectJuxFiles = findJuxFiles(projectRoot);
|
|
78
|
+
console.log(`Found ${projectJuxFiles.length} project .jux file(s)\n`);
|
|
79
|
+
|
|
80
|
+
for (const file of projectJuxFiles) {
|
|
81
|
+
try {
|
|
82
|
+
await compileJuxFile(file, { distDir, projectRoot, isServe });
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(`Error compiling ${file}:`, err.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Step 5: Find and compile vendor layout .jux files
|
|
89
|
+
const libRoot = path.resolve(projectRoot, '../lib');
|
|
90
|
+
const layoutsDir = path.join(libRoot, 'layouts');
|
|
91
|
+
|
|
92
|
+
if (fs.existsSync(layoutsDir)) {
|
|
93
|
+
console.log('\n📐 Compiling vendor layouts...');
|
|
94
|
+
const vendorJuxFiles = findJuxFiles(layoutsDir);
|
|
95
|
+
console.log(`Found ${vendorJuxFiles.length} vendor layout(s)\n`);
|
|
96
|
+
|
|
97
|
+
for (const file of vendorJuxFiles) {
|
|
98
|
+
try {
|
|
99
|
+
const relPath = path.relative(libRoot, file);
|
|
100
|
+
|
|
101
|
+
await compileJuxFile(file, {
|
|
102
|
+
distDir: path.join(distDir, 'lib'),
|
|
103
|
+
projectRoot: libRoot,
|
|
104
|
+
isServe
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log(` ✓ Compiled: ${relPath}`);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`Error compiling vendor layout ${file}:`, err.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`\n✅ Built ${projectJuxFiles.length} project file(s) + layouts\n`);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('❌ Build error:', err.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
(async () => {
|
|
122
|
+
if (command === 'build') {
|
|
123
|
+
await buildProject(false);
|
|
124
|
+
console.log(`✅ Build complete: ${distDir}`);
|
|
125
|
+
|
|
126
|
+
} else if (command === 'serve') {
|
|
127
|
+
// Build first
|
|
128
|
+
await buildProject(true); // isServe = true
|
|
129
|
+
|
|
130
|
+
// Start server with watcher
|
|
131
|
+
const config = await loadConfig();
|
|
132
|
+
await start(3000, config);
|
|
133
|
+
|
|
134
|
+
} else {
|
|
135
|
+
console.log(`
|
|
136
|
+
JUX CLI - A JavaScript UX authorship platform
|
|
137
|
+
|
|
138
|
+
Usage:
|
|
139
|
+
npx jux build Compile all .jux files to HTML/CSS/JS
|
|
140
|
+
npx jux serve [port] Start dev server with hot reload (default: 3000)
|
|
141
|
+
Builds automatically if dist/ doesn't exist
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
npx jux build Build for production
|
|
145
|
+
npx jux serve Start dev server (builds if needed)
|
|
146
|
+
npx jux serve 8080 Start on port 8080
|
|
147
|
+
`);
|
|
148
|
+
}
|
|
149
|
+
})();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base adapter interface that all database adapters must implement
|
|
3
|
+
*/
|
|
4
|
+
class BaseAdapter {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.connection = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async connect() {
|
|
11
|
+
throw new Error('connect() must be implemented');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async disconnect() {
|
|
15
|
+
throw new Error('disconnect() must be implemented');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async query(sql, params = []) {
|
|
19
|
+
throw new Error('query() must be implemented');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async execute(sql, params = []) {
|
|
23
|
+
throw new Error('execute() must be implemented');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async transaction(callback) {
|
|
27
|
+
throw new Error('transaction() must be implemented');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
escape(value) {
|
|
31
|
+
throw new Error('escape() must be implemented');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { BaseAdapter };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { SQLiteAdapter } = require('./sqlite-adapter');
|
|
2
|
+
const { MySQLAdapter } = require('./mysql-adapter');
|
|
3
|
+
const { PostgresAdapter } = require('./postgres-adapter');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adapter factory - creates the appropriate adapter based on config
|
|
7
|
+
*/
|
|
8
|
+
function createAdapter(type, config) {
|
|
9
|
+
switch (type.toLowerCase()) {
|
|
10
|
+
case 'sqlite':
|
|
11
|
+
case 'sqlite3':
|
|
12
|
+
return new SQLiteAdapter(config);
|
|
13
|
+
|
|
14
|
+
case 'mysql':
|
|
15
|
+
case 'mariadb':
|
|
16
|
+
return new MySQLAdapter(config);
|
|
17
|
+
|
|
18
|
+
case 'postgres':
|
|
19
|
+
case 'postgresql':
|
|
20
|
+
case 'pg':
|
|
21
|
+
return new PostgresAdapter(config);
|
|
22
|
+
|
|
23
|
+
default:
|
|
24
|
+
throw new Error(`Unsupported database type: ${type}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
createAdapter,
|
|
30
|
+
SQLiteAdapter,
|
|
31
|
+
MySQLAdapter,
|
|
32
|
+
PostgresAdapter
|
|
33
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { BaseAdapter } = require('./base-adapter');
|
|
2
|
+
const mysql = require('mysql2/promise');
|
|
3
|
+
|
|
4
|
+
class MySQLAdapter extends BaseAdapter {
|
|
5
|
+
async connect() {
|
|
6
|
+
this.connection = await mysql.createConnection({
|
|
7
|
+
host: this.config.host || 'localhost',
|
|
8
|
+
port: this.config.port || 3306,
|
|
9
|
+
user: this.config.user,
|
|
10
|
+
password: this.config.password,
|
|
11
|
+
database: this.config.database
|
|
12
|
+
});
|
|
13
|
+
return this.connection;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async disconnect() {
|
|
17
|
+
if (this.connection) {
|
|
18
|
+
await this.connection.end();
|
|
19
|
+
this.connection = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async query(sql, params = []) {
|
|
24
|
+
if (!this.connection) await this.connect();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const [rows] = await this.connection.execute(sql, params);
|
|
28
|
+
return { data: rows, rowCount: rows.length };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`MySQL query error: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async execute(sql, params = []) {
|
|
35
|
+
if (!this.connection) await this.connect();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const [result] = await this.connection.execute(sql, params);
|
|
39
|
+
return {
|
|
40
|
+
rowCount: result.affectedRows,
|
|
41
|
+
lastInsertId: result.insertId
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`MySQL execute error: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async transaction(callback) {
|
|
49
|
+
await this.connection.beginTransaction();
|
|
50
|
+
try {
|
|
51
|
+
const result = await callback(this);
|
|
52
|
+
await this.connection.commit();
|
|
53
|
+
return result;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
await this.connection.rollback();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
escape(value) {
|
|
61
|
+
return mysql.escape(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { MySQLAdapter };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const { BaseAdapter } = require('./base-adapter');
|
|
2
|
+
const { Pool } = require('pg');
|
|
3
|
+
|
|
4
|
+
class PostgresAdapter extends BaseAdapter {
|
|
5
|
+
async connect() {
|
|
6
|
+
this.connection = new Pool({
|
|
7
|
+
host: this.config.host || 'localhost',
|
|
8
|
+
port: this.config.port || 5432,
|
|
9
|
+
user: this.config.user,
|
|
10
|
+
password: this.config.password,
|
|
11
|
+
database: this.config.database
|
|
12
|
+
});
|
|
13
|
+
return this.connection;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async disconnect() {
|
|
17
|
+
if (this.connection) {
|
|
18
|
+
await this.connection.end();
|
|
19
|
+
this.connection = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async query(sql, params = []) {
|
|
24
|
+
if (!this.connection) await this.connect();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = await this.connection.query(sql, params);
|
|
28
|
+
return { data: result.rows, rowCount: result.rowCount };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`Postgres query error: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async execute(sql, params = []) {
|
|
35
|
+
if (!this.connection) await this.connect();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await this.connection.query(sql, params);
|
|
39
|
+
return {
|
|
40
|
+
rowCount: result.rowCount,
|
|
41
|
+
lastInsertId: result.rows[0]?.id
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`Postgres execute error: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async transaction(callback) {
|
|
49
|
+
const client = await this.connection.connect();
|
|
50
|
+
try {
|
|
51
|
+
await client.query('BEGIN');
|
|
52
|
+
const result = await callback(this);
|
|
53
|
+
await client.query('COMMIT');
|
|
54
|
+
return result;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await client.query('ROLLBACK');
|
|
57
|
+
throw error;
|
|
58
|
+
} finally {
|
|
59
|
+
client.release();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
escape(value) {
|
|
64
|
+
if (value === null) return 'NULL';
|
|
65
|
+
if (typeof value === 'number') return value.toString();
|
|
66
|
+
return `'${value.toString().replace(/'/g, "''")}'`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { PostgresAdapter };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { BaseAdapter } = require('./base-adapter');
|
|
2
|
+
const Database = require('better-sqlite3');
|
|
3
|
+
|
|
4
|
+
class SQLiteAdapter extends BaseAdapter {
|
|
5
|
+
async connect() {
|
|
6
|
+
this.connection = new Database(this.config.database || ':memory:');
|
|
7
|
+
return this.connection;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async disconnect() {
|
|
11
|
+
if (this.connection) {
|
|
12
|
+
this.connection.close();
|
|
13
|
+
this.connection = null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async query(sql, params = []) {
|
|
18
|
+
if (!this.connection) await this.connect();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const stmt = this.connection.prepare(sql);
|
|
22
|
+
const rows = stmt.all(...params);
|
|
23
|
+
return { data: rows, rowCount: rows.length };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`SQLite query error: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async execute(sql, params = []) {
|
|
30
|
+
if (!this.connection) await this.connect();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const stmt = this.connection.prepare(sql);
|
|
34
|
+
const result = stmt.run(...params);
|
|
35
|
+
return {
|
|
36
|
+
rowCount: result.changes,
|
|
37
|
+
lastInsertId: result.lastInsertRowid
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`SQLite execute error: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async transaction(callback) {
|
|
45
|
+
const transaction = this.connection.transaction(callback);
|
|
46
|
+
return transaction();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
escape(value) {
|
|
50
|
+
if (value === null) return 'NULL';
|
|
51
|
+
if (typeof value === 'number') return value.toString();
|
|
52
|
+
return `'${value.toString().replace(/'/g, "''")}'`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { SQLiteAdapter };
|