diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 59695bb..0000000 --- a/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@voxpelli/eslint-config/esm", - "root": true, - "ignorePatterns": ["examples/**"] -} diff --git a/.github/release-please/config.json b/.github/release-please/config.json new file mode 100644 index 0000000..336265d --- /dev/null +++ b/.github/release-please/config.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/v16.12.0/schemas/config.json", + "release-type": "node", + "include-component-in-tag": false, + "changelog-sections": [ + { "type": "feat", "section": "๐ŸŒŸ Features", "hidden": false }, + { "type": "fix", "section": "๐Ÿฉน Fixes", "hidden": false }, + { "type": "docs", "section": "๐Ÿ“š Documentation", "hidden": false }, + + { "type": "chore", "section": "๐Ÿงน Chores", "hidden": false }, + { "type": "perf", "section": "๐Ÿงน Chores", "hidden": false }, + { "type": "refactor", "section": "๐Ÿงน Chores", "hidden": false }, + { "type": "test", "section": "๐Ÿงน Chores", "hidden": false }, + + { "type": "build", "section": "๐Ÿค– Automation", "hidden": false }, + { "type": "ci", "section": "๐Ÿค– Automation", "hidden": true } + ], + "packages": { + ".": {} + } +} diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json new file mode 100644 index 0000000..fd407a8 --- /dev/null +++ b/.github/release-please/manifest.json @@ -0,0 +1 @@ +{".":"9.3.0"} diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml new file mode 100644 index 0000000..40382be --- /dev/null +++ b/.github/workflows/compliance.yml @@ -0,0 +1,21 @@ +name: Compliance + +on: + pull_request_target: + types: [opened, edited, reopened] + +permissions: + pull-requests: write + +jobs: + compliance: + runs-on: ubuntu-latest + steps: + - uses: mtfoley/pr-compliance-action@11b664f0fcf2c4ce954f05ccfcaab6e52b529f86 + with: + body-auto-close: false + body-regex: '.*' + ignore-authors: | + renovate + renovate[bot] + ignore-team-members: false diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9bdbe39..c2b2357 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -17,5 +17,5 @@ jobs: test: uses: voxpelli/ghatemplates/.github/workflows/test.yml@main with: - node-versions: '18,20,22' + node-versions: '20,22,24,25' os: 'ubuntu-latest,windows-latest' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..7caa5c5 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,25 @@ +name: Release Please + +on: + push: + branches: + - main + + workflow_dispatch: + inputs: + force-release: + description: 'Force release to npm' + required: false + type: boolean + +permissions: + contents: read + id-token: write + +jobs: + release-please: + uses: voxpelli/ghatemplates/.github/workflows/release-please-oidc.yml@main + secrets: inherit + with: + app-id: '1082006' + force-release: ${{ inputs.force-release || false }} diff --git a/cli-wrapper.cjs b/cli-wrapper.cjs index 60e2a36..1d7554a 100755 --- a/cli-wrapper.cjs +++ b/cli-wrapper.cjs @@ -2,4 +2,4 @@ 'use strict'; -require('version-guard')('./cli.js', 18, 6); +require('version-guard')('./cli.js', 20, 19); diff --git a/cli.js b/cli.js index 2276425..ad17677 100755 --- a/cli.js +++ b/cli.js @@ -1,6 +1,6 @@ /* eslint-disable no-console, unicorn/no-process-exit */ -import { resolve } from 'node:path'; +import pathModule from 'node:path'; import { createRequire } from 'node:module'; import chalk from 'chalk'; import { formatHelpMessage, peowly } from 'peowly'; @@ -218,7 +218,7 @@ let checks = [ // Detect if we're in a workspace within a larger monorepo // If so, use the parent workspace root to enable access to parent's node_modules -const requestedCwd = resolve(cli.input[0] || process.cwd()); +const requestedCwd = pathModule.resolve(cli.input[0] || process.cwd()); let resolvedCwd = requestedCwd; let workspaceFilter = workspace; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0d94932 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,5 @@ +import { voxpelli } from '@voxpelli/eslint-config'; + +export default voxpelli({ + noMocha: true, +}); diff --git a/package.json b/package.json index 3980259..0f19b3a 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "cli.js" ], "scripts": { - "check:lint": "eslint --report-unused-disable-directives .", - "check:installed-check": "node cli-wrapper.cjs -i eslint-plugin-jsdoc -i knip", + "check:lint": "eslint", + "check:installed-check": "node cli-wrapper.cjs", "check:knip": "knip", "check:tsc": "tsc", "check:type-coverage": "type-coverage --detail --strict --at-least 95", @@ -42,36 +42,27 @@ "author": "Pelle Wessman (http://kodfabrik.se/)", "license": "MIT", "engines": { - "node": ">=18.6.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "dependencies": { - "chalk": "^5.3.0", - "installed-check-core": "^8.3.0", + "chalk": "^5.6.2", + "installed-check-core": "^8.3.1", "peowly": "^1.3.3", "pony-cause": "^2.1.10", "resolve-workspace-root": "^2.0.1", - "version-guard": "^1.1.1" + "version-guard": "^1.1.3" }, "devDependencies": { - "@types/node": "^18.19.29", - "@voxpelli/eslint-config": "^19.0.0", - "@voxpelli/tsconfig": "^11.0.0", - "eslint": "^8.57.0", - "eslint-plugin-es-x": "^7.6.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^46.10.1", - "eslint-plugin-mocha": "^10.4.3", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-security": "^1.7.1", - "eslint-plugin-sort-destructure-keys": "^1.6.0", - "eslint-plugin-unicorn": "^48.0.1", - "husky": "^9.0.11", - "knip": "^5.13.0", - "npm-run-all2": "^6.1.2", + "@types/node": "^20.19.33", + "@voxpelli/eslint-config": "^23.0.0", + "@voxpelli/tsconfig": "^16.1.0", + "eslint": "^9.39.2", + "husky": "^9.1.7", + "knip": "^5.83.1", + "npm-run-all2": "^8.0.4", "remark-parse": "^11.0.0", - "type-coverage": "^2.28.2", - "typescript": "~5.4.5", + "type-coverage": "^2.29.7", + "typescript": "~5.9.0", "unified": "^11.0.5", "unist-util-visit": "^5.1.0" } diff --git a/test/helpers.js b/test/helpers.js index 50d7867..da3215a 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,12 +1,12 @@ -/** - * Test helper functions for integration tests - */ - import { exec } from 'node:child_process'; import { promisify } from 'node:util'; const execAsync = promisify(exec); +// Strip ANSI color codes from outputs for consistent assertions +// eslint-disable-next-line no-control-regex +export const stripAnsi = (/** @type {string} */ s) => s.replaceAll(/\u001B\[[0-9;]*m/g, ''); + /** * Run a command and return stdout/stderr * @@ -16,18 +16,20 @@ const execAsync = promisify(exec); */ export async function run (command, cwdOrOptions) { const options = typeof cwdOrOptions === 'string' ? { cwd: cwdOrOptions } : cwdOrOptions; + try { const { stderr, stdout } = await execAsync(command, options); - return { code: 0, output: stdout + stderr, stderr, stdout }; + return { code: 0, output: stripAnsi(stdout + stderr), stderr: stripAnsi(stderr), stdout: stripAnsi(stdout) }; } catch (/** @type {any} */ err) { const errCode = /** @type {number} */ (err.code || 1); const errStdout = /** @type {string} */ (err.stdout || ''); const errStderr = /** @type {string} */ (err.stderr || ''); + return { code: errCode, - output: errStdout + errStderr, - stderr: errStderr, - stdout: errStdout, + output: stripAnsi(errStdout + errStderr), + stderr: stripAnsi(errStderr), + stdout: stripAnsi(errStdout), }; } } diff --git a/test/integration.js b/test/integration.js index 7dbf914..de5958e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -7,6 +7,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { fileURLToPath } from 'node:url'; +// eslint-disable-next-line unicorn/import-style import { dirname, join } from 'node:path'; import { extractExpectedOutput, normalizeOutput } from './test-readme.js'; import { run } from './helpers.js'; diff --git a/test/test-readme.js b/test/test-readme.js index 14c100f..16f30ab 100644 --- a/test/test-readme.js +++ b/test/test-readme.js @@ -1,14 +1,10 @@ -/** - * Test utilities for validating example outputs against README documentation - * - * @module lib/test-readme - */ - import { readFile } from 'node:fs/promises'; import { unified } from 'unified'; import remarkParse from 'remark-parse'; import { visit } from 'unist-util-visit'; +import { stripAnsi } from './helpers.js'; + /** * Extract expected output from README markdown using unified/remark * @@ -17,6 +13,7 @@ import { visit } from 'unist-util-visit'; * @returns {Promise} The extracted code block content, or undefined if not found */ export async function extractExpectedOutput (readmePath, marker = 'EXPECTED OUTPUT') { + // eslint-disable-next-line security/detect-non-literal-fs-filename const content = await readFile(readmePath, 'utf8'); const tree = unified().use(remarkParse).parse(content); @@ -53,8 +50,10 @@ export async function extractExpectedOutput (readmePath, marker = 'EXPECTED OUTP * @returns {string} Normalized output */ export function normalizeOutput (output) { - return output - .replaceAll(/\/\S+\/examples\//g, '/absolute/path/to/examples/') - .replaceAll('\r\n', '\n') // Normalize Windows line endings to Unix + return stripAnsi( + output + .replaceAll(/\/\S+\/examples\//g, '/absolute/path/to/examples/') + .replaceAll('\r\n', '\n') // Normalize Windows line endings to Unix + ) .trim(); } diff --git a/tsconfig.json b/tsconfig.json index 69bf52a..a51f697 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@voxpelli/tsconfig/node18.json", + "extends": "@voxpelli/tsconfig/node20.json", "files": [ "cli-wrapper.cjs", "cli.js",