Commit b929822

Anton Golub <antongolub@antongolub.com>
2025-03-17 11:46:04
build: enhance 3rd party licenses digest (#1140)
* build: enhance 3rd party licenses digest * chore: update licence digest format
1 parent 9779deb
scripts/build-js.mjs
@@ -34,7 +34,7 @@ const argv = minimist(process.argv.slice(2), {
     entry: './src/index.ts',
     external: 'node:*',
     bundle: 'src', // 'all' | 'none'
-    license: 'eof',
+    license: 'none', // see digestLicenses below // 'eof',
     minify: false,
     sourcemap: false,
     format: 'cjs,esm',
@@ -72,6 +72,8 @@ const plugins = [
   }),
 ]
 
+const thirdPartyModules = new Set()
+
 if (_bundle && entryPoints.length > 1) {
   plugins.push(entryChunksPlugin())
 }
@@ -93,6 +95,14 @@ if (hybrid) {
 }
 
 plugins.push(
+  {
+    name: 'get-3rd-party-modules',
+    setup: (build) => {
+      build.onResolve({ filter: /./, namespace: 'file' }, async (args) => {
+        thirdPartyModules.add(args.resolveDir)
+      })
+    },
+  },
   transformHookPlugin({
     hooks: [
       {
@@ -157,13 +167,56 @@ plugins.push(
   {
     name: 'deno',
     setup(build) {
-      build.onEnd(() =>
+      build.onEnd(() => {
         fs.copyFileSync('./scripts/deno.polyfill.js', './build/deno.js')
-      )
+        fs.writeFileSync(
+          './build/3rd-party-licenses',
+          digestLicenses(thirdPartyModules)
+        )
+      })
     },
   }
 )
 
+// prettier-ignore
+function digestLicenses(dirs) {
+  const digest = [...[...dirs]
+    .reduce((m, d) => {
+      const chunks = d.split('/')
+      const i = chunks.lastIndexOf('node_modules')
+      const name = chunks[i + 1]
+      const shift = i + 1 + (name.startsWith('@') ? 2 : 1)
+      const root = chunks.slice(0, shift).join('/')
+      m.add(root)
+      return m
+    }, new Set())]
+    .map(d => {
+      const extractName = (entry) => entry?.name ? `${entry.name} <${entry.email}>` : entry
+      const pkg = path.join(d, 'package.json')
+      const pkgJson = JSON.parse(fs.readFileSync(pkg, 'utf-8'))
+      const author = extractName(pkgJson.author)
+      const contributors = (pkgJson.contributors || pkgJson.maintainers || []).map(extractName).join(', ')
+      const by = author || contributors || '<unknown>'
+      const repository = pkgJson.repository?.url || pkgJson.repository || ''
+      const license = pkgJson.license || '<unknown>'
+
+      if (pkgJson.name === 'zx') return
+
+      return `${pkgJson.name}@${pkgJson.version}
+  ${by}
+  ${repository}
+  ${license}`
+    })
+    .filter(Boolean)
+    .sort()
+    .join('\n\n')
+
+  return `THIRD PARTY LICENSES
+
+${digest}
+`
+}
+
 function entryPointsToRegexp(entryPoints) {
   return new RegExp(
     '(' +
scripts/prepublish-lite.mjs
@@ -43,6 +43,7 @@ const pkgJson = {
   },
   man: undefined,
   files: [
+    'build/3rd-party-licenses',
     'build/core.cjs',
     'build/core.js',
     'build/core.d.ts',
test/export.test.js
@@ -375,6 +375,7 @@ describe('index', () => {
     assert.equal(typeof index.glob.isDynamicPattern, 'function', 'index.glob.isDynamicPattern')
     assert.equal(typeof index.glob.isGitIgnored, 'function', 'index.glob.isGitIgnored')
     assert.equal(typeof index.glob.isGitIgnoredSync, 'function', 'index.glob.isGitIgnoredSync')
+    assert.equal(typeof index.glob.sync, 'function', 'index.glob.sync')
     assert.equal(typeof index.globby, 'function', 'index.globby')
     assert.equal(typeof index.globby.convertPathToPattern, 'function', 'index.globby.convertPathToPattern')
     assert.equal(typeof index.globby.generateGlobTasks, 'function', 'index.globby.generateGlobTasks')
@@ -385,6 +386,7 @@ describe('index', () => {
     assert.equal(typeof index.globby.isDynamicPattern, 'function', 'index.globby.isDynamicPattern')
     assert.equal(typeof index.globby.isGitIgnored, 'function', 'index.globby.isGitIgnored')
     assert.equal(typeof index.globby.isGitIgnoredSync, 'function', 'index.globby.isGitIgnoredSync')
+    assert.equal(typeof index.globby.sync, 'function', 'index.globby.sync')
     assert.equal(typeof index.kill, 'function', 'index.kill')
     assert.equal(typeof index.log, 'function', 'index.log')
     assert.equal(typeof index.minimist, 'function', 'index.minimist')
@@ -639,6 +641,7 @@ describe('vendor', () => {
     assert.equal(typeof vendor.glob.isDynamicPattern, 'function', 'vendor.glob.isDynamicPattern')
     assert.equal(typeof vendor.glob.isGitIgnored, 'function', 'vendor.glob.isGitIgnored')
     assert.equal(typeof vendor.glob.isGitIgnoredSync, 'function', 'vendor.glob.isGitIgnoredSync')
+    assert.equal(typeof vendor.glob.sync, 'function', 'vendor.glob.sync')
     assert.equal(typeof vendor.minimist, 'function', 'vendor.minimist')
     assert.equal(typeof vendor.ps, 'object', 'vendor.ps')
     assert.equal(typeof vendor.ps.kill, 'function', 'vendor.ps.kill')
test/package.test.js
@@ -76,6 +76,7 @@ describe('package', () => {
         'build/vendor.cjs',
         'build/vendor.d.ts',
         'build/vendor.js',
+        'build/3rd-party-licenses',
       ].sort()
     )
   })
.size-limit.json
@@ -2,6 +2,7 @@
   {
     "name": "zx-lite",
     "path": [
+      "build/3rd-party-licenses",
       "build/core.cjs",
       "build/core.js",
       "build/core.d.ts",
@@ -16,14 +17,14 @@
       "README.md",
       "LICENSE"
     ],
-    "limit": "111 kB",
+    "limit": "113.7 kB",
     "brotli": false,
     "gzip": false
   },
   {
     "name": "js parts",
     "path": "build/*.{js,cjs}",
-    "limit": "813.5 kB",
+    "limit": "811.6 kB",
     "brotli": false,
     "gzip": false
   },
@@ -37,14 +38,14 @@
   {
     "name": "vendor",
     "path": "build/vendor-*",
-    "limit": "767.12 kB",
+    "limit": "765.5 kB",
     "brotli": false,
     "gzip": false
   },
   {
     "name": "all",
     "path": ["build/*", "man/*", "README.md", "LICENSE"],
-    "limit": "866.5 kB",
+    "limit": "867.5 kB",
     "brotli": false,
     "gzip": false
   }