Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,815 @@
const fs = require('fs');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generate = require('@babel/generator').default;
// Parse command line arguments
let filePath = null;
let jsonOutput = false;
for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg === '--json') {
jsonOutput = true;
} else if (!filePath) {
filePath = arg;
}
}
// Error helper for JSON output
function outputError(error) {
if (jsonOutput) {
const errorObj = {
status: 'error',
error: {
type: error.constructor.name,
message: error.message,
line: error.loc ? error.loc.line : null,
column: error.loc ? error.loc.column : null,
file: filePath,
context: null,
suggestion: null
}
};
// Add suggestions for common errors
if (error.message.includes('Unexpected token')) {
errorObj.error.suggestion = 'Check JavaScript syntax - may have invalid characters or missing punctuation';
} else if (error.message.includes('Unterminated')) {
errorObj.error.suggestion = 'Check for unclosed strings, comments, or brackets';
}
console.log(JSON.stringify(errorObj));
} else {
console.error(`Parse error: ${error.message}`);
}
}
// Custom error for structure violations
function structureError(type, message, line, code = null) {
if (jsonOutput) {
console.log(JSON.stringify({
status: 'error',
error: {
type: type,
message: message,
line: line,
code: code,
file: filePath
}
}));
} else {
console.error(`${type}: ${message} at line ${line}`);
if (code) {
console.error(` Code: ${code}`);
}
}
process.exit(1);
}
if (!filePath) {
if (jsonOutput) {
console.log(JSON.stringify({
status: 'error',
error: {
type: 'ArgumentError',
message: 'No input file specified',
suggestion: 'Usage: node js-parser.js [--json] <file-path>'
}
}));
} else {
console.error('Usage: node js-parser.js [--json] <file-path>');
}
process.exit(1);
}
// Read file
let content;
try {
content = fs.readFileSync(filePath, 'utf8');
} catch (error) {
error.message = `Error reading file: ${error.message}`;
outputError(error);
process.exit(1);
}
// Parse result object
const result = {
classes: {},
functions: {},
exports: {},
imports: [],
globalFunctions: [], // All global function names
globalConstants: [], // All global const names
functionsWithDecorators: {}, // Global functions that have decorators
instanceMethodsWithDecorators: {} // Instance methods with decorators (for error detection)
};
// Tracking for validation
let hasES6Class = false;
let moduleExportsFound = null;
let codeOutsideAllowed = [];
// Helper function to extract decorator information in PHP-compatible compact format
function extractDecorators(decorators) {
if (!decorators || decorators.length === 0) {
return [];
}
return decorators.map(decorator => {
const expr = decorator.expression;
// Handle simple decorators like @readonly
if (t.isIdentifier(expr)) {
return [expr.name, []];
}
// Handle decorators with arguments like @Route('/api')
if (t.isCallExpression(expr)) {
const name = t.isIdentifier(expr.callee) ? expr.callee.name :
t.isMemberExpression(expr.callee) ? getMemberExpressionName(expr.callee) :
'unknown';
const args = expr.arguments.map(arg => extractArgumentValue(arg));
return [name, args];
}
// Handle member expression decorators like @Namespace.Decorator
if (t.isMemberExpression(expr)) {
return [getMemberExpressionName(expr), []];
}
return ['unknown', []];
});
}
// Helper to get full name from member expression
function getMemberExpressionName(node) {
if (t.isIdentifier(node.object) && t.isIdentifier(node.property)) {
return `${node.object.name}.${node.property.name}`;
}
if (t.isIdentifier(node.property)) {
return node.property.name;
}
return 'unknown';
}
// Helper to extract argument values
function extractArgumentValue(node) {
if (t.isStringLiteral(node)) {
return node.value;
}
if (t.isNumericLiteral(node)) {
return node.value;
}
if (t.isBooleanLiteral(node)) {
return node.value;
}
if (t.isNullLiteral(node)) {
return null;
}
if (t.isIdentifier(node)) {
return { identifier: node.name };
}
if (t.isObjectExpression(node)) {
const obj = {};
node.properties.forEach(prop => {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
obj[prop.key.name] = extractArgumentValue(prop.value);
}
});
return obj;
}
if (t.isArrayExpression(node)) {
return node.elements.map(el => el ? extractArgumentValue(el) : null);
}
if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
// Simple template literal without expressions
return node.quasis[0].value.raw;
}
// For complex expressions, store the type
return { type: node.type };
}
// Helper to get snippet of code for error messages
function getCodeSnippet(node) {
try {
const generated = generate(node, { compact: true });
let code = generated.code;
// Truncate long code snippets
if (code.length > 60) {
code = code.substring(0, 60) + '...';
}
return code;
} catch (e) {
return node.type;
}
}
// Helper to check if a value is static (no function calls)
function isStaticValue(node) {
if (!node) return false;
// Literals are always static
if (t.isStringLiteral(node) || t.isNumericLiteral(node) ||
t.isBooleanLiteral(node) || t.isNullLiteral(node)) {
return true;
}
// Template literals without expressions are static
if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
return true;
}
// Binary expressions with static operands (e.g., 3 * 365)
if (t.isBinaryExpression(node)) {
return isStaticValue(node.left) && isStaticValue(node.right);
}
// Unary expressions with static operands (e.g., -5, !true)
if (t.isUnaryExpression(node)) {
return isStaticValue(node.argument);
}
// Arrays with all static elements
if (t.isArrayExpression(node)) {
return node.elements.every(el => el === null || isStaticValue(el));
}
// Objects with all static properties
if (t.isObjectExpression(node)) {
return node.properties.every(prop => {
if (t.isObjectProperty(prop)) {
return isStaticValue(prop.value);
}
return false; // Methods are not static
});
}
// Everything else (identifiers, function calls, etc.) is not static
return false;
}
/**
* Helper function to check if a JSDoc comment contains @decorator tag
*/
function hasDecoratorTag(commentValue) {
if (!commentValue) return false;
// Parse JSDoc-style comment
// Look for @decorator anywhere in the comment
const lines = commentValue.split('\n');
for (const line of lines) {
// Remove leading asterisk and whitespace
const cleanLine = line.replace(/^\s*\*\s*/, '').trim();
// Check if line contains @decorator tag
if (cleanLine === '@decorator' || cleanLine.startsWith('@decorator ')) {
return true;
}
}
return false;
}
/**
* Extract JSDoc decorators from comments (e.g., @Instantiatable, @Monoprogenic)
* Returns compact format matching PHP: [[name, [args]], ...]
*/
function extractJSDocDecorators(leadingComments) {
const decorators = [];
if (!leadingComments) return decorators;
for (const comment of leadingComments) {
if (comment.type !== 'CommentBlock') continue;
const lines = comment.value.split('\n');
for (const line of lines) {
// Remove leading asterisk and whitespace
const cleanLine = line.replace(/^\s*\*\s*/, '').trim();
// Match @DecoratorName pattern (capital letter start, no spaces)
const match = cleanLine.match(/^@([A-Z][A-Za-z0-9_]*)\s*$/);
if (match) {
const decoratorName = match[1];
// Store in compact format matching PHP: [name, [args]]
decorators.push([decoratorName, []]);
}
}
}
return decorators;
}
try {
// No preprocessing needed - parse content directly
const processedContent = content;
// Parse with Babel
const ast = parser.parse(processedContent, {
sourceType: 'module',
attachComment: true, // Attach comments to AST nodes for /** @decorator */ detection
plugins: [
'jsx',
'typescript',
'classProperties',
'classPrivateProperties',
'classPrivateMethods',
['decorators', { decoratorsBeforeExport: true }],
'dynamicImport',
'exportDefaultFrom',
'exportNamespaceFrom',
'asyncGenerators',
'objectRestSpread',
'optionalCatchBinding',
'optionalChaining',
'nullishCoalescingOperator',
'classStaticBlock'
]
});
// First pass: Check for classes and track top-level statements
ast.program.body.forEach(node => {
if (t.isClassDeclaration(node) || (t.isExportNamedDeclaration(node) && t.isClassDeclaration(node.declaration))) {
hasES6Class = true;
}
});
// Traverse AST
traverse(ast, {
// Class declarations
ClassDeclaration(path) {
const className = path.node.id.name;
// Extract both ES decorators and JSDoc decorators, merge them
const esDecorators = extractDecorators(path.node.decorators);
const jsdocDecorators = extractJSDocDecorators(path.node.leadingComments);
const allDecorators = [...esDecorators, ...jsdocDecorators];
const classInfo = {
name: className,
extends: path.node.superClass ? path.node.superClass.name : null,
public_instance_methods: {},
public_static_methods: {},
properties: {},
staticProperties: {},
decorators: allDecorators
};
// Extract methods and properties
path.node.body.body.forEach(member => {
if (t.isClassMethod(member)) {
// Check for @decorator in JSDoc comments on the method
let hasDecoratorComment = false;
if (member.leadingComments) {
hasDecoratorComment = member.leadingComments.some(comment => {
if (comment.type !== 'CommentBlock') return false;
return hasDecoratorTag(comment.value);
});
}
// Determine visibility: private if starts with #, otherwise public
const methodName = member.key.name || (t.isPrivateName(member.key) ? '#' + member.key.id.name : 'unknown');
const visibility = methodName.startsWith('#') ? 'private' : 'public';
const methodInfo = {
// PHP-compatible fields
name: methodName,
static: member.static,
visibility: visibility,
line: member.loc ? member.loc.start.line : null,
// JavaScript-specific fields (keep existing)
params: member.params.map(p => {
if (t.isIdentifier(p)) return p.name;
if (t.isRestElement(p) && t.isIdentifier(p.argument)) return '...' + p.argument.name;
if (t.isObjectPattern(p)) return '{object}';
if (t.isArrayPattern(p)) return '[array]';
return p.type;
}),
// PHP-compatible parameters structure
parameters: member.params.map(p => {
const paramInfo = {};
if (t.isIdentifier(p)) {
paramInfo.name = p.name;
} else if (t.isRestElement(p) && t.isIdentifier(p.argument)) {
paramInfo.name = '...' + p.argument.name;
} else if (t.isObjectPattern(p)) {
paramInfo.name = '{object}';
} else if (t.isArrayPattern(p)) {
paramInfo.name = '[array]';
} else {
paramInfo.name = p.type;
}
return paramInfo;
}),
async: member.async,
generator: member.generator,
kind: member.kind,
decorators: extractDecorators(member.decorators),
isDecoratorFunction: hasDecoratorComment
};
if (member.static) {
classInfo.public_static_methods[member.key.name] = methodInfo;
// Track static methods that are decorator functions
if (hasDecoratorComment) {
if (!result.functionsWithDecorators[className]) {
result.functionsWithDecorators[className] = {};
}
result.functionsWithDecorators[className][member.key.name] = {
decorators: [['decorator', []]],
line: member.loc ? member.loc.start.line : null
};
}
} else {
classInfo.public_instance_methods[member.key.name] = methodInfo;
}
} else if (t.isClassProperty(member) || t.isClassPrivateProperty(member)) {
const propName = t.isIdentifier(member.key) ? member.key.name :
t.isPrivateName(member.key) ? '#' + member.key.id.name :
'unknown';
const propInfo = {
name: propName,
static: member.static,
value: member.value ? getValueType(member.value) : null,
decorators: extractDecorators(member.decorators)
};
if (member.static) {
classInfo.staticProperties[propName] = propInfo;
} else {
classInfo.properties[propName] = propInfo;
}
}
});
result.classes[className] = classInfo;
},
// Function declarations
FunctionDeclaration(path) {
if (path.node.id) {
const funcName = path.node.id.name;
// Add to global functions list
result.globalFunctions.push(funcName);
// Check if it has decorators (either @decorator syntax or /** @decorator */ comment)
const decorators = extractDecorators(path.node.decorators);
// Check for @decorator in JSDoc comments
let hasDecoratorComment = false;
if (path.node.leadingComments) {
hasDecoratorComment = path.node.leadingComments.some(comment => {
if (comment.type !== 'CommentBlock') return false;
// Check if JSDoc contains @decorator tag
return hasDecoratorTag(comment.value);
});
}
if (decorators || hasDecoratorComment) {
result.functionsWithDecorators[funcName] = {
decorators: hasDecoratorComment ? [['decorator', []]] : decorators,
line: path.node.loc ? path.node.loc.start.line : null
};
}
result.functions[funcName] = {
name: funcName,
params: path.node.params.map(p => {
if (t.isIdentifier(p)) return p.name;
if (t.isRestElement(p) && t.isIdentifier(p.argument)) return '...' + p.argument.name;
if (t.isObjectPattern(p)) return '{object}';
if (t.isArrayPattern(p)) return '[array]';
return p.type;
}),
async: path.node.async,
generator: path.node.generator,
decorators: decorators
};
}
},
// Variable declarations (check for function expressions)
VariableDeclaration(path) {
// Only check top-level variables (directly in Program body)
if (path.parent && path.parent.type === 'Program') {
path.node.declarations.forEach(decl => {
if (t.isIdentifier(decl.id)) {
const varName = decl.id.name;
// Check if it's a function expression
if (decl.init && (t.isFunctionExpression(decl.init) || t.isArrowFunctionExpression(decl.init))) {
// Add to global functions list
result.globalFunctions.push(varName);
// Check for decorators (function expressions don't support decorators directly)
// But we still track them as functions
result.functions[varName] = {
name: varName,
params: decl.init.params ? decl.init.params.map(p => {
if (t.isIdentifier(p)) return p.name;
if (t.isRestElement(p) && t.isIdentifier(p.argument)) return '...' + p.argument.name;
if (t.isObjectPattern(p)) return '{object}';
if (t.isArrayPattern(p)) return '[array]';
return p.type;
}) : [],
async: decl.init.async || false,
generator: decl.init.generator || false,
decorators: null
};
} else if (path.node.kind === 'const' && decl.init && isStaticValue(decl.init)) {
// Const with static value - track it
result.globalConstants.push(varName);
} else if (!hasES6Class) {
// Non-allowed variable in a file without classes
// (not a function, not a const with static value)
codeOutsideAllowed.push({
line: path.node.loc ? path.node.loc.start.line : null,
code: getCodeSnippet(decl)
});
}
}
});
}
},
// Check for module.exports
AssignmentExpression(path) {
const left = path.node.left;
if (t.isMemberExpression(left)) {
if ((t.isIdentifier(left.object) && left.object.name === 'module') &&
(t.isIdentifier(left.property) && left.property.name === 'exports')) {
moduleExportsFound = path.node.loc ? path.node.loc.start.line : 1;
}
}
},
// Check for exports.something =
MemberExpression(path) {
if (path.parent && t.isAssignmentExpression(path.parent) && path.parent.left === path.node) {
if (t.isIdentifier(path.node.object) && path.node.object.name === 'exports') {
moduleExportsFound = path.node.loc ? path.node.loc.start.line : 1;
}
}
},
// Imports
ImportDeclaration(path) {
const importInfo = {
source: path.node.source.value,
specifiers: []
};
path.node.specifiers.forEach(spec => {
if (t.isImportDefaultSpecifier(spec)) {
importInfo.specifiers.push({
type: 'default',
local: spec.local.name
});
} else if (t.isImportSpecifier(spec)) {
importInfo.specifiers.push({
type: 'named',
imported: spec.imported.name,
local: spec.local.name
});
} else if (t.isImportNamespaceSpecifier(spec)) {
importInfo.specifiers.push({
type: 'namespace',
local: spec.local.name
});
}
});
result.imports.push(importInfo);
},
// Exports
ExportNamedDeclaration(path) {
if (path.node.declaration) {
if (t.isClassDeclaration(path.node.declaration) && path.node.declaration.id) {
result.exports[path.node.declaration.id.name] = 'class';
} else if (t.isFunctionDeclaration(path.node.declaration) && path.node.declaration.id) {
result.exports[path.node.declaration.id.name] = 'function';
} else if (t.isVariableDeclaration(path.node.declaration)) {
path.node.declaration.declarations.forEach(decl => {
if (t.isIdentifier(decl.id)) {
result.exports[decl.id.name] = 'variable';
}
});
}
}
// Handle export specifiers
if (path.node.specifiers) {
path.node.specifiers.forEach(spec => {
if (t.isExportSpecifier(spec)) {
result.exports[spec.exported.name] = 'named';
}
});
}
},
ExportDefaultDeclaration(path) {
result.exports.default = getExportType(path.node.declaration);
},
Program: {
exit(path) {
// After traversal, check structure violations
// Check for module.exports
if (moduleExportsFound) {
structureError(
'ModuleExportsFound',
'Module exports detected. JavaScript files are concatenated, use direct class references.',
moduleExportsFound,
null
);
}
// Check if this is a compiled/generated file (not originally a .js file)
// These files are generated from other sources (.jqhtml, etc) and should be exempt
const fileContent = fs.readFileSync(filePath, 'utf8');
const firstLine = fileContent.split('\n')[0];
if (firstLine && firstLine.includes('Compiled from:')) {
// This is a compiled file, skip all structure validation
return;
}
// TEMPORARY: Exempt specific utility files from strict validation
// TODO: Replace this with a proper @RULE comment-based system
const fileName = filePath.split('/').pop();
const exemptFiles = ['functions.js'];
if (exemptFiles.includes(fileName)) {
// Skip validation for these files temporarily
return;
}
// Check structure based on whether file has ES6 class
if (hasES6Class) {
// Files with classes should only have class and comments
let invalidCode = null;
path.node.body.forEach(node => {
// Allow comments (handled by parser)
if (t.isClassDeclaration(node)) {
// Class is allowed
return;
}
if (t.isExportNamedDeclaration(node) && t.isClassDeclaration(node.declaration)) {
// Exported class is allowed
return;
}
if (t.isExportDefaultDeclaration(node) && t.isClassExpression(node.declaration)) {
// Export default class is allowed
return;
}
if (t.isImportDeclaration(node)) {
// Imports are allowed (they're removed during bundling)
return;
}
// Anything else is not allowed
if (!invalidCode) {
invalidCode = {
line: node.loc ? node.loc.start.line : null,
code: getCodeSnippet(node)
};
}
});
if (invalidCode) {
structureError(
'CodeOutsideClass',
'JavaScript files with classes may only contain one class declaration and comments.',
invalidCode.line,
invalidCode.code
);
}
} else {
// Files without classes should only have functions and comments
let invalidCode = null;
path.node.body.forEach(node => {
// Allow function declarations
if (t.isFunctionDeclaration(node)) {
return;
}
// Allow variable declarations that are functions or static const values
if (t.isVariableDeclaration(node)) {
const allAllowed = node.declarations.every(decl => {
// Functions are always allowed
if (decl.init && (
t.isFunctionExpression(decl.init) ||
t.isArrowFunctionExpression(decl.init)
)) {
return true;
}
// Const declarations with static values are allowed
if (node.kind === 'const' && decl.init && isStaticValue(decl.init)) {
return true;
}
return false;
});
if (allAllowed) {
return;
}
}
// Allow imports (they're removed during bundling)
if (t.isImportDeclaration(node)) {
return;
}
// Allow exports that wrap functions
if (t.isExportNamedDeclaration(node)) {
if (t.isFunctionDeclaration(node.declaration)) {
return;
}
if (!node.declaration && node.specifiers) {
// Export of existing identifiers
return;
}
}
// Anything else is not allowed
if (!invalidCode) {
invalidCode = {
line: node.loc ? node.loc.start.line : null,
code: getCodeSnippet(node)
};
}
});
if (invalidCode) {
structureError(
'CodeOutsideAllowed',
'JavaScript files without classes may only contain function declarations, const variables with static values, and comments.',
invalidCode.line,
invalidCode.code
);
}
}
}
}
});
} catch (error) {
// Parse Babel error location if available
if (!error.loc && error.message) {
// Try to extract from message (e.g., "Unexpected token (10:5)")
const match = error.message.match(/\((\d+):(\d+)\)/);
if (match) {
error.loc = {
line: parseInt(match[1]),
column: parseInt(match[2])
};
}
}
outputError(error);
process.exit(1);
}
// Helper functions
function getValueType(node) {
if (t.isStringLiteral(node)) return `"${node.value}"`;
if (t.isNumericLiteral(node)) return node.value;
if (t.isBooleanLiteral(node)) return node.value;
if (t.isNullLiteral(node)) return null;
if (t.isIdentifier(node)) return node.name;
if (t.isArrayExpression(node)) return 'array';
if (t.isObjectExpression(node)) return 'object';
if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) return 'function';
return node.type;
}
function getExportType(node) {
if (t.isClassDeclaration(node)) return 'class';
if (t.isFunctionDeclaration(node)) return 'function';
if (t.isIdentifier(node)) return 'identifier';
if (t.isCallExpression(node)) return 'expression';
return node.type;
}
// Output result
if (jsonOutput) {
console.log(JSON.stringify({
status: 'success',
result: result,
file: filePath
}));
} else {
console.log(JSON.stringify(result, null, 2));
}

View File

@@ -0,0 +1,292 @@
#!/usr/bin/env node
const fs = require('fs');
const crypto = require('crypto');
const babel = require('@babel/core');
// Parse command line arguments
let filePath = null;
let target = 'modern';
let hashPath = null;
let jsonOutput = false;
// Process arguments
for (let i = 2; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg === '--json') {
jsonOutput = true;
} else if (!filePath) {
filePath = arg;
} else if (!target || target === 'modern') {
target = arg;
} else if (!hashPath) {
hashPath = arg;
}
}
// Default hashPath to filePath if not provided
if (!hashPath) {
hashPath = filePath;
}
// Error helper for JSON output
function outputError(error) {
if (jsonOutput) {
const errorObj = {
status: 'error',
error: {
type: error.constructor.name,
message: error.message,
line: error.loc ? error.loc.line : null,
column: error.loc ? error.loc.column : null,
file: filePath,
context: null,
suggestion: null
}
};
// Add suggestions for common errors
if (error.message.includes('Cannot find module')) {
errorObj.error.suggestion = 'Missing Babel dependencies. Run: npm install';
} else if (error.message.includes('Unexpected token')) {
errorObj.error.suggestion = 'Check JavaScript syntax in the source file';
} else if (error.message.includes('decorator')) {
errorObj.error.suggestion = 'Ensure decorators are properly formatted (e.g., @decorator before class/method)';
}
console.log(JSON.stringify(errorObj));
} else {
console.error(`Transformation error: ${error.message}`);
if (error.loc) {
console.error(` at line ${error.loc.line}, column ${error.loc.column}`);
}
// Provide helpful error messages
if (error.message.includes('Cannot find module')) {
console.error('\nMissing dependencies. Please run:');
console.error(`cd ${__dirname} && npm install`);
} else if (error.message.includes('Unexpected token')) {
console.error('\nSyntax error in source file. The file may contain invalid JavaScript.');
} else if (error.message.includes('decorator')) {
console.error('\nDecorator syntax error. Ensure decorators are properly formatted.');
}
}
}
if (!filePath) {
const error = new Error('No input file specified');
if (jsonOutput) {
console.log(JSON.stringify({
status: 'error',
error: {
type: 'ArgumentError',
message: error.message,
suggestion: 'Usage: node js-transformer.js [--json] <file-path> [target] [hash-path]'
}
}));
} else {
console.error('Usage: node js-transformer.js [--json] <file-path> [target] [hash-path]');
console.error('Targets: modern, es6, es5');
}
process.exit(1);
}
// Read file
let content;
try {
content = fs.readFileSync(filePath, 'utf8');
} catch (error) {
error.message = `Error reading file: ${error.message}`;
outputError(error);
process.exit(1);
}
/**
* Preprocessor to handle @decorator on standalone functions
* Converts @decorator to decorator comment when no ES6 classes are present
*/
function preprocessDecorators(content, filePath) {
// Check if file contains ES6 class declarations
// Using regex to avoid parsing errors from decorators
const es6ClassRegex = /^\s*class\s+[A-Z]\w*\s*(?:extends\s+\w+\s*)?\{/m;
const hasES6Class = es6ClassRegex.test(content);
if (hasES6Class) {
// File has ES6 classes, leave @decorator syntax unchanged
return content;
}
// No ES6 classes, convert @decorator to /** @decorator */
// Match @decorator at the start of a line (with optional whitespace)
// that appears before a function declaration
const decoratorRegex = /^(\s*)@decorator\s*\n(\s*(?:async\s+)?function\s+\w+)/gm;
const processed = content.replace(decoratorRegex, (match, indent, funcDecl) => {
return `${indent}/** @decorator */\n${funcDecl}`;
});
return processed;
}
// Preprocess content before transformation
content = preprocessDecorators(content, filePath);
// Generate file hash for prefixing (HARDCODED - NOT CONFIGURABLE)
// This prevents namespace collisions when files are concatenated in bundles
const fileHash = crypto.createHash('md5')
.update(hashPath)
.digest('hex')
.substring(0, 8);
// Target environment presets
const targetPresets = {
modern: {
targets: {
chrome: '90',
firefox: '88',
safari: '14',
edge: '90'
}
},
es6: {
targets: {
chrome: '60',
firefox: '60',
safari: '10.1',
edge: '15'
}
},
es5: {
targets: {
ie: '11'
}
}
};
// Create custom plugin to prefix generated WeakMap variables and Babel helper functions
// This plugin runs AFTER all other transformations to catch Babel-generated helpers
const prefixGeneratedVariables = function() {
return {
name: 'prefix-generated-variables',
post(file) {
// Run after all transformations are complete
const program = file.path;
// Track all top-level variables and functions that start with underscore
const generatedNames = new Set();
// First pass: collect all generated variable and function names at top level
for (const statement of program.node.body) {
if (statement.type === 'VariableDeclaration') {
for (const declarator of statement.declarations) {
const name = declarator.id?.name;
if (name && name.startsWith('_')) {
generatedNames.add(name);
}
}
} else if (statement.type === 'FunctionDeclaration') {
const name = statement.id?.name;
if (name && name.startsWith('_')) {
generatedNames.add(name);
}
}
}
// Second pass: rename all references
if (generatedNames.size > 0) {
program.traverse({
Identifier(idPath) {
if (generatedNames.has(idPath.node.name)) {
// Don't rename if it's already prefixed
if (!idPath.node.name.startsWith(`_${fileHash}`)) {
const newName = `_${fileHash}${idPath.node.name}`;
idPath.scope.rename(idPath.node.name, newName);
}
}
}
});
}
}
};
};
try {
// Configure Babel transformation
const result = babel.transformSync(content, {
filename: filePath,
sourceMaps: 'inline',
presets: [
['@babel/preset-env', targetPresets[target] || targetPresets.modern]
],
plugins: [
// Apply custom prefixing plugin first
prefixGeneratedVariables,
// Transform decorators (Stage 3 proposal)
// Note: We're NOT transforming private fields - native support only
['@babel/plugin-proposal-decorators', {
version: '2023-11',
// Ensure decorators are transpiled to compatible code
}],
// Transform class properties
'@babel/plugin-transform-class-properties',
// Transform optional chaining and nullish coalescing
'@babel/plugin-transform-optional-chaining',
'@babel/plugin-transform-nullish-coalescing-operator'
]
});
if (!result || !result.code) {
const error = new Error('Babel transformation produced no output');
outputError(error);
process.exit(1);
}
// Add comment header with file information
const header = `/* Transformed from: ${hashPath} (hash: ${fileHash}) */\n`;
const output = header + result.code;
// Output result
if (jsonOutput) {
console.log(JSON.stringify({
status: 'success',
result: output,
file: filePath,
hash: fileHash
}));
} else {
console.log(output);
}
} catch (error) {
// Parse Babel error location if available
if (error.loc) {
// Babel provides loc.line and loc.column
} else if (error.codeFrame) {
// Try to extract line/column from codeFrame
const lineMatch = error.codeFrame.match(/>\s*(\d+)\s*\|/);
const colMatch = error.codeFrame.match(/\n\s+\|\s+(\^+)/);
if (lineMatch) {
error.loc = {
line: parseInt(lineMatch[1]),
column: colMatch ? colMatch[1].indexOf('^') + 1 : 0
};
}
} else if (error.message) {
// Try to extract from message (e.g., "file.js: Unexpected token (10:5)")
const match = error.message.match(/\((\d+):(\d+)\)/);
if (match) {
error.loc = {
line: parseInt(match[1]),
column: parseInt(match[2])
};
}
}
outputError(error);
process.exit(1);
}