build-cloud-functions.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import { execFileSync } from 'node:child_process';
  4. import { fileURLToPath } from 'node:url';
  5. const __dirname = path.dirname(fileURLToPath(import.meta.url));
  6. const rootDir = path.resolve(__dirname, '..');
  7. const cloudDir = path.join(rootDir, 'cloud-functions');
  8. const outDir = path.join(cloudDir, 'deployable');
  9. const sessionSourcePath = path.join(cloudDir, '_session.js');
  10. const parseClassStoreSourcePath = path.join(cloudDir, '_parseClassStore.js');
  11. const targetFiles = [
  12. '01-manifestManager.js',
  13. '02-taskManager.js',
  14. '03-historyManager.js',
  15. '04-resultManager.js',
  16. '05-remixManager.js',
  17. '06-voiceManager.js',
  18. '09-uploadManager.js',
  19. '13-douyinInsightManager.js',
  20. '14-systemStorageManager.js',
  21. '15-fileAssetManager.js',
  22. ];
  23. const sessionSource = stripCommonJsExport(read(sessionSourcePath)).trim();
  24. const parseClassStoreSource = stripCommonJsExport(read(parseClassStoreSourcePath)).trim();
  25. fs.mkdirSync(outDir, { recursive: true });
  26. for (const file of targetFiles) {
  27. const sourcePath = path.join(cloudDir, file);
  28. const source = read(sourcePath);
  29. const transformed = inlineLocalRequires(source, file);
  30. const helperSources = [sessionSource];
  31. if (transformed.usesParseClassStore) {
  32. helperSources.push(parseClassStoreSource);
  33. }
  34. const banner = [
  35. '/**',
  36. ` * ${file} 的 fmode 单文件部署版。`,
  37. ' * 本文件由 scripts/build-cloud-functions.mjs 生成。',
  38. ' * 请不要手动修改本文件;如需改动,请修改 cloud-functions 源码后重新生成。',
  39. ' */',
  40. '',
  41. ].join('\n');
  42. const output = `${banner}${helperSources.join('\n\n')}\n\n${transformed.source}`;
  43. const outputPath = path.join(outDir, file);
  44. fs.writeFileSync(outputPath, output, 'utf8');
  45. checkSyntax(outputPath);
  46. console.log(`built ${path.relative(rootDir, outputPath)}`);
  47. }
  48. console.log('云函数单文件部署包已生成');
  49. function inlineLocalRequires(source, file) {
  50. const sessionResult = inlineRequire(
  51. source,
  52. './_session',
  53. '上方已内联 _session.js 的登录态和权限校验逻辑,用于 fmode 单文件部署。',
  54. );
  55. if (!sessionResult.replaced) {
  56. throw new Error(`${file} does not import ./_session`);
  57. }
  58. const parseStoreResult = inlineRequire(
  59. sessionResult.source,
  60. './_parseClassStore',
  61. '上方已内联 _parseClassStore.js 的 Parse Class 存储工具,用于 fmode 单文件部署。',
  62. );
  63. const withoutRequire = parseStoreResult.source;
  64. if (withoutRequire.includes("require('./_session')") || withoutRequire.includes('require("./_session")')) {
  65. throw new Error(`${file} still contains ./_session require after transform`);
  66. }
  67. if (
  68. withoutRequire.includes("require('./_parseClassStore')") ||
  69. withoutRequire.includes('require("./_parseClassStore")')
  70. ) {
  71. throw new Error(`${file} still contains ./_parseClassStore require after transform`);
  72. }
  73. return {
  74. source: withoutRequire.trim(),
  75. usesParseClassStore: parseStoreResult.replaced,
  76. };
  77. }
  78. function inlineRequire(source, requirePath, comment) {
  79. let replaced = false;
  80. const escapedPath = requirePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  81. const pattern = new RegExp(`const\\s*\\{([\\s\\S]*?)\\}\\s*=\\s*require\\(['"]${escapedPath}['"]\\);?`, 'm');
  82. const next = source.replace(pattern, (_match, imports) => {
  83. replaced = true;
  84. return buildAliases(imports, comment);
  85. });
  86. return { source: next, replaced };
  87. }
  88. function buildAliases(imports, comment) {
  89. const lines = [];
  90. for (const raw of imports.split(',')) {
  91. const item = raw.trim();
  92. if (!item) continue;
  93. const aliasMatch = item.match(/^([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$]*)$/);
  94. if (aliasMatch) {
  95. lines.push(`const ${aliasMatch[2]} = ${aliasMatch[1]};`);
  96. }
  97. }
  98. return lines.length ? `// ${comment}\n${lines.join('\n')}` : `// ${comment}`;
  99. }
  100. function stripCommonJsExport(source) {
  101. return source.replace(/module\.exports\s*=\s*\{[\s\S]*?\};?\s*$/m, '');
  102. }
  103. function read(filePath) {
  104. return fs.readFileSync(filePath, 'utf8');
  105. }
  106. function checkSyntax(filePath) {
  107. execFileSync(process.execPath, ['--check', filePath], { stdio: 'pipe' });
  108. }