Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ jobs:
title: Integration Test Project
token: ${{ steps.get-auth-token.outputs.token }}

- name: Check Copied Project
uses: ./github-script/
env:
PROJECT_NUMBER: ${{ steps.copy-project.outputs.number }}
OWNER: ${{ steps.copy-project.outputs.owner }}
GH_TOKEN: ${{ steps.get-auth-token.outputs.token }}
README: ${{ steps.copy-project.outputs.readme }}
DESCRIPTION: ${{ steps.copy-project.outputs.description }}
with:
script: |
const assert = require('node:assert');

assert.strictEqual(process.env.README, 'README for bar');
assert.strictEqual(process.env.DESCRIPTION, 'Short description of bar');

- name: Get Draft Issue ID
id: get-draft-issue-id
run: |
Expand Down
82 changes: 63 additions & 19 deletions __tests__/copy-project.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const projectNumber = '94';
const title = 'New Title';
const fieldCount = 4;
const itemCount = 50;
const shortDescription = 'Description';
const readme = 'README';
const shortDescription = 'Description for {{ foo }}';
const readme = 'README for {{ foo }}';
const url = 'url';
const templateView = JSON.stringify({
foo: 'bar'
Expand Down Expand Up @@ -88,23 +88,6 @@ describe('copyProjectAction', () => {
);
});

it('requires drafts if the template-view input is set', async () => {
mockGetInput({
owner,
'project-number': projectNumber,
title,
'template-view': templateView
});

await index.copyProjectAction();
expect(copyProjectActionSpy).toHaveReturned();

expect(core.setFailed).toHaveBeenCalledTimes(1);
expect(core.setFailed).toHaveBeenLastCalledWith(
'Can only use template-view input if drafts are being copied'
);
});

it('handles project not found', async () => {
mockGetInput({ owner, 'project-number': projectNumber, title });
vi.mocked(copyProject).mockImplementation(() => {
Expand Down Expand Up @@ -209,6 +192,67 @@ describe('copyProjectAction', () => {
);
});

it('updates description if template changes', async () => {
const newProjectId = 'project-id-2';
const newProjectNumber = parseInt(projectNumber) + 1;
mockCopyProject(newProjectId, newProjectNumber);
mockGetInput({
owner,
'project-number': projectNumber,
title,
'template-view': templateView
});

await index.copyProjectAction();
expect(copyProjectActionSpy).toHaveReturned();
expect(editProject).toHaveBeenCalledWith(
owner,
newProjectNumber.toString(),
{
description: 'Description for bar',
readme: expect.anything()
}
);
});

it('updates readme if template changes', async () => {
const newProjectId = 'project-id-2';
const newProjectNumber = parseInt(projectNumber) + 1;
mockCopyProject(newProjectId, newProjectNumber);
mockGetInput({
owner,
'project-number': projectNumber,
title,
'template-view': templateView
});

await index.copyProjectAction();
expect(copyProjectActionSpy).toHaveReturned();
expect(editProject).toHaveBeenCalledWith(
owner,
newProjectNumber.toString(),
{
description: expect.anything(),
readme: 'README for bar'
}
);
});

it('does not update readme or description if no template view', async () => {
const newProjectId = 'project-id-2';
const newProjectNumber = parseInt(projectNumber) + 1;
mockCopyProject(newProjectId, newProjectNumber);
mockGetInput({
owner,
'project-number': projectNumber,
title
});

await index.copyProjectAction();
expect(copyProjectActionSpy).toHaveReturned();
expect(editProject).not.toHaveBeenCalled();
});

it('does not get draft issues if no template view', async () => {
mockGetInput({
owner,
Expand Down
6 changes: 3 additions & 3 deletions copy-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Copy a GitHub project

### Template Interpolation

The title and body for draft issues can include
[mustache.js](https://github.com/janl/mustache.js) templates which will be
interpolated after the project is copied if the `template-view` input is
The project README, description, and the title and body for draft issues can
include [mustache.js](https://github.com/janl/mustache.js) templates which will
be interpolated after the project is copied if the `template-view` input is
provided.

### Field Values
Expand Down
2 changes: 1 addition & 1 deletion copy-project/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ inputs:
description: Team to link the project to, in the format `org/team`
required: false
template-view:
description: A `mustache` template view to be used for template interpolation on draft issues
description: A `mustache` template view to be used for template interpolation on project README, description, and draft issues
required: false
public:
description: Set the project to public or private
Expand Down
2 changes: 1 addition & 1 deletion dist/copy-project.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 26 additions & 11 deletions src/copy-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
getDraftIssues,
getProject,
linkProjectToRepository,
linkProjectToTeam
linkProjectToTeam,
type ProjectEdit
} from './lib.js';

// oxlint-disable-next-line typescript/no-require-imports, typescript/no-var-requires
Expand All @@ -30,13 +31,6 @@ export async function copyProjectAction(): Promise<void> {
const linkToTeam = core.getInput('link-to-team');
const templateViewString = core.getInput('template-view');

if (!!templateViewString && !drafts) {
core.setFailed(
'Can only use template-view input if drafts are being copied'
);
return;
}

let templateView: Record<string, string> | null = null;

if (templateViewString) {
Expand All @@ -51,13 +45,34 @@ export async function copyProjectAction(): Promise<void> {
drafts
);

const edit: ProjectEdit = {};

// This is a special case because core.getBooleanInput
// will always return false even if not set, but we
// need to know if the input has been set at all
if (core.getInput('public')) {
await editProject(project.owner.login, project.number.toString(), {
public: core.getBooleanInput('public')
});
edit.public = core.getBooleanInput('public');
}

// Do template interpolation on the project readme and description if a template view was provided
if (templateView) {
const newReadme = Mustache.render(project.readme, templateView);
const newDescription = Mustache.render(
project.shortDescription,
templateView
);

if (newReadme !== project.readme) {
edit.readme = newReadme;
}

if (newDescription !== project.shortDescription) {
edit.description = newDescription;
}
}

if (Object.keys(edit).length > 0) {
await editProject(project.owner.login, project.number.toString(), edit);
}

if (linkToRepository) {
Expand Down
Loading