diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1497d85..72a9631 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + permissions: contents: read @@ -50,6 +54,9 @@ jobs: - name: Checkout id: checkout uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' - name: Test Local Action id: test-action @@ -61,6 +68,75 @@ jobs: "${{ runner.temp }}/post.sh" fi + - name: Test Local Action (login shell) + id: test-longin-shell + uses: ./ + with: + shell: bash -l -ex {0} + post-run: | + echo "test" | tee "${{ runner.temp }}/test.txt" + if [ -f "${{ runner.temp }}/post.sh" ]; then + "${{ runner.temp }}/post.sh" + fi + + - name: Test Local Action (Python) + id: test-python + uses: ./ + with: + shell: python + post-run: | + print("Hello, world!") + + - name: Test file + run: | + test ! -f "${{ runner.temp }}/test.txt" + + test-action-windows: + name: GitHub Actions Test (Windows) + runs-on: windows-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Test Local Action + id: test-action + uses: ./ + with: + post-run: | + echo "test" | tee "${{ runner.temp }}/test.txt" + if [ -f "${{ runner.temp }}/post.sh" ]; then + "${{ runner.temp }}/post.sh" + fi + + - name: Test Local Action (pwsh) + id: test-pwsh + uses: ./ + with: + shell: pwsh + post-run: | + Write-Host "Hello World!!" + + - name: Test Local Action (cmd) + id: test-cmd + uses: ./ + with: + shell: cmd + post-run: | + echo "Hello World!!" + + - name: Test Local Action (Python) + id: test-python + uses: ./ + with: + shell: python + post-run: | + print("Hello, world!") + - name: Test file run: | test ! -f "${{ runner.temp }}/test.txt" diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 0a2e675..c2de7b7 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -5,6 +5,10 @@ on: - main workflow_dispatch: +permissions: + contents: write + pull-requests: read + jobs: draft_release: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 1631e33..1deed73 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ See [action.yml](./action.yml) - name: Post Action uses: srz-zumix/post-run-action@main with: + # custom shell + # Default : bash -e {0} + # bash : bash --noprofile --norc -eo pipefail {0} + # custom : e.g. `bash -l -ex {0}` + # see https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell + shell: base -ex {0} # post run script text post-run: | echo "test" | tee "${{ runner.temp }}/test.txt" diff --git a/action.yml b/action.yml index 8e745bb..5aff903 100644 --- a/action.yml +++ b/action.yml @@ -6,6 +6,13 @@ inputs: post-run: description: 'post run script text' required: true + shell: + # Default : bash -e {0} + # bash : bash --noprofile --norc -eo pipefail {0} + # custom : e.g. `bash -l -ex {0}` + # see https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell + description: 'custom shell command' + default: "" runs: using: node16 diff --git a/dist/post/index.js b/dist/post/index.js index 1d24e2e..e861536 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -25970,16 +25970,61 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); const fs_1 = __nccwpck_require__(7147); +const path = __importStar(__nccwpck_require__(1017)); const io = __importStar(__nccwpck_require__(7436)); const exec = __importStar(__nccwpck_require__(1514)); +async function resolveShell() { + const defaultCommands = { + default: ['bash', '-e', '{0}'], + sh: ['sh', '-e', '{0}'], + bash: ['bash', '--noprofile', '--norc', '-eo', 'pipefail', '{0}'], + cmd: ['cmd', '/D', '/E:ON', '/V:OFF', '/S', '/C', '"CALL "{0}""'], + pwsh: ['pwsh', '-command', ". '{0}'"], + powershell: ['powershell', '-command', ". '{0}'"] + }; + const shellCommand = core.getInput('shell', { required: false }); + if (!shellCommand) { + return defaultCommands['default']; + } + const shellCommands = shellCommand.split(' '); + if (shellCommands.length === 1) { + if (shellCommands[0] in defaultCommands) { + return defaultCommands[shellCommands[0]]; + } + else { + return [shellCommands[0], '{0}']; + } + } + return shellCommands; +} +function resolveExtension(command) { + const commandExtensions = { + python: 'py', + cmd: 'cmd', + pwsh: 'ps1', + powershell: 'ps1' + }; + if (command in commandExtensions) { + return commandExtensions[command]; + } + return 'sh'; +} async function run() { try { const content = core.getInput('post-run', { required: true }); + const shellCommands = await resolveShell(); + const command = shellCommands[0]; + const commandPath = await io.which(command, true); const runnerTempPath = process.env.RUNNER_TEMP; - const scriptPath = `${runnerTempPath}/post-run.sh`; + const extension = resolveExtension(command); + const scriptPath = path.join(runnerTempPath, `post-run.${extension}`); await fs_1.promises.writeFile(scriptPath, content); - const bashPath = await io.which('bash', true); - await exec.exec(`"${bashPath}"`, [scriptPath]); + const commandArgs = shellCommands + .slice(1) + .map(item => item.replace('{0}', scriptPath)); + const options = {}; + options.windowsVerbatimArguments = command === 'cmd'; + await exec.exec(`"${commandPath}"`, commandArgs, options); } catch (error) { // Fail the workflow run if an error occurs diff --git a/src/post/index.ts b/src/post/index.ts index e711999..54911ab 100644 --- a/src/post/index.ts +++ b/src/post/index.ts @@ -4,17 +4,68 @@ import * as core from '@actions/core' import { promises as fs } from 'fs' +import * as path from 'path' import * as io from '@actions/io' import * as exec from '@actions/exec' +async function resolveShell(): Promise { + const defaultCommands: { [key: string]: string[] } = { + default: ['bash', '-e', '{0}'], + sh: ['sh', '-e', '{0}'], + bash: ['bash', '--noprofile', '--norc', '-eo', 'pipefail', '{0}'], + cmd: ['cmd', '/D', '/E:ON', '/V:OFF', '/S', '/C', '"CALL "{0}""'], + pwsh: ['pwsh', '-command', ". '{0}'"], + powershell: ['powershell', '-command', ". '{0}'"] + } + const shellCommand = core.getInput('shell', { required: false }) + if (!shellCommand) { + return defaultCommands['default'] + } + + const shellCommands = shellCommand.split(' ') + if (shellCommands.length === 1) { + if (shellCommands[0] in defaultCommands) { + return defaultCommands[shellCommands[0]] + } else { + return [shellCommands[0], '{0}'] + } + } + return shellCommands +} + +function resolveExtension(command: string): string { + const commandExtensions: { [key: string]: string } = { + python: 'py', + cmd: 'cmd', + pwsh: 'ps1', + powershell: 'ps1' + } + if (command in commandExtensions) { + return commandExtensions[command] + } + return 'sh' +} + async function run(): Promise { try { - const content = core.getInput('post-run', { required: true }) + const content: string = core.getInput('post-run', { required: true }) + const shellCommands: string[] = await resolveShell() + const command = shellCommands[0] + const commandPath: string = await io.which(command, true) + const runnerTempPath: string = process.env.RUNNER_TEMP as string - const scriptPath = `${runnerTempPath}/post-run.sh` + const extension: string = resolveExtension(command) + const scriptPath = path.join(runnerTempPath, `post-run.${extension}`) await fs.writeFile(scriptPath, content) - const bashPath: string = await io.which('bash', true) - await exec.exec(`"${bashPath}"`, [scriptPath]) + + const commandArgs = shellCommands + .slice(1) + .map(item => item.replace('{0}', scriptPath)) + + const options: exec.ExecOptions = {} + options.windowsVerbatimArguments = command === 'cmd' + + await exec.exec(`"${commandPath}"`, commandArgs, options) } catch (error) { // Fail the workflow run if an error occurs if (error instanceof Error) core.setFailed(error.message)