diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8677f87..28a9b79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,3 +37,18 @@ jobs: - name: Run lint run: npm run lint + + package-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + + - uses: actions/setup-node@v6.3.0 + with: + node-version: 22 + + - name: Install modules + run: npm install + + - name: Validate published package + run: npm run prepublishOnly diff --git a/CHANGELOG.md b/CHANGELOG.md index 2201b4c..56b2281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [Unreleased] + +### Tests + +- add npm tarball smoke test for the packed CLI and installed `lint-md`, with a dedicated Node 22 CI job ([#103](https://github.com/lint-md/cli/issues/103), [#104](https://github.com/lint-md/cli/pull/104)) + +### Dependencies + +- upgrade `@lint-md/core` from `^2.1.5` to `^2.1.6` for parser 0.2.0 source-map fixes and accurate inline-code value ranges ([#104](https://github.com/lint-md/cli/pull/104)) + ## [2.2.2](https://github.com/lint-md/cli/compare/v2.1.1...v2.2.2) (2026-07-13) ### Features diff --git a/package.json b/package.json index b9abb4a..b6b7c27 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "watch": "tsc -w", "cli-run": "node ./lib/src/lint-md.js examples/correct-title-trailing-punctuation.md", "benchmark:memory": "node scripts/benchmark-memory.mjs", - "test:benchmark-memory": "node scripts/benchmark-memory-smoke.mjs" + "test:benchmark-memory": "node scripts/benchmark-memory-smoke.mjs", + "test:package": "node scripts/test-package.mjs", + "prepublishOnly": "npm run lint && npm test && npm run test:package" }, "files": [ "esm/package.json", @@ -58,7 +60,7 @@ ] }, "dependencies": { - "@lint-md/core": "^2.1.5", + "@lint-md/core": "^2.1.6", "chalk": "^4", "commander": "^9.4.1", "glob": "^13.0.6", diff --git a/scripts/test-package.mjs b/scripts/test-package.mjs new file mode 100644 index 0000000..75b2e0a --- /dev/null +++ b/scripts/test-package.mjs @@ -0,0 +1,101 @@ +import { spawnSync } from "node:child_process"; +import { + cpSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { basename, join, resolve } from "node:path"; + +const projectRoot = resolve(import.meta.dirname, ".."); +const temporaryDirectory = mkdtempSync(join(tmpdir(), "lint-md-package-")); + +const run = (command, args, options = {}) => { + const result = spawnSync(command, args, { + cwd: projectRoot, + encoding: "utf8", + stdio: ["pipe", "pipe", "pipe"], + ...options, + }); + + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error( + `${command} ${args.join(" ")} exited with ${result.status}\n${result.stderr}` + ); + } + + return result.stdout; +}; + +const assert = (condition, message) => { + if (!condition) { + throw new Error(message); + } +}; + +try { + const packOutput = run("npm", [ + "pack", + "--json", + "--pack-destination", + temporaryDirectory, + ]); + const [packedPackage] = JSON.parse(packOutput); + const packedFiles = new Set(packedPackage.files.map(({ path }) => path)); + + for (const expectedFile of [ + "CHANGELOG.md", + "lib/src/lint-md.js", + "lib/src/utils/lint-worker.js", + "esm/src/lint-md.js", + ]) { + assert(packedFiles.has(expectedFile), `tarball is missing ${expectedFile}`); + } + + const tarball = join(temporaryDirectory, basename(packedPackage.filename)); + assert(existsSync(tarball), `npm pack did not create ${tarball}`); + + const installPrefix = join(temporaryDirectory, "installed"); + run("npm", [ + "install", + "--global", + "--prefix", + installPrefix, + "--omit=dev", + "--no-audit", + "--no-fund", + tarball, + ]); + + const lintMd = join(installPrefix, "bin", "lint-md"); + assert(existsSync(lintMd), "global installation did not expose lint-md"); + run(lintMd, ["--version"]); + + const cleanMarkdown = "# Hello\n\nThis is clean.\n"; + run(lintMd, ["--stdin"], { input: cleanMarkdown }); + + const fixtureDirectory = join(temporaryDirectory, "fixtures"); + const cleanFile = join(fixtureDirectory, "clean.md"); + const fixableFile = join(fixtureDirectory, "fixable.md"); + mkdirSync(fixtureDirectory); + cpSync(join(projectRoot, "examples", "space-around.md"), fixableFile, { + recursive: false, + }); + writeFileSync(cleanFile, cleanMarkdown, "utf8"); + + run(lintMd, [cleanFile]); + const originalFixableContent = readFileSync(fixableFile, "utf8"); + run(lintMd, [fixableFile, "--fix"]); + const fixedContent = readFileSync(fixableFile, "utf8"); + assert(fixedContent !== originalFixableContent, "--fix did not update the fixture"); + run(lintMd, [fixableFile]); +} finally { + rmSync(temporaryDirectory, { recursive: true, force: true }); +}