check-license-header.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #!/usr/bin/env node
  2. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  3. // SPDX-License-Identifier: Apache-2.0
  4. // SPDX-License-Identifier: MIT
  5. const fs = require('fs')
  6. const path = require('path')
  7. const readline = require('readline')
  8. const header = `Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  9. SPDX-License-Identifier: Apache-2.0
  10. SPDX-License-Identifier: MIT`
  11. const bundlerLicense =
  12. '// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>'
  13. const denoLicense =
  14. '// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.'
  15. const extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt']
  16. const ignore = [
  17. 'target',
  18. 'templates',
  19. 'node_modules',
  20. 'gen',
  21. 'dist',
  22. 'bundle.global.js'
  23. ]
  24. async function checkFile(file) {
  25. if (
  26. extensions.some((e) => file.endsWith(e)) &&
  27. !ignore.some((i) => file.includes(`/${i}/`) || path.basename(file) == i)
  28. ) {
  29. const fileStream = fs.createReadStream(file)
  30. const rl = readline.createInterface({
  31. input: fileStream,
  32. crlfDelay: Infinity
  33. })
  34. let contents = ``
  35. let i = 0
  36. for await (let line of rl) {
  37. // ignore empty lines, allow shebang and bundler license
  38. if (
  39. line.length === 0 ||
  40. line.startsWith('#!') ||
  41. line.startsWith('// swift-tools-version:') ||
  42. line === bundlerLicense ||
  43. line === denoLicense
  44. ) {
  45. continue
  46. }
  47. // strip comment marker
  48. if (line.startsWith('// ')) {
  49. line = line.substring(3)
  50. } else if (line.startsWith('# ')) {
  51. line = line.substring(2)
  52. }
  53. contents += line
  54. if (++i === 3) {
  55. break
  56. }
  57. contents += '\n'
  58. }
  59. if (contents !== header) {
  60. return true
  61. }
  62. }
  63. return false
  64. }
  65. async function check(src) {
  66. const missingHeader = []
  67. for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
  68. const p = path.join(src, entry.name)
  69. if (entry.isSymbolicLink() || ignore.includes(entry.name)) {
  70. continue
  71. }
  72. if (entry.isDirectory()) {
  73. const missing = await check(p)
  74. missingHeader.push(...missing)
  75. } else {
  76. const isMissing = await checkFile(p)
  77. if (isMissing) {
  78. missingHeader.push(p)
  79. }
  80. }
  81. }
  82. return missingHeader
  83. }
  84. const [_bin, _script, ...files] = process.argv
  85. if (files.length > 0) {
  86. async function run() {
  87. const missing = []
  88. for (const f of files) {
  89. const isMissing = await checkFile(f)
  90. if (isMissing) {
  91. missing.push(f)
  92. }
  93. }
  94. if (missing.length > 0) {
  95. console.log(missing.join('\n'))
  96. process.exit(1)
  97. }
  98. }
  99. run()
  100. } else {
  101. check('.').then((missing) => {
  102. if (missing.length > 0) {
  103. console.log(missing.join('\n'))
  104. process.exit(1)
  105. }
  106. })
  107. }