build-five-views-package.mjs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import { fileURLToPath } from 'node:url';
  5. import { FILES, RELEASE } from './publish-five-views.mjs';
  6. import { buildIdentityFunctions } from '../server/identity/build-release.mjs';
  7. const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
  8. const sha = (b) => crypto.createHash('sha256').update(b).digest('hex');
  9. const baseline = JSON.parse(fs.readFileSync(path.join(root, 'deploy/five-views-production-baseline-20261103.json')));
  10. const prior = new Map(baseline.records.map((r) => [r.path, r]));
  11. const manifest = {
  12. version: 'ksf.five-views.publish.v1', release: RELEASE,
  13. homepageSha256: prior.get('index.html').rawSha256,
  14. files: FILES.map((name) => ({ path: name, bytes: fs.statSync(path.join(root, name)).size,
  15. sha256: sha(fs.readFileSync(path.join(root, name))), allowMissing: !prior.get(name)?.present,
  16. priorNormalizedSha256: prior.get(name)?.present ? [prior.get(name).normalizedSha256] : [] })),
  17. preserved: ['src/services/board-service.js', 'src/components/kf-opportunity-card.js', 'src/pages/view-pool.js', 'src/core/report.js', 'src/core/evidence/chain.js']
  18. .map((name) => {
  19. const normalized = sha(Buffer.from(fs.readFileSync(path.join(root, name), 'utf8').replaceAll('\r\n', '\n')));
  20. if (normalized !== prior.get(name)?.normalizedSha256) throw new Error('Published repair would change: ' + name);
  21. return { path: name, normalizedSha256: normalized };
  22. }),
  23. };
  24. fs.writeFileSync(path.join(root, 'deploy/five-views-files-20261103.json'), JSON.stringify(manifest, null, 2));
  25. if (!process.argv.includes('--manifest-only')) {
  26. const index = process.argv.indexOf('--out');
  27. if (index < 0 || !process.argv[index + 1]) throw new Error('Use --out ABSOLUTE_NEW_DIRECTORY or --manifest-only');
  28. const out = process.argv[index + 1];
  29. if (!path.isAbsolute(out) || fs.existsSync(out) || out === root) throw new Error('Output must be a new absolute directory');
  30. fs.mkdirSync(out, { recursive: true });
  31. const copy = (source, target = source) => { const dest = path.join(out, target); fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(path.join(root, source), dest); };
  32. for (const file of FILES) copy(file, 'payload/' + file);
  33. for (const file of ['scripts/publish-five-views.mjs', 'deploy/five-views-files-20261103.json', 'deploy/five-views-production-baseline-20261103.json',
  34. 'docs/showcase/prd.md', 'docs/showcase/schema.md', 'docs/showcase/api.md', 'docs/showcase/design.md', 'docs/showcase/test.md', 'docs/showcase/deploy.md', 'docs/showcase/validation-result.json']) copy(file);
  35. const identityIndex = process.argv.indexOf('--identity-base');
  36. if (identityIndex >= 0) {
  37. const uploadIndex = process.argv.indexOf('--identity-upload-bases');
  38. const functions = buildIdentityFunctions(JSON.parse(fs.readFileSync(process.argv[identityIndex + 1], 'utf8')), {
  39. uploadBases: uploadIndex < 0 ? [] : JSON.parse(fs.readFileSync(process.argv[uploadIndex + 1], 'utf8')),
  40. });
  41. const dist = path.join(out, 'server/identity/dist'); fs.mkdirSync(dist, { recursive: true });
  42. for (const entry of functions) fs.writeFileSync(path.join(dist, entry.name + '.js'), entry.code);
  43. fs.writeFileSync(path.join(dist, 'manifest.json'), JSON.stringify({ version: 'ksf.wecom-identity.20261103.v1', functions: functions.map(({ code, ...rest }) => rest) }, null, 2));
  44. for (const file of ['wecom-directory.mjs', 'employee-handlers.mjs', 'build-release.mjs', 'manage.mjs', 'scheduled-sync.mjs', 'directory.example.json', 'platform.example.json']) copy('server/identity/' + file);
  45. copy('docs/wecom-identity-integration-20261103.md');
  46. }
  47. const all = fs.readdirSync(out, { recursive: true }).filter((p) => fs.statSync(path.join(out, p)).isFile()).sort();
  48. fs.writeFileSync(path.join(out, 'FILES.sha256'), all.map((p) => sha(fs.readFileSync(path.join(out, p))) + ' ' + p.replaceAll('\\', '/')).join('\n') + '\n');
  49. console.log(JSON.stringify({ ok: true, release: RELEASE, out, payloadFiles: FILES.length, files: all.length + 1, deployed: false }));
  50. } else console.log(JSON.stringify({ ok: true, payloadFiles: FILES.length, manifestOnly: true }));