deploy-cloud-function.mjs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import 'dotenv/config';
  2. import { readFile } from 'node:fs/promises';
  3. import { randomUUID } from 'node:crypto';
  4. import { fileURLToPath } from 'node:url';
  5. import { dirname, resolve } from 'node:path';
  6. const required = ['PARSE_SERVER_URL', 'PARSE_APP_ID', 'PARSE_MASTER_KEY'];
  7. for (const name of required) if (!process.env[name]?.trim()) throw new Error(`${name} is required`);
  8. const registryBase = (process.env.FUNCTION_REGISTRY_SERVER_URL || process.env.PARSE_SERVER_URL).replace(/\/+$/, '');
  9. const registryAppId = process.env.FUNCTION_REGISTRY_APP_ID || process.env.PARSE_APP_ID;
  10. const registryMasterKey = process.env.FUNCTION_REGISTRY_MASTER_KEY || process.env.PARSE_MASTER_KEY;
  11. const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': registryAppId, 'X-Parse-Master-Key': registryMasterKey };
  12. const __dirname = dirname(fileURLToPath(import.meta.url));
  13. const cloudFunctionsDir = resolve(__dirname, '..', 'cloud-functions');
  14. // Cloud function definitions
  15. const CLOUD_FUNCTIONS = [
  16. { name: 'vContext', path: '/v-context', file: 'vContext.js', desc: 'SaaS VOC context and workspace management' },
  17. { name: 'vDomesticVoc', path: '/v-domestic-voc', file: 'vDomesticVoc.js', desc: 'SaaS VOC domestic VOC data' },
  18. { name: 'vSync', path: '/v-sync', file: 'vSync.js', desc: 'SaaS VOC sync job management' },
  19. { name: 'vCompetitor', path: '/v-competitor', file: 'vCompetitor.js', desc: 'SaaS VOC competitor monitor' },
  20. { name: 'vListing', path: '/v-listing', file: 'vListing.js', desc: 'SaaS VOC listing scores' },
  21. { name: 'vListingJob', path: '/v-listing-job', file: 'vListingJob.js', desc: 'SaaS VOC listing score jobs' },
  22. { name: 'vAnalysis', path: '/v-analysis', file: 'vAnalysis.js', desc: 'SaaS VOC analysis and insights' },
  23. { name: 'vActions', path: '/v-actions', file: 'vActions.js', desc: 'SaaS VOC actions and alerts' },
  24. { name: 'vKnowledge', path: '/v-knowledge', file: 'vKnowledge.js', desc: 'SaaS VOC product knowledge' },
  25. { name: 'vAI', path: '/v-ai', file: 'vAI.js', desc: 'SaaS VOC AI gateway' },
  26. { name: 'vUpstream', path: '/v-upstream', file: 'vUpstream.js', desc: 'SaaS VOC upstream API proxy' },
  27. ];
  28. async function deployFunction(func) {
  29. const filePath = resolve(cloudFunctionsDir, func.file);
  30. const code = await readFile(filePath, 'utf8');
  31. const query = new URL(`${registryBase}/classes/Function`);
  32. query.searchParams.set('where', JSON.stringify({ path: func.path }));
  33. const existingResponse = await fetch(query, { headers });
  34. if (!existingResponse.ok) throw new Error(`Function lookup failed for ${func.name}: HTTP ${existingResponse.status}`);
  35. const existing = await existingResponse.json();
  36. const payload = {
  37. name: func.name,
  38. desc: func.desc,
  39. type: 'standalone',
  40. path: func.path,
  41. paramList: [{ name: 'params', type: 'Object', required: true }],
  42. code,
  43. respType: 'json',
  44. respJson: { success: true, data: null, requestId: randomUUID() },
  45. isDeleted: false,
  46. };
  47. const target = existing.results?.[0];
  48. const url = target
  49. ? `${registryBase}/classes/Function/${target.objectId}`
  50. : `${registryBase}/classes/Function`;
  51. const method = target ? 'PUT' : 'POST';
  52. const response = await fetch(url, {
  53. method,
  54. headers,
  55. body: JSON.stringify(payload),
  56. });
  57. if (!response.ok) {
  58. const errorText = await response.text();
  59. throw new Error(`Function deployment failed for ${func.name}: HTTP ${response.status} - ${errorText}`);
  60. }
  61. const result = await response.json();
  62. return {
  63. ok: true,
  64. name: func.name,
  65. path: func.path,
  66. objectId: target?.objectId || result.objectId,
  67. action: target ? 'updated' : 'created',
  68. };
  69. }
  70. async function main() {
  71. console.log(`Deploying ${CLOUD_FUNCTIONS.length} cloud functions...`);
  72. const results = [];
  73. for (const func of CLOUD_FUNCTIONS) {
  74. try {
  75. const result = await deployFunction(func);
  76. console.log(`✓ ${result.name} (${result.action}): ${result.objectId}`);
  77. results.push(result);
  78. } catch (error) {
  79. console.error(`✗ ${func.name}: ${error.message}`);
  80. results.push({ ok: false, name: func.name, error: error.message });
  81. }
  82. }
  83. const failed = results.filter((r) => !r.ok);
  84. if (failed.length > 0) {
  85. console.error(`\n${failed.length} function(s) failed to deploy`);
  86. process.exit(1);
  87. }
  88. console.log(`\nAll ${results.length} cloud functions deployed successfully!`);
  89. console.log(`\nCloud function paths:`);
  90. results.forEach((r) => console.log(` ${r.name}: ${r.path}`));
  91. }
  92. main().catch((error) => {
  93. console.error('Deployment failed:', error);
  94. process.exitCode = 1;
  95. });