juxscript 1.0.129 → 1.0.131

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/index.js CHANGED
@@ -16,7 +16,7 @@ export { List } from './lib/componentsv2/list/List.js';
16
16
  export { BaseEngine } from './lib/componentsv2/base/BaseEngine.js';
17
17
  export { BaseSkin } from './lib/componentsv2/base/BaseSkin.js';
18
18
  export { State } from './lib/componentsv2/base/State.js';
19
- export { GlobalBus } from './lib/componentsv2/base/Neighborhood.js';
19
+ export { Neighborhood } from './lib/componentsv2/base/Neighborhood.js';
20
20
 
21
21
  // Utilities
22
22
  export { validateOptions } from './lib/componentsv2/base/OptionsContract.js';
@@ -36,15 +36,14 @@ export declare abstract class BaseSkin<TState extends BaseState, TEngine extends
36
36
  *
37
37
  * Use this for internal signals like "loginSuccess", "error", or "animationStart".
38
38
  */
39
- protected listenToMe(event: string, callback: (data: any) => void): void;
39
+ protected onSelf(event: string, callback: (data: any) => void): void;
40
40
  /**
41
41
  * Utility: Subscribe to the Global Bus (The Neighborhood).
42
42
  * "Neighbors" refers to other components or global system events.
43
43
  *
44
- * ⚠️ USE SPARINGLY. Ideally, data flows down from the Engine via State.
45
44
  * Use this for purely visual coordination like "theme:switched" or "layout:collapse-all".
46
45
  */
47
- protected listenToNeighbors(channel: string, callback: (data: any) => void): void;
46
+ protected onNeighbor(channel: string, callback: (data: any) => void): void;
48
47
  /**
49
48
  * Component Teardown.
50
49
  * Removes the root element and cleans up all event listeners.
@@ -40,7 +40,7 @@ export class BaseSkin {
40
40
  *
41
41
  * Use this for internal signals like "loginSuccess", "error", or "animationStart".
42
42
  */
43
- listenToMe(event, callback) {
43
+ onSelf(event, callback) {
44
44
  this.engine.on(event, callback);
45
45
  this._cleanupTasks.push(() => this.engine.off(event, callback));
46
46
  }
@@ -48,10 +48,9 @@ export class BaseSkin {
48
48
  * Utility: Subscribe to the Global Bus (The Neighborhood).
49
49
  * "Neighbors" refers to other components or global system events.
50
50
  *
51
- * ⚠️ USE SPARINGLY. Ideally, data flows down from the Engine via State.
52
51
  * Use this for purely visual coordination like "theme:switched" or "layout:collapse-all".
53
52
  */
54
- listenToNeighbors(channel, callback) {
53
+ onNeighbor(channel, callback) {
55
54
  Neighborhood.on(channel, callback);
56
55
  this._cleanupTasks.push(() => Neighborhood.off(channel, callback));
57
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.129",
3
+ "version": "1.0.131",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "./index.js",
@@ -1,22 +0,0 @@
1
- type GlobalListener = (data: any) => void;
2
- /**
3
- * THE NEIGHBORHOOD (Global Event Mediator)
4
- *
5
- * Acts as the certal nervous system where all components post updates.
6
- * Other components can tune in to specific frequencies (channels) here.
7
- */
8
- export declare class JuxGlobalBus {
9
- private listeners;
10
- on(channel: string, callback: GlobalListener): void;
11
- off(channel: string, callback: GlobalListener): void;
12
- emit(channel: string, data: any): void;
13
- /**
14
- * DEBUG: Read-only Snapshot of active channels and listener identities.
15
- * usage: juxV2.events.registry
16
- * Returns: { "channelName": ["functionName", "(anonymous)"] }
17
- */
18
- get registry(): Record<string, string[]>;
19
- }
20
- export declare const GlobalBus: JuxGlobalBus;
21
- export {};
22
- //# sourceMappingURL=GlobalBus.d.ts.map
@@ -1,56 +0,0 @@
1
- /**
2
- * THE NEIGHBORHOOD (Global Event Mediator)
3
- *
4
- * Acts as the certal nervous system where all components post updates.
5
- * Other components can tune in to specific frequencies (channels) here.
6
- */
7
- export class JuxGlobalBus {
8
- constructor() {
9
- this.listeners = new Map();
10
- }
11
- on(channel, callback) {
12
- if (!this.listeners.has(channel)) {
13
- this.listeners.set(channel, new Set());
14
- }
15
- this.listeners.get(channel).add(callback);
16
- }
17
- off(channel, callback) {
18
- const set = this.listeners.get(channel);
19
- if (set) {
20
- set.delete(callback);
21
- if (set.size === 0) {
22
- this.listeners.delete(channel);
23
- }
24
- }
25
- }
26
- emit(channel, data) {
27
- const set = this.listeners.get(channel);
28
- if (set) {
29
- set.forEach(callback => {
30
- try {
31
- callback(data);
32
- }
33
- catch (e) {
34
- console.error(`Jux GlobalBus Error [${channel}]:`, e);
35
- }
36
- });
37
- }
38
- // Optional: We could have a wildcard '*' listener here for debuggers/loggers
39
- }
40
- /**
41
- * DEBUG: Read-only Snapshot of active channels and listener identities.
42
- * usage: juxV2.events.registry
43
- * Returns: { "channelName": ["functionName", "(anonymous)"] }
44
- */
45
- get registry() {
46
- const snapshot = {};
47
- this.listeners.forEach((set, channel) => {
48
- // Map the Set of functions to their names for easier debugging
49
- snapshot[channel] = Array.from(set).map(fn => fn.name || '(anonymous)');
50
- });
51
- return snapshot;
52
- }
53
- }
54
- // Singleton Instance
55
- export const GlobalBus = new JuxGlobalBus();
56
- //# sourceMappingURL=GlobalBus.js.map
@@ -1,62 +0,0 @@
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 COMPONENTS_DIR = path.resolve(__dirname, '..');
8
-
9
- const componentName = process.argv[2];
10
- const skinName = process.argv[3];
11
-
12
- if (!componentName || !skinName) {
13
- console.error('❌ Usage: npm run create-skin <component-name> <skin-name>');
14
- console.error(' Example: npm run create-skin list dark-mode');
15
- process.exit(1);
16
- }
17
-
18
- const targetDir = path.join(COMPONENTS_DIR, componentName, 'skins');
19
-
20
- if (!fs.existsSync(targetDir)) {
21
- console.error(`❌ Component "${componentName}" does not exist or has no skins folder.`);
22
- process.exit(1);
23
- }
24
-
25
- const fileName = `${skinName}.css`;
26
- const filePath = path.join(targetDir, fileName);
27
-
28
- const cssContent = `/**
29
- * 🎨 Skin: ${skinName}
30
- * Component: ${componentName}
31
- *
32
- * Usage:
33
- * import theme from './skins/${fileName}';
34
- * component.skin.setTheme(theme);
35
- */
36
-
37
- /* Override Base Variables for this Skin */
38
- .jux-${componentName} {
39
- --color-background: #000;
40
- --color-text-primary: #fff;
41
- --radius-sm: 0px; /* Square Look */
42
- }
43
-
44
- /* Component Specific Styles */
45
- .jux-${componentName} {
46
- border: 2px solid var(--color-brand);
47
- box-shadow: 0 4px 12px rgba(0,0,0,0.2);
48
- }
49
-
50
- /* Interactive States */
51
- .jux-${componentName}:hover {
52
- border-color: var(--color-brand-hover);
53
- }
54
- `;
55
-
56
- fs.writeFileSync(filePath, cssContent);
57
-
58
- console.log(`\n✨ New Skin Created:`);
59
- console.log(` 📂 ${filePath}`);
60
- console.log(`\nTo use this skin in your code:`);
61
- console.log(` 1. Import the CSS file (as string using a loader or inlining)`);
62
- console.log(` 2. Call component.skin.setTheme(myNewCssString)`);
@@ -1,134 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- // --- Configuration ---
6
- const EMOJI_REGEX = /\p{Extended_Pictographic}/u;
7
- const IGNORE_DIRS = ['node_modules', '.git', 'dist', '.jux-dist'];
8
- const EXTENSIONS = ['.ts', '.js', '.jux', '.md'];
9
-
10
- // --- Patterns ---
11
- // 1. Console Spam: console.log("✨...")
12
- const PATTERN_CONSOLE = /console\.(log|warn|error|info|group|groupCollapsed)\((['"`])(.*?)\2/g;
13
-
14
- // 2. Line Comments: // ✨ ...
15
- const PATTERN_LINE_COMMENT = /\/\/(.*)$/gm;
16
-
17
- // 3. Block Comments: /* ✨ ... */
18
- const PATTERN_BLOCK_COMMENT = /\/\*([\s\S]*?)\*\//gm;
19
-
20
- function scanFile(filePath) {
21
- const content = fs.readFileSync(filePath, 'utf-8');
22
- const ext = path.extname(filePath);
23
- const findings = [];
24
-
25
- // 1. Check Console Logs (JS/TS Files)
26
- if (['.js', '.ts', '.jux'].includes(ext)) {
27
- let match;
28
- while ((match = PATTERN_CONSOLE.exec(content)) !== null) {
29
- const logContent = match[3]; // The text inside quotes
30
- if (EMOJI_REGEX.test(logContent)) {
31
- findings.push({
32
- type: 'Console',
33
- line: getLineNumber(content, match.index),
34
- match: match[0].trim()
35
- });
36
- }
37
- }
38
- }
39
-
40
- // 2. Check Line Comments
41
- if (['.js', '.ts', '.jux'].includes(ext)) {
42
- let match;
43
- while ((match = PATTERN_LINE_COMMENT.exec(content)) !== null) {
44
- const commentText = match[1];
45
- if (EMOJI_REGEX.test(commentText)) {
46
- findings.push({
47
- type: 'Line Comment',
48
- line: getLineNumber(content, match.index),
49
- match: match[0].trim()
50
- });
51
- }
52
- }
53
- }
54
-
55
- // 3. Check Block Comments
56
- if (['.js', '.ts', '.jux'].includes(ext)) {
57
- let match;
58
- while ((match = PATTERN_BLOCK_COMMENT.exec(content)) !== null) {
59
- const commentText = match[1];
60
- if (EMOJI_REGEX.test(commentText)) {
61
- findings.push({
62
- type: 'Block Comment',
63
- line: getLineNumber(content, match.index),
64
- match: match[0].replace(/\n/g, ' ').substring(0, 50) + '...'
65
- });
66
- }
67
- }
68
- }
69
-
70
- // 4. Markdown (Treat almost everything as "Content" but flag emojis)
71
- if (ext === '.md') {
72
- const lines = content.split('\n');
73
- lines.forEach((line, i) => {
74
- if (EMOJI_REGEX.test(line)) {
75
- findings.push({
76
- type: 'Markdown Content',
77
- line: i + 1,
78
- match: line.trim().substring(0, 60)
79
- });
80
- }
81
- });
82
- }
83
-
84
- return findings;
85
- }
86
-
87
- function getLineNumber(content, index) {
88
- return content.substring(0, index).split('\n').length;
89
- }
90
-
91
- function walkDir(dir) {
92
- const results = {};
93
- const list = fs.readdirSync(dir);
94
-
95
- list.forEach(file => {
96
- if (IGNORE_DIRS.includes(file)) return;
97
-
98
- const filePath = path.join(dir, file);
99
- const stat = fs.statSync(filePath);
100
-
101
- if (stat && stat.isDirectory()) {
102
- Object.assign(results, walkDir(filePath));
103
- } else {
104
- if (EXTENSIONS.includes(path.extname(filePath))) {
105
- const findings = scanFile(filePath);
106
- if (findings.length > 0) {
107
- results[filePath] = findings;
108
- }
109
- }
110
- }
111
- });
112
- return results;
113
- }
114
-
115
- // --- Main Execution ---
116
- const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();
117
-
118
- console.log(`\n🔎 Scanning for Emoji Spam in: ${targetDir}\n`);
119
- const report = walkDir(targetDir);
120
- const fileCount = Object.keys(report).length;
121
-
122
- if (fileCount === 0) {
123
- console.log("✅ No emoji spam found! Clean codebase.");
124
- } else {
125
- Object.entries(report).forEach(([file, items]) => {
126
- const relativePath = path.relative(process.cwd(), file);
127
- console.log(`\n📄 \x1b[1m${relativePath}\x1b[0m`);
128
- items.forEach(item => {
129
- const typeColor = item.type === 'Console' ? '\x1b[31m' : '\x1b[33m'; // Red for console, Yellow for comments
130
- console.log(` L${item.line.toString().padEnd(4)} ${typeColor}[${item.type}]\x1b[0m ${item.match}`);
131
- });
132
- });
133
- console.log(`\n🚨 Found emoji usage in ${fileCount} files.`);
134
- }
@@ -1,141 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import ts from 'typescript';
4
-
5
- const TARGET_DIR = path.resolve('./lib/componentsv2');
6
- const ENGINE_SUFFIX = 'Engine.ts';
7
-
8
- // Methods to ignore (Lifecycle, Private, Getters)
9
- const IGNORE_LIST = [
10
- 'constructor', 'prepareState', 'subscribe', 'debug', 'dispose',
11
- 'on', 'off', 'listenTo', 'addPlugin', 'removePlugin',
12
- 'get eventRegistry', 'get stateHistory', 'get emitHistory'
13
- ];
14
-
15
- /**
16
- * AST Traversal to analyze method behaviors
17
- */
18
- function analyzeMethod(node) {
19
- let returnsThis = false;
20
- let updatesState = false;
21
- let emitsEvent = false;
22
-
23
- // Recursive visitor to inspect code inside the method body
24
- function visit(n) {
25
- // Check 1: Return This ("return this;")
26
- if (ts.isReturnStatement(n) && n.expression && n.expression.kind === ts.SyntaxKind.ThisKeyword) {
27
- returnsThis = true;
28
- }
29
-
30
- // Check 2 & 3: Method Calls ("this.updateState(...)", "this.emit(...)")
31
- if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression)) {
32
- const propAccess = n.expression;
33
- // Ensure caller is 'this'
34
- if (propAccess.expression.kind === ts.SyntaxKind.ThisKeyword) {
35
- const methodName = propAccess.name.text;
36
- if (methodName === 'updateState') updatesState = true;
37
- if (methodName === 'emit') emitsEvent = true;
38
- }
39
- }
40
-
41
- ts.forEachChild(n, visit);
42
- }
43
-
44
- if (node.body) {
45
- visit(node.body);
46
- }
47
-
48
- return { returnsThis, updatesState, emitsEvent };
49
- }
50
-
51
- function gradeFile(filePath) {
52
- const code = fs.readFileSync(filePath, 'utf-8');
53
-
54
- // Create AST from TS Source
55
- const sourceFile = ts.createSourceFile(
56
- filePath,
57
- code,
58
- ts.ScriptTarget.Latest,
59
- true
60
- );
61
-
62
- let score = 0;
63
- let totalMutators = 0;
64
- const report = [];
65
-
66
- function visitNode(node) {
67
- // Look for Classes
68
- if (ts.isClassDeclaration(node)) {
69
- node.members.forEach(member => {
70
- // Look for Methods
71
- if (ts.isMethodDeclaration(member)) {
72
- const name = member.name.getText(sourceFile);
73
-
74
- // Skip ignored, private (#), or static methods
75
- if (
76
- IGNORE_LIST.includes(name) ||
77
- name.startsWith('#') ||
78
- (ts.getCombinedModifierFlags(member) & ts.ModifierFlags.Static)
79
- ) {
80
- return;
81
- }
82
-
83
- // Analyze
84
- const analysis = analyzeMethod(member);
85
-
86
- // Logic: If it mutates state, it MUST emit and rely on the fluent chain.
87
- // We only grade methods that exhibit at least one of these traits to avoid grading helpers.
88
- if (analysis.updatesState || analysis.emitsEvent || analysis.returnsThis) {
89
- totalMutators++;
90
-
91
- const issues = [];
92
- if (!analysis.returnsThis) issues.push('❌ Return `this`');
93
- if (!analysis.updatesState) issues.push('⚠️ No State Update');
94
- if (!analysis.emitsEvent) issues.push('❌ No Emission');
95
-
96
- if (issues.length === 0) {
97
- score++;
98
- } else {
99
- report.push(` METHOD: ${name}`);
100
- issues.forEach(i => report.push(` ${i}`));
101
- }
102
- }
103
- }
104
- });
105
- }
106
- ts.forEachChild(node, visitNode);
107
- }
108
-
109
- visitNode(sourceFile);
110
-
111
- return { total: totalMutators, passed: score, report };
112
- }
113
-
114
- function walk(dir) {
115
- const files = fs.readdirSync(dir);
116
- files.forEach(f => {
117
- const full = path.join(dir, f);
118
- if (fs.statSync(full).isDirectory()) {
119
- walk(full);
120
- } else if (full.endsWith(ENGINE_SUFFIX) && !full.endsWith('BaseEngine.ts')) {
121
- const relative = path.relative(process.cwd(), full);
122
- const { total, passed, report } = gradeFile(full);
123
-
124
- if (total > 0) {
125
- const percent = Math.round((passed / total) * 100);
126
- const color = percent === 100 ? '\x1b[32m' : (percent > 80 ? '\x1b[33m' : '\x1b[31m');
127
-
128
- console.log(`\n📄 ${relative}`);
129
- console.log(` Fluency Score: ${color}${percent}% (${passed}/${total})\x1b[0m`);
130
- if (report.length > 0) {
131
- console.log(report.join('\n'));
132
- }
133
- }
134
- }
135
- });
136
- }
137
-
138
- console.log('\n🕵️ JUX FLUENCY AUDIT (AST Powered)');
139
- console.log(' Checking for: 1. return this, 2. updateState(), 3. emit()\n');
140
- walk(TARGET_DIR);
141
- console.log('\n');
@@ -1,177 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import ts from 'typescript';
4
-
5
- const TARGET_DIR = path.resolve('./lib/componentsv2');
6
- const ENGINE_SUFFIX = 'Engine.ts'; // Only scan engines
7
-
8
- // BaseState properties that EVERY engine must initialize
9
- const BASE_STATE_KEYS = ['id', 'classes', 'visible', 'disabled', 'loading', 'attributes'];
10
-
11
- function getInterfaceProperties(node, sourceFile) {
12
- const props = [];
13
- node.members.forEach(member => {
14
- if (ts.isPropertySignature(member)) {
15
- props.push(member.name.getText(sourceFile));
16
- }
17
- });
18
- return props;
19
- }
20
-
21
- function analyzePrepareState(node, sourceFile, stateKeys, optionsKeys) {
22
- const report = [];
23
- let passed = true;
24
-
25
- // 1. Capture Return Object Keys
26
- let returnedKeys = [];
27
- let foundReturn = false;
28
-
29
- function visitReturn(n) {
30
- // ✅ STOP recursion if entering a nested function (like .map(() => ...))
31
- // We only care about the return statement of `prepareState` itself.
32
- if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) {
33
- return;
34
- }
35
-
36
- if (ts.isReturnStatement(n)) {
37
- foundReturn = true;
38
- if (n.expression && ts.isObjectLiteralExpression(n.expression)) {
39
- n.expression.properties.forEach(prop => {
40
- // Start Update: Handle both Assignment types
41
- if (ts.isPropertyAssignment(prop)) {
42
- returnedKeys.push(prop.name.getText(sourceFile));
43
- }
44
- // ✅ ADDED: Handle shorthand assignments (e.g. { id })
45
- else if (ts.isShorthandPropertyAssignment(prop)) {
46
- returnedKeys.push(prop.name.getText(sourceFile));
47
- }
48
- // End Update
49
- });
50
- }
51
- }
52
- ts.forEachChild(n, visitReturn);
53
- }
54
-
55
- // Start visiting from the function body
56
- if (node.body) {
57
- ts.forEachChild(node.body, visitReturn);
58
- }
59
-
60
- if (!foundReturn) {
61
- return { passed: false, report: ['❌ prepareState does not return an object literal directly.'] };
62
- }
63
-
64
- // 2. Check State Coverage (Expected = BaseState + ComponentState)
65
- const expectedKeys = new Set([...BASE_STATE_KEYS, ...stateKeys]);
66
- const actualKeys = new Set(returnedKeys);
67
-
68
- // Missing Keys
69
- const missing = [...expectedKeys].filter(k => !actualKeys.has(k));
70
- if (missing.length > 0) {
71
- report.push(`❌ Missing Initialization for: ${missing.join(', ')}`);
72
- passed = false;
73
- }
74
-
75
- // Extra Keys (Pollution / Typos)
76
- const extra = [...actualKeys].filter(k => !expectedKeys.has(k));
77
- if (extra.length > 0) {
78
- report.push(`⚠️ Unknown properties in State: ${extra.join(', ')}`);
79
- }
80
-
81
- // 3. Check Options Usage
82
- // We scan for `options.KEY` usage in the function body
83
- const usedOptions = new Set();
84
-
85
- function visitOptionsUsage(n) {
86
- if (ts.isPropertyAccessExpression(n)) {
87
- if (n.expression.getText(sourceFile) === 'options') {
88
- usedOptions.add(n.name.getText(sourceFile));
89
- }
90
- }
91
- ts.forEachChild(n, visitOptionsUsage);
92
- }
93
- if (node.body) {
94
- visitOptionsUsage(node.body);
95
- }
96
-
97
- const unusedOptions = optionsKeys.filter(k => !usedOptions.has(k));
98
- if (unusedOptions.length > 0) {
99
- report.push(`⚠️ Unused Options: ${unusedOptions.join(', ')}`);
100
- }
101
-
102
- return { passed, report };
103
- }
104
-
105
- function auditFile(filePath) {
106
- const code = fs.readFileSync(filePath, 'utf-8');
107
- const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
108
-
109
- let stateInterfaceProps = [];
110
- let optionsInterfaceProps = [];
111
- let prepareStateMethod = null;
112
-
113
- function visit(node) {
114
- // Find Interfaces
115
- if (ts.isInterfaceDeclaration(node)) {
116
- const name = node.name.text;
117
- if (name.endsWith('State') && name !== 'BaseState') {
118
- stateInterfaceProps = getInterfaceProperties(node, sourceFile);
119
- } else if (name.endsWith('Options')) {
120
- optionsInterfaceProps = getInterfaceProperties(node, sourceFile);
121
- }
122
- }
123
-
124
- // Find Class & prepareState
125
- if (ts.isClassDeclaration(node)) {
126
- node.members.forEach(member => {
127
- if (ts.isMethodDeclaration(member) && member.name.getText(sourceFile) === 'prepareState') {
128
- prepareStateMethod = member;
129
- }
130
- });
131
- }
132
- ts.forEachChild(node, visit);
133
- }
134
-
135
- visit(sourceFile);
136
-
137
- // If no prepareState found, it might be abstract or a utility class; skip nicely
138
- if (!prepareStateMethod) return null;
139
-
140
- const result = analyzePrepareState(prepareStateMethod, sourceFile, stateInterfaceProps, optionsInterfaceProps);
141
-
142
- return {
143
- file: path.relative(process.cwd(), filePath),
144
- ...result
145
- };
146
- }
147
-
148
- function printResult(res) {
149
- if (!res) return;
150
- const color = res.passed ? '\x1b[32m' : '\x1b[31m'; // Green / Red
151
-
152
- console.log(`📄 ${res.file}`);
153
- if (res.passed && res.report.length === 0) {
154
- console.log(` ${color}100% Match\x1b[0m`);
155
- } else {
156
- res.report.forEach(msg => console.log(` ${msg}`));
157
- if (!res.passed) console.log(` ${color}Validation Failed\x1b[0m`);
158
- }
159
- console.log('');
160
- }
161
-
162
- function walk(dir) {
163
- const files = fs.readdirSync(dir);
164
- files.forEach(f => {
165
- const full = path.join(dir, f);
166
- if (fs.statSync(full).isDirectory()) {
167
- walk(full);
168
- } else if (full.endsWith(ENGINE_SUFFIX) && !full.endsWith('BaseEngine.ts')) {
169
- const res = auditFile(full);
170
- printResult(res);
171
- }
172
- });
173
- }
174
-
175
- console.log(`\n📋 JUX OPTIONS AUDIT`);
176
- console.log(` Verifying prepareState() maps all Options -> State correctly.\n`);
177
- walk(TARGET_DIR);
@@ -1,124 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { execSync } from 'child_process';
4
- import { fileURLToPath } from 'url';
5
- import readline from 'readline';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
- const COMPONENTS_DIR = path.resolve(__dirname, '..');
10
- const STUBS_DIR = path.join(COMPONENTS_DIR, 'stubs');
11
- const INDEX_FILE = path.join(COMPONENTS_DIR, 'index.ts');
12
- const RESERVED = ['base', 'bases', 'plugin', 'plugins', 'tool', 'tools', 'stub', 'stubs', 'skin', 'skins', 'structure', 'structures', 'engine', 'engines', 'component', 'components'];
13
-
14
- const toPascalCase = (str) => str.replace(/(^\w|-\w)/g, (clear) => clear.replace('-', '').toUpperCase());
15
- const log = (msg, color = '\x1b[36m') => console.log(`${color}${msg}\x1b[0m`);
16
- const error = (msg) => { console.error(`\x1b[31m❌ ${msg}\x1b[0m`); process.exit(1); };
17
-
18
- const componentName = process.argv[2];
19
-
20
- if (!componentName) error('Usage: npm run scaffold -- <component-name>');
21
- if (!/^[a-z0-9-]+$/.test(componentName)) error('Name must be kebab-case (e.g. task-list)');
22
- if (RESERVED.includes(componentName)) error(`"${componentName}" is a reserved system name.`);
23
-
24
- const ComponentName = toPascalCase(componentName);
25
- const targetDir = path.join(COMPONENTS_DIR, componentName);
26
-
27
- if (fs.existsSync(targetDir)) error(`Directory already exists: ${targetDir}`);
28
-
29
- log(`🚀 Scaffolding Jux v2 Component: ${ComponentName} (${componentName})...`);
30
-
31
- const indexBackup = fs.readFileSync(INDEX_FILE, 'utf-8');
32
-
33
- (async function main() {
34
- try {
35
- // 1. Create Directory
36
- fs.mkdirSync(targetDir);
37
-
38
- // 2. Generate Files from Stubs
39
- const stubs = {
40
- 'ComponentComposition.ts.stub': `component.ts`,
41
- 'ComponentEngine.ts.stub': `engine.ts`,
42
- 'ComponentSkin.ts.stub': `skin.ts`,
43
- 'ComponentStructure.css.stub': `structure.css`
44
- };
45
-
46
- Object.entries(stubs).forEach(([stub, file]) => {
47
- let content = fs.readFileSync(path.join(STUBS_DIR, stub), 'utf-8');
48
-
49
- content = content.replace(/\[%\s*ComponentName\s*%\]/g, ComponentName)
50
- .replace(/\[%\s*component-name\s*%\]/g, componentName);
51
-
52
- fs.writeFileSync(path.join(targetDir, file), content);
53
- log(` + Created ${file}`);
54
- });
55
-
56
- // 3. Update Index
57
- log(` ... Registering in ${path.basename(INDEX_FILE)}`);
58
- let indexContent = indexBackup;
59
-
60
- const importLine = `import { ${ComponentName} } from './${componentName}/component.js';`; // Updated import path
61
- if (!indexContent.includes(importLine) && !indexContent.includes(`import { ${ComponentName} }`)) {
62
- if (indexContent.includes(`// Export individually`)) {
63
- indexContent = indexContent.replace(`// Export individually`, `${importLine}\n\n// Export individually`);
64
- } else {
65
- indexContent = `${importLine}\n${indexContent}`;
66
- }
67
- }
68
-
69
- const exportRegex = /export\s*\{([^}]+)\};/;
70
- if (exportRegex.test(indexContent)) {
71
- indexContent = indexContent.replace(exportRegex, (match, content) => {
72
- const parts = content.split(',').map(s => s.trim()).filter(Boolean);
73
- const unique = new Set(parts);
74
- unique.add(ComponentName);
75
- return `export { ${Array.from(unique).join(', ')} };`;
76
- });
77
- } else {
78
- indexContent += `\nexport { ${ComponentName} };`;
79
- }
80
-
81
- fs.writeFileSync(INDEX_FILE, indexContent);
82
-
83
- console.log(`\n\x1b[33m⚠️ MANUAL ACTION REQUIRED:\x1b[0m`);
84
- console.log(` Please register \x1b[1m${ComponentName}\x1b[0m in the \x1b[1mjuxV2\x1b[0m namespace object.`);
85
- console.log(` File: \x1b[36m${INDEX_FILE}\x1b[0m`);
86
- console.log(` add: \x1b[32m${ComponentName},\x1b[0m\n`);
87
-
88
-
89
- // 5. Verification
90
- log(`\n🕵️ Verifying Integrity (Targeted TypeScript Check)...`);
91
- try {
92
- const filesToCheck = Object.values(stubs)
93
- .filter(f => f.endsWith('.ts'))
94
- .map(f => path.join(targetDir, f))
95
- .join(' ');
96
-
97
- execSync(`npx tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --esModuleInterop --skipLibCheck ${filesToCheck}`, { stdio: 'inherit' });
98
-
99
- log(`\n✅ Verification Passed! Component Ready.`);
100
- } catch (e) {
101
- throw new Error("Type check failed on new component.");
102
- }
103
-
104
- } catch (e) {
105
- console.error(`\n\x1b[31m❌ ERROR: ${e.message}\x1b[0m`);
106
- // Rollback...
107
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
108
- const answer = await new Promise(resolve => {
109
- rl.question(`\n⚠️ An error occurred. Rollback changes (delete created files)? [y/n]: `, (ans) => {
110
- resolve(ans.trim().toLowerCase());
111
- });
112
- });
113
- rl.close();
114
-
115
- if (answer === 'y' || answer === 'yes') {
116
- log(`⏪ Rolling back changes...`, '\x1b[33m');
117
- fs.writeFileSync(INDEX_FILE, indexBackup);
118
- if (fs.existsSync(targetDir)) {
119
- fs.rmSync(targetDir, { recursive: true, force: true });
120
- }
121
- }
122
- process.exit(1);
123
- }
124
- })();