cloud-functions-readiness.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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 deployableDir = path.join(cloudDir, 'deployable');
  9. const cloudFunctionsTs = path.join(rootDir, 'src', 'app', 'services', 'cloud-functions.ts');
  10. const deployDoc = path.join(cloudDir, 'DEPLOY.md');
  11. const expectedFiles = [
  12. '_session.js',
  13. '_parseClassStore.js',
  14. '09-uploadManager.js',
  15. '10-authCreditManager.js',
  16. '11-jimengManager.js',
  17. '14-systemStorageManager.js',
  18. '15-fileAssetManager.js',
  19. ];
  20. const sessionUsers = [
  21. '09-uploadManager.js',
  22. '14-systemStorageManager.js',
  23. '15-fileAssetManager.js',
  24. ];
  25. const deployableSessionUsers = [
  26. '01-manifestManager.js',
  27. '02-taskManager.js',
  28. '03-historyManager.js',
  29. '04-resultManager.js',
  30. '05-remixManager.js',
  31. '06-voiceManager.js',
  32. '09-uploadManager.js',
  33. '13-douyinInsightManager.js',
  34. '14-systemStorageManager.js',
  35. '15-fileAssetManager.js',
  36. ];
  37. const deployKeys = [
  38. 'authCredit',
  39. 'systemStorage',
  40. 'fileAsset',
  41. 'upload',
  42. ];
  43. const errors = [];
  44. const warnings = [];
  45. for (const file of expectedFiles) {
  46. assertFile(path.join(cloudDir, file), `Missing cloud function source: ${file}`);
  47. }
  48. for (const file of expectedFiles) {
  49. const fullPath = path.join(cloudDir, file);
  50. if (fs.existsSync(fullPath)) checkNodeSyntax(fullPath);
  51. }
  52. for (const file of deployableSessionUsers) {
  53. const fullPath = path.join(deployableDir, file);
  54. assertFile(fullPath, `Missing deployable single-file cloud function: ${file}. Run npm run build:cloud-functions`);
  55. if (fs.existsSync(fullPath)) {
  56. checkNodeSyntax(fullPath);
  57. const text = read(fullPath);
  58. if (text.includes("require('./_session')") || text.includes('require("./_session")')) {
  59. errors.push(`${file} deployable build must inline _session and must not require it`);
  60. }
  61. if (text.includes("require('./_parseClassStore')") || text.includes('require("./_parseClassStore")')) {
  62. errors.push(`${file} deployable build must inline _parseClassStore and must not require it`);
  63. }
  64. if (!text.includes('本文件由 scripts/build-cloud-functions.mjs 生成')) {
  65. warnings.push(`${file} deployable build is missing generated-file banner`);
  66. }
  67. }
  68. }
  69. for (const file of sessionUsers) {
  70. const text = read(path.join(cloudDir, file));
  71. if (!text.includes("require('./_session')") && !text.includes('require("./_session")')) {
  72. errors.push(`${file} must use shared _session.js for session ownership checks`);
  73. }
  74. }
  75. const cloudConfig = read(cloudFunctionsTs);
  76. for (const key of deployKeys) {
  77. if (!new RegExp(`\\b${key}\\s*:`).test(cloudConfig)) {
  78. errors.push(`CLOUD_FN.${key} is missing in src/app/services/cloud-functions.ts`);
  79. }
  80. }
  81. const allCloudSource = expectedFiles.map((file) => read(path.join(cloudDir, file))).join('\n');
  82. if (allCloudSource.includes('6pF6EAdKT')) {
  83. errors.push('Wrong video workflow APIG id 6pF6EAdKT is still present; expected 6pFf6EAdKT');
  84. }
  85. if (!allCloudSource.includes('6pFf6EAdKT')) {
  86. errors.push('Expected video workflow APIG id 6pFf6EAdKT was not found in cloud function sources');
  87. }
  88. const deployText = read(deployDoc);
  89. for (const file of expectedFiles) {
  90. if (!deployText.includes(file)) {
  91. warnings.push(`DEPLOY.md does not mention ${file}`);
  92. }
  93. }
  94. if (!deployText.includes('fmode') || !deployText.includes('authCredit') || !deployText.includes('systemStorage') || !deployText.includes('fileAsset')) {
  95. warnings.push('DEPLOY.md should explicitly state new functions use the existing fmode cloud function mechanism');
  96. }
  97. if (!deployText.includes('SMOKE_SESSION_TOKEN')) {
  98. warnings.push('DEPLOY.md should document SMOKE_SESSION_TOKEN deployment validation');
  99. }
  100. if (!deployText.includes('cloud-functions/deployable')) {
  101. warnings.push('DEPLOY.md should tell users to deploy cloud-functions/deployable single-file builds on fmode');
  102. }
  103. for (const file of ['14-systemStorageManager.js', '15-fileAssetManager.js']) {
  104. const text = read(path.join(cloudDir, file));
  105. if (!text.includes('adminInspect')) {
  106. errors.push(`${file} must expose adminInspect for storage governance inspection`);
  107. }
  108. if (/\bPsql\.query\b|\bSystemEntity\b|\bSystemAudit\b|\bDataMigration\b|\bFileAsset\b/.test(text)) {
  109. errors.push(`${file} must use VideoWorkflow* Parse Class storage and must not reference old PSQL table names`);
  110. }
  111. const deployableText = read(path.join(deployableDir, file));
  112. if (/\bPsql\.query\b|\bSystemEntity\b|\bSystemAudit\b|\bDataMigration\b|\bFileAsset\b/.test(deployableText)) {
  113. errors.push(`${file} deployable must not reference old PSQL table names`);
  114. }
  115. }
  116. if (warnings.length) {
  117. for (const warning of warnings) console.warn(`WARN ${warning}`);
  118. }
  119. if (errors.length) {
  120. for (const error of errors) console.error(`FAIL ${error}`);
  121. process.exit(1);
  122. }
  123. console.log('Cloud function readiness passed');
  124. function assertFile(filePath, message) {
  125. if (!fs.existsSync(filePath)) errors.push(message);
  126. }
  127. function read(filePath) {
  128. try {
  129. return fs.readFileSync(filePath, 'utf8');
  130. } catch {
  131. return '';
  132. }
  133. }
  134. function checkNodeSyntax(filePath) {
  135. try {
  136. execFileSync(process.execPath, ['--check', filePath], { stdio: 'pipe' });
  137. } catch (error) {
  138. const output = `${error.stdout || ''}${error.stderr || ''}`.trim();
  139. errors.push(`${path.relative(rootDir, filePath)} syntax check failed${output ? `: ${output}` : ''}`);
  140. }
  141. }