juxscript 1.0.0 → 1.0.2

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 CHANGED
@@ -21,7 +21,19 @@ Have you ever considered the energy requirements to ship chunks of HTML markup l
21
21
 
22
22
  ## GETTING STARTED
23
23
 
24
-
24
+ ```bash
25
+ # New project
26
+ mkdir my-project
27
+ cd my-project
28
+ npm init -y
29
+ npm install juxscript
30
+
31
+ # Initialize (creates jux/ directory)
32
+ npx jux init
33
+
34
+ # Builds jux-dist and serves index.jux
35
+ npx jux serve
36
+ ```
25
37
  > install
26
38
  `npm i juxscript`
27
39
 
package/bin/cli.js CHANGED
@@ -10,11 +10,42 @@ import { fileURLToPath } from 'url';
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = path.dirname(__filename);
12
12
 
13
+ // CLEAR PATH CONTRACT - CONVENTIONS
14
+ const PATHS = {
15
+ // Where jux package is installed (in node_modules/juxscript or local dev)
16
+ packageRoot: path.resolve(__dirname, '..'),
17
+
18
+ // Where the user's project root is (where they run `npx jux`)
19
+ projectRoot: process.cwd(),
20
+
21
+ // Where user's .jux source files live (CONVENTION: ./jux/)
22
+ get juxSource() {
23
+ return path.join(this.projectRoot, 'jux');
24
+ },
25
+
26
+ // Where jux lib files are (components, layouts, etc.)
27
+ get juxLib() {
28
+ return path.join(this.packageRoot, 'lib');
29
+ },
30
+
31
+ // Where frontend build output goes (CONVENTION: ./jux-dist/)
32
+ get frontendDist() {
33
+ return path.join(this.projectRoot, 'jux-dist');
34
+ }
35
+ };
36
+
37
+ console.log('šŸ“ JUX Paths:');
38
+ console.log(` Package: ${PATHS.packageRoot}`);
39
+ console.log(` Project: ${PATHS.projectRoot}`);
40
+ console.log(` Source: ${PATHS.juxSource}`);
41
+ console.log(` Output: ${PATHS.frontendDist}`);
42
+ console.log(` Lib: ${PATHS.juxLib}\n`);
43
+
13
44
  const command = process.argv[2];
14
- const projectRoot = process.cwd();
15
- const distDir = path.join(projectRoot, 'dist');
16
45
 
17
46
  function findJuxFiles(dir, fileList = []) {
47
+ if (!fs.existsSync(dir)) return fileList;
48
+
18
49
  const files = fs.readdirSync(dir);
19
50
 
20
51
  files.forEach(file => {
@@ -22,7 +53,7 @@ function findJuxFiles(dir, fileList = []) {
22
53
  const stat = fs.statSync(filePath);
23
54
 
24
55
  if (stat.isDirectory()) {
25
- if (file !== 'node_modules' && file !== 'dist' && file !== '.git') {
56
+ if (file !== 'node_modules' && file !== 'jux-dist' && file !== '.git' && file !== 'server') {
26
57
  findJuxFiles(filePath, fileList);
27
58
  }
28
59
  } else if (file.endsWith('.jux')) {
@@ -34,7 +65,7 @@ function findJuxFiles(dir, fileList = []) {
34
65
  }
35
66
 
36
67
  async function loadConfig() {
37
- const configPath = path.join(projectRoot, 'jux.config.js');
68
+ const configPath = path.join(PATHS.projectRoot, 'jux.config.js');
38
69
 
39
70
  if (fs.existsSync(configPath)) {
40
71
  try {
@@ -50,44 +81,55 @@ async function loadConfig() {
50
81
  }
51
82
 
52
83
  async function buildProject(isServe = false) {
53
- console.log('šŸ”Ø Building JUX project...\n');
84
+ console.log('šŸ”Ø Building JUX frontend...\n');
54
85
 
55
86
  try {
56
- if (fs.existsSync(distDir)) {
57
- fs.rmSync(distDir, { recursive: true, force: true });
87
+ // Verify jux source directory exists
88
+ if (!fs.existsSync(PATHS.juxSource)) {
89
+ console.error(`āŒ Source directory not found: ${PATHS.juxSource}`);
90
+ console.error(` Please create a 'jux/' directory with your .jux files`);
91
+ process.exit(1);
58
92
  }
59
- fs.mkdirSync(distDir, { recursive: true });
60
93
 
61
- // Step 1: Generate documentation FIRST
94
+ // Clean and create frontend dist
95
+ if (fs.existsSync(PATHS.frontendDist)) {
96
+ fs.rmSync(PATHS.frontendDist, { recursive: true, force: true });
97
+ }
98
+ fs.mkdirSync(PATHS.frontendDist, { recursive: true });
99
+
100
+ // Step 1: Generate documentation from jux lib
62
101
  console.log('šŸ“š Generating documentation...');
63
102
  try {
64
- await generateDocs(projectRoot);
103
+ await generateDocs(PATHS.juxLib);
65
104
  console.log('āœ… Documentation generated\n');
66
105
  } catch (error) {
67
106
  console.warn('āš ļø Failed to generate docs:', error.message);
68
107
  }
69
108
 
70
- // Step 2: Copy lib/ to dist/lib/ (includes docs-data.json)
71
- await copyLibToOutput(projectRoot, distDir);
109
+ // Step 2: Copy jux lib to frontend dist
110
+ await copyLibToOutput(PATHS.juxLib, PATHS.frontendDist);
72
111
 
73
- // Step 3: Copy project assets (CSS, JS) from project root
74
- await copyProjectAssets(projectRoot, distDir);
112
+ // Step 3: Copy project assets from jux/ (CSS, JS, images)
113
+ await copyProjectAssets(PATHS.juxSource, PATHS.frontendDist);
75
114
 
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`);
115
+ // Step 4: Compile .jux files from jux/ directory ONLY
116
+ const projectJuxFiles = findJuxFiles(PATHS.juxSource);
117
+ console.log(`Found ${projectJuxFiles.length} .jux file(s) in /jux\n`);
79
118
 
80
119
  for (const file of projectJuxFiles) {
81
120
  try {
82
- await compileJuxFile(file, { distDir, projectRoot, isServe });
121
+ await compileJuxFile(file, {
122
+ distDir: PATHS.frontendDist,
123
+ projectRoot: PATHS.juxSource,
124
+ isServe
125
+ });
83
126
  } catch (err) {
84
127
  console.error(`Error compiling ${file}:`, err.message);
85
128
  }
86
129
  }
87
130
 
88
- // Step 5: Find and compile vendor layout .jux files
89
- const libRoot = path.resolve(projectRoot, '../lib');
90
- const layoutsDir = path.join(libRoot, 'layouts');
131
+ // Step 5: Compile vendor layouts
132
+ const layoutsDir = path.join(PATHS.juxLib, 'layouts');
91
133
 
92
134
  if (fs.existsSync(layoutsDir)) {
93
135
  console.log('\nšŸ“ Compiling vendor layouts...');
@@ -96,11 +138,11 @@ async function buildProject(isServe = false) {
96
138
 
97
139
  for (const file of vendorJuxFiles) {
98
140
  try {
99
- const relPath = path.relative(libRoot, file);
141
+ const relPath = path.relative(PATHS.juxLib, file);
100
142
 
101
143
  await compileJuxFile(file, {
102
- distDir: path.join(distDir, 'lib'),
103
- projectRoot: libRoot,
144
+ distDir: path.join(PATHS.frontendDist, 'lib'),
145
+ projectRoot: PATHS.juxLib,
104
146
  isServe
105
147
  });
106
148
 
@@ -111,38 +153,123 @@ async function buildProject(isServe = false) {
111
153
  }
112
154
  }
113
155
 
114
- console.log(`\nāœ… Built ${projectJuxFiles.length} project file(s) + layouts\n`);
156
+ console.log(`\nāœ… Built ${projectJuxFiles.length} file(s) → ${PATHS.frontendDist}\n`);
157
+
158
+ // Show backend integration examples
159
+ console.log('šŸ“¦ Serve from your backend:');
160
+ console.log(` Express: app.use(express.static('jux-dist'))`);
161
+ console.log(` Flask: app = Flask(__name__, static_folder='jux-dist')`);
162
+ console.log(` FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")`);
163
+ console.log(` Laravel: Route::view('/', 'jux-dist/index.html')`);
164
+ console.log('');
165
+
115
166
  } catch (err) {
116
167
  console.error('āŒ Build error:', err.message);
168
+ console.error(err.stack);
117
169
  process.exit(1);
118
170
  }
119
171
  }
120
172
 
121
173
  (async () => {
122
- if (command === 'build') {
174
+ if (command === 'init') {
175
+ console.log('šŸŽØ Initializing JUX project...\n');
176
+
177
+ const juxDir = PATHS.juxSource;
178
+
179
+ if (fs.existsSync(juxDir)) {
180
+ console.error('āŒ jux/ directory already exists');
181
+ process.exit(1);
182
+ }
183
+
184
+ // Create structure
185
+ fs.mkdirSync(juxDir, { recursive: true });
186
+
187
+ // Copy template file from lib/templates/index.juxt
188
+ const templatePath = path.join(PATHS.packageRoot, 'lib', 'templates', 'index.juxt');
189
+ const targetPath = path.join(juxDir, 'index.jux');
190
+
191
+ if (fs.existsSync(templatePath)) {
192
+ fs.copyFileSync(templatePath, targetPath);
193
+ console.log('āœ… Created jux/index.jux from template');
194
+ } else {
195
+ // Fallback if template doesn't exist
196
+ console.warn('āš ļø Template not found, creating basic index.jux');
197
+ const fallbackContent = `// Welcome to JUX!
198
+ import { jux } from '/lib/jux.js';
199
+
200
+ jux.style('/lib/layouts/default.css');
201
+ jux.theme('light');
202
+
203
+ const header = jux.header('header').render("#app");
204
+ const main = jux.main('main').render("#app");
205
+ const footer = jux.footer('footer').render("#app");
206
+
207
+ jux.hero('hero1', {
208
+ title: 'Welcome to JUX',
209
+ subtitle: 'A JavaScript UX authorship platform'
210
+ }).render('#main');
211
+ `;
212
+ fs.writeFileSync(targetPath, fallbackContent);
213
+ console.log('āœ… Created jux/index.jux');
214
+ }
215
+
216
+ // Create .gitignore
217
+ const gitignorePath = path.join(PATHS.projectRoot, '.gitignore');
218
+ const gitignoreContent = `jux-dist/
219
+ node_modules/
220
+ .DS_Store
221
+ `;
222
+
223
+ if (!fs.existsSync(gitignorePath)) {
224
+ fs.writeFileSync(gitignorePath, gitignoreContent);
225
+ console.log('āœ… Created .gitignore');
226
+ }
227
+
228
+ console.log('āœ… Created jux/ directory\n');
229
+ console.log('Next steps:');
230
+ console.log(' 1. Edit jux/index.jux');
231
+ console.log(' 2. Run: npx jux build');
232
+ console.log(' 3. Serve jux-dist/ from your backend\n');
233
+
234
+ } else if (command === 'build') {
123
235
  await buildProject(false);
124
- console.log(`āœ… Build complete: ${distDir}`);
236
+ console.log(`āœ… Build complete: ${PATHS.frontendDist}`);
125
237
 
126
238
  } else if (command === 'serve') {
127
- // Build first
128
- await buildProject(true); // isServe = true
239
+ await buildProject(true);
129
240
 
130
- // Start server with watcher
131
241
  const config = await loadConfig();
132
- await start(3000, config);
242
+ config.distDir = PATHS.frontendDist; // Pass the correct dist directory
243
+
244
+ const port = parseInt(process.argv[3]) || 3000;
245
+ await start(port, config);
133
246
 
134
247
  } else {
135
248
  console.log(`
136
249
  JUX CLI - A JavaScript UX authorship platform
137
250
 
138
251
  Usage:
139
- npx jux build Compile all .jux files to HTML/CSS/JS
252
+ npx jux init Initialize a new JUX project
253
+ npx jux build Compile .jux files from ./jux/ to ./jux-dist/
140
254
  npx jux serve [port] Start dev server with hot reload (default: 3000)
141
- Builds automatically if dist/ doesn't exist
255
+
256
+ Project Structure (Convention):
257
+ my-project/
258
+ ā”œā”€ā”€ jux/ # Your .jux source files (REQUIRED)
259
+ │ ā”œā”€ā”€ index.jux
260
+ │ └── pages/
261
+ ā”œā”€ā”€ jux-dist/ # Build output (generated, git-ignore this)
262
+ ā”œā”€ā”€ server/ # Your backend (untouched by jux)
263
+ └── package.json
264
+
265
+ Getting Started:
266
+ 1. npx jux init # Create jux/ directory
267
+ 2. npx jux build # Build to jux-dist/
268
+ 3. Serve jux-dist/ from your backend
142
269
 
143
270
  Examples:
144
271
  npx jux build Build for production
145
- npx jux serve Start dev server (builds if needed)
272
+ npx jux serve Start dev server
146
273
  npx jux serve 8080 Start on port 8080
147
274
  `);
148
275
  }
@@ -1207,5 +1207,5 @@
1207
1207
  }
1208
1208
  ],
1209
1209
  "version": "1.0.0",
1210
- "lastUpdated": "2026-01-16T19:58:49.958Z"
1210
+ "lastUpdated": "2026-01-16T23:06:56.674Z"
1211
1211
  }
@@ -0,0 +1,33 @@
1
+ // Welcome to JUX!
2
+ import { code } from '/lib/components/code.js';
3
+ import { jux } from '/lib/jux.js';
4
+
5
+ jux.style('/lib/layouts/notion.css');
6
+ jux.theme('light');
7
+
8
+ const appheader = jux.header('appheader', {}).render("#app");
9
+ const appsubheader = jux.header('appsubheader', {}).render("#app");
10
+ const appsidebar = jux.sidebar('appsidebar', {}).render("#app");
11
+ const appmain = jux.main('appmain', {}).render("#app");
12
+ const appaside = jux.sidebar('appaside', {}).render("#app");
13
+ const appfooter = jux.footer('appfooter', {}).render("#app");
14
+ const appmodal = jux.modal('appmodal', {}).render("#app");
15
+
16
+
17
+ await jux.hero('hero1', {
18
+ title: 'Welcome to JUX',
19
+ subtitle: 'A JavaScript UX authorship platform'
20
+ }).render();
21
+
22
+ const code1 = code('code1').code(`Serve from your backend:
23
+ Express: app.use(express.static('jux-dist'))
24
+ Flask: app = Flask(__name__, static_folder='jux-dist')
25
+ FastAPI: app.mount("/", StaticFiles(directory="jux-dist"), name="static")
26
+ Laravel: Route::view('/', 'jux-dist/index.html')`)
27
+ .language('bash');
28
+
29
+ code1.render();
30
+
31
+ // note
32
+ // render()
33
+ // renderTo()
@@ -4,10 +4,15 @@ import { glob } from 'glob';
4
4
 
5
5
  /**
6
6
  * Generate documentation from TypeScript component files
7
+ *
8
+ * @param {string} juxLibPath - Absolute path to jux/lib directory
7
9
  */
8
- export async function generateDocs(projectRoot) {
9
- const libRoot = path.resolve(projectRoot, '../lib');
10
- const componentsDir = path.join(libRoot, 'components');
10
+ export async function generateDocs(juxLibPath) {
11
+ const componentsDir = path.join(juxLibPath, 'components');
12
+
13
+ if (!fs.existsSync(componentsDir)) {
14
+ throw new Error(`Components directory not found: ${componentsDir}`);
15
+ }
11
16
 
12
17
  console.log(` Scanning: ${componentsDir}`);
13
18
 
@@ -15,7 +20,7 @@ export async function generateDocs(projectRoot) {
15
20
  const componentFiles = glob.sync('*.ts', {
16
21
  cwd: componentsDir,
17
22
  absolute: true,
18
- ignore: ['reactivity.js', 'error-handler.ts', 'docs.ts']
23
+ ignore: ['reactivity.ts', 'error-handler.ts']
19
24
  });
20
25
 
21
26
  console.log(` Found ${componentFiles.length} component files`);
@@ -62,23 +67,14 @@ function parseComponentFile(content, filePath) {
62
67
  const fileName = path.basename(filePath, '.ts');
63
68
  const className = fileName.charAt(0).toUpperCase() + fileName.slice(1);
64
69
 
65
- // Extract category from file content or infer
66
70
  const category = inferCategory(className, content);
67
-
68
- // Extract description from class JSDoc
69
71
  const descMatch = content.match(/\/\*\*\s*\n\s*\*\s*([^\n*]+)/);
70
72
  const description = descMatch ? descMatch[1].trim() : `${className} component`;
71
-
72
- // Extract constructor/factory pattern
73
73
  const factoryMatch = content.match(/export function\s+\w+\(([^)]*)\)/);
74
74
  const constructorSig = factoryMatch
75
75
  ? `jux.${fileName}(${factoryMatch[1]})`
76
76
  : `new ${className}()`;
77
-
78
- // Extract fluent methods
79
77
  const fluentMethods = extractFluentMethods(content, className);
80
-
81
- // Extract usage example from JSDoc
82
78
  const exampleMatch = content.match(/\*\s+Usage:\s*\n\s*\*\s+(.+?)(?:\n|$)/);
83
79
  let example = exampleMatch
84
80
  ? exampleMatch[1].trim()
@@ -99,28 +95,14 @@ function parseComponentFile(content, filePath) {
99
95
  */
100
96
  function extractFluentMethods(content, className) {
101
97
  const methods = [];
102
-
103
- // Match method patterns: methodName(params): this
104
98
  const methodRegex = /^\s*(\w+)\(([^)]*)\):\s*this\s*\{/gm;
105
99
  let match;
106
100
 
107
101
  while ((match = methodRegex.exec(content)) !== null) {
108
102
  const methodName = match[1];
109
103
  const params = match[2];
110
-
111
- // Skip private methods and constructor
112
104
  if (methodName.startsWith('_') || methodName === 'constructor') continue;
113
-
114
- // Clean up params
115
- const cleanParams = params
116
- .split(',')
117
- .map(p => {
118
- const parts = p.trim().split(':');
119
- return parts[0].trim();
120
- })
121
- .filter(p => p)
122
- .join(', ');
123
-
105
+ const cleanParams = params.split(',').map(p => p.trim().split(':')[0].trim()).filter(p => p).join(', ');
124
106
  methods.push({
125
107
  name: methodName,
126
108
  params: cleanParams ? `(${cleanParams})` : '()',
@@ -129,7 +111,6 @@ function extractFluentMethods(content, className) {
129
111
  });
130
112
  }
131
113
 
132
- // Also look for render methods
133
114
  const renderMatch = content.match(/^\s*render\(([^)]*)\):\s*(\w+)/m);
134
115
  if (renderMatch && !methods.find(m => m.name === 'render')) {
135
116
  methods.push({
@@ -149,12 +130,7 @@ function extractFluentMethods(content, className) {
149
130
  function inferCategory(name, content) {
150
131
  const dataComponents = ['Table', 'List', 'Chart', 'Data'];
151
132
  const coreComponents = ['App', 'Layout', 'Theme', 'Style', 'Script', 'Import'];
152
-
153
- if (dataComponents.some(dc => name.includes(dc))) {
154
- return 'Data Components';
155
- }
156
- if (coreComponents.includes(name)) {
157
- return 'Core';
158
- }
133
+ if (dataComponents.some(dc => name.includes(dc))) return 'Data Components';
134
+ if (coreComponents.includes(name)) return 'Core';
159
135
  return 'UI Components';
160
136
  }
@@ -12,7 +12,7 @@ const __dirname = path.dirname(__filename);
12
12
 
13
13
  let db = null;
14
14
 
15
- async function serve(port = 3000, distDir = './dist') {
15
+ async function serve(port = 3000, distDir = './jux-dist') { // Changed default
16
16
  const app = express();
17
17
  const absoluteDistDir = path.resolve(distDir);
18
18
  const projectRoot = path.resolve('.');
@@ -20,8 +20,8 @@ async function serve(port = 3000, distDir = './dist') {
20
20
  app.use(express.json());
21
21
 
22
22
  if (!fs.existsSync(absoluteDistDir)) {
23
- console.error(`Error: dist directory not found at ${absoluteDistDir}`);
24
- console.log('Building project first...\n');
23
+ console.error(`āŒ Error: ${path.basename(distDir)}/ directory not found at ${absoluteDistDir}`);
24
+ console.error(' Run: npx jux build\n');
25
25
  process.exit(1);
26
26
  }
27
27
 
@@ -146,16 +146,23 @@ async function serve(port = 3000, distDir = './dist') {
146
146
  // Start HTTP server
147
147
  server.listen(port, () => {
148
148
  console.log(`šŸš€ JUX dev server running at http://localhost:${port}`);
149
+ console.log(` Serving: ${absoluteDistDir}`);
150
+ console.log(` Press Ctrl+C to stop\n`);
149
151
  });
150
152
 
151
153
  // Start file watcher
152
- startWatcher(projectRoot, absoluteDistDir, clients);
154
+ const juxSource = path.join(projectRoot, 'jux');
155
+ if (fs.existsSync(juxSource)) {
156
+ console.log(`šŸ‘€ Watching: ${juxSource}\n`);
157
+ startWatcher(juxSource, absoluteDistDir, clients);
158
+ }
153
159
 
154
160
  // Graceful shutdown
155
161
  const shutdown = async () => {
156
162
  console.log('\n\nšŸ‘‹ Shutting down server...');
157
163
  wss.close();
158
164
  server.close();
165
+ if (db) db = null;
159
166
  process.exit(0);
160
167
  };
161
168
 
@@ -181,5 +188,5 @@ async function initDatabase() {
181
188
 
182
189
  export async function start(port = 3000, config = {}) {
183
190
  await initDatabase();
184
- return serve(port, config.distDir || './dist');
191
+ return serve(port, config.distDir || './jux-dist'); // Changed default
185
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",