Skip to content

Commit 5f5761a

Browse files
Merge pull request #10 from fern-api/patrick/v3
cut `addTimestamp`, release v3
2 parents 96b39c7 + bb193dd commit 5f5761a

4 files changed

Lines changed: 566 additions & 437 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ jobs:
3636
token: ${{ secrets.OPENAPI_SYNC_TOKEN }}
3737
branch: 'update-api'
3838
auto_merge: false # you MUST use auto_merge: true with branch: main
39-
add_timestamp: true
4039

4140
```
4241

@@ -87,7 +86,6 @@ jobs:
8786
| `token` | GitHub token for authentication | No | `${{ github.token }}` | 1, 2 |
8887
| `branch` | Branch to push to in the target repository | Yes | - | 1, 2 |
8988
| `auto_merge` | If `true`, pushes directly to the branch; if `false`, creates a PR from the branch onto `main` | No | `false` | 1, 2 |
90-
| `add_timestamp` | If `true`, appends a timestamp to the branch name | No | `true` | 1, 2 |
9189
| `sources` | Array of mappings with `from`, `to`, and optional `exclude` fields | Yes | - | 2 |
9290
| `repository` | Target repository in format `org/repo` | Yes | - | 2 |
9391
| `update_from_source`| If `true`, syncs from the source spec files rather than using existing intermediate formats | No | `false` | 1 |

action.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ inputs:
1717
description: 'Whether to push directly to the branch or create a PR'
1818
required: false
1919
default: 'false'
20-
add_timestamp:
21-
description: 'Whether to add a timestamp to the branch name'
22-
required: false
23-
default: 'true'
2420
update_from_source:
2521
description: 'Whether to run "fern api update" on the current repository instead of syncing files'
2622
required: false

dist/index.js

Lines changed: 83 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -36991,13 +36991,12 @@ const glob = __importStar(__nccwpck_require__(8211));
3699136991
const minimatch_1 = __nccwpck_require__(4501);
3699236992
async function run() {
3699336993
try {
36994-
const token = core.getInput('token') || process.env.GITHUB_TOKEN;
36995-
let branch = core.getInput('branch', { required: true });
36996-
const autoMerge = core.getBooleanInput('auto_merge') || false;
36997-
const addTimestamp = core.getBooleanInput('add_timestamp') || true;
36998-
const updateFromSource = core.getBooleanInput('update_from_source') || false;
36994+
const token = core.getInput("token") || process.env.GITHUB_TOKEN;
36995+
const branch = core.getInput("branch", { required: true });
36996+
const autoMerge = core.getBooleanInput("auto_merge");
36997+
const updateFromSource = core.getBooleanInput("update_from_source");
3699936998
if (!token) {
37000-
throw new Error('GitHub token is required. Please provide a token with appropriate permissions.');
36999+
throw new Error("GitHub token is required. Please provide a token with appropriate permissions.");
3700137000
}
3700237001
if (updateFromSource) {
3700337002
await updateFromSourceSpec(token, branch, autoMerge);
@@ -37011,48 +37010,48 @@ async function run() {
3701137010
core.setFailed(error.message);
3701237011
}
3701337012
else {
37014-
core.setFailed('An unknown error occurred');
37013+
core.setFailed("An unknown error occurred");
3701537014
}
3701637015
}
3701737016
}
3701837017
async function updateFromSourceSpec(token, branch, autoMerge) {
37019-
if (!token) {
37020-
throw new Error('GitHub token is required. Please provide a token with appropriate permissions.');
37021-
}
3702237018
const owner = github.context.repo.owner;
3702337019
const repo = github.context.repo.repo;
3702437020
try {
37025-
await exec.exec('git', ['config', 'user.name', 'github-actions']);
37026-
await exec.exec('git', ['config', 'user.email', 'github-actions@github.com']);
37021+
await exec.exec("git", ["config", "user.name", "github-actions"]);
37022+
await exec.exec("git", [
37023+
"config",
37024+
"user.email",
37025+
"github-actions@github.com",
37026+
]);
3702737027
core.info(`Creating and checking out branch: ${branch}`);
3702837028
const octokit = github.getOctokit(token);
3702937029
const doesBranchExist = await branchExists(owner, repo, branch, octokit);
3703037030
await setupBranch(branch, doesBranchExist);
3703137031
await runFernApiUpdate();
37032-
const diff = await exec.getExecOutput('git', ['status', '--porcelain'], { silent: true });
37032+
const diff = await exec.getExecOutput("git", ["status", "--porcelain"], { silent: true });
3703337033
if (!diff.stdout.trim()) {
37034-
core.info('No changes detected from fern api update. Skipping further actions.');
37034+
core.info("No changes detected from fern api update. Skipping further actions.");
3703537035
return;
3703637036
}
37037-
await exec.exec('git', ['add', '.'], { silent: true });
37038-
await exec.exec('git', ['commit', '-m', 'Update API specifications with fern api update'], { silent: true });
37037+
await exec.exec("git", ["add", "."], { silent: true });
37038+
await exec.exec("git", ["commit", "-m", "Update API specifications with fern api update"], { silent: true });
3703937039
core.info(`Pushing changes to branch: ${branch}`);
37040-
await exec.exec('git', ['push', '--force', '--verbose', 'origin', branch], { silent: false });
37040+
await exec.exec("git", ["push", "--force", "--verbose", "origin", branch], { silent: false });
3704137041
if (!autoMerge) {
37042-
const octokit = github.getOctokit(token);
37043-
await createPR(octokit, owner, repo, branch, github.context.ref.replace('refs/heads/', ''), true);
37042+
await createPR(octokit, owner, repo, branch, github.context.ref.replace("refs/heads/", ""), true);
3704437043
}
3704537044
else {
3704637045
core.info(`Changes pushed directly to branch '${branch}' because auto-merge is enabled.`);
3704737046
}
3704837047
}
3704937048
catch (error) {
37050-
throw new Error(`Failed to update from source: ${error instanceof Error ? error.message : 'Unknown error'}`);
37049+
throw new Error(`Failed to update from source: ${error instanceof Error ? error.message : "Unknown error"}`);
3705137050
}
3705237051
}
3705337052
async function updateTargetSpec(token, branch, autoMerge) {
37054-
const repository = core.getInput('repository', { required: true });
37055-
const fileMappingInput = core.getInput('sources', { required: true });
37053+
const repository = core.getInput("repository", { required: true });
37054+
const fileMappingInput = core.getInput("sources", { required: true });
3705637055
let fileMapping;
3705737056
try {
3705837057
fileMapping = yaml.load(fileMappingInput);
@@ -37066,7 +37065,7 @@ async function updateTargetSpec(token, branch, autoMerge) {
3706637065
}
3706737066
}
3706837067
if (!Array.isArray(fileMapping) || fileMapping.length === 0) {
37069-
throw new Error('File mapping must be a non-empty array');
37068+
throw new Error("File mapping must be a non-empty array");
3707037069
}
3707137070
for (const [index, mapping] of fileMapping.entries()) {
3707237071
if (!mapping.from || !mapping.to) {
@@ -37078,7 +37077,7 @@ async function updateTargetSpec(token, branch, autoMerge) {
3707837077
mappings: fileMapping,
3707937078
token,
3708037079
branch,
37081-
autoMerge
37080+
autoMerge,
3708237081
};
3708337082
await cloneRepository(options);
3708437083
await syncChanges(options);
@@ -37087,62 +37086,62 @@ async function runFernApiUpdate() {
3708737086
try {
3708837087
core.info('Running "fern api update" command');
3708937088
try {
37090-
await exec.exec('fern', ['--version'], { silent: true });
37091-
core.info('Fern CLI is already installed');
37089+
await exec.exec("fern", ["--version"], { silent: true });
37090+
core.info("Fern CLI is already installed");
3709237091
}
3709337092
catch (error) {
37094-
core.info('Fern CLI not found. Installing Fern CLI...');
37095-
await exec.exec('npm', ['install', '-g', 'fern-api']);
37093+
core.info("Fern CLI not found. Installing Fern CLI...");
37094+
await exec.exec("npm", ["install", "-g", "fern-api"]);
3709637095
}
37097-
await exec.exec('fern', ['api', 'update']);
37098-
core.info('Fern API update completed');
37096+
await exec.exec("fern", ["api", "update"]);
37097+
core.info("Fern API update completed");
3709937098
}
3710037099
catch (error) {
37101-
throw new Error(`Failed to run "fern api update": ${error instanceof Error ? error.message : 'Unknown error'}`);
37100+
throw new Error(`Failed to run "fern api update": ${error instanceof Error ? error.message : "Unknown error"}`);
3710237101
}
3710337102
}
3710437103
async function cloneRepository(options) {
3710537104
if (!options.token) {
37106-
throw new Error('GitHub token is required to authenticate and clone the repository. Please provide a token with appropriate permissions.');
37105+
throw new Error("GitHub token is required to authenticate and clone the repository. Please provide a token with appropriate permissions.");
3710737106
}
3710837107
try {
3710937108
const octokit = github.getOctokit(options.token);
37110-
const [owner, repo] = options.repository.split('/');
37109+
const [owner, repo] = options.repository.split("/");
3711137110
await octokit.rest.repos.get({
3711237111
owner,
37113-
repo
37112+
repo,
3711437113
});
37115-
core.info('Successfully authenticated with the target repository');
37114+
core.info("Successfully authenticated with the target repository");
3711637115
}
3711737116
catch (error) {
3711837117
if (error instanceof Error) {
3711937118
throw new Error(`Failed to verify repository access: ${error.message}`);
3712037119
}
3712137120
else {
37122-
throw new Error('An unknown error occurred while verifying repository access');
37121+
throw new Error("An unknown error occurred while verifying repository access");
3712337122
}
3712437123
}
3712537124
const repoUrl = `https://x-access-token:${options.token}@github.com/${options.repository}.git`;
37126-
const repoDir = 'temp-fern-config';
37125+
const repoDir = "temp-fern-config";
3712737126
core.info(`Cloning repository ${options.repository} to ${repoDir}`);
3712837127
await io.mkdirP(repoDir);
3712937128
try {
37130-
await exec.exec('git', ['clone', repoUrl, repoDir]);
37129+
await exec.exec("git", ["clone", repoUrl, repoDir]);
3713137130
}
3713237131
catch (error) {
3713337132
throw new Error(`Failed to clone repository. Please ensure your token has 'repo' scope and you have write access to ${options.repository}.`);
3713437133
}
3713537134
process.chdir(repoDir);
37136-
await exec.exec('git', ['config', 'user.name', 'github-actions']);
37137-
await exec.exec('git', ['config', 'user.email', 'github-actions@github.com']);
37135+
await exec.exec("git", ["config", "user.name", "github-actions"]);
37136+
await exec.exec("git", [
37137+
"config",
37138+
"user.email",
37139+
"github-actions@github.com",
37140+
]);
3713837141
}
3713937142
async function syncChanges(options) {
37140-
if (!options.token) {
37141-
core.warning('GitHub token not provided. Skipping changes.');
37142-
return;
37143-
}
3714437143
const octokit = github.getOctokit(options.token);
37145-
const [owner, repo] = options.repository.split('/');
37144+
const [owner, repo] = options.repository.split("/");
3714637145
try {
3714737146
const workingBranch = options.branch;
3714837147
if (options.autoMerge) {
@@ -37154,9 +37153,9 @@ async function syncChanges(options) {
3715437153
const doesBranchExist = await branchExists(owner, repo, workingBranch, octokit);
3715537154
await setupBranch(workingBranch, doesBranchExist);
3715637155
await processSourceMappings(options);
37157-
const diff = await exec.getExecOutput('git', ['status', '--porcelain'], { silent: true });
37156+
const diff = await exec.getExecOutput("git", ["status", "--porcelain"], { silent: true });
3715837157
if (!diff.stdout.trim()) {
37159-
core.info('No changes detected. Skipping further actions.');
37158+
core.info("No changes detected. Skipping further actions.");
3716037159
return;
3716137160
}
3716237161
await commitChanges();
@@ -37169,23 +37168,23 @@ async function syncChanges(options) {
3716937168
await updatePR(octokit, owner, repo, existingPRNumber);
3717037169
}
3717137170
else {
37172-
await createPR(octokit, owner, repo, workingBranch, 'main', false);
37171+
await createPR(octokit, owner, repo, workingBranch, "main", false);
3717337172
}
3717437173
}
3717537174
else {
3717637175
core.info(`Changes pushed directly to branch '${workingBranch}' because auto-merge is enabled.`);
3717737176
}
3717837177
}
3717937178
catch (error) {
37180-
throw new Error(`Failed to sync changes: ${error instanceof Error ? error.message : 'Unknown error'}`);
37179+
throw new Error(`Failed to sync changes: ${error instanceof Error ? error.message : "Unknown error"}`);
3718137180
}
3718237181
}
3718337182
async function branchExists(owner, repo, branchName, octokit) {
3718437183
try {
3718537184
await octokit.rest.git.getRef({
3718637185
owner,
3718737186
repo,
37188-
ref: `heads/${branchName}`
37187+
ref: `heads/${branchName}`,
3718937188
});
3719037189
return true;
3719137190
}
@@ -37196,24 +37195,26 @@ async function branchExists(owner, repo, branchName, octokit) {
3719637195
async function setupBranch(branchName, exists) {
3719737196
try {
3719837197
if (exists) {
37199-
await exec.exec('git', ['fetch', 'origin']);
37198+
await exec.exec("git", ["fetch", "origin"]);
3720037199
core.info(`Branch ${branchName} exists. Checking it out.`);
37201-
await exec.exec('git', ['checkout', branchName]);
37202-
await exec.exec('git', ['pull', 'origin', branchName], { silent: true });
37200+
await exec.exec("git", ["checkout", branchName]);
37201+
await exec.exec("git", ["pull", "origin", branchName], {
37202+
silent: true,
37203+
});
3720337204
}
3720437205
else {
3720537206
core.info(`Branch ${branchName} does not exist. Creating it.`);
37206-
await exec.exec('git', ['checkout', '-b', branchName]);
37207+
await exec.exec("git", ["checkout", "-b", branchName]);
3720737208
}
3720837209
}
3720937210
catch (error) {
37210-
throw new Error(`Failed to setup branch ${branchName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
37211+
throw new Error(`Failed to setup branch ${branchName}: ${error instanceof Error ? error.message : "Unknown error"}`);
3721137212
}
3721237213
}
3721337214
async function processSourceMappings(options) {
37214-
core.info('Processing source mappings');
37215-
const sourceRepoRoot = path.resolve(process.env.GITHUB_WORKSPACE || '');
37216-
const destRepoRoot = path.resolve('.');
37215+
core.info("Processing source mappings");
37216+
const sourceRepoRoot = path.resolve(process.env.GITHUB_WORKSPACE || "");
37217+
const destRepoRoot = path.resolve(".");
3721737218
for (const mapping of options.mappings) {
3721837219
const sourcePath = path.join(sourceRepoRoot, mapping.from);
3721937220
const destPath = path.join(destRepoRoot, mapping.to);
@@ -37233,10 +37234,10 @@ async function processSourceMappings(options) {
3723337234
}
3723437235
async function syncDirectory(sourceDirPath, destDirPath, excludePatterns) {
3723537236
await io.mkdirP(destDirPath);
37236-
const files = glob.sync('**/*', {
37237+
const files = glob.sync("**/*", {
3723737238
cwd: sourceDirPath,
3723837239
nodir: true,
37239-
absolute: false
37240+
absolute: false,
3724037241
});
3724137242
for (const file of files) {
3724237243
const sourceFilePath = path.join(sourceDirPath, file);
@@ -37253,18 +37254,20 @@ async function syncFile(sourceFilePath, destFilePath) {
3725337254
fs.copyFileSync(sourceFilePath, destFilePath);
3725437255
}
3725537256
function isExcluded(filePath, excludePatterns) {
37256-
const sourceRepoRoot = path.resolve(process.env.GITHUB_WORKSPACE || '');
37257+
const sourceRepoRoot = path.resolve(process.env.GITHUB_WORKSPACE || "");
3725737258
const relativePath = path.relative(sourceRepoRoot, filePath);
37258-
return excludePatterns.some(pattern => (0, minimatch_1.minimatch)(relativePath, pattern));
37259+
return excludePatterns.some((pattern) => (0, minimatch_1.minimatch)(relativePath, pattern));
3725937260
}
3726037261
async function commitChanges() {
37261-
await exec.exec('git', ['add', '.'], { silent: true });
37262-
await exec.exec('git', ['commit', '-m', `Sync OpenAPI files from ${github.context.repo.repo}`], { silent: true });
37262+
await exec.exec("git", ["add", "."], { silent: true });
37263+
await exec.exec("git", ["commit", "-m", `Sync OpenAPI files from ${github.context.repo.repo}`], { silent: true });
3726337264
}
3726437265
async function hasDifferenceWithRemote(branchName) {
3726537266
try {
37266-
await exec.exec('git', ['fetch', 'origin', branchName], { silent: true });
37267-
const diff = await exec.getExecOutput('git', ['diff', `HEAD`, `origin/${branchName}`], { silent: true });
37267+
await exec.exec("git", ["fetch", "origin", branchName], {
37268+
silent: true,
37269+
});
37270+
const diff = await exec.getExecOutput("git", ["diff", `HEAD`, `origin/${branchName}`], { silent: true });
3726837271
return !!diff.stdout.trim();
3726937272
}
3727037273
catch (error) {
@@ -37279,7 +37282,9 @@ async function pushChanges(branchName, options) {
3727937282
shouldPush = await hasDifferenceWithRemote(branchName);
3728037283
}
3728137284
if (shouldPush) {
37282-
await exec.exec('git', ['push', '--force', 'origin', branchName], { silent: true });
37285+
await exec.exec("git", ["push", "--force", "origin", branchName], {
37286+
silent: true,
37287+
});
3728337288
return true;
3728437289
}
3728537290
else {
@@ -37288,7 +37293,7 @@ async function pushChanges(branchName, options) {
3728837293
}
3728937294
}
3729037295
catch (error) {
37291-
throw new Error(`Failed to push changes to the repository: ${error instanceof Error ? error.message : 'Unknown error'}`);
37296+
throw new Error(`Failed to push changes to the repository: ${error instanceof Error ? error.message : "Unknown error"}`);
3729237297
}
3729337298
}
3729437299
// Check if a PR exists for a branch
@@ -37297,7 +37302,7 @@ async function prExists(owner, repo, branchName, octokit) {
3729737302
owner,
3729837303
repo,
3729937304
head: `${owner}:${branchName}`,
37300-
state: 'open'
37305+
state: "open",
3730137306
});
3730237307
return prs.data.length > 0 ? prs.data[0].number : null;
3730337308
}
@@ -37308,26 +37313,26 @@ async function updatePR(octokit, owner, repo, prNumber) {
3730837313
owner,
3730937314
repo,
3731037315
pull_number: prNumber,
37311-
body: `Update OpenAPI specifications based on changes in the source repository.\nUpdated: ${new Date().toISOString()}`
37316+
body: `Update OpenAPI specifications based on changes in the source repository.\nUpdated: ${new Date().toISOString()}`,
3731237317
});
3731337318
}
3731437319
// Create a new PR
3731537320
async function createPR(octokit, owner, repo, branchName, targetBranch, isFromFern) {
3731637321
core.info(`Creating new PR from ${branchName} to ${targetBranch}`);
37317-
const date = new Date().toISOString().replace(/[:.]/g, '-');
37318-
let prTitle = isFromFern ?
37319-
`chore: Update API specifications with fern api update (${date})` :
37320-
`chore: Update OpenAPI specifications (${date})`;
37321-
let prBody = isFromFern ?
37322-
'Update API specifications by running fern api update.' :
37323-
'Update OpenAPI specifications based on changes in the source repository.';
37322+
const date = new Date().toISOString().replace(/[:.]/g, "-");
37323+
let prTitle = isFromFern
37324+
? `chore: Update API specifications with fern api update (${date})`
37325+
: `chore: Update OpenAPI specifications (${date})`;
37326+
let prBody = isFromFern
37327+
? "Update API specifications by running fern api update."
37328+
: "Update OpenAPI specifications based on changes in the source repository.";
3732437329
const prResponse = await octokit.rest.pulls.create({
3732537330
owner,
3732637331
repo,
3732737332
title: prTitle,
3732837333
head: branchName,
3732937334
base: targetBranch,
37330-
body: prBody
37335+
body: prBody,
3733137336
});
3733237337
core.info(`Pull request created: ${prResponse.data.html_url}`);
3733337338
return prResponse;

0 commit comments

Comments
 (0)