juxscript 1.0.3 → 1.0.4

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 (71) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/button.ts +188 -53
  7. package/lib/components/card.ts +75 -61
  8. package/lib/components/chart.ts +17 -15
  9. package/lib/components/checkbox.ts +228 -0
  10. package/lib/components/code.ts +66 -152
  11. package/lib/components/container.ts +104 -208
  12. package/lib/components/data.ts +1 -3
  13. package/lib/components/datepicker.ts +226 -0
  14. package/lib/components/dialog.ts +258 -0
  15. package/lib/components/docs-data.json +1697 -388
  16. package/lib/components/dropdown.ts +244 -0
  17. package/lib/components/element.ts +271 -0
  18. package/lib/components/fileupload.ts +319 -0
  19. package/lib/components/footer.ts +37 -18
  20. package/lib/components/header.ts +53 -33
  21. package/lib/components/heading.ts +119 -0
  22. package/lib/components/helpers.ts +34 -0
  23. package/lib/components/hero.ts +57 -31
  24. package/lib/components/include.ts +292 -0
  25. package/lib/components/input.ts +166 -78
  26. package/lib/components/layout.ts +144 -18
  27. package/lib/components/list.ts +83 -74
  28. package/lib/components/loading.ts +263 -0
  29. package/lib/components/main.ts +43 -17
  30. package/lib/components/menu.ts +108 -24
  31. package/lib/components/modal.ts +50 -21
  32. package/lib/components/nav.ts +60 -18
  33. package/lib/components/paragraph.ts +111 -0
  34. package/lib/components/progress.ts +276 -0
  35. package/lib/components/radio.ts +236 -0
  36. package/lib/components/req.ts +300 -0
  37. package/lib/components/script.ts +33 -74
  38. package/lib/components/select.ts +247 -0
  39. package/lib/components/sidebar.ts +86 -36
  40. package/lib/components/style.ts +47 -70
  41. package/lib/components/switch.ts +261 -0
  42. package/lib/components/table.ts +47 -24
  43. package/lib/components/tabs.ts +105 -63
  44. package/lib/components/theme-toggle.ts +361 -0
  45. package/lib/components/token-calculator.ts +380 -0
  46. package/lib/components/tooltip.ts +244 -0
  47. package/lib/components/view.ts +36 -20
  48. package/lib/components/write.ts +284 -0
  49. package/lib/globals.d.ts +21 -0
  50. package/lib/jux.ts +172 -68
  51. package/lib/presets/notion.css +521 -0
  52. package/lib/presets/notion.jux +27 -0
  53. package/lib/reactivity/state.ts +364 -0
  54. package/machinery/compiler.js +126 -38
  55. package/machinery/generators/html.js +2 -3
  56. package/machinery/server.js +2 -2
  57. package/package.json +29 -3
  58. package/lib/components/import.ts +0 -430
  59. package/lib/components/node.ts +0 -200
  60. package/lib/components/reactivity.js +0 -104
  61. package/lib/components/theme.ts +0 -97
  62. package/lib/layouts/notion.css +0 -258
  63. package/lib/styles/base-theme.css +0 -186
  64. package/lib/styles/dark-theme.css +0 -144
  65. package/lib/styles/light-theme.css +0 -144
  66. package/lib/styles/tokens/dark.css +0 -86
  67. package/lib/styles/tokens/light.css +0 -86
  68. package/lib/templates/index.juxt +0 -33
  69. package/lib/themes/dark.css +0 -86
  70. package/lib/themes/light.css +0 -86
  71. /package/lib/{styles → presets}/global.css +0 -0
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Reactive state container
3
+ * Notifies subscribers when value changes
4
+ */
5
+ export class State<T> {
6
+ private _value: T;
7
+ private _subscribers: Set<(value: T) => void> = new Set();
8
+ private _effects: Set<Effect> = new Set();
9
+
10
+ constructor(initialValue: T) {
11
+ this._value = initialValue;
12
+ }
13
+
14
+ /**
15
+ * Get current value
16
+ */
17
+ get value(): T {
18
+ return this._value;
19
+ }
20
+
21
+ /**
22
+ * Set new value and notify subscribers
23
+ */
24
+ set(newValue: T): void {
25
+ if (this._value !== newValue) {
26
+ this._value = newValue;
27
+ this._notify();
28
+ this._runEffects();
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Update value using a function
34
+ */
35
+ update(fn: (current: T) => T): void {
36
+ this.set(fn(this._value));
37
+ }
38
+
39
+ /**
40
+ * Subscribe to value changes
41
+ * Returns unsubscribe function
42
+ */
43
+ subscribe(callback: (value: T) => void): () => void {
44
+ this._subscribers.add(callback);
45
+ callback(this._value); // Call immediately with current value
46
+ return () => this._subscribers.delete(callback);
47
+ }
48
+
49
+ /**
50
+ * Notify all subscribers of value change
51
+ */
52
+ private _notify(): void {
53
+ this._subscribers.forEach(callback => callback(this._value));
54
+ }
55
+
56
+ /**
57
+ * Run all registered effects
58
+ */
59
+ private _runEffects(): void {
60
+ this._effects.forEach(effect => effect.run(this._value));
61
+ }
62
+
63
+ /* -------------------------
64
+ * Higher-Order Effects
65
+ * ------------------------- */
66
+
67
+ /**
68
+ * Bind visibility to element (v-show style)
69
+ * Toggles display: none on/off
70
+ * For animations, use bindClass() instead
71
+ */
72
+ bindVisibility(elementId: string): () => void {
73
+ if (typeof this._value !== 'boolean') {
74
+ console.warn(`State.bindVisibility: State value must be boolean, got ${typeof this._value}`);
75
+ return () => { };
76
+ }
77
+
78
+ const effect = new VisibilityEffect(elementId);
79
+ this._effects.add(effect);
80
+ effect.run(this._value); // Initial run
81
+
82
+ return () => this._effects.delete(effect);
83
+ }
84
+
85
+ /**
86
+ * Bind value property to element (for progress, input, range, etc.)
87
+ * @param elementId - DOM element ID
88
+ * @param transform - Optional transform function
89
+ */
90
+ bindValue(elementId: string, transform?: (val: T) => number | string): () => void {
91
+ const effect = new ValueEffect(elementId, transform);
92
+ this._effects.add(effect);
93
+ effect.run(this._value);
94
+
95
+ return () => this._effects.delete(effect);
96
+ }
97
+
98
+ /**
99
+ * Bind text content to element
100
+ * @param elementId - DOM element ID
101
+ * @param transform - Optional transform function
102
+ */
103
+ bindText(elementId: string, transform?: (val: T) => string): () => void {
104
+ const effect = new TextEffect(elementId, transform);
105
+ this._effects.add(effect);
106
+ effect.run(this._value);
107
+
108
+ return () => this._effects.delete(effect);
109
+ }
110
+
111
+ /**
112
+ * Bind attribute to element
113
+ * @param elementId - DOM element ID
114
+ * @param attrName - Attribute name
115
+ * @param transform - Optional transform function
116
+ */
117
+ bindAttr(elementId: string, attrName: string, transform?: (val: T) => string): () => void {
118
+ const effect = new AttrEffect(elementId, attrName, transform);
119
+ this._effects.add(effect);
120
+ effect.run(this._value);
121
+
122
+ return () => this._effects.delete(effect);
123
+ }
124
+
125
+ /**
126
+ * Bind CSS class to element
127
+ * @param elementId - DOM element ID
128
+ * @param className - Class to toggle
129
+ * @param condition - Optional condition function
130
+ */
131
+ bindClass(elementId: string, className: string, condition?: (val: T) => boolean): () => void {
132
+ const effect = new ClassEffect(elementId, className, condition);
133
+ this._effects.add(effect);
134
+ effect.run(this._value);
135
+
136
+ return () => this._effects.delete(effect);
137
+ }
138
+
139
+ /**
140
+ * Bind innerHTML to element
141
+ * @param elementId - DOM element ID
142
+ * @param transform - Optional transform function
143
+ */
144
+ bindHTML(elementId: string, transform?: (val: T) => string): () => void {
145
+ const effect = new HTMLEffect(elementId, transform);
146
+ this._effects.add(effect);
147
+ effect.run(this._value);
148
+
149
+ return () => this._effects.delete(effect);
150
+ }
151
+
152
+ /* -------------------------
153
+ * Number Helpers
154
+ * ------------------------- */
155
+
156
+ /**
157
+ * Increment (numbers only)
158
+ */
159
+ increment = (): void => {
160
+ if (typeof this._value === 'number') {
161
+ this.set((this._value + 1) as T);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Decrement (numbers only)
167
+ */
168
+ decrement = (): void => {
169
+ if (typeof this._value === 'number') {
170
+ this.set((this._value - 1) as T);
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Add amount (returns function for event handlers)
176
+ */
177
+ add = (amount: number) => (): void => {
178
+ if (typeof this._value === 'number') {
179
+ this.set((this._value + amount) as T);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Subtract amount
185
+ */
186
+ subtract = (amount: number) => (): void => {
187
+ if (typeof this._value === 'number') {
188
+ this.set((this._value - amount) as T);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Multiply by factor
194
+ */
195
+ multiply = (factor: number) => (): void => {
196
+ if (typeof this._value === 'number') {
197
+ this.set((this._value * factor) as T);
198
+ }
199
+ }
200
+
201
+ /* -------------------------
202
+ * Boolean Helpers
203
+ * ------------------------- */
204
+
205
+ /**
206
+ * Toggle (booleans only)
207
+ */
208
+ toggle = (): void => {
209
+ if (typeof this._value === 'boolean') {
210
+ this.set((!this._value) as T);
211
+ }
212
+ }
213
+
214
+ /* -------------------------
215
+ * General Helpers
216
+ * ------------------------- */
217
+
218
+ /**
219
+ * Reset to initial value (returns function)
220
+ */
221
+ reset = (initialValue: T) => (): void => {
222
+ this.set(initialValue);
223
+ }
224
+ }
225
+
226
+ /* -------------------------
227
+ * Effect Classes
228
+ * ------------------------- */
229
+
230
+ interface Effect {
231
+ run(value: any): void;
232
+ }
233
+
234
+ class VisibilityEffect implements Effect {
235
+ constructor(
236
+ private elementId: string
237
+ ) { }
238
+
239
+ run(visible: boolean): void {
240
+ const el = document.getElementById(this.elementId);
241
+ if (!el) return;
242
+
243
+ // Simple display toggle (Vue v-show style)
244
+ el.style.display = visible ? '' : 'none';
245
+ }
246
+ }
247
+
248
+ interface VisibilityOptions {
249
+ mode?: 'display' | 'visibility' | 'opacity' | 'class';
250
+ visibleClass?: string;
251
+ hiddenClass?: string;
252
+ }
253
+
254
+ class TextEffect implements Effect {
255
+ constructor(
256
+ private elementId: string,
257
+ private transform?: (val: any) => string
258
+ ) { }
259
+
260
+ run(value: any): void {
261
+ const el = document.getElementById(this.elementId);
262
+ if (!el) return;
263
+
264
+ const text = this.transform ? this.transform(value) : String(value);
265
+ el.textContent = text;
266
+ }
267
+ }
268
+
269
+ class HTMLEffect implements Effect {
270
+ constructor(
271
+ private elementId: string,
272
+ private transform?: (val: any) => string
273
+ ) { }
274
+
275
+ run(value: any): void {
276
+ const el = document.getElementById(this.elementId);
277
+ if (!el) return;
278
+
279
+ const html = this.transform ? this.transform(value) : String(value);
280
+ el.innerHTML = html;
281
+ }
282
+ }
283
+
284
+ class AttrEffect implements Effect {
285
+ constructor(
286
+ private elementId: string,
287
+ private attrName: string,
288
+ private transform?: (val: any) => string
289
+ ) { }
290
+
291
+ run(value: any): void {
292
+ const el = document.getElementById(this.elementId);
293
+ if (!el) return;
294
+
295
+ const attrValue = this.transform ? this.transform(value) : String(value);
296
+ el.setAttribute(this.attrName, attrValue);
297
+ }
298
+ }
299
+
300
+ class ClassEffect implements Effect {
301
+ constructor(
302
+ private elementId: string,
303
+ private className: string,
304
+ private condition?: (val: any) => boolean
305
+ ) { }
306
+
307
+ run(value: any): void {
308
+ const el = document.getElementById(this.elementId);
309
+ if (!el) return;
310
+
311
+ const shouldAdd = this.condition ? this.condition(value) : Boolean(value);
312
+ if (shouldAdd) {
313
+ el.classList.add(this.className);
314
+ } else {
315
+ el.classList.remove(this.className);
316
+ }
317
+ }
318
+ }
319
+
320
+ class ValueEffect implements Effect {
321
+ constructor(
322
+ private elementId: string,
323
+ private transform?: (val: any) => number | string
324
+ ) { }
325
+
326
+ run(value: any): void {
327
+ const el = document.getElementById(this.elementId);
328
+ if (!el) return;
329
+
330
+ const newValue = this.transform ? this.transform(value) : value;
331
+
332
+ // For progress/jux components, set data-value and trigger update
333
+ if (el.classList.contains('jux-progress')) {
334
+ el.setAttribute('data-value', String(newValue));
335
+
336
+ // Find the progress instance and call value() to trigger update
337
+ const progressBar = el.querySelector('.jux-progress-bar');
338
+ if (progressBar) {
339
+ const percentage = typeof newValue === 'number' ? newValue : parseFloat(String(newValue));
340
+ const max = parseFloat(progressBar.getAttribute('aria-valuemax') || '100');
341
+ const percentageValue = (percentage / max) * 100;
342
+ (progressBar as HTMLElement).style.width = `${percentageValue}%`;
343
+ progressBar.setAttribute('aria-valuenow', String(percentage));
344
+ }
345
+ return;
346
+ }
347
+
348
+ // For native HTML elements (input, progress, meter)
349
+ if ('value' in el) {
350
+ (el as any).value = String(newValue);
351
+ }
352
+
353
+ if (el instanceof HTMLProgressElement || el.tagName === 'METER') {
354
+ el.setAttribute('value', String(newValue));
355
+ }
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Create reactive state
361
+ */
362
+ export function state<T>(initialValue: T): State<T> {
363
+ return new State(initialValue);
364
+ }
@@ -1,14 +1,23 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import esbuild from 'esbuild';
4
- import { generateDocs } from './doc-generator.js';
5
4
 
5
+ /**
6
+ * Compile a .jux file to .js and .html
7
+ *
8
+ * @param {string} juxFilePath - Path to the .jux file
9
+ * @param {Object} options - Compilation options
10
+ * @param {string} options.distDir - Output directory
11
+ * @param {string} options.projectRoot - Project root directory
12
+ * @param {boolean} options.isServe - Whether serving for development
13
+ * @returns {Promise<{jsOutputPath: string, htmlOutputPath: string}>}
14
+ */
6
15
  export async function compileJuxFile(juxFilePath, options = {}) {
7
16
  const { distDir, projectRoot, isServe = false } = options;
8
-
17
+
9
18
  const relativePath = path.relative(projectRoot, juxFilePath);
10
19
  const parsedPath = path.parse(relativePath);
11
-
20
+
12
21
  // Output paths
13
22
  const outputDir = path.join(distDir, parsedPath.dir);
14
23
  const jsOutputPath = path.join(outputDir, `${parsedPath.name}.js`);
@@ -27,17 +36,17 @@ export async function compileJuxFile(juxFilePath, options = {}) {
27
36
  // Calculate depth for relative paths
28
37
  const depth = parsedPath.dir.split(path.sep).filter(p => p).length;
29
38
  const libPath = depth === 0 ? './lib/jux.js' : '../'.repeat(depth) + 'lib/jux.js';
30
- const styleBasePath = depth === 0 ? './lib/styles/' : '../'.repeat(depth) + 'lib/styles/';
39
+ const styleBasePath = depth === 0 ? './lib/presets/' : '../'.repeat(depth) + 'lib/presets/';
31
40
 
32
41
  // Transform imports
33
42
  let transformedContent = juxContent;
34
-
43
+
35
44
  // Replace common import patterns with calculated path
36
45
  transformedContent = transformedContent.replace(
37
46
  /from\s+['"]\.\.?\/lib\/jux\.js['"]/g,
38
47
  `from '${libPath}'`
39
48
  );
40
-
49
+
41
50
  // Only inject import if:
42
51
  // 1. File is not empty (ignoring whitespace and comments)
43
52
  // 2. File uses 'jux.' but has no import statement
@@ -45,11 +54,11 @@ export async function compileJuxFile(juxFilePath, options = {}) {
45
54
  .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
46
55
  .replace(/\/\/.*/g, '') // Remove line comments
47
56
  .trim();
48
-
57
+
49
58
  const hasContent = contentWithoutComments.length > 0;
50
59
  const usesJux = /\bjux\./g.test(contentWithoutComments);
51
60
  const hasImport = /import\s+.*from/.test(transformedContent);
52
-
61
+
53
62
  if (hasContent && usesJux && !hasImport) {
54
63
  transformedContent = `import { jux } from '${libPath}';\n\n${transformedContent}`;
55
64
  }
@@ -61,28 +70,19 @@ export async function compileJuxFile(juxFilePath, options = {}) {
61
70
 
62
71
  // Generate HTML with correct script path
63
72
  const scriptPath = `./${parsedPath.name}.js`;
64
-
73
+
65
74
  const html = `<!DOCTYPE html>
66
75
  <html lang="en">
67
76
  <head>
68
77
  <meta charset="UTF-8">
69
78
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
70
79
  <title>${parsedPath.name}</title>
71
-
72
80
  <!-- JUX Core Styles -->
73
- <link rel="stylesheet" href="${styleBasePath}tokens/dark.css">
74
81
  <link rel="stylesheet" href="${styleBasePath}global.css">
75
82
  </head>
76
- <body data-theme="dark">
83
+ <body data-theme="">
77
84
  <!-- App container -->
78
85
  <div id="app" data-jux-page="${parsedPath.name}"></div>
79
-
80
- <!-- Main content -->
81
- <div id="appmain"></div>
82
-
83
- <!-- Modal overlay -->
84
- <div id="appmodal" aria-hidden="true" role="dialog"></div>
85
-
86
86
  <script type="module" src="${scriptPath}"></script>
87
87
  ${isServe ? `
88
88
  <!-- Hot reload -->
@@ -107,10 +107,16 @@ export async function compileJuxFile(juxFilePath, options = {}) {
107
107
  return { jsOutputPath, htmlOutputPath };
108
108
  }
109
109
 
110
+ /**
111
+ * Copy and build the JUX library from TypeScript to JavaScript
112
+ *
113
+ * @param {string} projectRoot - Root directory containing lib/
114
+ * @param {string} distDir - Destination directory for built files
115
+ */
110
116
  export async function copyLibToOutput(projectRoot, distDir) {
111
117
  // Simplified lib path resolution
112
118
  const libSrc = path.resolve(projectRoot, '../lib');
113
-
119
+
114
120
  if (!fs.existsSync(libSrc)) {
115
121
  throw new Error(`lib/ directory not found at ${libSrc}`);
116
122
  }
@@ -129,7 +135,7 @@ export async function copyLibToOutput(projectRoot, distDir) {
129
135
 
130
136
  // Find all TypeScript entry points
131
137
  const tsFiles = findFiles(libSrc, '.ts');
132
-
138
+
133
139
  if (tsFiles.length === 0) {
134
140
  console.warn('⚠️ No TypeScript files found in lib/');
135
141
  return;
@@ -168,50 +174,123 @@ export async function copyLibToOutput(projectRoot, distDir) {
168
174
  console.log('✅ Library ready\n');
169
175
  }
170
176
 
177
+ /**
178
+ * Copy project assets (CSS, JS, images) from jux/ to dist/
179
+ *
180
+ * @param {string} projectRoot - Source directory (jux/)
181
+ * @param {string} distDir - Destination directory (jux-dist/)
182
+ */
171
183
  export async function copyProjectAssets(projectRoot, distDir) {
172
184
  console.log('📦 Copying project assets...');
173
-
185
+
174
186
  // Find all CSS and JS files in project root (excluding node_modules, dist, .git)
175
187
  const allFiles = [];
176
188
  findProjectFiles(projectRoot, ['.css', '.js'], allFiles, projectRoot);
177
-
189
+
178
190
  console.log(` Found ${allFiles.length} asset file(s)`);
179
-
191
+
180
192
  for (const srcPath of allFiles) {
181
193
  const relativePath = path.relative(projectRoot, srcPath);
182
194
  const destPath = path.join(distDir, relativePath);
183
195
  const destDir = path.dirname(destPath);
184
-
196
+
185
197
  // Create destination directory if needed
186
198
  if (!fs.existsSync(destDir)) {
187
199
  fs.mkdirSync(destDir, { recursive: true });
188
200
  }
189
-
201
+
190
202
  // Copy file
191
203
  fs.copyFileSync(srcPath, destPath);
192
204
  console.log(` ✓ ${relativePath}`);
193
205
  }
194
-
206
+
195
207
  console.log('✅ Project assets copied\n');
196
208
  }
197
209
 
210
+ /**
211
+ * Transpile TypeScript files from jux/ to jux-dist/, preserving folder structure
212
+ *
213
+ * @param {string} srcDir - Source directory (jux/)
214
+ * @param {string} destDir - Destination directory (jux-dist/)
215
+ * @example
216
+ * // jux/samples/mypage.ts -> jux-dist/samples/mypage.js
217
+ * await transpileProjectTypeScript('jux/', 'jux-dist/');
218
+ */
219
+ export async function transpileProjectTypeScript(srcDir, destDir) {
220
+ console.log('🔷 Transpiling TypeScript files...');
221
+
222
+ // Find all TypeScript files in the project
223
+ const tsFiles = findFiles(srcDir, '.ts');
224
+
225
+ if (tsFiles.length === 0) {
226
+ console.log(' No TypeScript files found in project');
227
+ return;
228
+ }
229
+
230
+ console.log(` Found ${tsFiles.length} TypeScript file(s)`);
231
+
232
+ try {
233
+ // Build all TypeScript files with esbuild
234
+ await esbuild.build({
235
+ entryPoints: tsFiles,
236
+ bundle: false,
237
+ format: 'esm',
238
+ outdir: destDir,
239
+ outbase: srcDir,
240
+ platform: 'browser',
241
+ target: 'es2020',
242
+ loader: {
243
+ '.ts': 'ts'
244
+ },
245
+ logLevel: 'warning'
246
+ });
247
+
248
+ // Log each transpiled file
249
+ tsFiles.forEach(tsFile => {
250
+ const relativePath = path.relative(srcDir, tsFile);
251
+ const jsPath = relativePath.replace(/\.ts$/, '.js');
252
+ console.log(` ✓ ${relativePath} → ${jsPath}`);
253
+ });
254
+
255
+ console.log('✅ TypeScript transpiled\n');
256
+
257
+ } catch (err) {
258
+ console.error('❌ Failed to transpile TypeScript:', err.message);
259
+ throw err;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Recursively find files with a specific extension
265
+ *
266
+ * @param {string} dir - Directory to search
267
+ * @param {string} extension - File extension (e.g., '.ts')
268
+ * @param {string[]} fileList - Accumulator for found files
269
+ * @returns {string[]} Array of file paths
270
+ */
198
271
  function findFiles(dir, extension, fileList = []) {
199
272
  const files = fs.readdirSync(dir);
200
-
273
+
201
274
  files.forEach(file => {
202
275
  const filePath = path.join(dir, file);
203
276
  const stat = fs.statSync(filePath);
204
-
277
+
205
278
  if (stat.isDirectory()) {
206
279
  findFiles(filePath, extension, fileList);
207
280
  } else if (file.endsWith(extension)) {
208
281
  fileList.push(filePath);
209
282
  }
210
283
  });
211
-
284
+
212
285
  return fileList;
213
286
  }
214
287
 
288
+ /**
289
+ * Copy non-TypeScript files (CSS, JSON, JS, SVG, etc.)
290
+ *
291
+ * @param {string} src - Source directory
292
+ * @param {string} dest - Destination directory
293
+ */
215
294
  function copyNonTsFiles(src, dest) {
216
295
  const entries = fs.readdirSync(src, { withFileTypes: true });
217
296
 
@@ -226,23 +305,32 @@ function copyNonTsFiles(src, dest) {
226
305
  copyNonTsFiles(srcPath, destPath);
227
306
  } else if (entry.isFile()) {
228
307
  const ext = path.extname(entry.name);
229
- // Copy CSS, JSON, and JS files (but not .ts files)
230
- if (ext === '.css' || ext === '.json' || (ext === '.js' && !srcPath.endsWith('.ts'))) {
308
+ // Copy CSS, JSON, SVG, and JS files (but not .ts files)
309
+ if (['.css', '.json', '.js', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp'].includes(ext)) {
231
310
  fs.copyFileSync(srcPath, destPath);
232
- console.log(` → Copied: ${path.relative(src, srcPath)}`);
233
311
  }
234
312
  }
235
313
  }
236
314
  }
237
315
 
238
- function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, excludeDirs = ['node_modules', 'dist', '.git', 'lib']) {
316
+ /**
317
+ * Find project files with specific extensions, excluding certain directories
318
+ *
319
+ * @param {string} dir - Directory to search
320
+ * @param {string[]} extensions - File extensions to find
321
+ * @param {string[]} fileList - Accumulator for found files
322
+ * @param {string} rootDir - Root directory for relative paths
323
+ * @param {string[]} excludeDirs - Directories to exclude
324
+ * @returns {string[]} Array of file paths
325
+ */
326
+ function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, excludeDirs = ['node_modules', 'jux-dist', '.git', 'lib']) {
239
327
  if (!fs.existsSync(dir)) return fileList;
240
-
328
+
241
329
  const entries = fs.readdirSync(dir, { withFileTypes: true });
242
-
330
+
243
331
  for (const entry of entries) {
244
332
  const fullPath = path.join(dir, entry.name);
245
-
333
+
246
334
  if (entry.isDirectory()) {
247
335
  // Skip excluded directories
248
336
  if (excludeDirs.includes(entry.name)) {
@@ -257,6 +345,6 @@ function findProjectFiles(dir, extensions, fileList = [], rootDir = dir, exclude
257
345
  }
258
346
  }
259
347
  }
260
-
348
+
261
349
  return fileList;
262
350
  }
@@ -42,8 +42,7 @@ export function generateHTML(fileName, options = {}) {
42
42
  <title>${fileName}</title>
43
43
 
44
44
  <!-- JUX Core Styles -->
45
- <link rel="stylesheet" href="${prefix}lib/styles/tokens/dark.css">
46
- <link rel="stylesheet" href="${prefix}lib/styles/global.css">
45
+ <link rel="stylesheet" href="${prefix}lib/presets/global.css">
47
46
  </head>
48
47
  <body data-theme="dark">
49
48
  ${appStructure}
@@ -61,7 +60,7 @@ ${appStructure}
61
60
  */
62
61
  function buildAppStructure(pageName) {
63
62
  const page = pageName || 'index';
64
-
63
+
65
64
  return ` <!-- App Container -->
66
65
  <div id="app" data-jux-page="${page}">
67
66
  <!-- Header -->
@@ -186,7 +186,7 @@ async function initDatabase() {
186
186
  }
187
187
  }
188
188
 
189
- export async function start(port = 3000, config = {}) {
189
+ export async function start(port = 3000) {
190
190
  await initDatabase();
191
- return serve(port, config.distDir || './jux-dist'); // Changed default
191
+ return serve(port, './jux-dist'); // Changed default
192
192
  }