diff --git a/README.md b/README.md index ec743ce..d489c36 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,12 @@ The release gate covers: - `npm run check` - tsc --noEmit - `npm run lint` - eslint src --ext .ts -- `npm run build` - tsc +- `npm run build` - tsc && chmod +x dist/cli.js - `npm test` - vitest run --coverage - `npm run smoke` - npm run build && node dist/cli.js --help && node dist/cli.js fixtures --format text - `npm run validate` - bash scripts/validate.sh -- `npm run package:smoke` - npm pack --dry-run -- `npm run release:check` - npm run check && npm test && npm run smoke && npm run package:smoke +- `npm run package:smoke` - npm run build && node scripts/package-smoke.mjs +- `npm run release:check` - npm run check && npm run lint && npm test && npm run smoke && npm run package:smoke ## Release readiness diff --git a/package-lock.json b/package-lock.json index 16156c9..6bbd518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "prettier": "^3.0.0", "tsx": "^4.0.0", "typescript": "^5.0.0", + "typescript-eslint": "^8.63.0", "vitest": "^3.0.0" }, "engines": { @@ -1326,14 +1327,78 @@ "undici-types": ">=7.24.0 <7.24.7" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", + "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/type-utils": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.63.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", + "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", - "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", + "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.4", - "@typescript-eslint/types": "^8.59.4", + "@typescript-eslint/tsconfig-utils": "^8.63.0", + "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "engines": { @@ -1347,10 +1412,28 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", + "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", - "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", + "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1363,10 +1446,35 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", + "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", - "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", + "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1377,15 +1485,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", - "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", + "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.4", - "@typescript-eslint/tsconfig-utils": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", + "@typescript-eslint/project-service": "8.63.0", + "@typescript-eslint/tsconfig-utils": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1403,13 +1511,37 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", + "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", - "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", + "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3305,6 +3437,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz", + "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.63.0", + "@typescript-eslint/parser": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", diff --git a/package.json b/package.json index f240d72..a77f86e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "CONTRIBUTING.md" ], "scripts": { - "build": "tsc", + "build": "tsc && chmod +x dist/cli.js", "dev": "tsx src/cli.ts", "check": "tsc --noEmit", "lint": "eslint src --ext .ts", @@ -32,8 +32,8 @@ "test": "vitest run --coverage", "test:watch": "vitest", "prepublishOnly": "npm run build", - "release:check": "npm run check && npm test && npm run smoke && npm run package:smoke", - "package:smoke": "npm pack --dry-run", + "release:check": "npm run check && npm run lint && npm test && npm run smoke && npm run package:smoke", + "package:smoke": "npm run build && node scripts/package-smoke.mjs", "smoke": "npm run build && node dist/cli.js --help && node dist/cli.js fixtures --format text" }, "keywords": [ @@ -71,6 +71,7 @@ "prettier": "^3.0.0", "tsx": "^4.0.0", "typescript": "^5.0.0", + "typescript-eslint": "^8.63.0", "vitest": "^3.0.0" } } diff --git a/scripts/package-smoke.mjs b/scripts/package-smoke.mjs new file mode 100644 index 0000000..60c4d84 --- /dev/null +++ b/scripts/package-smoke.mjs @@ -0,0 +1,67 @@ +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; + +const result = spawnSync("npm", ["pack", "--dry-run", "--json"], { + encoding: "utf8", +}); + +if (result.status !== 0) { + process.stderr.write(result.stderr); + process.exit(result.status ?? 1); +} + +const [packument] = JSON.parse(result.stdout); +const packedFiles = new Set(packument.files.map((file) => file.path)); + +const requiredFiles = [ + "dist/cli.js", + "dist/index.js", + "dist/analyzer.js", + "dist/parser.js", + "dist/reporter.js", + "README.md", + "LICENSE", + "SECURITY.md", + "CHANGELOG.md", + "CONTRIBUTING.md", +]; + +const forbiddenFiles = [ + "src/cli.ts", + "fixtures/fully-typed/math.ts", + "coverage/index.html", +]; + +const missing = requiredFiles.filter((file) => !packedFiles.has(file)); +const leaked = forbiddenFiles.filter((file) => packedFiles.has(file)); + +if (missing.length > 0 || leaked.length > 0) { + for (const file of missing) { + console.error(`package smoke missing required file: ${file}`); + } + for (const file of leaked) { + console.error(`package smoke leaked development file: ${file}`); + } + process.exit(1); +} + +const packageJson = JSON.parse(readFileSync("package.json", "utf8")); +const binPath = packageJson.bin?.typegap?.replace(/^\.\//, ""); +const binEntry = packument.files.find((file) => file.path === binPath); + +if (!binPath || !binEntry) { + console.error("package smoke missing typegap bin entry."); + process.exit(1); +} + +if ((binEntry.mode & 0o111) === 0) { + console.error("package smoke found a non-executable typegap bin."); + process.exit(1); +} + +if (!readFileSync(binPath, "utf8").startsWith("#!/usr/bin/env node")) { + console.error("package smoke found typegap bin without a Node shebang."); + process.exit(1); +} + +console.log(`typegap package smoke passed with ${packument.files.length} packed file(s).`); diff --git a/src/analyzer.test.ts b/src/analyzer.test.ts index b107d59..577eda2 100644 --- a/src/analyzer.test.ts +++ b/src/analyzer.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect } from 'vitest'; import { buildFileResult, buildProjectResult, analyzeDirectory } from './analyzer.js'; -import { parseFile } from './parser.js'; import type { NodeInfo } from './types.js'; import { AnnotationStatus } from './types.js'; diff --git a/src/analyzer.ts b/src/analyzer.ts index 70a3e23..c9fb255 100644 --- a/src/analyzer.ts +++ b/src/analyzer.ts @@ -3,7 +3,6 @@ * applies ignore patterns, calculates coverage. */ -import { relative } from 'node:path'; import type { NodeInfo, FileResult, ProjectResult } from './types.js'; import { parseFile } from './parser.js'; import { glob } from 'glob'; @@ -34,6 +33,7 @@ export async function analyzeFiles( filePaths: string[], options: AnalyzeOptions = {}, ): Promise { + void options; const fileResults: FileResult[] = []; for (const file of filePaths) { diff --git a/src/parser.test.ts b/src/parser.test.ts index 161bc4c..e44c2f9 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { parseSource, parseFile, classifyTypeAnnotation, extractTypeName } from './parser.js'; +import { parseSource, parseFile, extractTypeName } from './parser.js'; import { AnnotationStatus, IssueType } from './types.js'; import type { TSESTree } from '@typescript-eslint/typescript-estree'; import { parse } from '@typescript-eslint/typescript-estree'; diff --git a/src/reporter.test.ts b/src/reporter.test.ts index d5bc31c..252854c 100644 --- a/src/reporter.test.ts +++ b/src/reporter.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { describe, it, expect, afterEach } from 'vitest'; import { generateReport, saveBaseline, loadBaseline, compareWithBaseline, getBaselineComparison } from './reporter.js'; import { buildProjectResult } from './analyzer.js'; import { AnnotationStatus } from './types.js'; @@ -25,7 +25,7 @@ function makeResult(override: Partial & Partial { diff --git a/src/reporter.ts b/src/reporter.ts index 94d3916..7b50806 100644 --- a/src/reporter.ts +++ b/src/reporter.ts @@ -5,8 +5,8 @@ import { relative } from 'node:path'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import type { ProjectResult, FileResult, Baseline, BaselineComparison, NodeInfo } from './types.js'; -import { AnnotationStatus, IssueType } from './types.js'; +import type { ProjectResult, Baseline, BaselineComparison, NodeInfo } from './types.js'; +import { IssueType } from './types.js'; import pc from 'picocolors'; /* ------------------------------------------------------------------ */