diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4a8485f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Node ${{ matrix.node-version }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + node-version: [20, 22] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npm run typecheck + + - name: Lint + run: npm run lint + + - name: Format check + run: npm run format + + - name: Run unit tests + run: npm run test:cov + + - name: Build + run: npm run build:ci + + - name: Run integration tests + run: npm run test:integration:ci + + - name: Upload coverage + if: matrix.node-version == 22 + uses: codecov/codecov-action@v4 + with: + files: ./coverage/coverage-final.json + fail_ci_if_error: false diff --git a/CHANGELOG.md b/CHANGELOG.md index b0f3362..e152c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,80 +2,48 @@ All notable changes to this project will be documented in this file. -## [0.3.1] - 2025-12-09 +## [0.3.2] - 2025-12-10 ### Changed -- **Build Process Simplification**: Removed wrapper script complexity in favor of direct SWC compilation -- **Postbuild Script**: Replaced `insert-shebang.sh` with lightweight `add-shebang.js` Node script -- **CJS Package Generation**: Now generated inline in build script instead of via wrapper -- **README**: Updated "How It Works" section to reflect simplified build architecture - -### Technical Details - -**Build Process (Simplified):** +- **CI**: Main-only triggers, concurrency cancellation, Node 20/22 matrix, unit tests run with coverage +- **Tests**: Unit and integration suites split into separate Vitest configs +- **Build**: CI builds once (`build:ci`) before running integration tests; Codecov upload is best-effort -1. Prebuild: format:fix → lint → typecheck → clean -2. Compile: SWC compiles source to both `build/esm/src/` and `build/cjs/src/` -3. Path Resolution: `tsc-alias` transforms `@/` imports to relative paths -4. Executables: `add-shebang.js` adds shebangs and sets executable permissions -5. CJS Marker: `package.json` with `"type":"commonjs"` created in `build/cjs/` +## [0.3.1] - 2025-12-09 -**Entry Points:** +### Changed -- ESM: `build/esm/src/cli.js` (executable) -- CJS: `build/cjs/src/cli.js` (executable) +- **Build**: Simplified to direct SWC outputs plus a lightweight `add-shebang.js` script +- **Docs**: README "How It Works" refreshed for the new build shape +- **CI**: Added GitHub Actions on Node 20/22 ## [0.3.0] - 2025-12-09 ### Added -- **Dual Build System**: Project compiles to both ESM (`build/esm/`) and CommonJS (`build/cjs/`) with proper module format markers -- **Path Aliases**: TypeScript path aliases (`@/*`) work throughout codebase and resolve to relative paths in output -- **Build Validation**: Code quality checks (format, lint, typecheck) run automatically in prebuild step -- **Source Maps**: Both ESM and CJS builds include source maps for debugging -- **ESM dirname Utility**: New `getDirname()` utility for ES module-compatible directory resolution -- **Comprehensive Tests**: 27 total tests (9 unit + 18 integration) with 100% code coverage -- **Integration Test Suite**: Dedicated `tests/integration/` with build output and module format validation -- **JSDoc Comments**: All functions documented for IDE support and clarity -- **Improved README**: Comprehensive documentation with features, usage, and architecture explanation +- **Dual Outputs**: ESM and CJS builds with source maps and path alias resolution +- **Tests**: 27 total (unit + integration) plus coverage; integration suite validates build outputs +- **Docs**: README expanded; JSDoc coverage improved ### Changed -- **Tooling Migration**: Replaced ESLint + Prettier with Biome for unified linting/formatting -- **Node Version**: Updated from 16+ to 20+ for stable ESM support without flags -- **Build Scripts**: Restructured with separate SWC configurations for ESM and CJS -- **CLI Implementation**: Enhanced with better error handling and version management -- **Testing Framework**: Upgraded from Vite test to dedicated Vitest with coverage -- **Project Structure**: Added utilities folder and proper test file organization +- **Tooling**: ESLint/Prettier replaced by Biome; Vitest adopted with coverage +- **Runtime**: Node requirement raised to 20+ for stable ESM; build scripts split per format ### Removed -- ESLint configuration and plugins -- Prettier configuration -- Old single build configuration (`.swcrc`) -- Unreachable Node version check code +- Legacy lint/format configs and the single `.swcrc` +- Unreachable Node version guard ### Fixed -- Test file exclusion from production builds -- Path alias transformation for both module formats -- Vitest configuration to prevent test discovery in build directory -- CJS module format declaration +- Test files excluded from builds; aliases resolved correctly; build dir no longer scanned by tests ### Dependencies Updated -- TypeScript: 4.9.5 → 5.9.3 -- SWC: 1.3.35 → 1.15.3 -- Commander: 10.0.0 → 14.0.2 -- Vitest: 0.28.5 → 4.0.15 -- Biome: 2.3.8 (new) -- tsc-alias: 1.8.16 (new) -- @vitest/coverage-v8: 4.0.15 (new) +- TypeScript, SWC, Commander, Vitest, Biome, tsc-alias, @vitest/coverage-v8 ### Breaking Changes -- Requires Node.js 20+ (was 16+) -- Dual ESM/CJS structure replaces single build output -- Package exports field specifies import/require conditions -- CLI now uses Biome instead of ESLint/Prettier +- Requires Node 20+ and dual ESM/CJS outputs; exports now specify import/require conditions diff --git a/package-lock.json b/package-lock.json index ea57d4a..8c82794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "hello-cli", - "version": "0.2.0", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hello-cli", - "version": "0.2.0", + "version": "0.3.2", "license": "MIT", "dependencies": { "commander": "^14.0.2" }, "bin": { - "hello-cli": "build/esm/cli.js" + "hello-cli": "build/esm/src/cli.js" }, "devDependencies": { "@biomejs/biome": "2.3.8", @@ -26,7 +26,7 @@ "vitest": "^4.0.15" }, "engines": { - "node": ">=22" + "node": ">=20" } }, "node_modules/@babel/helper-string-parser": { @@ -3975,21 +3975,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", diff --git a/package.json b/package.json index 076e88c..890eada 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hello-cli", - "version": "0.3.1", + "version": "0.3.2", "description": "A basic CLI written in TypeScript meant to be used as an example or project starter", "type": "module", "bin": "./build/esm/src/cli.js", @@ -31,9 +31,10 @@ }, "scripts": { "prebuild": "npm run format:fix && npm run lint && npm run typecheck && rm -rf ./build", - "build:esm": "swc src --config-file .swcrc-esm -d build/esm --ignore '**/*.test.ts' && tsc-alias -p tsconfig.json --outDir build/esm --resolve-full-paths", - "build:cjs": "swc src --config-file .swcrc-cjs -d build/cjs --ignore '**/*.test.ts' && tsc-alias -p tsconfig.json --outDir build/cjs --resolve-full-paths && echo '{\"type\":\"commonjs\"}' > build/cjs/package.json", + "build:esm": "swc src --config-file .swcrc-esm -d build/esm --ignore '**/*.test.ts' && tsc-alias -p tsconfig.json --outDir build/esm --resolve-full-paths || exit 1", + "build:cjs": "swc src --config-file .swcrc-cjs -d build/cjs --ignore '**/*.test.ts' && tsc-alias -p tsconfig.json --outDir build/cjs --resolve-full-paths && echo '{\"type\":\"commonjs\"}' > build/cjs/package.json || exit 1", "build": "npm run build:esm && npm run build:cjs && node ./add-shebang.js", + "build:ci": "rm -rf ./build && npm run build:esm && npm run build:cjs && node ./add-shebang.js", "build:debug": "swc --config-file .swcrc-esm -s true -D src -d build/esm", "typecheck": "tsc --noEmit", "prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build", @@ -43,7 +44,8 @@ "lint": "biome lint ./src", "lint:fix": "biome lint --write ./src", "test": "vitest run", - "test:integration": "npm run build && vitest run tests/integration", + "test:integration": "npm run build && vitest run --config vitest.config.integration.ts", + "test:integration:ci": "vitest run --config vitest.config.integration.ts", "test:all": "npm test && npm run test:integration", "test:cov": "vitest run --coverage" }, diff --git a/vitest.config.integration.ts b/vitest.config.integration.ts new file mode 100644 index 0000000..1f47c64 --- /dev/null +++ b/vitest.config.integration.ts @@ -0,0 +1,17 @@ +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import { defineConfig } from "vitest/config"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + test: { + exclude: ["**/node_modules/**", "**/build/**"], + include: ["tests/integration/**/*.{test,spec}.{js,ts}"], + }, + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + }, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts index 4fa96e8..ef19bf6 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); export default defineConfig({ test: { exclude: ["**/node_modules/**", "**/build/**"], + include: ["src/**/*.{test,spec}.{js,ts}"], coverage: { reporter: ["text", "json", "html"], },