diff --git a/.eslintrc.yml b/.eslintrc.yml index 897635d..d00cef2 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -34,6 +34,7 @@ extends: rules: { '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', 'camelcase': 'off', 'eslint-comments/no-use': 'off', 'i18n-text/no-en': 'off', diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b188d6f..7030064 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,10 +56,10 @@ against various repositories with different configurations. 1. Test your updated version ```bash - local-action run + local-action run # Or... - npm exec local-action run + npm exec local-action run ``` Once you're finished testing, make sure to unlink! diff --git a/README.md b/README.md index 227d060..81b7f95 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,12 @@ For additional information about transpiled action code, see git clone https://github.com/github/local-action.git ``` +1. Install dependencies + + ```bash + npm ci + ``` + 1. Install via `npm` ```bash diff --git a/__tests__/command.test.ts b/__tests__/command.test.ts index c40ac27..6b5b1d3 100644 --- a/__tests__/command.test.ts +++ b/__tests__/command.test.ts @@ -194,7 +194,7 @@ describe('Commmand', () => { process_stderrSpy.mockRestore() }) - it('Throws if the env file does not exist', async () => { + it('Throws if the dotenv file does not exist', async () => { process_stderrSpy = jest .spyOn(process.stderr, 'write') .mockImplementation() diff --git a/bin/local-action b/bin/local-action index 81fc708..131c96d 100755 --- a/bin/local-action +++ b/bin/local-action @@ -1,46 +1,72 @@ #!/usr/bin/env node +const fs = require('fs') const path = require('path') const { execSync } = require('child_process') -// Back up the environment -const envBackup = { ...process.env } -const pathBackup = process.env.PATH - /** * This script is used to run the local action. It sets the NODE_OPTIONS - * environment variable to require the bootstrap file, which sets up the + * environment variable to require the bootstrap script, which sets up the * TypeScript environment for the action. */ -// Set the first argument (path to action directory) as an environment variable. -// This is used in the bootstrap file to check for a `tsconfig.json`. -const actionPath = process.argv[2] ? path.resolve(process.argv[2]) : '' -process.env.TARGET_ACTION_PATH = actionPath - -// Get the other arguments, if present. Validation and error handling is done -// within the package itself. -const entrypoint = process.argv[3] ?? '' -const dotenvFile = process.argv[4] ? path.resolve(process.argv[4]) : '' - -// Get the absolute path to the `@github/local-action` package. -const packagePath = path.resolve(__dirname, '..') -const packageIndex = path.join(packagePath, 'src', 'index.ts') - -// Set the NODE_OPTIONS environment variable to require the bootstrap file -const options = `--require "${path.join(packagePath, 'src', 'bootstrap.js')}"` -process.env.NODE_OPTIONS = process.env.NODE_OPTIONS - ? `${process.env.NODE_OPTIONS} ${options}` - : options - -// Run the action -const command = `npx tsx "${packageIndex}" "${actionPath}" "${entrypoint}" "${dotenvFile}"` - -try { - execSync(command, { cwd: packagePath, stdio: 'inherit' }) -} catch (error) { - process.exit(error.status) -} finally { - // Restore the environment - process.env = { ...envBackup } - process.env.PATH = pathBackup +function entrypoint() { + // Save the current environment and path. + const envBackup = { ...process.env } + const pathBackup = process.env.PATH + + // Delete the TARGET_ACTION_PATH environment variable. + delete process.env.TARGET_ACTION_PATH + + try { + // Get the absolute path to the `@github/local-action` package. + const packagePath = path.resolve(__dirname, '..') + + // Get the absolute path to the bootstrap script. On Windows systems, this + // need to be double-escaped so the path resolves correctly. + const bootstrapPath = + process.platform === 'win32' + ? path.join(packagePath, 'src', 'bootstrap.js').replaceAll('\\', '\\\\') + : path.join(packagePath, 'src', 'bootstrap.js') + + // Require the bootstrap script in NODE_OPTIONS. + process.env.NODE_OPTIONS = process.env.NODE_OPTIONS + ? `${process.env.NODE_OPTIONS} --require ${bootstrapPath}` + : `--require ${bootstrapPath}` + + // Start building the command to run local-action. + let command = `npx tsx "${path.join(packagePath, 'src', 'index.ts')}"` + + // Process the input arguments. + if (process.argv.length === 2) { + // No arguments...display the help message. + command += ' --help' + } else { + // Iterate over the arguments and build the command. + for (const arg of process.argv.slice(2)) { + // If the argument is a directory and TARGET_ACTION_PATH is not set, set + // it to the absolute path of the directory. The first directory is the + // target action path. + if ( + !process.env.TARGET_ACTION_PATH && + fs.existsSync(path.resolve(arg)) && + fs.lstatSync(path.resolve(arg)).isDirectory() + ) + process.env.TARGET_ACTION_PATH = path.resolve(arg) + + // Append the argument to the command. + command += ` ${arg}` + } + } + + // Run the command. + execSync(command, { cwd: packagePath, stdio: 'inherit' }) + } catch (error) { + process.exit(error.status) + } finally { + // Restore the environment. + process.env = { ...envBackup } + process.env.PATH = pathBackup + } } + +entrypoint() diff --git a/package-lock.json b/package-lock.json index 1677302..cf839bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@github/local-action", - "version": "1.4.2", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@github/local-action", - "version": "1.4.2", + "version": "1.5.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/package.json b/package.json index 54a5daa..d66ebcd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@github/local-action", "description": "Local Debugging for GitHub Actions", - "version": "1.4.2", + "version": "1.5.0", "author": "Nick Alteen ", "private": false, "homepage": "https://github.com/github/local-action", diff --git a/src/bootstrap.js b/src/bootstrap.js index 4b2e77f..87f5334 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-var-requires */ /** diff --git a/src/command.ts b/src/command.ts index 0ab23d7..66601d7 100644 --- a/src/command.ts +++ b/src/command.ts @@ -26,11 +26,9 @@ export async function makeProgram(): Promise { if (!fs.statSync(actionPath).isDirectory()) throw new InvalidArgumentError('Action path must be a directory') } catch (err: any) { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ if ('code' in err && err.code === 'ENOENT') throw new InvalidArgumentError('Action path does not exist') else throw new InvalidArgumentError(err.message as string) - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ } // Save the action path to environment metadata @@ -91,7 +89,9 @@ export async function makeProgram(): Promise { program .name('local-action') .description('Test a GitHub Action locally') - .version('1.0.0') + .version( + `Version: ${JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf-8')).version as string}` + ) program .command('run', { isDefault: true }) @@ -102,7 +102,7 @@ export async function makeProgram(): Promise { 'Action entrypoint (relative to the action directory)', checkEntrypoint ) - .argument('', 'Path to the local .env file', checkDotenvFile) + .argument('', 'Path to the local .env file', checkDotenvFile) .action(async () => { await runAction() })