diff --git a/ace-loader/main.product.js b/ace-loader/main.product.js index 8441b8051e35b0c0735be77abf14ac6fc65c1f10..75171137a8359bd5a73dbb1649bf660d9f9a1fa6 100644 --- a/ace-loader/main.product.js +++ b/ace-loader/main.product.js @@ -51,249 +51,322 @@ function deleteFolderRecursive(url) { } /** - * Read manifest file for configuration information. - * @param {string} manifestFilePath Path to configuration file. - * @returns + * Reads and parses a manifest JSON file synchronously. + * @param {string} manifestFilePath - Path to the manifest JSON file. + * @returns {Object} Parsed manifest object (empty object if file doesn't exist). + * @throws {Error} If file exists but contains invalid JSON. */ function readManifest(manifestFilePath) { - let manifest = {}; + // Return empty object immediately if file doesn't exist + if (!fs.existsSync(manifestFilePath)) { + return {}; + } + try { - if (fs.existsSync(manifestFilePath)) { - const jsonString = fs.readFileSync(manifestFilePath).toString(); - manifest = JSON.parse(jsonString); - } - } catch (e) { - throw Error('\u001b[31m' + 'ERROR: the manifest.json file format is invalid.' + - '\u001b[39m').message; + // Read and parse file synchronously + const fileContent = fs.readFileSync(manifestFilePath, 'utf8'); + return JSON.parse(fileContent); + } catch (error) { + // Throw formatted error message if parsing fails + throw new Error('\u001b[31mERROR: Invalid manifest.json file format.\u001b[39m'); } - return manifest; } /** - * Load pages from manifest infomation. - * @param {string} projectPath Current compiled file. - * @param {string} device_level Current device version. - * @param {string} abilityType Current Ability type. - * @param {string} manifestFilePath Path to configuration file. - * @returns {object} result of compiling the configuration infomation. + * Loads entry object configuration based on project type and ability. + * @param {string} projectPath - Root path of the project + * @param {string} device_level - Device level ('card' or others) + * @param {string} abilityType - Type of ability ('page', 'form', 'testrunner' etc.) + * @param {string} manifestFilePath - Path to manifest.json file + * @returns {Object} Configured entry object + * @throws {Error} When required files are missing */ function loadEntryObj(projectPath, device_level, abilityType, manifestFilePath) { - let entryObj = {}; + const entryObj = {}; + switch (abilityType) { case 'page': - const appJSPath = path.resolve(projectPath, 'app.js'); if (device_level === 'card') { - entryObj = addPageEntryObj(readManifest(manifestFilePath), projectPath); + Object.assign(entryObj, addPageEntryObj(readManifest(manifestFilePath), projectPath)); } else { + const appJSPath = path.resolve(projectPath, 'app.js'); if (!fs.existsSync(appJSPath)) { - throw Error(red + 'ERROR: missing app.js' + reset).message; + throw new Error(`${red}ERROR: missing app.js${reset}`); } - entryObj['./app'] = path.resolve(projectPath, './app.js?entry'); + entryObj['./app'] = `${path.resolve(projectPath, './app.js')}?entry`; } break; + case 'form': - entryObj = addPageEntryObj(readManifest(manifestFilePath), projectPath); - entryObj[`./${abilityType}`] = path.resolve(projectPath, `./${abilityType}.js?entry`); + Object.assign(entryObj, addPageEntryObj(readManifest(manifestFilePath), projectPath)); + entryObj[`./${abilityType}`] = `${path.resolve(projectPath, `./${abilityType}.js`)}?entry`; break; + case 'testrunner': + // No entry needed for testrunner break; + default: - entryObj[`./${abilityType}`] = path.resolve(projectPath, `./${abilityType}.js?entry`); + entryObj[`./${abilityType}`] = `${path.resolve(projectPath, `./${abilityType}.js`)}?entry`; break; } + return entryObj; } /** - * Read papes from manifest infomation. - * @param {object} manifest Configuration infomation. - * @param {string} projectPath Current compiled file. - * @returns {object} Pages in configuration infomation. + * Extracts page entries from manifest information. + * @param {Object} manifest - Configuration information containing pages. + * @param {string} projectPath - Root path of the current project. + * @returns {Object} Entry object containing all page configurations. + * @throws {Error} When pages are missing or have both hml/visual files. */ function addPageEntryObj(manifest, projectPath) { - let entryObj = {}; - const pages = manifest.pages; - if (pages === undefined) { - throw Error('ERROR: missing pages').message; + const entryObj = {}; + const { pages } = manifest; + + // Validate pages existence + if (!pages) { + throw new Error(`${red}ERROR: missing pages${reset}`); } - pages.forEach((element) => { - const sourcePath = element; - const hmlPath = path.join(projectPath, sourcePath + '.hml'); + + // Process each page entry + pages.forEach((sourcePath) => { + const hmlPath = path.join(projectPath, `${sourcePath}.hml`); const aceSuperVisualPath = process.env.aceSuperVisualPath || ''; - const visualPath = path.join(aceSuperVisualPath, sourcePath + '.visual'); + const visualPath = path.join(aceSuperVisualPath, `${sourcePath}.visual`); + const isHml = fs.existsSync(hmlPath); const isVisual = fs.existsSync(visualPath); + + // Validate file existence if (isHml && isVisual) { - throw Error(red + 'ERROR: ' + sourcePath + ' cannot both have hml && visual').message; - } else if (isHml) { - entryObj['./' + element] = path.resolve(projectPath, './' + sourcePath + '.hml?entry'); + throw new Error(`${red}ERROR: ${sourcePath} cannot have both hml && visual${reset}`); + } + + // Build entry path + if (isHml) { + entryObj[`./${sourcePath}`] = `${path.resolve(projectPath, `./${sourcePath}.hml`)}?entry`; } else if (isVisual) { - entryObj['./' + element] = path.resolve(aceSuperVisualPath, './' + sourcePath + - '.visual?entry'); + entryObj[`./${sourcePath}`] = `${path.resolve(aceSuperVisualPath, `./${sourcePath}.visual`)}?entry`; } - }) + }); + return entryObj; } /** - * Match card mode. - * @param {object} env Collection of environmental variables. + * Compiles card module based on environment configuration. + * @param {Object} env - Collection of environmental variables. + * @throws {Error} When compilation fails or module configuration is invalid. */ - function compileCardModule(env) { - if (process.env.aceModuleJsonPath && fs.existsSync(process.env.aceModuleJsonPath)) { - const moduleJsonConfig = JSON.parse(fs.readFileSync(process.env.aceModuleJsonPath).toString()); - if (moduleJsonConfig.module && - (moduleJsonConfig.module.uiSyntax === 'ets' || moduleJsonConfig.module.language === 'ets')) { - process.env.DEVICE_LEVEL = 'card'; - } else if (validateCardModule(moduleJsonConfig) && !process.env.compileCardModule) { - process.env.compileCardModule = true; - const cmd = `webpack --config webpack.rich.config.js --env compilerType=${env.compilerType} ` + - `DEVICE_LEVEL=card aceModuleRoot=${process.env.projectPath} ` + - `aceModuleJsonPath=${process.env.aceModuleJsonPath} aceProfilePath=${process.env.aceProfilePath} ` + - `watchMode=${process.env.watchMode} cachePath=${process.env.cachePath} ` + - `aceModuleBuild=${process.env.buildPath}`; - shell.exec(cmd, (err) => { - if (err) { - throw Error(err).message; - } - }) - } + const { aceModuleJsonPath } = process.env; + + // Early return if module path doesn't exist + if (!aceModuleJsonPath || !fs.existsSync(aceModuleJsonPath)) { + return; + } + + // Parse module configuration + const moduleJsonConfig = JSON.parse(fs.readFileSync(aceModuleJsonPath, 'utf8')); + + // Handle ETS module case + if (moduleJsonConfig.module?.uiSyntax === 'ets' || moduleJsonConfig.module?.language === 'ets') { + process.env.DEVICE_LEVEL = 'card'; + return; + } + + // Validate and compile card module + if (validateCardModule(moduleJsonConfig) && !process.env.compileCardModule) { + process.env.compileCardModule = true; + + const cmdArgs = [ + 'webpack', + '--config webpack.rich.config.js', + `--env compilerType=${env.compilerType}`, + 'DEVICE_LEVEL=card', + `aceModuleRoot=${process.env.projectPath}`, + `aceModuleJsonPath=${aceModuleJsonPath}`, + `aceProfilePath=${process.env.aceProfilePath}`, + `watchMode=${process.env.watchMode}`, + `cachePath=${process.env.cachePath}`, + `aceModuleBuild=${process.env.buildPath}` + ].join(' '); + + shell.exec(cmdArgs, (err) => { + if (err) { + throw new Error(`Card module compilation failed: ${err}`); + } + }); } } /** - * Determine whether the current compilation is in card mode. - * @param {object} moduleJsonConfig Configration for module. - * @returns + * Determines if the current compilation is in card mode by checking for form extension abilities. + * @param {Object} moduleJsonConfig - Module configuration object + * @returns {boolean} True if configuration contains form extension abilities, false otherwise */ function validateCardModule(moduleJsonConfig) { - if (moduleJsonConfig.module && moduleJsonConfig.module.extensionAbilities) { - for (let i = 0; i < moduleJsonConfig.module.extensionAbilities.length; i++) { - const extensionAbility = moduleJsonConfig.module.extensionAbilities[i]; - if (extensionAbility.type && extensionAbility.type === 'form') { - return true; - } - } + const { module } = moduleJsonConfig; + + // Early return if no module or extensionAbilities configuration + if (!module?.extensionAbilities) { + return false; } - return false; + + // Check for form type in extension abilities + return module.extensionAbilities.some( + ({ type }) => type === 'form' + ); } /** - * Hash for projectPath. - * @param {string} projectPath Current compiled file. + * Generates a SHA-256 hash for the project path and stores it in environment variables. + * @param {string} projectPath - Path of the current compiled file/project + * @returns {void} */ function hashProjectPath(projectPath) { - const hash = crypto.createHash('sha256') - hash.update(projectPath.toString()) - process.env.hashProjectPath = "_" + hash.digest('hex'); + const hash = crypto.createHash('sha256').update(projectPath).digest('hex'); + process.env.hashProjectPath = `_${hash}`; } /** - * Check if the current compilation requires multiple resource. - * @param {string} aceBuildJson Current compilation result path folder. + * Checks if current compilation requires multi-resource build by parsing build config. + * @param {string} aceBuildJson - Path to compilation result JSON file + * @returns {void} */ function checkMultiResourceBuild(aceBuildJson) { - if (aceBuildJson && fs.existsSync(aceBuildJson)) { - try { - const content = JSON.parse(fs.readFileSync(aceBuildJson)); - if (content["multiResourceBuild"]) { - multiResourceBuild.value = content["multiResourceBuild"] - } - if (content["projectRootPath"]) { - process.env.projectRootPath = content["projectRootPath"] - } - } catch (error) { + // Early return if path is invalid + if (!aceBuildJson || !fs.existsSync(aceBuildJson)) { + return; + } + try { + // Parse config file with explicit utf-8 encoding + const content = JSON.parse(fs.readFileSync(aceBuildJson, 'utf8')); + + // Set multiResourceBuild flag if present + if (content.multiResourceBuild !== undefined) { + multiResourceBuild.value = content.multiResourceBuild; } + + // Set project root path if present + if (content.projectRootPath) { + process.env.projectRootPath = content.projectRootPath; + } + } catch (error) { + // Silently handle parse errors (maintaining original behavior) + console.debug('Failed to parse build config:', error.message); } } /** - * Read worker configuration information. - * @returns {object || null} Worker enttry pages. + * Reads and processes worker configuration from build JSON. + * @returns {Object|null} Worker entry mapping object or null if not configured */ function readWorkerFile() { - const workerEntryObj = {}; - if (process.env.aceBuildJson && fs.existsSync(process.env.aceBuildJson)) { - const workerConfig = JSON.parse(fs.readFileSync(process.env.aceBuildJson).toString()); - if(workerConfig.workers) { + // Early return if build file doesn't exist + if (!process.env.aceBuildJson || !fs.existsSync(process.env.aceBuildJson)) { + return null; + } + + try { + const workerConfig = JSON.parse(fs.readFileSync(process.env.aceBuildJson, 'utf8')); + const workerEntryObj = {}; + + // Process workers if configured + if (workerConfig.workers?.length) { workerConfig.workers.forEach(worker => { - if (!/\.(js)$/.test(worker)) { - worker += '.js'; - } - const relativePath = path.relative(process.env.projectPath, worker); + const jsFile = worker.endsWith('.js') ? worker : `${worker}.js`; + const relativePath = path.relative(process.env.projectPath, jsFile); + if (filterWorker(relativePath)) { - workerEntryObj[relativePath.replace(/\.(ts|js)$/,'').replace(/\\/g, '/')] = worker; + const entryKey = relativePath + .replace(/\.(ts|js)$/, '') + .replace(/\\/g, '/'); + workerEntryObj[entryKey] = jsFile; } - }) + }); return workerEntryObj; } + } catch (error) { + console.error('Failed to parse worker config:', error); } + return null; } /** - * Regular for worker. - * @param {string} workerPath Path of worker. - * @returns {boolean} is pages. + * Determines if a path points to a valid worker file (JS/TS extension). + * @param {string} workerPath - Path to check for worker file + * @returns {boolean} True if path has .js or .ts extension */ function filterWorker(workerPath) { - return /\.(ts|js)$/.test(workerPath); + return /\.(js|ts)$/i.test(workerPath); } /** - * Determine cache and decide whether to delete it. - * @param {string} cachePath Current compilation cache directory. - * @returns + * Checks cache validity and deletes cache directory if invalid. + * @param {string} cachePath - Path to compilation cache directory + * @returns {void} */ function compareCache(cachePath) { const entryFile = path.join(cachePath, 'entry.json'); const cssFile = process.env.watchCSSFiles; + const files = new Set(); - let files = []; - let cssObject = {}; + // Process CSS clear flag if (fs.existsSync(cssFile)) { - cssObject = JSON.parse(fs.readFileSync(cssFile)); - if (cssObject['clear'] === true) { + const cssObject = JSON.parse(fs.readFileSync(cssFile, 'utf8')); + + if (cssObject.clear === true) { deleteFolderRecursive(cachePath); return; } - Object.keys(cssObject).forEach(key => { - if (key !== 'entry') { - files.push(key); + + // Collect CSS files excluding special entries + for (const [key, value] of Object.entries(cssObject)) { + if (key !== 'entry' && key !== 'atime') { + files.add(key); } - }) + } } + // Merge with entry files if (fs.existsSync(entryFile)) { - files = files.concat(JSON.parse(fs.readFileSync(entryFile))); + const entryFiles = JSON.parse(fs.readFileSync(entryFile, 'utf8')); + entryFiles.forEach(file => files.add(file)); } - for(let file of files) { - if (!fs.existsSync(file)) { - deleteFolderRecursive(cachePath); - break; - } else if (cssObject['atime'] && cssObject['atime'][file]) { - if (cssObject['atime'][file] !== fs.statSync(file).atime.toString()) { + // Validate file existence and access times + for (const file of files) { + try { + const stats = fs.statSync(file); + + if (!cssObject?.atime?.[file]) { + continue; + } + if (cssObject.atime[file] !== stats.atime.toString()) { deleteFolderRecursive(cachePath); - break; + return; } + } catch (error) { + // File doesn't exist or inaccessible + deleteFolderRecursive(cachePath); + return; } } } /** - * Parse ability filePath. - * @param {string} abilityType Current Ability type. - * @param {string} projectPath Current compiled file. - * @returns {string} ability filePath. + * Resolves the file path for a given ability type. + * @param {string} abilityType - Type of the ability ('page' or other) + * @param {string} projectPath - Root path of the project + * @returns {string} Absolute path to the ability file */ function parseAbilityName(abilityType, projectPath) { - if (abilityType === 'page') { - return path.resolve(__dirname, projectPath, 'app.js'); - } else { - return path.resolve(__dirname, projectPath, abilityType + '.js'); - } + const fileName = abilityType === 'page' ? 'app.js' : `${abilityType}.js`; + return path.resolve(__dirname, projectPath, fileName); } module.exports = {