name: Test readlink behavior across Node versions on: workflow_dispatch: pull_request: paths: - 'packages/io/src/io-util.ts' - 'packages/io/__tests__/io.test.ts' jobs: test-readlink-windows: name: Test readlink on Windows - Node ${{ matrix.node-version }} runs-on: windows-latest strategy: matrix: node-version: [20, 24] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Show Node version run: node --version - name: Create test script shell: pwsh run: | $script = @' const fs = require('fs'); const path = require('path'); const os = require('os'); async function testReadlink() { const testDir = path.join(os.tmpdir(), 'readlink-test-' + Date.now()); const actualDir = path.join(testDir, 'actual_directory'); const directSymlink = path.join(testDir, 'direct_symlink'); const intermediateSymlink = path.join(testDir, 'intermediate_symlink'); const chainSymlink = path.join(testDir, 'chain_symlink'); try { // Create test structure await fs.promises.mkdir(actualDir, { recursive: true }); await fs.promises.symlink(actualDir, directSymlink, 'junction'); await fs.promises.symlink(actualDir, intermediateSymlink, 'junction'); await fs.promises.symlink(intermediateSymlink, chainSymlink, 'junction'); console.log('='.repeat(60)); console.log('Node.js version:', process.version); console.log('Platform:', os.platform()); console.log('='.repeat(60)); const directTarget = await fs.promises.readlink(directSymlink); console.log('\n[Test 1] Direct symlink → directory:'); console.log(' Path:', JSON.stringify(directTarget)); console.log(' Length:', directTarget.length); console.log(' Last char:', JSON.stringify(directTarget.slice(-1))); console.log(' Ends with backslash:', directTarget.endsWith('\\')); const chainTarget = await fs.promises.readlink(chainSymlink); console.log('\n[Test 2] Chain symlink → intermediate symlink:'); console.log(' Path:', JSON.stringify(chainTarget)); console.log(' Length:', chainTarget.length); console.log(' Last char:', JSON.stringify(chainTarget.slice(-1))); console.log(' Ends with backslash:', chainTarget.endsWith('\\')); const intermediateTarget = await fs.promises.readlink(intermediateSymlink); console.log('\n[Test 3] Intermediate symlink → directory:'); console.log(' Path:', JSON.stringify(intermediateTarget)); console.log(' Length:', intermediateTarget.length); console.log(' Last char:', JSON.stringify(intermediateTarget.slice(-1))); console.log(' Ends with backslash:', intermediateTarget.endsWith('\\')); console.log('\n' + '='.repeat(60)); console.log('SUMMARY:'); console.log(' Test 1 (dir): trailing backslash =', directTarget.endsWith('\\')); console.log(' Test 2 (symlink): trailing backslash =', chainTarget.endsWith('\\')); console.log(' Test 3 (dir): trailing backslash =', intermediateTarget.endsWith('\\')); console.log('='.repeat(60)); // Cleanup await fs.promises.rm(testDir, { recursive: true, force: true }); } catch (err) { console.error('Error:', err); process.exit(1); } } testReadlink(); '@ $script | Out-File -FilePath test-native-readlink.js -Encoding UTF8 - name: Test native fs.promises.readlink() behavior run: node test-native-readlink.js - name: Install dependencies run: npm ci working-directory: packages/io - name: Build package run: npm run tsc working-directory: packages/io - name: Run readlink tests run: npm test -- --testNamePattern="readlink" working-directory: . env: CI: true