├── index.esm.mjs ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── README.md ├── index.bin.js ├── package.json └── src ├── get-repo-url.ts └── index.ts /index.esm.mjs: -------------------------------------------------------------------------------- 1 | import './lib/index.js'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .DS_Store 4 | .build 5 | .idea 6 | .vercel 7 | www/_site 8 | www/index.md 9 | lerna-debug.log 10 | node_modules 11 | package-lock.json 12 | create-snowpack-app/*/build 13 | test/build/**/build 14 | test/create-snowpack-app/test-install 15 | test/esinstall/**/web_modules 16 | yarn-error.log 17 | test/build/config-out/TEST_BUILD_OUT -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "sourceMap": true, 12 | "noImplicitAny": false, 13 | "skipLibCheck": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @skypack/package-check 2 | 3 | A quality score checker for npm packages. More documentation coming soon! 4 | 5 | ``` 6 | # yarn (run this in your package directory) 7 | yarn add @skypack/package-check --dev 8 | yarn run package-check 9 | ``` 10 | 11 | ``` 12 | # npm (run this in your package directory) 13 | npm install @skypack/package-check --dev 14 | npx package-check 15 | ``` 16 | 17 | 18 | ## CLI Arguments 19 | 20 | - `--cwd [./pkg]`: Change the tested package directory. Defaults to your current working directory. -------------------------------------------------------------------------------- /index.bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const ver = process.versions.node; 5 | const majorVer = parseInt(ver.split('.')[0], 10); 6 | 7 | if (majorVer < 10) { 8 | console.error('Node version ' + ver + ' is not supported, please use Node.js 10.0 or higher.'); 9 | process.exit(1); 10 | } 11 | 12 | const cli = require('./lib/index.js'); 13 | const run = cli.run || cli.cli || cli.default; 14 | run(process.argv.slice(2)).catch(function (error) { 15 | console.error(` 16 | ${error.stack || error.message || error} 17 | `); 18 | process.exit(1); 19 | }); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@skypack/package-check", 3 | "version": "0.2.2", 4 | "description": "A quality score checker for npm packages.", 5 | "types": "lib/index.d.ts", 6 | "main": "lib/index.js", 7 | "exports": { 8 | "import": "./index.esm.mjs", 9 | "require": "./lib/index.js" 10 | }, 11 | "bin": { 12 | "package-check": "./index.bin.js" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "scripts": { 18 | "build": "tsc", 19 | "build:watch": "tsc --watch", 20 | "lint": "tsc --noEmit --noUnusedLocals true --noUnusedParameters true" 21 | }, 22 | "keywords": [ 23 | "skypack", 24 | "package", 25 | "quality", 26 | "lint" 27 | ], 28 | "files": [ 29 | "lib" 30 | ], 31 | "author": "Fred K. Schott", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "@types/node": "^14.14.0", 35 | "@types/yargs": "^15.0.12", 36 | "typescript": "^4.0.3" 37 | }, 38 | "dependencies": { 39 | "kleur": "^4.1.3", 40 | "yargs-parser": "^20.2.3" 41 | }, 42 | "directories": { 43 | "lib": "lib" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/skypackjs/package-check.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/skypackjs/package-check/issues" 51 | }, 52 | "homepage": "https://github.com/skypackjs/package-check#readme" 53 | } 54 | -------------------------------------------------------------------------------- /src/get-repo-url.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | /** Get URL for any asset from a repo */ 4 | export function repoAssetURL( 5 | url: string, 6 | filepath: string, 7 | version?: string 8 | ): string | undefined { 9 | let sanitizedURL = repoURL(url); 10 | // @ts-ignore this is a real thing 11 | const { hostname, pathname } = new URL(sanitizedURL); 12 | 13 | switch (hostname) { 14 | case "bitbucket.org": { 15 | return `https://bitbucket.org${path.join( 16 | "/", 17 | pathname, 18 | version || "HEAD", 19 | filepath 20 | )}`; 21 | } 22 | case "github.com": { 23 | const [repo, dir] = pathname.split("/tree/"); 24 | return `https://raw.githubusercontent.com${path.join( 25 | "/", 26 | repo, 27 | version || "HEAD", 28 | dir ? dir.replace(/[^/]+/, "") : "", 29 | filepath 30 | )}`; 31 | } 32 | case "gitlab.com": { 33 | return `https://gitlab.com${path.join( 34 | "/", 35 | pathname, 36 | "-", 37 | "raw", 38 | version || "HEAD", 39 | filepath 40 | )}`; 41 | } 42 | default: { 43 | return undefined; // Dunno what this is; return as-is 44 | } 45 | } 46 | } 47 | 48 | /** Turn repo URL into normal URL */ 49 | export function repoURL(url: string): string { 50 | return url 51 | .trim() 52 | .replace(/^git\+/i, "") 53 | .replace(/\.git$/i, "") 54 | .replace(/git@/, "") 55 | .replace(/(bitbucket|github|gitlab)\.([a-z]+):/, "$1.$2/") 56 | .replace(/bitbucket:/, "$1.org/") 57 | .replace(/(github|gitlab):/, "$1.com/") 58 | .replace(/^(http\s*:\/\/|https\s*:\/\/|\/\/)?/, "https://"); 59 | } 60 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import yargs from 'yargs-parser'; 4 | import * as colors from 'kleur/colors'; 5 | import { repoURL } from './get-repo-url'; 6 | 7 | function runCheck({ pass, title, url }) { 8 | try { 9 | const result = pass(); 10 | if (result) { 11 | console.error(colors.green(`✓`) + colors.dim(` ${title}`)); 12 | 13 | } else if (!result) { 14 | console.error(colors.red(`✖ ${title}`)); 15 | console.error(''); 16 | console.error(colors.red('check failed:'), title); 17 | console.error(colors.red(' how to fix:'), url); 18 | process.exit(1); 19 | } 20 | } catch (err) { 21 | console.error('run failed (internal tool error, please report this!)'); 22 | throw err; 23 | } 24 | } 25 | 26 | export async function cli(args: string[]) { 27 | const cliFlags = yargs(args, {}); 28 | const cwd = cliFlags.cwd ? path.resolve(cliFlags.cwd) : process.cwd(); 29 | const files = fs.readdirSync(cwd); 30 | 31 | // Check: Has a package.json 32 | runCheck({ 33 | title: 'package.json', 34 | url: 'https://docs.skypack.dev/package-authors/package-checks#esm', 35 | pass: () => { 36 | return !!files.includes('package.json'); 37 | }, 38 | }); 39 | 40 | // Load package.json 41 | const pkg = await fs.promises 42 | .readFile(path.join(cwd, 'package.json'), { 43 | encoding: 'utf-8', 44 | }) 45 | .then((packageJsonContents) => JSON.parse(packageJsonContents)); 46 | 47 | // Check: Has ESM 48 | runCheck({ 49 | title: 'ES Module Entrypoint', 50 | url: 'https://docs.skypack.dev/package-authors/package-checks#esm', 51 | pass: () => { 52 | if (pkg.type === 'module') { 53 | return true; 54 | } 55 | if (pkg.module) { 56 | return true; 57 | } 58 | if (typeof pkg.main === 'string' && pkg.main.endsWith('.mjs')) { 59 | return true; 60 | } 61 | if ( 62 | pkg.exports && 63 | (pkg.exports['import'] || 64 | !!Object.values(pkg.exports).find( 65 | (x: any) => typeof x === 'object' && x.import, 66 | )) 67 | ) { 68 | return true; 69 | } 70 | return false; 71 | }, 72 | }); 73 | // Check: Export Map 74 | runCheck({ 75 | title: 'Export Map', 76 | url: 'https://docs.skypack.dev/package-authors/package-checks#export-map', 77 | pass: () => { 78 | return !!pkg.exports; 79 | }, 80 | }); 81 | // Check: Has "files" 82 | runCheck({ 83 | title: 'No Unnecessary Files', 84 | url: 'https://docs.skypack.dev/package-authors/package-checks#files', 85 | pass: () => { 86 | return !!pkg.files; 87 | }, 88 | }); 89 | // Check: Has "keywords" 90 | runCheck({ 91 | title: 'Keywords', 92 | url: 'https://docs.skypack.dev/package-authors/package-checks#keywords', 93 | pass: () => { 94 | return !!pkg.keywords; 95 | }, 96 | }); 97 | // Check: Has "keywords" 98 | runCheck({ 99 | title: 'Keywords (Empty)', 100 | url: 'https://docs.skypack.dev/package-authors/package-checks#keywords', 101 | pass: () => { 102 | return !!pkg.keywords.length; 103 | }, 104 | }); 105 | // Check: Has "license" 106 | runCheck({ 107 | title: 'License', 108 | url: 'https://docs.skypack.dev/package-authors/package-checks#license', 109 | 110 | pass: () => { 111 | return !!pkg.license; 112 | }, 113 | }); 114 | // Check: Has "repository url" 115 | runCheck({ 116 | title: 'Repository URL', 117 | url: 'https://docs.skypack.dev/package-authors/package-checks#repository', 118 | pass: () => { 119 | let repositoryUrl: string | undefined; 120 | if (!pkg.repository) { 121 | return false; 122 | } 123 | if (typeof pkg.repository === 'string') { 124 | return true; 125 | } 126 | if (pkg.repository.url) { 127 | return !!new URL(repoURL(pkg.repository.url)); 128 | } 129 | return false; 130 | }, 131 | }); 132 | // Check: Has types 133 | runCheck({ 134 | title: 'TypeScript Types', 135 | url: 'https://docs.skypack.dev/package-authors/package-checks#types', 136 | pass: () => { 137 | const isOk = !!pkg.types || !!pkg.typings || !!pkg.typesVersions; 138 | if (isOk) { 139 | return true; 140 | } 141 | if (files.includes('index.d.ts')) { 142 | console.error( 143 | colors.yellow( 144 | '"./index.d.ts" file found, but package.json "types" entry is missing.', 145 | ), 146 | ); 147 | console.error( 148 | colors.yellow( 149 | 'Learn more about why this is still required: https://github.com/skypackjs/package-check/issues/6#issuecomment-714840634', 150 | ), 151 | ); 152 | return false; 153 | } 154 | return false; 155 | }, 156 | }); 157 | // Check: Has "README" 158 | runCheck({ 159 | title: 'README', 160 | url: 'https://docs.skypack.dev/package-authors/package-checks#readme', 161 | pass: () => { 162 | return !!files.find((f) => /^readme\.?/i.test(f)); 163 | }, 164 | }); 165 | 166 | console.error(''); 167 | console.error( 168 | colors.green(`[100/100] ${pkg.name} passes all quality checks.`), 169 | ); 170 | } 171 | --------------------------------------------------------------------------------