From 156f65436c61dd3f350e472a7b9db9a6537a4248 Mon Sep 17 00:00:00 2001 From: Lachlan Collins <1667261+lachlancollins@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:26:25 +1100 Subject: [PATCH] feat: Add script to check if packages pass publint (#523) * Add zod schemas for CI validation * Require npm field for components.json * Remove svelte-layout-resizable * Stricter Zod validation * Stricter repository field validation * Implement requested changes * Add back accidentally removed field * Move SvelteStore to templates.json * Update category and tags * Add script to get npm data * Add script to get publint data * Re-run updateNpm.js * Re-run updatePublint.js * Implement initial feedback * Use npm.json data * Update npm.js * Switch async/await to then/catch Co-authored-by: MacFJA * Fix prettier * Run script * Re-run updateNpm * Also read tools.json * Add @types/node * Restructure npm.json output * Fix updatePublint.js * Display last update on components page * Add to weekly workflow * Clarify updating npm data * Update src/lib/utils/injectNpmData.ts Co-authored-by: MacFJA * Smaller date font, add version * Improve error text * Fix property title * Move json to lib/data directory * Fix npm.json path * Also check tools.json * Add to automated PR * Add injectPublintData function * Update publint --------- Co-authored-by: MacFJA --- .github/workflows/fetch-latest-data.yml | 6 +- package.json | 3 + pnpm-lock.yaml | 82 ++++ scripts/tarball.js | 44 +++ scripts/untar.js | 306 +++++++++++++++ scripts/updatePublint.js | 77 ++++ src/lib/data/publint.json | 366 ++++++++++++++++++ .../utils/{injectNpmData.ts => injectData.ts} | 10 + src/routes/components/+page.svelte | 2 +- src/routes/tools/+page.svelte | 2 +- 10 files changed, 894 insertions(+), 4 deletions(-) create mode 100644 scripts/tarball.js create mode 100644 scripts/untar.js create mode 100644 scripts/updatePublint.js create mode 100644 src/lib/data/publint.json rename src/lib/utils/{injectNpmData.ts => injectData.ts} (54%) diff --git a/.github/workflows/fetch-latest-data.yml b/.github/workflows/fetch-latest-data.yml index 2b99b8493..f31e6e0e2 100644 --- a/.github/workflows/fetch-latest-data.yml +++ b/.github/workflows/fetch-latest-data.yml @@ -25,6 +25,8 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Update npm data run: node scripts/updateNpm.js + - name: Update publint data + run: node scripts/updatePublint.js - name: Run format run: pnpm run format - name: Create Pull Request @@ -32,8 +34,8 @@ jobs: with: commit-message: "(AUTO) Update data" title: "🤖 Update data" - body: Automatically fetch latest data from NPM, GitHub and GitLab + body: Automatically fetch latest data from GitHub, GitLab, NPM and Publint. branch: ci-update-data - add-paths: src/lib/data/npm.json,src/lib/data/stars.json + add-paths: src/lib/data/npm.json,src/lib/data/publint.json,src/lib/data/stars.json delete-branch: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index da6a4311d..fbf006a7f 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,15 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.35.1", + "get-npm-tarball-url": "^2.1.0", "highlight.js": "^11.9.0", "itemsjs": "^2.1.24", "mdsvex": "^0.11.0", "package-name-regex": "^3.1.1", + "pako": "^2.1.0", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", + "publint": "^0.2.6", "rehype-slug": "^6.0.0", "svelte": "^4.2.8", "svelte-check": "^3.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3113d2280..35631cb10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ devDependencies: eslint-plugin-svelte: specifier: ^2.35.1 version: 2.35.1(eslint@8.56.0)(svelte@4.2.8) + get-npm-tarball-url: + specifier: ^2.1.0 + version: 2.1.0 highlight.js: specifier: ^11.9.0 version: 11.9.0 @@ -62,12 +65,18 @@ devDependencies: package-name-regex: specifier: ^3.1.1 version: 3.1.1 + pako: + specifier: ^2.1.0 + version: 2.1.0 prettier: specifier: ^3.1.1 version: 3.1.1 prettier-plugin-svelte: specifier: ^3.1.2 version: 3.1.2(prettier@3.1.1)(svelte@4.2.8) + publint: + specifier: ^0.2.6 + version: 0.2.6 rehype-slug: specifier: ^6.0.0 version: 6.0.0 @@ -993,6 +1002,12 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -1488,6 +1503,11 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true + /get-npm-tarball-url@2.1.0: + resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==} + engines: {node: '>=12.17'} + dev: true + /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -1522,6 +1542,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + /globals@13.20.0: resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} @@ -1590,6 +1621,13 @@ packages: safari-14-idb-fix: 1.0.6 dev: true + /ignore-walk@5.0.1: + resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + minimatch: 5.1.6 + dev: true + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1807,6 +1845,13 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true @@ -1862,6 +1907,29 @@ packages: engines: {node: '>=0.10.0'} dev: true + /npm-bundled@2.0.1: + resolution: {integrity: sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + npm-normalize-package-bin: 2.0.0 + dev: true + + /npm-normalize-package-bin@2.0.0: + resolution: {integrity: sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dev: true + + /npm-packlist@5.1.3: + resolution: {integrity: sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + glob: 8.1.0 + ignore-walk: 5.0.1 + npm-bundled: 2.0.1 + npm-normalize-package-bin: 2.0.0 + dev: true + /npm-run-path@5.1.0: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1920,6 +1988,10 @@ packages: engines: {node: '>=14'} dev: true + /pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + dev: true + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2094,6 +2166,16 @@ packages: engines: {node: '>=6'} dev: true + /publint@0.2.6: + resolution: {integrity: sha512-zMwDVwrlLnCsviDXlczhuc5nIljsjZUgbLeKNyMYqbIJLRhcW81xrKsHlEu21YUaIxpa8T66tdIqP0mZm9ym3A==} + engines: {node: '>=16'} + hasBin: true + dependencies: + npm-packlist: 5.1.3 + picocolors: 1.0.0 + sade: 1.8.1 + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} diff --git a/scripts/tarball.js b/scripts/tarball.js new file mode 100644 index 000000000..b34c9f499 --- /dev/null +++ b/scripts/tarball.js @@ -0,0 +1,44 @@ +// @ts-check +// Source: https://github.com/bluwy/publint/blob/master/site/src/utils/tarball.js + +/** @typedef {{ name: string, buffer: ArrayBuffer }} TarballFile */ + +/** + * @param {TarballFile[]} files + * @return {import('publint').Vfs} + * */ +export function createTarballVfs(files) { + return { + getDirName: (path) => path.replace(/\/[^/]*$/, ''), + getExtName: (path) => path.replace(/^.*\./, '.'), + isPathDir: async (path) => { + path = path.endsWith('/') ? path : path + '/'; + return files.some((file) => file.name.startsWith(path)); + }, + isPathExist: async (path) => { + const pathDirVariant = path.endsWith('/') ? path : path + '/'; + return files.some((file) => file.name === path || file.name.startsWith(pathDirVariant)); + }, + pathJoin: (...parts) => + parts + .map((v) => (v.startsWith('./') ? v.slice(2) : v)) + .join('/') + .replace('///', '/') + .replace('//', '/'), // TODO: optimize this please + pathRelative: (from, to) => to.replace(from, '').slice(1), + readDir: async (path) => { + path = path.endsWith('/') ? path : path + '/'; + return files + .filter((file) => file.name.startsWith(path) && file.name !== path) + .map((file) => file.name.slice(path.length)); + }, + readFile: async (path) => { + const file = files.find((file) => file.name === path); + if (file) { + return new TextDecoder('utf-8').decode(file.buffer); + } else { + throw new Error(`Unable to read file at path: ${path}`); + } + } + }; +} diff --git a/scripts/untar.js b/scripts/untar.js new file mode 100644 index 000000000..6a1c9f3e2 --- /dev/null +++ b/scripts/untar.js @@ -0,0 +1,306 @@ +// @ts-check +// Source: https://github.com/bluwy/publint/blob/master/site/src/utils/untar.js + +export function untar(arrayBuffer) { + const tarFileStream = new UntarFileStream(arrayBuffer); + const files = []; + while (tarFileStream.hasNext()) { + const file = tarFileStream.next(); + files.push(file); + } + return files; +} + +// Source: https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330 +// Unmarshals an Uint8Array to string. +function decodeUTF8(bytes) { + var s = ''; + var i = 0; + while (i < bytes.length) { + var c = bytes[i++]; + if (c > 127) { + if (c > 191 && c < 224) { + if (i >= bytes.length) throw 'UTF-8 decode: incomplete 2-byte sequence'; + c = ((c & 31) << 6) | (bytes[i] & 63); + } else if (c > 223 && c < 240) { + if (i + 1 >= bytes.length) throw 'UTF-8 decode: incomplete 3-byte sequence'; + c = ((c & 15) << 12) | ((bytes[i] & 63) << 6) | (bytes[++i] & 63); + } else if (c > 239 && c < 248) { + if (i + 2 >= bytes.length) throw 'UTF-8 decode: incomplete 4-byte sequence'; + c = + ((c & 7) << 18) | ((bytes[i] & 63) << 12) | ((bytes[++i] & 63) << 6) | (bytes[++i] & 63); + } else + throw 'UTF-8 decode: unknown multibyte start 0x' + c.toString(16) + ' at index ' + (i - 1); + ++i; + } + + if (c <= 0xffff) s += String.fromCharCode(c); + else if (c <= 0x10ffff) { + c -= 0x10000; + s += String.fromCharCode((c >> 10) | 0xd800); + s += String.fromCharCode((c & 0x3ff) | 0xdc00); + } else throw 'UTF-8 decode: code point 0x' + c.toString(16) + ' exceeds UTF-16 reach'; + } + return s; +} + +function PaxHeader(fields) { + this._fields = fields; +} + +PaxHeader.parse = function (buffer) { + // https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm + // An extended header shall consist of one or more records, each constructed as follows: + // "%d %s=%s\n", , , + + // The extended header records shall be encoded according to the ISO/IEC10646-1:2000 standard (UTF-8). + // The field, , equals sign, and shown shall be limited to the portable character set, as + // encoded in UTF-8. The and fields can be any UTF-8 characters. The field shall be the + // decimal length of the extended header record in octets, including the trailing . + + var bytes = new Uint8Array(buffer); + var fields = []; + + while (bytes.length > 0) { + // Decode bytes up to the first space character; that is the total field length + var fieldLength = parseInt(decodeUTF8(bytes.subarray(0, bytes.indexOf(0x20)))); + var fieldText = decodeUTF8(bytes.subarray(0, fieldLength)); + var fieldMatch = fieldText.match(/^\d+ ([^=]+)=(.*)\n$/); + + if (fieldMatch === null) { + throw new Error('Invalid PAX header data format.'); + } + + var fieldName = fieldMatch[1]; + var fieldValue = fieldMatch[2]; + + if (fieldValue.length === 0) { + fieldValue = null; + } else if (fieldValue.match(/^\d+$/) !== null) { + // If it's a integer field, parse it as int + fieldValue = parseInt(fieldValue); + } + // Don't parse float values since precision is lost + + var field = { + name: fieldName, + value: fieldValue + }; + + fields.push(field); + + bytes = bytes.subarray(fieldLength); // Cut off the parsed field data + } + + return new PaxHeader(fields); +}; + +PaxHeader.prototype = { + applyHeader: function (file) { + // Apply fields to the file + // If a field is of value null, it should be deleted from the file + // https://www.mkssoftware.com/docs/man4/pax.4.asp + + this._fields.forEach(function (field) { + var fieldName = field.name; + var fieldValue = field.value; + + if (fieldName === 'path') { + // This overrides the name and prefix fields in the following header block. + fieldName = 'name'; + + if (file.prefix !== undefined) { + delete file.prefix; + } + } else if (fieldName === 'linkpath') { + // This overrides the linkname field in the following header block. + fieldName = 'linkname'; + } + + if (fieldValue === null) { + delete file[fieldName]; + } else { + file[fieldName] = fieldValue; + } + }); + } +}; + +function UntarStream(arrayBuffer) { + this._bufferView = new DataView(arrayBuffer); + this._position = 0; +} + +UntarStream.prototype = { + readString: function (charCount) { + //console.log("readString: position " + this.position() + ", " + charCount + " chars"); + var charSize = 1; + var byteCount = charCount * charSize; + + var charCodes = []; + + for (var i = 0; i < charCount; ++i) { + var charCode = this._bufferView.getUint8(this.position() + i * charSize, true); + if (charCode !== 0) { + charCodes.push(charCode); + } else { + break; + } + } + + this.seek(byteCount); + + return String.fromCharCode.apply(null, charCodes); + }, + + readBuffer: function (byteCount) { + var buf; + + if (typeof ArrayBuffer.prototype.slice === 'function') { + buf = this._bufferView.buffer.slice(this.position(), this.position() + byteCount); + } else { + buf = new ArrayBuffer(byteCount); + var target = new Uint8Array(buf); + var src = new Uint8Array(this._bufferView.buffer, this.position(), byteCount); + target.set(src); + } + + this.seek(byteCount); + return buf; + }, + + seek: function (byteCount) { + this._position += byteCount; + }, + + peekUint32: function () { + return this._bufferView.getUint32(this.position(), true); + }, + + position: function (newpos) { + if (newpos === undefined) { + return this._position; + } else { + this._position = newpos; + } + }, + + size: function () { + return this._bufferView.byteLength; + } +}; + +function UntarFileStream(arrayBuffer) { + this._stream = new UntarStream(arrayBuffer); + this._globalPaxHeader = null; +} + +UntarFileStream.prototype = { + hasNext: function () { + // A tar file ends with 4 zero bytes + return this._stream.position() + 4 < this._stream.size() && this._stream.peekUint32() !== 0; + }, + + next: function () { + return this._readNextFile(); + }, + + _readNextFile: function () { + var stream = this._stream; + var file = {}; + var isHeaderFile = false; + var paxHeader = null; + + var headerBeginPos = stream.position(); + var dataBeginPos = headerBeginPos + 512; + + // Read header + file.name = stream.readString(100); + file.mode = stream.readString(8); + file.uid = parseInt(stream.readString(8)); + file.gid = parseInt(stream.readString(8)); + file.size = parseInt(stream.readString(12), 8); + file.mtime = parseInt(stream.readString(12), 8); + file.checksum = parseInt(stream.readString(8)); + file.type = stream.readString(1); + file.linkname = stream.readString(100); + file.ustarFormat = stream.readString(6); + + if (file.ustarFormat.indexOf('ustar') > -1) { + file.version = stream.readString(2); + file.uname = stream.readString(32); + file.gname = stream.readString(32); + file.devmajor = parseInt(stream.readString(8)); + file.devminor = parseInt(stream.readString(8)); + file.namePrefix = stream.readString(155); + + if (file.namePrefix.length > 0) { + file.name = file.namePrefix + '/' + file.name; + } + } + + stream.position(dataBeginPos); + + // Derived from https://www.mkssoftware.com/docs/man4/pax.4.asp + // and https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/pxarchfm.htm + switch (file.type) { + case '0': // Normal file is either "0" or "\0". + case '': // In case of "\0", readString returns an empty string, that is "". + file.buffer = stream.readBuffer(file.size); + break; + case '1': // Link to another file already archived + // TODO Should we do anything with these? + break; + case '2': // Symbolic link + // TODO Should we do anything with these? + break; + case '3': // Character special device (what does this mean??) + break; + case '4': // Block special device + break; + case '5': // Directory + break; + case '6': // FIFO special file + break; + case '7': // Reserved + break; + case 'g': // Global PAX header + isHeaderFile = true; + this._globalPaxHeader = PaxHeader.parse(stream.readBuffer(file.size)); + break; + case 'x': // PAX header + isHeaderFile = true; + paxHeader = PaxHeader.parse(stream.readBuffer(file.size)); + break; + default: // Unknown file type + break; + } + + if (file.buffer === undefined) { + file.buffer = new ArrayBuffer(0); + } + + var dataEndPos = dataBeginPos + file.size; + + // File data is padded to reach a 512 byte boundary; skip the padded bytes too. + if (file.size % 512 !== 0) { + dataEndPos += 512 - (file.size % 512); + } + + stream.position(dataEndPos); + + if (isHeaderFile) { + file = this._readNextFile(); + } + + if (this._globalPaxHeader !== null) { + this._globalPaxHeader.applyHeader(file); + } + + if (paxHeader !== null) { + paxHeader.applyHeader(file); + } + + return file; + } +}; diff --git a/scripts/updatePublint.js b/scripts/updatePublint.js new file mode 100644 index 000000000..3d9e5b41b --- /dev/null +++ b/scripts/updatePublint.js @@ -0,0 +1,77 @@ +// @ts-check +// Source: https://github.com/bluwy/publint/blob/master/site/src/utils/worker.js + +import { writeFileSync } from 'node:fs'; +import { inflate } from 'pako'; +import getNpmTarballUrl from 'get-npm-tarball-url'; +import { componentsSchema, toolsSchema } from '../src/lib/schemas.js'; +import components from '../src/routes/components/components.json' assert { type: 'json' }; +import tools from '../src/routes/tools/tools.json' assert { type: 'json' }; +import npm from '../src/lib/data/npm.json' assert { type: 'json' }; +import { publint } from 'publint'; +import { untar } from './untar.js'; +import { createTarballVfs } from './tarball.js'; + +const dataWithoutVersions = [...componentsSchema.parse(components), ...toolsSchema.parse(tools)]; + +/** @param input {import('zod').infer} */ +const injectVersions = (input) => { + const output = []; + for (const item of input) { + /** @type {string} */ + const version = npm[item.npm]?.version; + if (version) { + output.push({ ...item, version }); + } + } + return output; +}; + +const dataWithVersions = injectVersions(dataWithoutVersions); + +const output = await Promise.all( + dataWithVersions.map(async (pkg) => { + try { + return await processPackage(pkg); + } catch (error) { + console.log(error.message); + } + }) +).then((values) => { + let versions = {}; + for (const value of values) { + if (value) { + versions[value.name] = value.valid; + } + } + return versions; +}); + +writeFileSync('src/lib/data/publint.json', JSON.stringify(output)); + +/** @param pkg {ReturnType[0]} */ +async function processPackage(pkg) { + const tarballUrl = getNpmTarballUrl(pkg.npm, pkg.version); + let resultBuffer; + try { + const result = await fetch(tarballUrl); + resultBuffer = await result.arrayBuffer(); + } catch (e) { + postMessage({ type: 'error', data: 'Package not found' }); + console.error(e); + } + let files; + try { + const tarBuffer = inflate(resultBuffer).buffer; // Handles gzip (gz) + files = untar(tarBuffer); // Handles tar (t) + } catch (e) { + postMessage({ type: 'error', data: 'Failed to unpack package' }); + console.error(e); + return; + } + const vfs = createTarballVfs(files); + + const pkgDir = files.length ? files[0].name.split('/')[0] : 'package'; + const { messages } = await publint({ pkgDir, vfs, level: 'warning' }); + return { name: pkg.npm, valid: messages.length === 0 }; +} diff --git a/src/lib/data/publint.json b/src/lib/data/publint.json new file mode 100644 index 000000000..7f512208a --- /dev/null +++ b/src/lib/data/publint.json @@ -0,0 +1,366 @@ +{ + "svelte-stopwatch": true, + "curseur": true, + "svelte-zod-form": true, + "svelte-selecto": false, + "svelte-pilot": true, + "super-sitemap": true, + "svelte-scrollactive": true, + "svelte-tel-input": true, + "svault": true, + "svelte-datatables-net": true, + "stwui": false, + "@sveltejs/adapter-auto": true, + "@sveltejs/adapter-netlify": true, + "@sveltejs/adapter-cloudflare-workers": true, + "@sveltejs/adapter-cloudflare": true, + "@sveltejs/adapter-node": true, + "@sveltejs/adapter-static": true, + "@sveltejs/adapter-vercel": true, + "svelte-lazy-loader": true, + "svelte-carbonbadge": true, + "svelte-form-validation": true, + "date-picker-svelte": true, + "svelte-virtual-table": false, + "svelte-number-spinner": false, + "svelte-remixicon": true, + "svelte-fast-marquee": true, + "sswr": false, + "svelte-adapter-firebase": true, + "@architect/sveltekit-adapter": true, + "svelte-adapter-deno": true, + "svelte-client-router": false, + "felte": false, + "sveltefire": true, + "svelte-time-picker": true, + "svelte-formula": false, + "svelte-calendar": true, + "svelte-tags-input": false, + "sveltedoc-parser": true, + "aovi-svelte": false, + "svelte-chota": false, + "svelte-eventbus": false, + "tinro": false, + "fa-svelte": true, + "svelte-fullscreen": true, + "svelte-grid-responsive": false, + "svelte-infinite-scroll": true, + "svelte-formly": true, + "svelte-websocket-store": true, + "sveltestrap": false, + "@beyonk/gdpr-cookie-consent-banner": true, + "@beyonk/svelte-carousel": true, + "@beyonk/svelte-facebook-customer-chat": true, + "@beyonk/svelte-facebook-pixel": false, + "@beyonk/svelte-google-analytics": false, + "@beyonk/svelte-googlemaps": false, + "@beyonk/svelte-mapbox": true, + "@beyonk/svelte-notifications": true, + "@beyonk/svelte-scrollspy": false, + "svelte-simple-icons": false, + "@beyonk/svelte-trustpilot": true, + "@bjornlu/svelte-router": false, + "svelte-sortable-list": true, + "svelte-data-grid": false, + "query-store": true, + "svelma": false, + "svelte-forms": true, + "svelte-credit-cards": false, + "svelte-headroom": false, + "svelte-fa": false, + "svelte-table": false, + "svelte-moveable": true, + "svelte-ruler": false, + "waxwing-rating": true, + "@dopry/svelte-auth0": false, + "svelte-feather-icons": false, + "@easylogic/svelte-summernote": false, + "svelte-dev-helper": false, + "svelte-routing": false, + "svelte-rate-it": true, + "@equipmentshare/date-range-input": true, + "svelte-tree": false, + "svelteify": true, + "svelte-simple-modal": true, + "@urql/svelte": false, + "svelte-icons": false, + "svelte-native": false, + "svelte-copyright": true, + "svelte-flex": true, + "svelte-frappe-charts": false, + "svelte-material-ui": true, + "@ikun-ui/core": false, + "carbon-components-svelte": false, + "attractions": false, + "svelte-spa-router": true, + "svelte-flatpickr": false, + "svelte-navaid": false, + "@jamen/svelte-router": false, + "svelte-router": true, + "svelte-pick-a-place": false, + "svelte-tabs": false, + "svelteml": false, + "svelte-compare-image-slider": false, + "svelte-router-spa": false, + "svelte-css-vars": true, + "svelte-i18n": true, + "@nubolab-ffwd/svelte-fluent": true, + "svelte-loadable": true, + "svero": false, + "svelte-notifications": false, + "svelte-fragment-component": false, + "svelte-htm": false, + "svelte-jsx": true, + "svelte-favicon-badge": false, + "svelte-redux-connect": false, + "svelte-jester": true, + "svelte-jest": true, + "echarts-for-svelte": false, + "@lottiefiles/svelte-lottie-player": false, + "@pwa/cli": false, + "svelte-inview": true, + "smelte": false, + "@melt-ui/svelte": true, + "radix-svelte": true, + "svelte-image": true, + "svelte-waypoint": false, + "sveltejs-forms": false, + "svelte-navigator": false, + "layercake": true, + "@egjs/svelte-infinitegrid": false, + "svelte-page-progress": false, + "@okrad/svelte-progressbar": true, + "svelte-intl": false, + "svql": false, + "yrv": false, + "svelte-asyncable": false, + "svelte-content-loader": false, + "svelte-image-compare": false, + "svelte-imask": false, + "svelte-page-router": true, + "svelte-pathfinder": false, + "svelte-ticker": false, + "svelte-viewpoint": false, + "select-madu": true, + "svelte-match-media": false, + "svelte-webext-storage-adapter": true, + "svelte-writable-derived": false, + "storez": true, + "svelte-adapter": false, + "svelte-test": false, + "svelte-fusioncharts": false, + "simple-svelte-autocomplete": true, + "svelte-hash-router": false, + "svelte-color-picker": false, + "svelte-inspector": true, + "swheel": false, + "svelte-accessible-dialog": false, + "@sveltejs/pancake": true, + "svelte-select": true, + "svelte-awesome": true, + "jest-transform-svelte": false, + "svelte-image-encoder": true, + "multicarousel": false, + "svelte-heatmap": false, + "svelte-swipe": true, + "@slick-for/svelte": true, + "svelte-marquee": false, + "svelte-range-slider-pips": false, + "svelte-infinite-loading": false, + "@spaceavocado/svelte-form": false, + "@spaceavocado/svelte-router": false, + "svelte-multitoneimage": true, + "@storybook/sveltekit": false, + "@storybook/svelte": false, + "svelte-toolbox": false, + "routify": false, + "@sveltejs/gestures": true, + "@sveltejs/gl": false, + "@sveltejs/svelte-repl": false, + "@sveltejs/svelte-scroller": true, + "@sveltejs/svelte-subdivide": false, + "@sveltejs/svelte-virtual-list": true, + "svelte-virtual-list-ce": true, + "svelte-state-renderer": true, + "@testing-library/svelte": true, + "svelte-file-dropzone": true, + "svelte-apollo": true, + "svelte-observable": false, + "sveltemantic": false, + "svelte-forms-lib": false, + "svelte-grid": false, + "svelte-popover": true, + "svelte-easy-crop": true, + "svelte-mui": false, + "s-offline": true, + "minna-ui": true, + "shadcn-svelte": true, + "svelte-headlessui": true, + "svelte-media-query": true, + "svelte-mobx": false, + "svelte-input-mask": true, + "svelidation": false, + "svelte-fullcalendar": true, + "svelte-item-list": true, + "@zooplus/zoo-web-components": true, + "svelte-atoms": false, + "overmind-svelte": false, + "svelte-tiny-virtual-list": false, + "@svelte-parts/drop-file": true, + "@svelte-parts/form": true, + "@svelte-parts/icons": false, + "@svelte-parts/zoom": true, + "svelte-appwrite": false, + "svelte-pdf": true, + "svelte-micro": true, + "svantic": true, + "@macfja/svelte-undoable": true, + "renderless-svelte": false, + "svate": false, + "svelte-parallax": false, + "svelte-modals": true, + "@macfja/svelte-persistent-store": false, + "@macfja/svelte-invalidable": true, + "svelte-carousel": true, + "svelte-restate": true, + "svelte-pincode": true, + "svelte-particles": true, + "svelte-tiptap": true, + "spaper": true, + "@event-calendar/core": true, + "@kahi-ui/framework": true, + "@macfja/svelte-multi-adapter": true, + "svelte-multiselect": true, + "svelte-toc": true, + "svelte-bricks": true, + "@macfja/svelte-oauth2": false, + "svelte-boring-avatars": false, + "filedrop-svelte": true, + "focus-svelte": true, + "svelte-translate": false, + "svelecte": false, + "svelte-adapter-azure-swa": true, + "svelte-adapter-appengine": true, + "sveltekit-adapter-browser-extension": true, + "svelte-fsm": true, + "@yellowinq/svelte-pin-input": true, + "svelte-codesandbox": true, + "svelte-icons-pack": false, + "@joeinnes/svelte-image": true, + "@budgetdraw/sveltekit-cloudflare-adapter": true, + "svelte-intl-precompile": true, + "svelte-cleavejs": false, + "svelty-picker": true, + "svelte-slider": false, + "sveltekit-adapter-wordpress-shortcode": true, + "@macfja/svelte-expirable": false, + "svelte-store2": true, + "@macfja/svelte-adapter-neutralino": true, + "sveltekit-adapter-html-like": true, + "svelte-gestures": false, + "svelte-adapter-github": true, + "svelte-steps": true, + "agnostic-svelte": true, + "svelte-adapter-static-digitalocean": true, + "@kitql/all-in": true, + "svelte-brick-gallery": true, + "svelte-adapter-bun": true, + "@rgossiaux/svelte-headlessui": true, + "@brewer/beerui": false, + "@skeletonlabs/skeleton": true, + "svelte-hover-draw-svg": true, + "casual-ui-svelte": true, + "@specialdoom/proi-ui": false, + "@canutin/svelte-currency-input": true, + "flowbite-svelte": true, + "typesafe-i18n": false, + "@tolgee/svelte": true, + "sthemer": false, + "svelte-exstore": true, + "@macfja/svelte-scroll-video": true, + "@prgm/sveltekit-progress-bar": true, + "svelte-unicons": false, + "svelte-google-auth": true, + "sveltekit-adapter-firebase": true, + "sveltekit-search-params": true, + "@geoffcox/sterling-svelte": true, + "simple-ui-components-in-svelte": true, + "@tanstack/svelte-query": true, + "@pragmatic-engineering/svelte-form-builder-community": true, + "@ptkdev/sveltekit-cordova-adapter": true, + "@ptkdev/sveltekit-electron-adapter": true, + "@carlosv2/adapter-node-ws": true, + "leblog": true, + "html-svelte-parser": true, + "svelte-droplet": true, + "chat-embed": true, + "lucide-svelte": false, + "yesvelte": true, + "@bonosoft/sveltekit-qrcode": true, + "@bonosoft/sveltekit-progress": true, + "@radar-azdelta/svelte-datatable": true, + "svelte-svg-transform": true, + "@nerd-coder/svelte-zod-form": true, + "sveltekit-adapter-iis": true, + "svelte-dx-table": false, + "svelte-switch": false, + "svelte-pagination": false, + "stdf": true, + "@twicpics/components": false, + "@shipbit/svane": true, + "sveltekit-html-minifier": true, + "svelte-tex": false, + "drab": true, + "supasveltekit": true, + "@jill64/sveltekit-adapter-aws": true, + "sveltekit-adapter-versioned-worker": true, + "layerchart": true, + "@egjs/svelte-flicking": false, + "svelte-ux": true, + "@born05/sveltekit-proxy": true, + "@mismerge/core": true, + "svelte-ripple-action": true, + "sveltekit-superforms": true, + "svelte-reparent": true, + "svelte-inline-modal": true, + "@tanstack/svelte-table": false, + "@histoire/plugin-svelte": true, + "lucia": true, + "@monaco-auth/sveltekit": true, + "trpc-svelte-query": true, + "svelte-legos": false, + "@supabase/auth-helpers-sveltekit": true, + "svelte-markdown": false, + "svelte-exmarkdown": true, + "@sentry/svelte": false, + "svelte-french-toast": true, + "@zerodevx/svelte-toast": true, + "@mavthedev/svodals": true, + "rollup-plugin-svelte": true, + "svelte-loader": false, + "@sveltejs/vite-plugin-svelte": true, + "esbuild-svelte": true, + "rollup-plugin-svelte-hot": true, + "parcel-transformer-svelte3-plus": true, + "parcel-plugin-svelte": true, + "sveltify": true, + "gulp-svelte": true, + "sveltejs-brunch": true, + "svelte-preprocess": true, + "svelte-preprocess-markdown": true, + "mdsvex": false, + "svelte-preprocess-less": false, + "svelte-switch-case": true, + "@modular-css/svelte": true, + "svelte-preprocess-sass": false, + "svelte-preprocess-css-hash": true, + "svelte-preprocess-html-asset": true, + "svelte-preprocessor-fetch": false, + "prettier-plugin-svelte": true, + "svelte-check": true, + "svelte-reactive-css-preprocess": true, + "svelte-subcomponent-preprocessor": true, + "eslint-plugin-svelte": true, + "full-client-server-sveltekit": true, + "svelte-preprocess-delegate-events": true +} diff --git a/src/lib/utils/injectNpmData.ts b/src/lib/utils/injectData.ts similarity index 54% rename from src/lib/utils/injectNpmData.ts rename to src/lib/utils/injectData.ts index 812fab5f2..1f7322d9a 100644 --- a/src/lib/utils/injectNpmData.ts +++ b/src/lib/utils/injectData.ts @@ -1,4 +1,5 @@ import npm from '$lib/data/npm.json'; +import publint from '$lib/data/publint.json'; import type { z } from 'zod'; import type { componentsSchema } from '$lib/schemas'; @@ -10,3 +11,12 @@ export const injectNpmData = (input: z.infer) => { } return output; }; + +export const injectPublintData = (input: z.infer) => { + const output = []; + for (const item of input) { + const extra = publint[item.npm] ?? false; + output.push({ ...item, publint: extra }); + } + return output; +}; diff --git a/src/routes/components/+page.svelte b/src/routes/components/+page.svelte index 2bdcc83bc..b6220efa8 100644 --- a/src/routes/components/+page.svelte +++ b/src/routes/components/+page.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/tools/+page.svelte b/src/routes/tools/+page.svelte index 88ebcbe32..c000fec6c 100644 --- a/src/routes/tools/+page.svelte +++ b/src/routes/tools/+page.svelte @@ -1,7 +1,7 @@