Skip to content
Merged
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
7 changes: 4 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

- [ ] I have read and followed the [CONTRIBUTING.md](https://github.com/github/awesome-copilot/blob/main/CONTRIBUTING.md) guidelines.
- [ ] I have read and followed the [Guidance for submissions involving paid services](https://github.com/github/awesome-copilot/discussions/968).
- [ ] My contribution adds a new instruction, prompt, agent, skill, or workflow file in the correct directory.
- [ ] My contribution adds a new instruction, prompt, agent, skill, workflow, or canvas extension file in the correct directory.
- [ ] The file follows the required naming convention.
- [ ] The content is clearly structured and follows the example format.
- [ ] I have tested my instructions, prompt, agent, skill, or workflow with GitHub Copilot.
- [ ] I have tested my instructions, prompt, agent, skill, workflow, or canvas extension with GitHub Copilot.
- [ ] I have run `npm start` and verified that `README.md` is up to date.
- [ ] I am targeting the `staged` branch for this pull request.

Expand All @@ -25,7 +25,8 @@
- [ ] New plugin.
- [ ] New skill file.
- [ ] New agentic workflow.
- [ ] Update to existing instruction, prompt, agent, plugin, skill, or workflow.
- [ ] New canvas extension.
- [ ] Update to existing instruction, prompt, agent, plugin, skill, workflow, or canvas extension.
- [ ] Other (please specify):

---
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/label-pr-intent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ jobs:
'workflow': {
color: 'BFD4F2',
description: 'PR touches workflow automation'
},
'canvas-extension': {
color: 'E4B9FF',
description: 'PR touches canvas extensions'
}
};

Expand Down Expand Up @@ -139,12 +143,16 @@ jobs:
/^workflows\/.+\.md$/,
/^\.github\/workflows\/.+\.(?:ya?ml|md)$/
],
canvasExtension: [
/^extensions\/[^/]+\//
],
newSubmission: [
/^agents\/.+\.agent\.md$/,
/^instructions\/.+\.instructions\.md$/,
/^skills\/[^/]+\/SKILL\.md$/,
/^hooks\/[^/]+\/(?:README\.md|hooks\.json)$/,
/^plugins\/[^/]+\/\.github\/plugin\/plugin\.json$/,
/^extensions\/[^/]+\/extension\.mjs$/,
/^workflows\/.+\.md$/,
/^\.github\/workflows\/.+\.(?:ya?ml|md)$/,
/^website\//
Expand Down Expand Up @@ -197,6 +205,10 @@ jobs:
desiredLabels.add('workflow');
}

if (filenames.some((filename) => matchesAny(filename, patterns.canvasExtension))) {
desiredLabels.add('canvas-extension');
}

if (hasNewSubmission) {
desiredLabels.add('new-submission');
}
Expand Down
134 changes: 134 additions & 0 deletions .github/workflows/validate-canvas-extensions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Validate Canvas Extensions

on:
pull_request:
branches: [staged]
types: [opened, synchronize, reopened]
paths:
- "extensions/**"

permissions:
contents: read
pull-requests: write

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0

- name: Validate changed canvas extensions
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const fs = require('fs');
const path = require('path');

// Collect changed extension directories from the PR diff
const { execSync } = require('child_process');
const changedFiles = execSync(
`git diff --name-only origin/${{ github.base_ref }}...HEAD`
).toString().trim().split('\n').filter(Boolean);

const EXTENSIONS_DIR = 'extensions';
const EXTERNAL_ASSETS_DIR = 'external-assets';

const changedExtDirs = new Set();
for (const file of changedFiles) {
const parts = file.split('/');
if (parts[0] === EXTENSIONS_DIR && parts.length >= 2) {
const extName = parts[1];
// Skip the external-assets directory — it's not a canvas extension
if (extName !== EXTERNAL_ASSETS_DIR) {
changedExtDirs.add(path.join(EXTENSIONS_DIR, extName));
Comment on lines +42 to +46
}
}
}

if (changedExtDirs.size === 0) {
console.log('No canvas extension directories changed — skipping validation.');
return;
}

console.log(`Validating ${changedExtDirs.size} extension(s): ${[...changedExtDirs].join(', ')}`);

const errors = [];

for (const extDir of changedExtDirs) {
if (!fs.existsSync(extDir)) {
// Directory was deleted — skip
console.log(`${extDir} no longer exists (deleted?), skipping.`);
continue;
}

const extName = path.basename(extDir);

Comment on lines +67 to +68
// Rule 1: must contain extension.mjs
const mainFile = path.join(extDir, 'extension.mjs');
if (!fs.existsSync(mainFile)) {
errors.push(
`**\`${extDir}\`**: missing required \`extension.mjs\`. ` +
`Canvas extensions must have their entry point named \`extension.mjs\`.`
);
}

// Rule 2: must contain assets/preview.png
const previewFile = path.join(extDir, 'assets', 'preview.png');
if (!fs.existsSync(previewFile)) {
errors.push(
`**\`${extDir}\`**: missing required \`assets/preview.png\`. ` +
`Canvas extensions must include a screenshot at \`assets/preview.png\` ` +
`so reviewers and users can preview the extension before installing it.`
);
}
}

if (errors.length === 0) {
console.log('✅ All changed canvas extensions pass validation.');
return;
}

const isFork = context.payload.pull_request.head.repo.fork;
const body = [
'❌ **Canvas extension validation failed**',
'',
'The following issue(s) were found in changed canvas extension(s):',
'',
...errors.map(e => `- ${e}`),
'',
'---',
'',
'### Required structure for canvas extensions',
'',
'Each extension folder under `extensions/` must contain:',
'',
'| Path | Required | Description |',
'|------|----------|-------------|',
'| `extension.mjs` | ✅ | Entry point for the canvas extension |',
'| `assets/preview.png` | ✅ | Screenshot shown on the website and in the marketplace |',
'',
'Please add the missing file(s) and push an update to this PR.',
].join('\n');

if (!isFork) {
try {
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
event: 'REQUEST_CHANGES',
Comment on lines +118 to +122
body
});
} catch (error) {
core.warning(`Could not post PR review: ${error.message}`);
core.warning(body);
}
} else {
core.warning('PR is from a fork — skipping createReview to avoid permission errors.');
core.warning(body);
}

core.setFailed(`Canvas extension validation failed with ${errors.length} error(s).`);
Loading