import fs from 'node:fs'; import path from 'node:path'; import { execFileSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = path.resolve(__dirname, '..'); const cloudDir = path.join(rootDir, 'cloud-functions'); const outDir = path.join(cloudDir, 'deployable'); const sessionSourcePath = path.join(cloudDir, '_session.js'); const parseClassStoreSourcePath = path.join(cloudDir, '_parseClassStore.js'); const targetFiles = [ '01-manifestManager.js', '02-taskManager.js', '03-historyManager.js', '04-resultManager.js', '05-remixManager.js', '06-voiceManager.js', '09-uploadManager.js', '13-douyinInsightManager.js', '14-systemStorageManager.js', '15-fileAssetManager.js', ]; const sessionSource = stripCommonJsExport(read(sessionSourcePath)).trim(); const parseClassStoreSource = stripCommonJsExport(read(parseClassStoreSourcePath)).trim(); fs.mkdirSync(outDir, { recursive: true }); for (const file of targetFiles) { const sourcePath = path.join(cloudDir, file); const source = read(sourcePath); const transformed = inlineLocalRequires(source, file); const helperSources = [sessionSource]; if (transformed.usesParseClassStore) { helperSources.push(parseClassStoreSource); } const banner = [ '/**', ` * ${file} 的 fmode 单文件部署版。`, ' * 本文件由 scripts/build-cloud-functions.mjs 生成。', ' * 请不要手动修改本文件;如需改动,请修改 cloud-functions 源码后重新生成。', ' */', '', ].join('\n'); const output = `${banner}${helperSources.join('\n\n')}\n\n${transformed.source}`; const outputPath = path.join(outDir, file); fs.writeFileSync(outputPath, output, 'utf8'); checkSyntax(outputPath); console.log(`built ${path.relative(rootDir, outputPath)}`); } console.log('云函数单文件部署包已生成'); function inlineLocalRequires(source, file) { const sessionResult = inlineRequire( source, './_session', '上方已内联 _session.js 的登录态和权限校验逻辑,用于 fmode 单文件部署。', ); if (!sessionResult.replaced) { throw new Error(`${file} does not import ./_session`); } const parseStoreResult = inlineRequire( sessionResult.source, './_parseClassStore', '上方已内联 _parseClassStore.js 的 Parse Class 存储工具,用于 fmode 单文件部署。', ); const withoutRequire = parseStoreResult.source; if (withoutRequire.includes("require('./_session')") || withoutRequire.includes('require("./_session")')) { throw new Error(`${file} still contains ./_session require after transform`); } if ( withoutRequire.includes("require('./_parseClassStore')") || withoutRequire.includes('require("./_parseClassStore")') ) { throw new Error(`${file} still contains ./_parseClassStore require after transform`); } return { source: withoutRequire.trim(), usesParseClassStore: parseStoreResult.replaced, }; } function inlineRequire(source, requirePath, comment) { let replaced = false; const escapedPath = requirePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const pattern = new RegExp(`const\\s*\\{([\\s\\S]*?)\\}\\s*=\\s*require\\(['"]${escapedPath}['"]\\);?`, 'm'); const next = source.replace(pattern, (_match, imports) => { replaced = true; return buildAliases(imports, comment); }); return { source: next, replaced }; } function buildAliases(imports, comment) { const lines = []; for (const raw of imports.split(',')) { const item = raw.trim(); if (!item) continue; const aliasMatch = item.match(/^([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$]*)$/); if (aliasMatch) { lines.push(`const ${aliasMatch[2]} = ${aliasMatch[1]};`); } } return lines.length ? `// ${comment}\n${lines.join('\n')}` : `// ${comment}`; } function stripCommonJsExport(source) { return source.replace(/module\.exports\s*=\s*\{[\s\S]*?\};?\s*$/m, ''); } function read(filePath) { return fs.readFileSync(filePath, 'utf8'); } function checkSyntax(filePath) { execFileSync(process.execPath, ['--check', filePath], { stdio: 'pipe' }); }