check-runtime-version.mjs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import zlib from 'node:zlib';
  4. const root = process.cwd();
  5. const expected = process.env.EXPECTED_UNI_COMPILER || '';
  6. function readJson(file) {
  7. return JSON.parse(fs.readFileSync(file, 'utf8'));
  8. }
  9. function compilerOf(manifest) {
  10. return manifest?.plus?.['uni-app']?.compilerVersion || manifest?.['uni-app']?.compilerVersion || '';
  11. }
  12. function versionOf(manifest) {
  13. const version = manifest?.version || {};
  14. return [version.name, version.code].filter(Boolean).join(' / ');
  15. }
  16. function walk(dir, accept, out = []) {
  17. if (!fs.existsSync(dir)) return out;
  18. for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
  19. const full = path.join(dir, item.name);
  20. if (item.isDirectory()) {
  21. walk(full, accept, out);
  22. } else if (accept(full)) {
  23. out.push(full);
  24. }
  25. }
  26. return out;
  27. }
  28. function extractZipEntries(file, matcher) {
  29. const buf = fs.readFileSync(file);
  30. const eocdSig = 0x06054b50;
  31. let eocd = -1;
  32. for (let i = buf.length - 22; i >= Math.max(0, buf.length - 0x10000); i -= 1) {
  33. if (buf.readUInt32LE(i) === eocdSig) {
  34. eocd = i;
  35. break;
  36. }
  37. }
  38. if (eocd < 0) return [];
  39. const total = buf.readUInt16LE(eocd + 10);
  40. const cdOffset = buf.readUInt32LE(eocd + 16);
  41. const entries = [];
  42. let offset = cdOffset;
  43. for (let i = 0; i < total && offset < buf.length; i += 1) {
  44. if (buf.readUInt32LE(offset) !== 0x02014b50) break;
  45. const method = buf.readUInt16LE(offset + 10);
  46. const compressedSize = buf.readUInt32LE(offset + 20);
  47. const nameLen = buf.readUInt16LE(offset + 28);
  48. const extraLen = buf.readUInt16LE(offset + 30);
  49. const commentLen = buf.readUInt16LE(offset + 32);
  50. const localOffset = buf.readUInt32LE(offset + 42);
  51. const name = buf.toString('utf8', offset + 46, offset + 46 + nameLen);
  52. if (matcher(name)) {
  53. const localNameLen = buf.readUInt16LE(localOffset + 26);
  54. const localExtraLen = buf.readUInt16LE(localOffset + 28);
  55. const dataStart = localOffset + 30 + localNameLen + localExtraLen;
  56. const compressed = buf.subarray(dataStart, dataStart + compressedSize);
  57. let data;
  58. if (method === 0) {
  59. data = compressed;
  60. } else if (method === 8) {
  61. data = zlib.inflateRawSync(compressed);
  62. } else {
  63. throw new Error(`Unsupported zip compression method ${method} in ${file}:${name}`);
  64. }
  65. entries.push({ name, text: data.toString('utf8') });
  66. }
  67. offset += 46 + nameLen + extraLen + commentLen;
  68. }
  69. return entries;
  70. }
  71. const reports = [];
  72. const manifestFiles = [
  73. path.join(root, 'unpackage/dist/build/app-plus/manifest.json'),
  74. path.join(root, 'unpackage/dist/dev/app-plus/manifest.json'),
  75. ...walk(path.join(root, 'unpackage/cache/wgt'), file => path.basename(file) === 'manifest.json'),
  76. ].filter(file => fs.existsSync(file));
  77. for (const file of manifestFiles) {
  78. const manifest = readJson(file);
  79. reports.push({
  80. source: path.relative(root, file),
  81. version: versionOf(manifest),
  82. compiler: compilerOf(manifest),
  83. });
  84. }
  85. for (const apk of walk(path.join(root, 'unpackage/cache/apk'), file => file.endsWith('.apk'))) {
  86. for (const entry of extractZipEntries(apk, name => /assets\/apps\/.+\/www\/manifest\.json$/.test(name))) {
  87. const manifest = JSON.parse(entry.text);
  88. reports.push({
  89. source: `${path.relative(root, apk)}:${entry.name}`,
  90. version: versionOf(manifest),
  91. compiler: compilerOf(manifest),
  92. });
  93. }
  94. }
  95. if (!reports.length) {
  96. console.log('No App-Plus build manifests found.');
  97. process.exit(0);
  98. }
  99. const width = Math.max(...reports.map(item => item.source.length), 'source'.length);
  100. console.log(`${'source'.padEnd(width)} app version compiler`);
  101. console.log(`${'-'.repeat(width)} ----------- --------`);
  102. for (const item of reports) {
  103. console.log(`${item.source.padEnd(width)} ${(item.version || '-').padEnd(11)} ${item.compiler || '-'}`);
  104. }
  105. const compilers = [...new Set(reports.map(item => item.compiler).filter(Boolean))];
  106. const badExpected = expected && compilers.some(item => item !== expected);
  107. if (compilers.length > 1 || badExpected) {
  108. if (expected) {
  109. console.error(`Runtime compiler version mismatch. Expected ${expected}.`);
  110. } else {
  111. console.error(`Runtime compiler version mismatch: ${compilers.join(', ')}.`);
  112. }
  113. process.exit(1);
  114. }
  115. console.log(`Runtime compiler version OK: ${compilers[0] || 'unknown'}.`);