mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-24 04:15:14 +00:00
Simplify workflows to flat .md files instead of folders
Workflows are now standalone .md files in workflows/ — no subfolders or README.md needed. Each file contains both the metadata frontmatter (name, description, triggers, tags) and the agentic workflow definition (on, permissions, safe-outputs) in a single file. Updated all build scripts, CI workflows, docs, and review checklists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -135,7 +135,7 @@ Hooks enable automated workflows triggered by specific events during GitHub Copi
|
||||
workflowsUsage: `### How to Use Agentic Workflows
|
||||
|
||||
**What's Included:**
|
||||
- Each workflow is a folder containing a \`README.md\` and one or more \`.md\` workflow files
|
||||
- Each workflow is a single \`.md\` file with YAML frontmatter and natural language instructions
|
||||
- Workflows are compiled to \`.lock.yml\` GitHub Actions files via \`gh aw compile\`
|
||||
- Workflows follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw)
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ function generateHooksData(gitDates) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate workflows metadata (folder-based, similar to hooks)
|
||||
* Generate workflows metadata (flat .md files)
|
||||
*/
|
||||
function generateWorkflowsData(gitDates) {
|
||||
const workflows = [];
|
||||
@@ -210,37 +210,34 @@ function generateWorkflowsData(gitDates) {
|
||||
};
|
||||
}
|
||||
|
||||
const workflowFolders = fs.readdirSync(WORKFLOWS_DIR).filter((file) => {
|
||||
const filePath = path.join(WORKFLOWS_DIR, file);
|
||||
return fs.statSync(filePath).isDirectory();
|
||||
const workflowFiles = fs.readdirSync(WORKFLOWS_DIR).filter((file) => {
|
||||
return file.endsWith(".md") && file !== ".gitkeep";
|
||||
});
|
||||
|
||||
const allTriggers = new Set();
|
||||
const allTags = new Set();
|
||||
|
||||
for (const folder of workflowFolders) {
|
||||
const workflowPath = path.join(WORKFLOWS_DIR, folder);
|
||||
const metadata = parseWorkflowMetadata(workflowPath);
|
||||
for (const file of workflowFiles) {
|
||||
const filePath = path.join(WORKFLOWS_DIR, file);
|
||||
const metadata = parseWorkflowMetadata(filePath);
|
||||
if (!metadata) continue;
|
||||
|
||||
const relativePath = path
|
||||
.relative(ROOT_FOLDER, workflowPath)
|
||||
.relative(ROOT_FOLDER, filePath)
|
||||
.replace(/\\/g, "/");
|
||||
const readmeRelativePath = `${relativePath}/README.md`;
|
||||
|
||||
(metadata.triggers || []).forEach((t) => allTriggers.add(t));
|
||||
(metadata.tags || []).forEach((t) => allTags.add(t));
|
||||
|
||||
const id = path.basename(file, ".md");
|
||||
workflows.push({
|
||||
id: folder,
|
||||
id,
|
||||
title: metadata.name,
|
||||
description: metadata.description,
|
||||
triggers: metadata.triggers || [],
|
||||
tags: metadata.tags || [],
|
||||
assets: metadata.assets || [],
|
||||
path: relativePath,
|
||||
readmeFile: readmeRelativePath,
|
||||
lastUpdated: gitDates.get(readmeRelativePath) || null,
|
||||
lastUpdated: gitDates.get(relativePath) || null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -735,7 +732,7 @@ function generateSearchIndex(
|
||||
id: workflow.id,
|
||||
title: workflow.title,
|
||||
description: workflow.description,
|
||||
path: workflow.readmeFile,
|
||||
path: workflow.path,
|
||||
lastUpdated: workflow.lastUpdated,
|
||||
searchText: `${workflow.title} ${workflow.description} ${workflow.triggers.join(
|
||||
" "
|
||||
|
||||
@@ -588,26 +588,24 @@ function generateWorkflowsSection(workflowsDir) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Get all workflow folders (directories)
|
||||
const workflowFolders = fs.readdirSync(workflowsDir).filter((file) => {
|
||||
const filePath = path.join(workflowsDir, file);
|
||||
return fs.statSync(filePath).isDirectory();
|
||||
// Get all .md workflow files (flat, no subfolders)
|
||||
const workflowFiles = fs.readdirSync(workflowsDir).filter((file) => {
|
||||
return file.endsWith(".md") && file !== ".gitkeep";
|
||||
});
|
||||
|
||||
// Parse each workflow folder
|
||||
const workflowEntries = workflowFolders
|
||||
.map((folder) => {
|
||||
const workflowPath = path.join(workflowsDir, folder);
|
||||
const metadata = parseWorkflowMetadata(workflowPath);
|
||||
// Parse each workflow file
|
||||
const workflowEntries = workflowFiles
|
||||
.map((file) => {
|
||||
const filePath = path.join(workflowsDir, file);
|
||||
const metadata = parseWorkflowMetadata(filePath);
|
||||
if (!metadata) return null;
|
||||
|
||||
return {
|
||||
folder,
|
||||
file,
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
triggers: metadata.triggers,
|
||||
tags: metadata.tags,
|
||||
assets: metadata.assets,
|
||||
};
|
||||
})
|
||||
.filter((entry) => entry !== null)
|
||||
@@ -621,20 +619,16 @@ function generateWorkflowsSection(workflowsDir) {
|
||||
|
||||
// Create table header
|
||||
let content =
|
||||
"| Name | Description | Triggers | Bundled Assets |\n| ---- | ----------- | -------- | -------------- |\n";
|
||||
"| Name | Description | Triggers |\n| ---- | ----------- | -------- |\n";
|
||||
|
||||
// Generate table rows for each workflow
|
||||
for (const workflow of workflowEntries) {
|
||||
const link = `../workflows/${workflow.folder}/README.md`;
|
||||
const link = `../workflows/${workflow.file}`;
|
||||
const triggers = workflow.triggers.length > 0 ? workflow.triggers.join(", ") : "N/A";
|
||||
const assetsList =
|
||||
workflow.assets.length > 0
|
||||
? workflow.assets.map((a) => `\`${a}\``).join("<br />")
|
||||
: "None";
|
||||
|
||||
content += `| [${workflow.name}](${link}) | ${formatTableCell(
|
||||
workflow.description
|
||||
)} | ${triggers} | ${assetsList} |\n`;
|
||||
)} | ${triggers} |\n`;
|
||||
}
|
||||
|
||||
return `${TEMPLATES.workflowsSection}\n${TEMPLATES.workflowsUsage}\n\n${content}`;
|
||||
|
||||
@@ -254,24 +254,23 @@ function parseHookMetadata(hookPath) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse workflow metadata from a workflow folder
|
||||
* @param {string} workflowPath - Path to the workflow folder
|
||||
* Parse workflow metadata from a standalone .md workflow file
|
||||
* @param {string} filePath - Path to the workflow .md file
|
||||
* @returns {object|null} Workflow metadata or null on error
|
||||
*/
|
||||
function parseWorkflowMetadata(workflowPath) {
|
||||
function parseWorkflowMetadata(filePath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const readmeFile = path.join(workflowPath, "README.md");
|
||||
if (!fs.existsSync(readmeFile)) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frontmatter = parseFrontmatter(readmeFile);
|
||||
const frontmatter = parseFrontmatter(filePath);
|
||||
|
||||
// Validate required fields
|
||||
if (!frontmatter?.name || !frontmatter?.description) {
|
||||
console.warn(
|
||||
`Invalid workflow at ${workflowPath}: missing name or description in frontmatter`
|
||||
`Invalid workflow at ${filePath}: missing name or description in frontmatter`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -279,37 +278,15 @@ function parseWorkflowMetadata(workflowPath) {
|
||||
// Extract triggers from frontmatter if present
|
||||
const triggers = frontmatter.triggers || [];
|
||||
|
||||
// List bundled assets (all files except README.md), recursing through subdirectories
|
||||
const getAllFiles = (dirPath, arrayOfFiles = []) => {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(dirPath, file);
|
||||
if (fs.statSync(filePath).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
|
||||
} else {
|
||||
const relativePath = path.relative(workflowPath, filePath);
|
||||
if (relativePath !== "README.md") {
|
||||
arrayOfFiles.push(relativePath.replace(/\\/g, "/"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFiles;
|
||||
};
|
||||
|
||||
const assets = getAllFiles(workflowPath).sort();
|
||||
|
||||
return {
|
||||
name: frontmatter.name,
|
||||
description: frontmatter.description,
|
||||
triggers,
|
||||
tags: frontmatter.tags || [],
|
||||
assets,
|
||||
path: workflowPath,
|
||||
path: filePath,
|
||||
};
|
||||
},
|
||||
workflowPath,
|
||||
filePath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user