diff --git a/dist/index.js b/dist/index.js index c9f6b88..8f1f88f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -83130,6 +83130,12 @@ function validateConfig(config) { names.add(project.name); } const validated = { projects }; + if (c.automerge !== undefined) { + if (typeof c.automerge !== 'boolean') { + throw new Error('Configuration "automerge" must be a boolean'); + } + validated.automerge = c.automerge; + } return validated; } /** @@ -83249,8 +83255,17 @@ async function run() { let targetProjectNames = config.projects.map((p) => p.name); let command = 'plan'; let args = []; - // Extract comment body - if (github.context.eventName === 'issue_comment') { + // Handle automerge: when a PR is merged and automerge is enabled, apply all projects + if ((0, pr_validation_1.isPRMerged)(github.context)) { + if (!config.automerge) { + core.info('PR merged but automerge is not enabled in config, skipping'); + return; + } + core.info('PR merged with automerge enabled — running apply for all projects'); + command = 'apply'; + } + else if (github.context.eventName === 'issue_comment') { + // Extract comment body const commentBody = (0, pr_validation_1.getCommentBodyFromContext)(github.context); core.info(`Processing comment: ${commentBody}`); // Parse comment @@ -83268,21 +83283,25 @@ async function run() { command = parsedComment.command; args = parsedComment.args; } - // Get PR information + // Always resolve PR number — needed so tfcmt can post comments regardless + // of whether GITHUB_REF points to a PR ref (it doesn't for issue_comment + // or pull_request closed events) + const prNumber = (0, pr_validation_1.getPRNumberFromContext)(github.context); + // Get full PR information (only needed for apply requirements validation) let pr = null; if (command === 'apply') { - const prNumber = (0, pr_validation_1.getPRNumberFromContext)(github.context); pr = await (0, pr_validation_1.getPullRequestInfo)(token, github.context.repo.owner, github.context.repo.repo, prNumber); } // Setup tfcmt const tfcmtPath = await (0, tfcmt_1.setupTfcmt)(); + const tfcmtConfigPath = (0, tfcmt_1.writeTfcmtConfig)(); // Execute terraform for each target project serially for (const projectName of targetProjectNames) { const project = config.projects.find((p) => p.name === projectName); if (!project) { throw new Error(`Project not found: ${projectName}`); } - await executeProjectCommand(project, command, args, pr, tfcmtPath); + await executeProjectCommand(project, command, args, pr, tfcmtPath, tfcmtConfigPath, prNumber); } core.info('Terraform PR Comment Action completed successfully'); } @@ -83302,7 +83321,7 @@ async function run() { * @param tfcmtPath - Path to tfcmt binary * @param tfcmtConfig - Optional tfcmt configuration */ -async function executeProjectCommand(project, command, args, pr, tfcmtPath) { +async function executeProjectCommand(project, command, args, pr, tfcmtPath, tfcmtConfigPath, prNumber) { core.info(`\n${'='.repeat(60)}`); core.info(`Project: ${project.name}`); core.info(`Directory: ${project.dir}`); @@ -83331,7 +83350,7 @@ async function executeProjectCommand(project, command, args, pr, tfcmtPath) { } } // Execute terraform with tfcmt - const result = await (0, terraform_1.executeTerraformWithTfcmt)(tfcmtPath, command, project.name, workingDir, args, planFilePath); + const result = await (0, terraform_1.executeTerraformWithTfcmt)(tfcmtPath, command, project.name, workingDir, args, planFilePath, tfcmtConfigPath, prNumber); // Log results and upload plan file if this was a plan command if (command === 'plan') { if (result.hasChanges) { @@ -83406,6 +83425,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.getPullRequestInfo = getPullRequestInfo; exports.validateRequirements = validateRequirements; exports.validateEventType = validateEventType; +exports.isPRMerged = isPRMerged; exports.getPRNumberFromContext = getPRNumberFromContext; exports.getCommentBodyFromContext = getCommentBodyFromContext; const core = __importStar(__nccwpck_require__(59550)); @@ -83498,6 +83518,16 @@ function validateEventType(eventName) { throw new Error(`This action is designed for issue_comment or pull_request events, but was triggered by: ${eventName}`); } } +/** + * Returns true when the current context is a merged pull_request event. + * + * @param context - GitHub context + */ +function isPRMerged(context) { + return (context.eventName === 'pull_request' && + context.payload.action === 'closed' && + context.payload.pull_request?.merged === true); +} /** * Extracts PR number from the GitHub context * @@ -83506,10 +83536,12 @@ function validateEventType(eventName) { * @throws Error if PR number cannot be determined */ function getPRNumberFromContext(context) { - const prNumber = context.payload.issue?.number; + // issue_comment events expose the PR number via payload.issue.number + // pull_request events expose it via payload.pull_request.number + const prNumber = context.payload.issue?.number ?? context.payload.pull_request?.number; if (!prNumber) { throw new Error('Could not determine PR number from context. ' + - 'Ensure this action is triggered by an issue_comment event on a pull request.'); + 'Ensure this action is triggered by an issue_comment or pull_request event on a pull request.'); } return prNumber; } @@ -83576,6 +83608,8 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.executeTerraform = executeTerraform; exports.executeTerraformWithTfcmt = executeTerraformWithTfcmt; exports.validateTerraformInstalled = validateTerraformInstalled; +const fs = __importStar(__nccwpck_require__(73024)); +const os = __importStar(__nccwpck_require__(48161)); const path = __importStar(__nccwpck_require__(76760)); const core = __importStar(__nccwpck_require__(59550)); const exec = __importStar(__nccwpck_require__(24154)); @@ -83599,11 +83633,22 @@ const exec = __importStar(__nccwpck_require__(24154)); * - For plan commands, saves plan file to /tfplan- * - For apply commands, uses provided planFilePath if available */ -async function executeTerraform(tfcmtPath, command, workingDir, projectName, additionalArgs = [], planFilePath) { +async function executeTerraform(tfcmtPath, command, workingDir, projectName, additionalArgs = [], planFilePath, tfcmtConfigPath, prNumber) { const argsStr = additionalArgs.length > 0 ? ` ${additionalArgs.join(' ')}` : ''; core.info(`Executing terraform ${command}${argsStr} in ${workingDir}`); // Build tfcmt arguments: tfcmt [flags] -var "target:" plan|apply -- terraform [command] [args] const tfcmtArgs = []; + // Pass custom config file if provided (-config must come first) + if (tfcmtConfigPath) { + tfcmtArgs.push('-config'); + tfcmtArgs.push(tfcmtConfigPath); + } + // Explicitly pass PR number so tfcmt can post comments for issue_comment and + // pull_request closed events where GITHUB_REF is not a PR ref + if (prNumber !== undefined) { + tfcmtArgs.push('-pr'); + tfcmtArgs.push(String(prNumber)); + } // Add target variable for monorepo support // This will prefix PR labels and comment titles with the project name tfcmtArgs.push('-var'); @@ -83637,8 +83682,23 @@ async function executeTerraform(tfcmtPath, command, workingDir, projectName, add // Capture stdout and stderr let stdout = ''; let stderr = ''; + // Build env — when triggered by issue_comment the inherited GITHUB_EVENT_NAME + // is "issue_comment" and the event payload has no pull_request key, so tfcmt + // skips posting a PR comment. Override with a synthetic pull_request event so + // tfcmt always posts the comment regardless of the triggering event type. + const env = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)); + if (prNumber !== undefined) { + const syntheticEvent = JSON.stringify({ number: prNumber, pull_request: { number: prNumber } }); + const eventPath = path.join(os.tmpdir(), `tfcmt-event-${prNumber}.json`); + fs.writeFileSync(eventPath, syntheticEvent); + env['GITHUB_EVENT_NAME'] = 'pull_request'; + env['GITHUB_EVENT_PATH'] = eventPath; + env['GITHUB_REF'] = `refs/pull/${prNumber}/merge`; + core.info(`Overriding GITHUB_EVENT_NAME=pull_request, GITHUB_EVENT_PATH=${eventPath}`); + } const options = { cwd: workingDir, + env, ignoreReturnCode: true, listeners: { stdout: (data) => { @@ -83686,11 +83746,11 @@ async function executeTerraform(tfcmtPath, command, workingDir, projectName, add * @remarks * Executes terraform wrapped with tfcmt for automatic PR comment posting */ -async function executeTerraformWithTfcmt(tfcmtPath, command, projectName, workingDir, additionalArgs = [], planFilePath) { +async function executeTerraformWithTfcmt(tfcmtPath, command, projectName, workingDir, additionalArgs = [], planFilePath, tfcmtConfigPath, prNumber) { const argsStr = additionalArgs.length > 0 ? ` ${additionalArgs.join(' ')}` : ''; core.startGroup(`Executing terraform ${command}${argsStr} for project: ${projectName}`); try { - return await executeTerraform(tfcmtPath, command, workingDir, projectName, additionalArgs, planFilePath); + return await executeTerraform(tfcmtPath, command, workingDir, projectName, additionalArgs, planFilePath, tfcmtConfigPath, prNumber); } finally { core.endGroup(); @@ -83757,12 +83817,158 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.writeTfcmtConfig = writeTfcmtConfig; exports.setupTfcmt = setupTfcmt; const fs = __importStar(__nccwpck_require__(73024)); const os = __importStar(__nccwpck_require__(48161)); const path = __importStar(__nccwpck_require__(76760)); const core = __importStar(__nccwpck_require__(59550)); const tc = __importStar(__nccwpck_require__(42493)); +/** + * Full tfcmt configuration based on the upstream default, with the plan + * template extended to show a "Run this plan again" command using -project=. + * + * @see https://suzuki-shunsuke.github.io/tfcmt/config/#default-configuration + */ +const TFCMT_CONFIG = ` +embedded_var_names: [] + +templates: + plan_title: "## {{if eq .ExitCode 1}}:x: {{end}}Plan Result{{if .Vars.target}} ({{.Vars.target}}){{end}}" + apply_title: "## :{{if eq .ExitCode 0}}white_check_mark{{else}}x{{end}}: Apply Result{{if .Vars.target}} ({{.Vars.target}}){{end}}" + result: "{{if .Result}}
{{ .Result }}
{{end}}" + updated_resources: | + {{if .CreatedResources}} + * Create + {{- range .CreatedResources}} + * {{.}} + {{- end}}{{end}}{{if .UpdatedResources}} + * Update + {{- range .UpdatedResources}} + * {{.}} + {{- end}}{{end}}{{if .DeletedResources}} + * Delete + {{- range .DeletedResources}} + * {{.}} + {{- end}}{{end}}{{if .ReplacedResources}} + * Replace + {{- range .ReplacedResources}} + * {{.}} + {{- end}}{{end}}{{if .ImportedResources}} + * Import + {{- range .ImportedResources}} + * {{.}} + {{- end}}{{end}}{{if .MovedResources}} + * Move + {{- range .MovedResources}} + * {{.Before}} => {{.After}} + {{- end}}{{end}} + deletion_warning: | + {{if .HasDestroy}} + ### :warning: Resource Deletion will happen :warning: + This plan contains resource delete operation. Please check the plan result very carefully! + {{end}} + changed_result: | + {{if .ChangedResult}} +
Change Result (Click me) + {{wrapCode .ChangedResult}} +
+ {{end}} + change_outside_terraform: | + {{if .ChangeOutsideTerraform}} +
:information_source: Objects have changed outside of Terraform + _This feature was introduced from [Terraform v0.15.4](https://github.com/hashicorp/terraform/releases/tag/v0.15.4)._ + {{wrapCode .ChangeOutsideTerraform}} +
+ {{end}} + warning: | + {{if .Warning}} + ## :warning: Warnings :warning: + {{wrapCode .Warning}} + {{end}} + error_messages: | + {{if .ErrorMessages}} + ## :warning: Errors + {{range .ErrorMessages}} + * {{. -}} + {{- end}}{{end}} + guide_apply_failure: "" + guide_apply_parse_error: "" + +terraform: + plan: + disable_label: false + ignore_warning: false + template: | + {{template "plan_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{template "deletion_warning" .}} + {{template "result" .}} + {{template "updated_resources" .}} + {{template "changed_result" .}} + {{template "change_outside_terraform" .}} + {{template "warning" .}} + {{template "error_messages" .}} + {{if .Vars.target}} + --- + **Run this plan again:** + \`\`\` + terraform plan -project={{.Vars.target}} + \`\`\` + **Apply this plan:** + \`\`\` + terraform apply -project={{.Vars.target}} + \`\`\` + {{end}} + when_add_or_update_only: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}add-or-update" + label_color: 1d76db + when_destroy: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}destroy" + label_color: d93f0b + when_no_changes: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}no-changes" + label_color: 0e8a16 + when_plan_error: + label: + label_color: + when_parse_error: + template: | + {{template "plan_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + It failed to parse the result. +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+ apply: + template: | + {{template "apply_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{if ne .ExitCode 0}}{{template "guide_apply_failure" .}}{{end}} + {{template "result" .}} +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+ {{template "error_messages" .}} + when_parse_error: + template: | + {{template "apply_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{template "guide_apply_parse_error" .}} + It failed to parse the result. +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+`.trimStart(); +/** + * Writes a tfcmt configuration file to a temp directory and returns the path. + */ +function writeTfcmtConfig() { + const configPath = path.join(os.tmpdir(), '.tfcmt-action.yml'); + fs.writeFileSync(configPath, TFCMT_CONFIG, 'utf8'); + core.info(`tfcmt config written to ${configPath}`); + return configPath; +} /** * Maps Node.js platform to tfcmt platform naming */ diff --git a/src/main.ts b/src/main.ts index a6efc6a..f100ac0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,7 +17,7 @@ import { validateRequirements, } from './pr-validation'; import { executeTerraformWithTfcmt, validateTerraformInstalled } from './terraform'; -import { setupTfcmt } from './tfcmt'; +import { setupTfcmt, writeTfcmtConfig } from './tfcmt'; import type { ProjectConfig, PullRequestInfo, TerraformCommand } from './types'; /** @@ -78,10 +78,14 @@ async function run(): Promise { args = parsedComment.args; } - // Get PR information + // Always resolve PR number — needed so tfcmt can post comments regardless + // of whether GITHUB_REF points to a PR ref (it doesn't for issue_comment + // or pull_request closed events) + const prNumber = getPRNumberFromContext(github.context); + + // Get full PR information (only needed for apply requirements validation) let pr: PullRequestInfo | null = null; if (command === 'apply') { - const prNumber = getPRNumberFromContext(github.context); pr = await getPullRequestInfo( token, github.context.repo.owner, @@ -92,6 +96,7 @@ async function run(): Promise { // Setup tfcmt const tfcmtPath = await setupTfcmt(); + const tfcmtConfigPath = writeTfcmtConfig(); // Execute terraform for each target project serially for (const projectName of targetProjectNames) { @@ -104,7 +109,9 @@ async function run(): Promise { command, args, pr, - tfcmtPath + tfcmtPath, + tfcmtConfigPath, + prNumber ); } @@ -131,7 +138,9 @@ async function executeProjectCommand( command: 'plan' | 'apply', args: string[], pr: PullRequestInfo | null, - tfcmtPath: string + tfcmtPath: string, + tfcmtConfigPath: string, + prNumber: number ): Promise { core.info(`\n${'='.repeat(60)}`); core.info(`Project: ${project.name}`); @@ -175,7 +184,9 @@ async function executeProjectCommand( project.name, workingDir, args, - planFilePath + planFilePath, + tfcmtConfigPath, + prNumber ); // Log results and upload plan file if this was a plan command diff --git a/src/terraform.ts b/src/terraform.ts index 711ebb4..a85b756 100644 --- a/src/terraform.ts +++ b/src/terraform.ts @@ -2,6 +2,8 @@ * Terraform execution logic */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; import * as path from 'node:path'; import * as core from '@actions/core'; import * as exec from '@actions/exec'; @@ -33,7 +35,9 @@ export async function executeTerraform( workingDir: string, projectName: string, additionalArgs: string[] = [], - planFilePath?: string + planFilePath?: string, + tfcmtConfigPath?: string, + prNumber?: number ): Promise { const argsStr = additionalArgs.length > 0 ? ` ${additionalArgs.join(' ')}` : ''; core.info(`Executing terraform ${command}${argsStr} in ${workingDir}`); @@ -41,6 +45,19 @@ export async function executeTerraform( // Build tfcmt arguments: tfcmt [flags] -var "target:" plan|apply -- terraform [command] [args] const tfcmtArgs: string[] = []; + // Pass custom config file if provided (-config must come first) + if (tfcmtConfigPath) { + tfcmtArgs.push('-config'); + tfcmtArgs.push(tfcmtConfigPath); + } + + // Explicitly pass PR number so tfcmt can post comments for issue_comment and + // pull_request closed events where GITHUB_REF is not a PR ref + if (prNumber !== undefined) { + tfcmtArgs.push('-pr'); + tfcmtArgs.push(String(prNumber)); + } + // Add target variable for monorepo support // This will prefix PR labels and comment titles with the project name tfcmtArgs.push('-var'); @@ -79,8 +96,26 @@ export async function executeTerraform( let stdout = ''; let stderr = ''; + // Build env — when triggered by issue_comment the inherited GITHUB_EVENT_NAME + // is "issue_comment" and the event payload has no pull_request key, so tfcmt + // skips posting a PR comment. Override with a synthetic pull_request event so + // tfcmt always posts the comment regardless of the triggering event type. + const env: Record = Object.fromEntries( + Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined) + ); + if (prNumber !== undefined) { + const syntheticEvent = JSON.stringify({ number: prNumber, pull_request: { number: prNumber } }); + const eventPath = path.join(os.tmpdir(), `tfcmt-event-${prNumber}.json`); + fs.writeFileSync(eventPath, syntheticEvent); + env['GITHUB_EVENT_NAME'] = 'pull_request'; + env['GITHUB_EVENT_PATH'] = eventPath; + env['GITHUB_REF'] = `refs/pull/${prNumber}/merge`; + core.info(`Overriding GITHUB_EVENT_NAME=pull_request, GITHUB_EVENT_PATH=${eventPath}`); + } + const options: exec.ExecOptions = { cwd: workingDir, + env, ignoreReturnCode: true, listeners: { stdout: (data: Buffer) => { @@ -141,7 +176,9 @@ export async function executeTerraformWithTfcmt( projectName: string, workingDir: string, additionalArgs: string[] = [], - planFilePath?: string + planFilePath?: string, + tfcmtConfigPath?: string, + prNumber?: number ): Promise { const argsStr = additionalArgs.length > 0 ? ` ${additionalArgs.join(' ')}` : ''; core.startGroup(`Executing terraform ${command}${argsStr} for project: ${projectName}`); @@ -153,7 +190,9 @@ export async function executeTerraformWithTfcmt( workingDir, projectName, additionalArgs, - planFilePath + planFilePath, + tfcmtConfigPath, + prNumber ); } finally { core.endGroup(); diff --git a/src/tfcmt.ts b/src/tfcmt.ts index c652b95..3f76bf1 100644 --- a/src/tfcmt.ts +++ b/src/tfcmt.ts @@ -8,6 +8,153 @@ import * as path from 'node:path'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; +/** + * Full tfcmt configuration based on the upstream default, with the plan + * template extended to show a "Run this plan again" command using -project=. + * + * @see https://suzuki-shunsuke.github.io/tfcmt/config/#default-configuration + */ +const TFCMT_CONFIG = ` +embedded_var_names: [] + +templates: + plan_title: "## {{if eq .ExitCode 1}}:x: {{end}}Plan Result{{if .Vars.target}} ({{.Vars.target}}){{end}}" + apply_title: "## :{{if eq .ExitCode 0}}white_check_mark{{else}}x{{end}}: Apply Result{{if .Vars.target}} ({{.Vars.target}}){{end}}" + result: "{{if .Result}}
{{ .Result }}
{{end}}" + updated_resources: | + {{if .CreatedResources}} + * Create + {{- range .CreatedResources}} + * {{.}} + {{- end}}{{end}}{{if .UpdatedResources}} + * Update + {{- range .UpdatedResources}} + * {{.}} + {{- end}}{{end}}{{if .DeletedResources}} + * Delete + {{- range .DeletedResources}} + * {{.}} + {{- end}}{{end}}{{if .ReplacedResources}} + * Replace + {{- range .ReplacedResources}} + * {{.}} + {{- end}}{{end}}{{if .ImportedResources}} + * Import + {{- range .ImportedResources}} + * {{.}} + {{- end}}{{end}}{{if .MovedResources}} + * Move + {{- range .MovedResources}} + * {{.Before}} => {{.After}} + {{- end}}{{end}} + deletion_warning: | + {{if .HasDestroy}} + ### :warning: Resource Deletion will happen :warning: + This plan contains resource delete operation. Please check the plan result very carefully! + {{end}} + changed_result: | + {{if .ChangedResult}} +
Change Result (Click me) + {{wrapCode .ChangedResult}} +
+ {{end}} + change_outside_terraform: | + {{if .ChangeOutsideTerraform}} +
:information_source: Objects have changed outside of Terraform + _This feature was introduced from [Terraform v0.15.4](https://github.com/hashicorp/terraform/releases/tag/v0.15.4)._ + {{wrapCode .ChangeOutsideTerraform}} +
+ {{end}} + warning: | + {{if .Warning}} + ## :warning: Warnings :warning: + {{wrapCode .Warning}} + {{end}} + error_messages: | + {{if .ErrorMessages}} + ## :warning: Errors + {{range .ErrorMessages}} + * {{. -}} + {{- end}}{{end}} + guide_apply_failure: "" + guide_apply_parse_error: "" + +terraform: + plan: + disable_label: false + ignore_warning: false + template: | + {{template "plan_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{template "deletion_warning" .}} + {{template "result" .}} + {{template "updated_resources" .}} + {{template "changed_result" .}} + {{template "change_outside_terraform" .}} + {{template "warning" .}} + {{template "error_messages" .}} + {{if .Vars.target}} + --- + **Run this plan again:** + \`\`\` + terraform plan -project={{.Vars.target}} + \`\`\` + **Apply this plan:** + \`\`\` + terraform apply -project={{.Vars.target}} + \`\`\` + {{end}} + when_add_or_update_only: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}add-or-update" + label_color: 1d76db + when_destroy: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}destroy" + label_color: d93f0b + when_no_changes: + label: "{{if .Vars.target}}{{.Vars.target}}/{{end}}no-changes" + label_color: 0e8a16 + when_plan_error: + label: + label_color: + when_parse_error: + template: | + {{template "plan_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + It failed to parse the result. +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+ apply: + template: | + {{template "apply_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{if ne .ExitCode 0}}{{template "guide_apply_failure" .}}{{end}} + {{template "result" .}} +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+ {{template "error_messages" .}} + when_parse_error: + template: | + {{template "apply_title" .}} + {{if .Link}}[CI link]({{.Link}}){{end}} + {{template "guide_apply_parse_error" .}} + It failed to parse the result. +
Details (Click me) + {{wrapCode .CombinedOutput}} +
+`.trimStart(); + +/** + * Writes a tfcmt configuration file to a temp directory and returns the path. + */ +export function writeTfcmtConfig(): string { + const configPath = path.join(os.tmpdir(), '.tfcmt-action.yml'); + fs.writeFileSync(configPath, TFCMT_CONFIG, 'utf8'); + core.info(`tfcmt config written to ${configPath}`); + return configPath; +} + /** * Maps Node.js platform to tfcmt platform naming */