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.
Files changed (61) hide show
  1. package/README.md +292 -0
  2. package/bin/cli.js +149 -0
  3. package/lib/adapters/base-adapter.js +35 -0
  4. package/lib/adapters/index.js +33 -0
  5. package/lib/adapters/mysql-adapter.js +65 -0
  6. package/lib/adapters/postgres-adapter.js +70 -0
  7. package/lib/adapters/sqlite-adapter.js +56 -0
  8. package/lib/components/app.ts +124 -0
  9. package/lib/components/button.ts +136 -0
  10. package/lib/components/card.ts +205 -0
  11. package/lib/components/chart.ts +125 -0
  12. package/lib/components/code.ts +242 -0
  13. package/lib/components/container.ts +282 -0
  14. package/lib/components/data.ts +105 -0
  15. package/lib/components/docs-data.json +1211 -0
  16. package/lib/components/error-handler.ts +285 -0
  17. package/lib/components/footer.ts +146 -0
  18. package/lib/components/header.ts +167 -0
  19. package/lib/components/hero.ts +170 -0
  20. package/lib/components/import.ts +430 -0
  21. package/lib/components/input.ts +175 -0
  22. package/lib/components/layout.ts +113 -0
  23. package/lib/components/list.ts +392 -0
  24. package/lib/components/main.ts +111 -0
  25. package/lib/components/menu.ts +170 -0
  26. package/lib/components/modal.ts +216 -0
  27. package/lib/components/nav.ts +136 -0
  28. package/lib/components/node.ts +200 -0
  29. package/lib/components/reactivity.js +104 -0
  30. package/lib/components/script.ts +152 -0
  31. package/lib/components/sidebar.ts +168 -0
  32. package/lib/components/style.ts +129 -0
  33. package/lib/components/table.ts +279 -0
  34. package/lib/components/tabs.ts +191 -0
  35. package/lib/components/theme.ts +97 -0
  36. package/lib/components/view.ts +174 -0
  37. package/lib/jux.ts +203 -0
  38. package/lib/layouts/default.css +260 -0
  39. package/lib/layouts/default.jux +8 -0
  40. package/lib/layouts/figma.css +334 -0
  41. package/lib/layouts/figma.jux +0 -0
  42. package/lib/layouts/notion.css +258 -0
  43. package/lib/styles/base-theme.css +186 -0
  44. package/lib/styles/dark-theme.css +144 -0
  45. package/lib/styles/global.css +1131 -0
  46. package/lib/styles/light-theme.css +144 -0
  47. package/lib/styles/tokens/dark.css +86 -0
  48. package/lib/styles/tokens/light.css +86 -0
  49. package/lib/themes/dark.css +86 -0
  50. package/lib/themes/light.css +86 -0
  51. package/lib/utils/path-resolver.js +23 -0
  52. package/machinery/compiler.js +262 -0
  53. package/machinery/doc-generator.js +160 -0
  54. package/machinery/generators/css.js +128 -0
  55. package/machinery/generators/html.js +108 -0
  56. package/machinery/imports.js +155 -0
  57. package/machinery/server.js +185 -0
  58. package/machinery/validators/file-validator.js +123 -0
  59. package/machinery/watcher.js +148 -0
  60. package/package.json +58 -0
  61. package/types/globals.d.ts +16 -0
@@ -0,0 +1,155 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const REGISTRY_PATH = path.join(__dirname, '../lib/registry/packages.json');
8
+
9
+ /**
10
+ * Load package registry
11
+ */
12
+ function loadRegistry() {
13
+ if (!fs.existsSync(REGISTRY_PATH)) {
14
+ return {};
15
+ }
16
+ return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
17
+ }
18
+
19
+ /**
20
+ * Parse @import directives from Jux code
21
+ */
22
+ export function parseImports(juxCode) {
23
+ const imports = [];
24
+ const lines = juxCode.split('\n');
25
+
26
+ for (let i = 0; i < lines.length; i++) {
27
+ const line = lines[i].trim();
28
+
29
+ // Stop parsing when we hit actual code (non-directive, non-comment)
30
+ if (line && !line.startsWith('@') && !line.startsWith('//') && !line.startsWith('/*')) {
31
+ break;
32
+ }
33
+
34
+ // Match @import directive
35
+ const importMatch = line.match(/^@import\s+(.+)$/);
36
+ if (importMatch) {
37
+ const importSpec = importMatch[1].trim();
38
+ imports.push({
39
+ line: i + 1,
40
+ raw: importSpec,
41
+ resolved: resolveImport(importSpec)
42
+ });
43
+
44
+ if (process.env.JUX_VERBOSE) {
45
+ console.log(` [Line ${i + 1}] Found @import: ${importSpec}`);
46
+ }
47
+ }
48
+ }
49
+
50
+ return imports;
51
+ }
52
+
53
+ /**
54
+ * Resolve import specification to URL/path
55
+ */
56
+ export function resolveImport(importSpec) {
57
+ // Remove quotes if present
58
+ const cleaned = importSpec.replace(/^['"]|['"]$/g, '');
59
+
60
+ // Direct URL (starts with http:// or https://)
61
+ if (cleaned.startsWith('http://') || cleaned.startsWith('https://')) {
62
+ return {
63
+ type: 'cdn',
64
+ url: cleaned,
65
+ source: 'direct'
66
+ };
67
+ }
68
+
69
+ // Local file (starts with ./ or ../ or /)
70
+ if (cleaned.startsWith('./') || cleaned.startsWith('../') || cleaned.startsWith('/')) {
71
+ return {
72
+ type: 'local',
73
+ path: cleaned,
74
+ source: 'file'
75
+ };
76
+ }
77
+
78
+ // Unknown/unsupported
79
+ return {
80
+ type: 'unknown',
81
+ name: cleaned,
82
+ error: `Import "${cleaned}" must be a URL (https://...) or relative path (./...)`
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Auto-detect component dependencies
88
+ */
89
+ function detectComponentDependencies(usedComponents) {
90
+ const componentDeps = {
91
+ 'chart': 'chart.js'
92
+ // Add more mappings as needed
93
+ };
94
+
95
+ const deps = new Set();
96
+
97
+ usedComponents.forEach(comp => {
98
+ if (componentDeps[comp]) {
99
+ deps.add(componentDeps[comp]);
100
+ }
101
+ });
102
+
103
+ return Array.from(deps).map(pkg => ({
104
+ auto: true,
105
+ resolved: resolveImport(pkg)
106
+ }));
107
+ }
108
+
109
+ /**
110
+ * Generate HTML script tags from resolved imports
111
+ */
112
+ export function generateImportTags(imports) {
113
+ const tags = [];
114
+ const seen = new Set();
115
+
116
+ for (const imp of imports) {
117
+ const resolved = imp.resolved;
118
+
119
+ if (resolved.type === 'unknown') {
120
+ tags.push(`<!-- ERROR: ${resolved.error} -->`);
121
+ continue;
122
+ }
123
+
124
+ const key = resolved.url || resolved.path;
125
+ if (seen.has(key)) continue;
126
+ seen.add(key);
127
+
128
+ if (resolved.type === 'cdn') {
129
+ tags.push(` <script src="${resolved.url}"></script>`);
130
+ } else if (resolved.type === 'local') {
131
+ tags.push(` <script type="module" src="${resolved.path}"></script>`);
132
+ }
133
+ }
134
+
135
+ return tags.join('\n');
136
+ }
137
+
138
+ /**
139
+ * Validate all imports are resolvable
140
+ */
141
+ export function validateImports(imports) {
142
+ const errors = [];
143
+
144
+ imports.forEach(imp => {
145
+ if (imp.resolved.type === 'unknown') {
146
+ errors.push({
147
+ line: imp.line,
148
+ message: imp.resolved.error,
149
+ raw: imp.raw
150
+ });
151
+ }
152
+ });
153
+
154
+ return errors;
155
+ }
@@ -0,0 +1,185 @@
1
+ import express from 'express';
2
+ import http from 'http';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import initSqlJs from 'sql.js';
7
+ import { startWatcher } from './watcher.js';
8
+ import { WebSocketServer } from 'ws';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ let db = null;
14
+
15
+ async function serve(port = 3000, distDir = './dist') {
16
+ const app = express();
17
+ const absoluteDistDir = path.resolve(distDir);
18
+ const projectRoot = path.resolve('.');
19
+
20
+ app.use(express.json());
21
+
22
+ if (!fs.existsSync(absoluteDistDir)) {
23
+ console.error(`Error: dist directory not found at ${absoluteDistDir}`);
24
+ console.log('Building project first...\n');
25
+ process.exit(1);
26
+ }
27
+
28
+ // API endpoint for SQL queries
29
+ app.post('/api/query', async (req, res) => {
30
+ try {
31
+ const { sql, params = [] } = req.body;
32
+
33
+ if (!db) {
34
+ return res.status(500).json({ error: 'Database not initialized' });
35
+ }
36
+
37
+ const stmt = db.prepare(sql);
38
+ stmt.bind(params);
39
+
40
+ const rows = [];
41
+ while (stmt.step()) {
42
+ rows.push(stmt.getAsObject());
43
+ }
44
+ stmt.free();
45
+
46
+ res.json({ data: rows, rowCount: rows.length });
47
+ } catch (error) {
48
+ console.error('Query error:', error.message);
49
+ res.status(400).json({ error: error.message });
50
+ }
51
+ });
52
+
53
+ // Disable caching in dev mode
54
+ app.use((req, res, next) => {
55
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
56
+ res.setHeader('Pragma', 'no-cache');
57
+ res.setHeader('Expires', '0');
58
+ next();
59
+ });
60
+
61
+ // Serve HTML files with clean URLs
62
+ const heyPath = path.join(absoluteDistDir, 'hey.html');
63
+ const indexPath = path.join(absoluteDistDir, 'index.html');
64
+
65
+ app.use((req, res, next) => {
66
+ let requestPath = req.path.endsWith('/') && req.path.length > 1
67
+ ? req.path.slice(0, -1)
68
+ : req.path;
69
+
70
+ // Root path - serve hey.html or index.html
71
+ if (requestPath === '/') {
72
+ if (fs.existsSync(heyPath)) return res.sendFile(heyPath);
73
+ if (fs.existsSync(indexPath)) return res.sendFile(indexPath);
74
+ }
75
+
76
+ // Try to serve as HTML file
77
+ const htmlPath = path.join(absoluteDistDir, requestPath + '.html');
78
+ if (fs.existsSync(htmlPath) && fs.statSync(htmlPath).isFile()) {
79
+ return res.sendFile(htmlPath);
80
+ }
81
+
82
+ // Try to serve index.html in directory
83
+ const indexInDirPath = path.join(absoluteDistDir, requestPath, 'index.html');
84
+ if (fs.existsSync(indexInDirPath)) {
85
+ return res.sendFile(indexInDirPath);
86
+ }
87
+
88
+ next();
89
+ });
90
+
91
+ // Serve static files (CSS, JS, images, etc.)
92
+ app.use(express.static(absoluteDistDir));
93
+
94
+ // Enhanced 404 handler with debug info
95
+ app.use((req, res) => {
96
+ const notFoundPath = path.join(absoluteDistDir, '404.html');
97
+ const requestedPath = path.join(absoluteDistDir, req.path);
98
+ const fileType = path.extname(req.path) || 'directory';
99
+
100
+ // Log to console for debugging
101
+ console.log(`āŒ 404: ${req.path}`);
102
+ console.log(` Looked for: ${requestedPath}`);
103
+ console.log(` Type: ${fileType}`);
104
+ console.log(` Referer: ${req.get('referer') || 'direct'}`);
105
+
106
+ // If custom 404.html exists and this isn't already /404
107
+ if (fs.existsSync(notFoundPath) && req.path !== '/404') {
108
+ // Add debug info as query params
109
+ const debugUrl = `/404?path=${encodeURIComponent(req.path)}&type=${fileType}&from=${encodeURIComponent(req.get('referer') || 'direct')}`;
110
+ return res.redirect(debugUrl);
111
+ }
112
+
113
+ // Serve custom 404 page
114
+ if (fs.existsSync(notFoundPath)) {
115
+ return res.status(404).sendFile(notFoundPath);
116
+ }
117
+
118
+ // Fallback: minimal 404 response
119
+ res.status(404).send('<h1>404 - Not Found</h1>');
120
+ });
121
+
122
+ // Create HTTP server (wrap Express app)
123
+ const server = http.createServer(app);
124
+
125
+ // WebSocket server on separate port 3001
126
+ const wss = new WebSocketServer({ port: 3001 });
127
+ const clients = [];
128
+
129
+ wss.on('connection', (ws) => {
130
+ clients.push(ws);
131
+ console.log('šŸ”Œ WebSocket client connected');
132
+
133
+ ws.on('close', () => {
134
+ console.log('šŸ”Œ WebSocket client disconnected');
135
+ const index = clients.indexOf(ws);
136
+ if (index > -1) clients.splice(index, 1);
137
+ });
138
+
139
+ ws.on('error', (error) => {
140
+ console.error('WebSocket error:', error);
141
+ });
142
+ });
143
+
144
+ console.log('šŸ”Œ WebSocket server running at ws://localhost:3001');
145
+
146
+ // Start HTTP server
147
+ server.listen(port, () => {
148
+ console.log(`šŸš€ JUX dev server running at http://localhost:${port}`);
149
+ });
150
+
151
+ // Start file watcher
152
+ startWatcher(projectRoot, absoluteDistDir, clients);
153
+
154
+ // Graceful shutdown
155
+ const shutdown = async () => {
156
+ console.log('\n\nšŸ‘‹ Shutting down server...');
157
+ wss.close();
158
+ server.close();
159
+ process.exit(0);
160
+ };
161
+
162
+ process.on('SIGINT', shutdown);
163
+ process.on('SIGTERM', shutdown);
164
+
165
+ return { server };
166
+ }
167
+
168
+ async function initDatabase() {
169
+ const SQL = await initSqlJs();
170
+ const dbPath = path.join(__dirname, '../db/jux.db');
171
+
172
+ if (fs.existsSync(dbPath)) {
173
+ const buffer = fs.readFileSync(dbPath);
174
+ db = new SQL.Database(buffer);
175
+ console.log('šŸ“Š Database loaded:', dbPath);
176
+ } else {
177
+ db = new SQL.Database();
178
+ console.log('šŸ“Š Using in-memory database');
179
+ }
180
+ }
181
+
182
+ export async function start(port = 3000, config = {}) {
183
+ await initDatabase();
184
+ return serve(port, config.distDir || './dist');
185
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * File validation utilities for Jux compiler
3
+ * Validates file types and content security
4
+ */
5
+ export class FileValidator {
6
+ constructor() {
7
+ this.cssExtensions = /\.(css|scss|sass|less)$/i;
8
+ this.jsExtensions = /\.(js|mjs|ts|tsx|jsx)$/i;
9
+ this.imageExtensions = /\.(png|jpg|jpeg|gif|svg|webp|ico|bmp)$/i;
10
+ }
11
+
12
+ /**
13
+ * Check if file is a CSS file
14
+ */
15
+ isCSSFile(filepath) {
16
+ return this.cssExtensions.test(filepath);
17
+ }
18
+
19
+ /**
20
+ * Check if file is a JavaScript file
21
+ */
22
+ isJavaScriptFile(filepath) {
23
+ return this.jsExtensions.test(filepath);
24
+ }
25
+
26
+ /**
27
+ * Check if file is an image file
28
+ */
29
+ isImageFile(filepath) {
30
+ return this.imageExtensions.test(filepath);
31
+ }
32
+
33
+ /**
34
+ * Validate CSS content for security issues
35
+ * Detects <script> tags that could be injected
36
+ */
37
+ validateStyleContent(content, source = 'unknown') {
38
+ if (/<script/i.test(content)) {
39
+ throw new Error(
40
+ `🚨 Security Error: <script> tag detected in ${source}\n` +
41
+ `CSS content must not contain script tags.`
42
+ );
43
+ }
44
+
45
+ if (/<\/script/i.test(content)) {
46
+ throw new Error(
47
+ `🚨 Security Error: </script> tag detected in ${source}\n` +
48
+ `CSS content must not contain script tags.`
49
+ );
50
+ }
51
+
52
+ return content;
53
+ }
54
+
55
+ /**
56
+ * Determine import type based on file extension
57
+ * Returns: 'css' | 'js' | 'image' | 'unknown'
58
+ */
59
+ validateImportPath(importPath) {
60
+ // Handle URLs
61
+ if (importPath.startsWith('http://') || importPath.startsWith('https://')) {
62
+ // Try to infer from URL
63
+ if (this.isCSSFile(importPath)) return { type: 'css', path: importPath };
64
+ if (this.isJavaScriptFile(importPath)) return { type: 'js', path: importPath };
65
+ if (this.isImageFile(importPath)) return { type: 'image', path: importPath };
66
+
67
+ // Default to unknown for CDN URLs without clear extensions
68
+ return { type: 'unknown', path: importPath };
69
+ }
70
+
71
+ // Local files
72
+ if (this.isCSSFile(importPath)) {
73
+ return { type: 'css', path: importPath };
74
+ }
75
+
76
+ if (this.isJavaScriptFile(importPath)) {
77
+ return { type: 'js', path: importPath };
78
+ }
79
+
80
+ if (this.isImageFile(importPath)) {
81
+ return { type: 'image', path: importPath };
82
+ }
83
+
84
+ return { type: 'unknown', path: importPath };
85
+ }
86
+
87
+ /**
88
+ * Validate array of imports and categorize them
89
+ * Returns categorized imports with warnings
90
+ */
91
+ categorizeImports(imports) {
92
+ const categorized = {
93
+ css: [],
94
+ js: [],
95
+ images: [],
96
+ unknown: []
97
+ };
98
+
99
+ const warnings = [];
100
+
101
+ for (const importPath of imports) {
102
+ const result = this.validateImportPath(importPath);
103
+
104
+ if (result.type === 'unknown') {
105
+ warnings.push(
106
+ `āš ļø Unknown import type: ${importPath}\n` +
107
+ ` Supported: .css/.scss/.sass, .js/.ts, .png/.jpg/.svg`
108
+ );
109
+ }
110
+
111
+ categorized[result.type === 'unknown' ? 'unknown' : result.type].push(result.path);
112
+ }
113
+
114
+ return { categorized, warnings };
115
+ }
116
+
117
+ /**
118
+ * Check if inline style content is empty or whitespace-only
119
+ */
120
+ isEmptyStyle(styleContent) {
121
+ return !styleContent || styleContent.trim().length === 0;
122
+ }
123
+ }
@@ -0,0 +1,148 @@
1
+ import chokidar from 'chokidar';
2
+ import path from 'path';
3
+ import { compileJuxFile, copyLibToOutput } from './compiler.js';
4
+ import fs from 'fs';
5
+ import { glob } from 'glob';
6
+
7
+ export function startWatcher(projectRoot, distDir, clients = []) {
8
+ console.log('šŸ” Watcher Configuration:');
9
+ console.log(' Project Root:', projectRoot);
10
+ console.log(' Dist Dir:', distDir);
11
+
12
+ // Manually find all files to watch
13
+ console.log('\nšŸ“‹ Finding files to watch...');
14
+
15
+ // Project .jux files
16
+ const juxFiles = glob.sync('**/*.jux', {
17
+ cwd: projectRoot,
18
+ ignore: ['node_modules/**', 'dist/**', '.git/**'],
19
+ absolute: true
20
+ });
21
+
22
+ // Vendor layout .jux files
23
+ const libRoot = path.resolve(projectRoot, '../lib');
24
+ const vendorJuxFiles = glob.sync('layouts/**/*.jux', {
25
+ cwd: libRoot,
26
+ absolute: true
27
+ });
28
+
29
+ const cssFiles = glob.sync('**/*.css', {
30
+ cwd: projectRoot,
31
+ ignore: ['node_modules/**', 'dist/**', '.git/**'],
32
+ absolute: true
33
+ });
34
+
35
+ const libFiles = glob.sync('**/*.{ts,js,css}', {
36
+ cwd: libRoot,
37
+ absolute: true
38
+ });
39
+
40
+ const machineryFiles = glob.sync('machinery/**/*.js', {
41
+ cwd: path.resolve(projectRoot, '..'),
42
+ absolute: true
43
+ });
44
+
45
+ const allFiles = [...juxFiles, ...vendorJuxFiles, ...cssFiles, ...libFiles, ...machineryFiles];
46
+
47
+ console.log(` Found ${juxFiles.length} project .jux files`);
48
+ console.log(` Found ${vendorJuxFiles.length} vendor .jux files`);
49
+ console.log(` Found ${cssFiles.length} .css files`);
50
+ console.log(` Found ${libFiles.length} lib/ files`);
51
+ console.log(` Found ${machineryFiles.length} machinery/ files`);
52
+ console.log(` Total: ${allFiles.length} files\n`);
53
+
54
+ if (allFiles.length === 0) {
55
+ console.error('āŒ No files found to watch!');
56
+ return null;
57
+ }
58
+
59
+ // Watch the specific files we found
60
+ const watcher = chokidar.watch(allFiles, {
61
+ persistent: true,
62
+ ignoreInitial: true,
63
+ awaitWriteFinish: {
64
+ stabilityThreshold: 100,
65
+ pollInterval: 50
66
+ }
67
+ });
68
+
69
+ watcher.on('ready', () => {
70
+ console.log('āœ… Watching for changes...\n');
71
+ allFiles.forEach(file => {
72
+ const rel = path.relative(path.resolve(projectRoot, '..'), file);
73
+ console.log(` šŸ‘ļø ${rel}`);
74
+ });
75
+ console.log('');
76
+ });
77
+
78
+ watcher.on('change', async (filePath) => {
79
+ const relativePath = path.relative(path.resolve(projectRoot, '..'), filePath);
80
+ console.log(`\nšŸ”„ Changed: ${relativePath}`);
81
+
82
+ try {
83
+ if (filePath.endsWith('.jux')) {
84
+ console.log(' → Compiling .jux file...');
85
+
86
+ // Determine if it's a vendor or project file
87
+ if (filePath.includes('/lib/layouts/')) {
88
+ // Vendor layout file
89
+ await compileJuxFile(filePath, {
90
+ distDir: path.join(distDir, 'lib'),
91
+ projectRoot: libRoot,
92
+ isServe: true
93
+ });
94
+ } else {
95
+ // Project file
96
+ await compileJuxFile(filePath, { distDir, projectRoot, isServe: true });
97
+ }
98
+
99
+ console.log(`āœ… Recompiled: ${relativePath}`);
100
+ } else if (filePath.includes('/lib/')) {
101
+ console.log(' → Rebuilding lib files...');
102
+ await copyLibToOutput(projectRoot, distDir);
103
+ console.log(`āœ… Rebuilt lib files`);
104
+ } else if (filePath.endsWith('.css')) {
105
+ console.log(' → CSS file changed');
106
+ console.log(`āœ… CSS change detected`);
107
+ } else if (filePath.includes('/machinery/')) {
108
+ console.log(' → Machinery file changed');
109
+ console.log(`āš ļø Restart server to apply changes`);
110
+ }
111
+
112
+ console.log(` → Notifying ${clients.length} client(s)`);
113
+ notifyClients(clients);
114
+ } catch (err) {
115
+ console.error(`āŒ Error processing ${relativePath}:`, err.message);
116
+ console.error(err.stack);
117
+ }
118
+ });
119
+
120
+ watcher.on('add', (filePath) => {
121
+ const relativePath = path.relative(path.resolve(projectRoot, '..'), filePath);
122
+ console.log(`āž• New file detected: ${relativePath}`);
123
+ });
124
+
125
+ watcher.on('unlink', (filePath) => {
126
+ const relativePath = path.relative(path.resolve(projectRoot, '..'), filePath);
127
+ console.log(`āž– File deleted: ${relativePath}`);
128
+ });
129
+
130
+ watcher.on('error', (error) => {
131
+ console.error('āŒ Watcher error:', error);
132
+ });
133
+
134
+ return watcher;
135
+ }
136
+
137
+ function notifyClients(clients) {
138
+ let notified = 0;
139
+ clients.forEach(client => {
140
+ if (client.readyState === 1) {
141
+ client.send(JSON.stringify({ type: 'reload' }));
142
+ notified++;
143
+ }
144
+ });
145
+ if (notified > 0) {
146
+ console.log(` āœ‰ļø Reloaded ${notified} client(s)`);
147
+ }
148
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "juxscript",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "A JavaScript UX authorship platform",
6
+ "main": "lib/jux.js",
7
+ "types": "types/globals.d.ts",
8
+ "bin": {
9
+ "jux": "./bin/cli.js"
10
+ },
11
+ "scripts": {
12
+ "dev": "cd examples && npx jux serve",
13
+ "build:examples": "cd examples && rm -rf dist && npx jux build",
14
+ "test": "node test/run-tests.js"
15
+ },
16
+ "files": [
17
+ "lib",
18
+ "bin",
19
+ "machinery",
20
+ "types",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/juxscript/jux.git"
30
+ },
31
+ "keywords": [
32
+ "jux",
33
+ "ui",
34
+ "authoring",
35
+ "javascript"
36
+ ],
37
+ "author": "Tim Kerr",
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "acorn": "^8.15.0",
41
+ "chokidar": "^5.0.0",
42
+ "clean-css": "^5.3.3",
43
+ "esbuild": "^0.27.2",
44
+ "express": "^5.2.1",
45
+ "glob": "^13.0.0",
46
+ "node": "^24.12.0",
47
+ "sql.js": "^1.10.3",
48
+ "terser": "^5.44.1",
49
+ "ws": "^8.19.0"
50
+ },
51
+ "optionalDependencies": {
52
+ "mysql2": "^3.6.5",
53
+ "pg": "^8.11.3"
54
+ },
55
+ "devDependencies": {
56
+ "typescript": "^5.9.3"
57
+ }
58
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Global type declarations for .jux files
3
+ */
4
+
5
+ import type { jux } from '../lib/jux';
6
+
7
+ declare global {
8
+ const jux: typeof import('../lib/jux').jux;
9
+
10
+ interface Window {
11
+ REACTIVE_DEBUG?: boolean;
12
+ juxContext?: Record<string, any>;
13
+ }
14
+ }
15
+
16
+ export {};