Skip to content

Commit 1c720c3

Browse files
committed
ci: add action and workflow documentations to github pages
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent 951f075 commit 1c720c3

8 files changed

Lines changed: 678 additions & 73 deletions

File tree

.github/workflows/__test-action-deploy-jekyll-jampack.yml

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,58 @@ jobs:
1616
- name: Arrange - Checkout
1717
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
1818

19+
- name: Arrange - Create linked test pages
20+
run: |
21+
mkdir -p docs/tests
22+
cat <<'EOF' > docs/tests/source.md
23+
# Source Page
24+
25+
Link to [Target](docs/tests/target.md).
26+
EOF
27+
cat <<'EOF' > docs/tests/target.md
28+
# Target Page
29+
30+
Target content for link rewriting validation.
31+
EOF
32+
printf '\n- [Docs Source](docs/tests/source.md)\n' >> README.md
33+
1934
- name: Act - Deploy Jekyll
2035
id: deploy-jekyll
2136
uses: ./actions/deploy/jekyll
37+
with:
38+
pages: |
39+
.github/workflows/release-actions.md
40+
actions/deploy/jekyll/README.md
41+
docs/tests/source.md
42+
docs/tests/target.md
2243
2344
- name: Assert - Check outputs
2445
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
2546
with:
2647
script: |
2748
const assert = require("assert");
28-
const { existsSync } = require("fs");
49+
const { existsSync, readFileSync } = require("fs");
50+
const path = require("path");
2951
3052
const buildPathOutput = ${{ toJSON(steps.deploy-jekyll.outputs.build-path) }};
3153
assert(buildPathOutput, `"build-path" output is empty`);
3254
55+
const workspacePath = process.env.GITHUB_WORKSPACE;
56+
assert(workspacePath, "GITHUB_WORKSPACE environment variable is missing");
57+
58+
const jekyllSourcePath = path.join(workspacePath, "_site");
59+
const indexMarkdownPath = path.join(jekyllSourcePath, "index.md");
60+
assert(existsSync(indexMarkdownPath), `Index markdown does not exist at: ${indexMarkdownPath}`);
61+
62+
const sourceMarkdownPath = path.join(
63+
jekyllSourcePath,
64+
"docs/tests/source/index.md"
65+
);
66+
assert(
67+
existsSync(sourceMarkdownPath),
68+
`Generated source page markdown does not exist at: ${sourceMarkdownPath}`
69+
);
70+
3371
// Check if the build path exists
3472
assert(existsSync(buildPathOutput), `Build path does not exist: ${buildPathOutput}`);
3573
@@ -41,6 +79,33 @@ jobs:
4179
const cssFilePath = `${buildPathOutput}/assets/css/style.css`;
4280
assert(existsSync(cssFilePath), `Jekyll site CSS file does not exist in the build path: ${cssFilePath}`);
4381
82+
// Check if the custom page files exist in the build path
83+
const customPage1Path = `${buildPathOutput}/github/workflows/release-actions/index.html`;
84+
assert(existsSync(customPage1Path), `Custom page 1 does not exist in the build path: ${customPage1Path}`);
85+
const customPage2Path = `${buildPathOutput}/actions/deploy/jekyll/index.html`;
86+
assert(existsSync(customPage2Path), `Custom page 2 does not exist in the build path: ${customPage2Path}`);
87+
88+
const copiedAssetPath = `${buildPathOutput}/assets/github/logo.svg`;
89+
assert(existsSync(copiedAssetPath), `Expected local asset to be copied to: ${copiedAssetPath}`);
90+
91+
const customPage2Content = readFileSync(customPage2Path, "utf8");
92+
assert(
93+
customPage2Content.includes("/assets/github/logo.svg"),
94+
`Generated page should reference the copied asset using the /assets prefix`
95+
);
96+
97+
const indexMarkdownContent = readFileSync(indexMarkdownPath, "utf8");
98+
assert(
99+
indexMarkdownContent.includes("](docs/tests/source)"),
100+
"Index markdown should rewrite links to created site pages"
101+
);
102+
103+
const sourceMarkdownContent = readFileSync(sourceMarkdownPath, "utf8");
104+
assert(
105+
sourceMarkdownContent.includes("](../target)"),
106+
"Created site page should rewrite links to sibling pages"
107+
);
108+
44109
- name: Act - Run Jampack
45110
uses: ./actions/deploy/jampack
46111
with:
@@ -68,6 +133,9 @@ jobs:
68133
const cssFilePath = `${buildPathOutput}/assets/css/style.css`;
69134
assert(existsSync(cssFilePath), `Jekyll site CSS file does not exist in the build path: ${cssFilePath}`);
70135
136+
const copiedAssetPath = `${buildPathOutput}/assets/github/logo.svg`;
137+
assert(existsSync(copiedAssetPath), `Expected local asset to remain after packing: ${copiedAssetPath}`);
138+
71139
// Assert that there is no "_jampack" directory in the build path
72140
const jampackDir = `${buildPathOutput}/_jampack`;
73141
assert(!existsSync(jampackDir), `Jampack cache directory should not exist in the build path: ${jampackDir}`);

.github/workflows/release-actions.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,38 @@ jobs:
198198
artifact-ids: ${{ needs.generate-documentation.outputs.artifact-id }}
199199
path: /
200200

201+
- id: get-documentation-paths
202+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
203+
with:
204+
script: |
205+
const documentationPaths = [];
206+
207+
// Retrieve action manifests
208+
const globber = await glob.create('./actions/**/README.md');
209+
for await (const file of globber.globGenerator()) {
210+
core.debug(`Found action documentation: ${file}`);
211+
documentationPaths.push(file);
212+
}
213+
214+
// Retrieve workflow manifests
215+
const workflowGlobber = await glob.create('./.github/workflows/*.md');
216+
for await (const file of workflowGlobber.globGenerator()) {
217+
core.debug(`Found workflow documentation: ${file}`);
218+
// Ignore internal workflows
219+
if (file.match(/\/\.github\/workflows\/__.*\.md$/)) {
220+
core.debug(`Ignoring internal workflow documentation: ${file}`);
221+
continue;
222+
}
223+
core.debug(`Including workflow documentation: ${file}`);
224+
documentationPaths.push(file);
225+
}
226+
227+
core.setOutput(
228+
'documentation-paths',
229+
documentationPaths
230+
.join('\n')
231+
);
232+
201233
# FIXME: This is a workaround for having workflow actions. See https://github.com/orgs/community/discussions/38659
202234
- id: oidc
203235
uses: ChristopherHX/oidc@73eee1ff03fdfce10eda179f617131532209edbd # v3
@@ -215,6 +247,8 @@ jobs:
215247
216248
- id: build-jekyll
217249
uses: ./self-workflow/actions/deploy/jekyll
250+
with:
251+
pages: ${{ steps.get-documentation-paths.outputs.documentation-paths }}
218252

219253
- uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
220254
if: inputs.github-app-id

AGENTS.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# CI GitHub Publish - Agent Instructions
2+
3+
## Quick Start
4+
5+
This project is a collection of **opinionated GitHub Actions** for streamlined CI/CD workflows. For comprehensive documentation, see the main [README.md](README.md).
6+
7+
### Key Sections to Reference
8+
9+
- **[Overview](README.md#overview)** - Project purpose and scope
10+
- **[Actions](README.md#actions)** - Complete catalog of available actions by category
11+
- **[Reusable Workflows](README.md#reusable-workflows)** - Orchestration workflows for complex deployments
12+
- **[Contributing](README.md#contributing)** - Guidelines for contributing to the project; Structure patterns and development standards
13+
- **[Development Workflow](README.md#development-workflow)** - Commands for linting, testing, and local development
14+
15+
## Agent-Specific Development Patterns
16+
17+
### Critical Workflow Knowledge
18+
19+
```bash
20+
# Essential commands for development
21+
make lint # Run Super Linter (dockerized)
22+
make lint-fix # Auto-fix linting issues
23+
gh act -W .github/workflows/workflow-file-to-test.yml # Test workflows locally with `act`
24+
```
25+
26+
For detailed documentation on each action and workflow, refer to the individual README files linked in the main [README.md](README.md).

README.md

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
<!-- markdownlint-disable-next-line first-line-heading -->
2-
<div align="center" width="100%">
1+
# Continuous Integration - GitHub - Publish
32

4-
# <img src=".github/logo.svg" width="60px" align="center" alt="logo" /> Continuous Integration - GitHub - Publish
3+
<div align="center">
4+
<img src=".github/logo.svg" width="60px" align="center" alt="Logo for Continuous Integration - GitHub - Publish" />
5+
</div>
6+
7+
---
58

69
[![Continuous Integration](https://github.com/hoverkraft-tech/ci-github-publish/actions/workflows/__main-ci.yml/badge.svg)](https://github.com/hoverkraft-tech/ci-github-publish/actions/workflows/__main-ci.yml)
710
[![GitHub tag](https://img.shields.io/github/tag/hoverkraft-tech/ci-github-publish?include_prereleases=&sort=semver&color=blue)](https://github.com/hoverkraft-tech/ci-github-publish/releases/)
811
[![License](https://img.shields.io/badge/License-MIT-blue)](#license)
912
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
1013

11-
</div>
14+
## Overview
1215

1316
Opinionated GitHub Actions and workflows for streamlined release, deployment, and publishing.
1417

15-
---
16-
1718
## Actions
1819

1920
### ArgoCD
@@ -112,6 +113,104 @@ _Reusable workflows for managing release process._
112113

113114
👍 If you wish to contribute to this project, please read the [CONTRIBUTING.md](CONTRIBUTING.md) file, PRs are Welcome !
114115

116+
### Action Structure Pattern
117+
118+
All actions follow this consistent structure:
119+
120+
```text
121+
actions/{category}/{action-name}/
122+
├── action.yml # Action definition with inputs/outputs
123+
├── README.md # Usage documentation
124+
└── *.js # Optional Node.js scripts (e.g., prepare-site.js)
125+
```
126+
127+
### Development Standards
128+
129+
#### Action Definition Standards
130+
131+
1. **Consistent branding**: All actions use `author: hoverkraft`, `icon: <specific-icon>`, `color: blue`
132+
2. **Composite actions**: Use `using: "composite"` with GitHub Script for complex logic
133+
3. **Pinned dependencies**: Always pin action versions with SHA (e.g., `@ed597411d8f924073f98dfc5c65a23a2325f34cd`)
134+
4. **Input validation**: Validate inputs early in GitHub Script steps
135+
136+
#### JavaScript Patterns
137+
138+
- **Class-based architecture**: Use classes like `AssetManager` for complex functionality
139+
- **Path utilities**: Extensive use of Node.js `path` module for cross-platform compatibility
140+
- **Regular expression patterns**: Define constants for reusable patterns (`MARKDOWN_IMAGE_REGEX`, `HTML_IMAGE_REGEX`)
141+
- **Caching**: Implement Map-based caching for expensive operations
142+
143+
### Development Workflow
144+
145+
#### Linting & Testing
146+
147+
```bash
148+
make lint # Run Super Linter (dockerized)
149+
make lint-fix # Auto-fix issues where possible
150+
151+
# Use GitHub Actions locally with `act`
152+
gh act -W .github/workflows/workflow-file-to-test.yml
153+
```
154+
155+
#### File Conventions
156+
157+
- **Dockerfile**: Uses Super Linter slim image for consistent code quality
158+
- **Tests**: Located in `tests/` with expected vs actual file comparisons
159+
- **Workflows**: Private workflows prefixed with `__` (e.g., `__main-ci.yml`)
160+
161+
#### Action Development Conventions
162+
163+
**Always follow these patterns when creating/modifying actions:**
164+
165+
1. **Pinned Dependencies**: Use exact SHA commits for all action dependencies:
166+
167+
```yaml
168+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
169+
```
170+
171+
2. **Consistent Branding**: Every action.yml must include:
172+
173+
```yaml
174+
author: hoverkraft
175+
branding:
176+
icon: <specific-icon>
177+
color: blue
178+
```
179+
180+
3. **Input Validation**: Always validate inputs early in GitHub Script steps:
181+
```javascript
182+
const urlInput = ${{ toJson(inputs.url ) }};
183+
if (!urlInput) {
184+
return core.setFailed("URL input is required.");
185+
}
186+
```
187+
188+
#### JavaScript Development Patterns
189+
190+
**For Node.js scripts (like `prepare-site.js`):**
191+
192+
- Use class-based architecture for complex functionality
193+
- Define regex patterns as constants (`MARKDOWN_IMAGE_REGEX`, `HTML_IMAGE_REGEX`)
194+
- Implement Map-based caching for expensive operations
195+
- Always use Node.js `path` module for cross-platform compatibility
196+
197+
#### File Structure Understanding
198+
199+
```text
200+
actions/{category}/{action-name}/ # Modular action organization
201+
├── action.yml # Action definition with inputs/outputs
202+
├── README.md # Usage documentation
203+
└── *.js # Optional Node.js scripts
204+
205+
.github/workflows/ # Reusable workflows
206+
├── deploy-*.yml # Deployment orchestration
207+
├── clean-deploy-*.yml # Cleanup workflows
208+
└── __*.yml # Private/internal workflows
209+
210+
tests/ # Expected vs actual comparisons
211+
└── argocd-app-of-apps/ # Template testing structure
212+
```
213+
115214
## Author
116215

117216
🏢 **Hoverkraft <contact@hoverkraft.cloud>**

actions/deploy/jekyll/action.yml

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,74 +27,18 @@ runs:
2727
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
2828
with:
2929
script: |
30-
const { randomUUID } = require('crypto');
31-
const { join, relative, basename, extname, dirname } = require('path');
32-
const { existsSync, readFileSync, writeFileSync } = require('fs');
33-
34-
// Prepare paths
35-
const workspacePath = "${{ github.workspace }}";
36-
const sitePath = join(workspacePath, "_site");
37-
const buildPath = join(sitePath, "build");
38-
core.setOutput("build-path", buildPath);
39-
40-
core.setOutput("jekyll-source", relative(workspacePath, sitePath));
41-
core.setOutput("jekyll-destination", relative(workspacePath, buildPath));
42-
43-
// Prepare site pages
44-
await io.mkdirP(sitePath);
45-
46-
// Set config
47-
const configPath = join(sitePath, "_config.yml");
48-
if (!existsSync(configPath)) {
49-
const theme = "${{ inputs.theme }}";
50-
if (!theme) {
51-
throw new Error("Theme input is required.");
30+
const prepareSite = require('./actions/deploy/jekyll/prepare-site.js');
31+
await prepareSite({
32+
core,
33+
io,
34+
glob,
35+
inputs: {
36+
theme: ${{ toJson(inputs.theme) }},
37+
pages: ${{ toJson(inputs.pages) }}
5238
}
53-
const isGitHubSupportedTheme = theme.startsWith("jekyll-theme-");
54-
55-
const configContent = isGitHubSupportedTheme
56-
? `theme: ${theme}`
57-
: `remote_theme: ${theme}\nplugins:\n - jekyll-remote-theme`;
58-
59-
writeFileSync(configPath, configContent);
60-
}
61-
62-
function getPageSection(pageFile) {
63-
const sectionDir = basename(pageFile, extname(pageFile))
64-
.toLowerCase()
65-
.replace(/[^a-z0-9]+/g, '-')
66-
.replace(/^-|-$/g, '');
67-
68-
return join(sitePath, sectionDir, "index.md");
69-
}
70-
71-
function getPageTitle(pageFile) {
72-
return getPageSection(pageFile)
73-
.split('-')
74-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
75-
.join(' ');
76-
}
77-
78-
async function createSitePage(pageFile, pageTitle = null, pagePath = null) {
79-
pageTitle = pageTitle || getPageTitle(pageFile);
80-
pagePath = pagePath || getPageSection(pageFile);
81-
await io.mkdirP(dirname(pagePath));
82-
83-
const pageContent = `---\nlayout: default\ntitle: ${pageTitle}\n---\n\n${readFileSync(pageFile, 'utf8')}`;
84-
return writeFileSync(pagePath, pageContent);
85-
}
86-
87-
const indexPath = join(sitePath, "index.md");
88-
if (!existsSync(indexPath)) {
89-
await createSitePage("./README.md", "Home", indexPath);
90-
}
39+
});
9140
92-
const pages = "${{ inputs.pages }}";
93-
const pageFilePatterns = pages.split([" ", "\n"]).map(p => p.trim()).filter(Boolean);
94-
const globber = await glob.create(pageFilePatterns.join('\n'));
95-
for await (const pageFile of globber.globGenerator()) {
96-
await createSitePage(pageFile);
97-
}
41+
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
9842

9943
- name: Build Jekyll site
10044
uses: actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697 # v1.0.13

0 commit comments

Comments
 (0)