Skip to content

Commit 45f19e5

Browse files
jongioCopilot
andauthored
ci: harden PR pipeline and add DevOps hygiene (#13)
- validate.yml: add concurrency (cancel superseded PR runs), workflow_dispatch, a dependency-free lint step, and an actionlint job to lint the workflows themselves - scripts/lint.mjs: node --check every owned .mjs + JSON.parse every .json (skips vendor/node_modules/artifacts), no new dependencies - dependabot.yml: weekly grouped github-actions updates to keep pinned SHAs current - add .editorconfig, CODEOWNERS, and a PR template; document the lint step in README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7db6561 commit 45f19e5

7 files changed

Lines changed: 150 additions & 8 deletions

File tree

.editorconfig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# https://editorconfig.org — shared editor defaults so contributions match the
2+
# repo's existing style (tabs, LF, final newline) without needing a formatter.
3+
root = true
4+
5+
[*]
6+
charset = utf-8
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
indent_style = tab
11+
12+
# Data/config files conventionally use 2-space indentation.
13+
[*.{json,yml,yaml}]
14+
indent_style = space
15+
indent_size = 2
16+
17+
# Markdown uses trailing spaces for hard line breaks.
18+
[*.md]
19+
trim_trailing_whitespace = false

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
2+
# Default owner for everything in this repo.
3+
* @jongio

.github/dependabot.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates
2+
# Keeps the SHA-pinned GitHub Actions in .github/workflows/ current. Grouped into a
3+
# single weekly PR to keep the noise down; Dependabot updates both the SHA and the
4+
# `# vX` comment together.
5+
version: 2
6+
updates:
7+
- package-ecosystem: github-actions
8+
directory: /
9+
schedule:
10+
interval: weekly
11+
commit-message:
12+
prefix: ci
13+
groups:
14+
github-actions:
15+
patterns:
16+
- "*"

.github/pull_request_template.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## What & why
2+
3+
<!-- What does this change, and why? Link any related issue. -->
4+
5+
## Type
6+
7+
- [ ] New extension
8+
- [ ] Fix / improvement to an existing extension
9+
- [ ] Tooling / CI / docs
10+
11+
## Checklist
12+
13+
- [ ] `node scripts/lint.mjs` passes
14+
- [ ] `node scripts/validate-extensions.mjs` passes
15+
- [ ] `node scripts/run-tests.mjs` passes
16+
- [ ] New/changed extension has a smoke test under `extensions/<name>/test/`
17+
- [ ] README extensions table updated (when adding or removing an extension)

.github/workflows/validate.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@ on:
55
branches: [main]
66
pull_request:
77
branches: [main]
8+
workflow_dispatch:
89

910
permissions: {}
1011

12+
# Newer pushes to the same branch/PR supersede in-flight runs; cancel the stale
13+
# ones on PRs to save CI minutes, but let pushes to main run to completion.
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
17+
1118
jobs:
1219
validate:
20+
name: Lint, validate & test extensions
1321
runs-on: ubuntu-latest
1422
timeout-minutes: 5
1523
permissions:
@@ -23,6 +31,25 @@ jobs:
2331
with:
2432
node-version: 22
2533

26-
- run: node scripts/validate-extensions.mjs
34+
- name: Lint (syntax + JSON)
35+
run: node scripts/lint.mjs
36+
37+
- name: Validate extension structure
38+
run: node scripts/validate-extensions.mjs
39+
40+
- name: Run extension smoke tests
41+
run: node scripts/run-tests.mjs
42+
43+
actionlint:
44+
name: Lint workflows
45+
runs-on: ubuntu-latest
46+
timeout-minutes: 5
47+
permissions:
48+
contents: read
49+
steps:
50+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
51+
with:
52+
persist-credentials: false
2753

28-
- run: node scripts/run-tests.mjs
54+
- name: actionlint
55+
uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,20 @@ copilot-extensions/
6666
│ ├─ wiki-discover/
6767
│ └─ code-tutor/
6868
├─ scripts/
69+
│ ├─ lint.mjs # dependency-free syntax + JSON check (run in CI)
6970
│ ├─ validate-extensions.mjs # structure check (run in CI)
7071
│ └─ run-tests.mjs # runs every extension's smoke test (run in CI)
71-
└─ .github/workflows/
72-
└─ validate.yml
72+
└─ .github/
73+
├─ dependabot.yml # keeps the SHA-pinned Actions current
74+
└─ workflows/
75+
└─ validate.yml # lint + validate + tests, and lints the workflows
7376
```
7477

75-
Validate locally with `node scripts/validate-extensions.mjs`, and run the smoke
76-
tests with `node scripts/run-tests.mjs` (each boots its canvas's kit runtime over
77-
loopback HTTP — no SDK, no network). CI runs both the structure check and the
78-
smoke tests on every push and pull request.
78+
Validate locally with `node scripts/lint.mjs` (syntax-checks every `.mjs` and parses
79+
every `.json`) and `node scripts/validate-extensions.mjs` (structure check), and run
80+
the smoke tests with `node scripts/run-tests.mjs` (each boots its canvas's kit runtime
81+
over loopback HTTP — no SDK, no network). CI runs the lint, the structure check, and
82+
the smoke tests on every push and pull request, and lints the workflow files themselves.
7983

8084
## License
8185

scripts/lint.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env node
2+
// Dependency-free static lint for the repo. Two cheap, fast checks that fit the
3+
// "no package.json, no toolchain" philosophy:
4+
// 1. `node --check` every .mjs we own → catches syntax errors before tests run.
5+
// 2. JSON.parse every .json we own → catches broken manifests / kit metadata.
6+
// Vendored bundles (canvas-kit/vendor/), node_modules/ and per-user artifacts/ are
7+
// skipped — we don't own them. Run with `node scripts/lint.mjs`.
8+
9+
import { readdirSync, readFileSync } from "node:fs";
10+
import { spawnSync } from "node:child_process";
11+
import { dirname, join, relative, sep } from "node:path";
12+
import { fileURLToPath } from "node:url";
13+
14+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
15+
const SKIP_DIRS = new Set(["node_modules", "artifacts", "vendor", ".git"]);
16+
17+
function* walk(dir) {
18+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
19+
if (entry.isDirectory()) {
20+
if (!SKIP_DIRS.has(entry.name)) yield* walk(join(dir, entry.name));
21+
} else {
22+
yield join(dir, entry.name);
23+
}
24+
}
25+
}
26+
27+
const files = [...walk(root)];
28+
const rel = (p) => relative(root, p).split(sep).join("/");
29+
30+
let failures = 0;
31+
const fail = (p, msg) => {
32+
failures++;
33+
console.error(`✗ ${rel(p)}: ${msg}`);
34+
};
35+
36+
const mjs = files.filter((f) => f.endsWith(".mjs"));
37+
for (const f of mjs) {
38+
const res = spawnSync(process.execPath, ["--check", f], { encoding: "utf8" });
39+
if (res.status !== 0) fail(f, `syntax error\n${(res.stderr || "").trim()}`);
40+
}
41+
42+
const json = files.filter((f) => f.endsWith(".json"));
43+
for (const f of json) {
44+
try {
45+
JSON.parse(readFileSync(f, "utf8"));
46+
} catch (err) {
47+
fail(f, `invalid JSON: ${err.message}`);
48+
}
49+
}
50+
51+
if (failures > 0) {
52+
console.error(`\n${failures} lint problem(s) found.`);
53+
process.exit(1);
54+
}
55+
56+
console.log(`✓ lint clean: ${mjs.length} .mjs syntax-checked, ${json.length} .json parsed.`);

0 commit comments

Comments
 (0)