-
-
Notifications
You must be signed in to change notification settings - Fork 4k
190 lines (174 loc) · 7.82 KB
/
Copy pathvalidate-companies.yml
File metadata and controls
190 lines (174 loc) · 7.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
name: Validate Company Profiles
on:
pull_request_target:
branches: [main]
paths:
- 'src/companies/**'
- 'company-profiles/**'
permissions:
contents: read
pull-requests: write
jobs:
validate:
# Skip validation for repo owner and bots
if: >-
github.event.pull_request.user.login != 'dougaitken' &&
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.author_association != 'OWNER' &&
github.event.pull_request.author_association != 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: Checkout base (for workflow scripts)
uses: actions/checkout@v4
- name: Checkout PR company files
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
path: pr-head
sparse-checkout: |
src/companies
company-profiles
# Safe here: sparse-checkout limits this to data files (company
# profiles) only, never .github/ workflows or scripts, and nothing
# from the fork is executed. See https://gh.io/securely-using-pull_request_target
allow-unsafe-pr-checkout: true
# Defense-in-depth: don't persist the workflow token into this
# fork-controlled checkout's .git/config.
persist-credentials: false
- name: Get changed files
id: changed
run: |
FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '(^src/companies/.*\.md$|^company-profiles/)' || true)
echo "files<<EOF" >> "$GITHUB_OUTPUT"
echo "$FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run validation
id: validate
if: steps.changed.outputs.files != ''
env:
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: |
set +e
# Remap file paths to the PR checkout directory. Read line-by-line
# into an array so multi-file PRs don't break shell syntax, and so
# filenames are passed to node as properly quoted argv entries.
MAPPED_FILES=()
while IFS= read -r f; do
[ -z "$f" ] && continue
MAPPED_FILES+=("pr-head/$f")
done <<< "$CHANGED_FILES"
RESULT=$(node .github/scripts/validate-companies.js "${MAPPED_FILES[@]}")
EXIT_CODE=$?
set -e
echo "json<<EOF" >> "$GITHUB_OUTPUT"
echo "$RESULT" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
- name: Post PR comment
if: steps.changed.outputs.files != ''
uses: actions/github-script@v7
with:
script: |
const result = JSON.parse(process.env.VALIDATION_JSON);
const exitCode = parseInt(process.env.EXIT_CODE, 10);
let body = '';
// Old-format detection
if (result.oldFormatFiles && result.oldFormatFiles.length > 0) {
body += '### Thanks for your contribution!\n\n';
body += 'It looks like your PR uses an older file format. We\'ve updated how company profiles work. ';
body += 'Please create a new file in `src/companies/` instead.\n\n';
body += '**Files using the old format:**\n';
for (const f of result.oldFormatFiles) {
body += `- \`${f}\`\n`;
}
body += '\n<details>\n<summary>New format template</summary>\n\n';
body += '```markdown\n';
body += '---\n';
body += 'title: "Your Company Name"\n';
body += 'slug: your-company-slug\n';
body += 'website: https://yourcompany.com\n';
body += 'careers_url: https://yourcompany.com/careers\n';
body += 'region: worldwide\n';
body += 'remote_policy: fully-remote\n';
body += 'company_size: small\n';
body += 'technologies:\n';
body += ' - javascript\n';
body += ' - python\n';
body += '---\n\n';
body += '> **Note:** `addedAt` and `updatedAt` dates are managed by maintainers — do not include them in your PR.\n\n';
body += '## Company blurb\n\n';
body += 'A short description of your company.\n\n';
body += '## Remote status\n\n';
body += 'Describe your remote work culture.\n\n';
body += '## How to apply\n\n';
body += 'Link to your careers page or application instructions.\n';
body += '```\n\n';
body += '</details>\n\n';
body += 'The filename should be `src/companies/{slug}.md` where `{slug}` matches the `slug` field in the frontmatter.\n\n';
}
// Validation results for new-format files
if (result.summary && result.summary.total > 0) {
if (result.summary.failed === 0 && result.oldFormatFiles.length === 0) {
body += '### Company profile validation passed!\n\n';
body += `All ${result.summary.total} company file(s) look good. Thanks for following the format!\n`;
if (result.summary.warnings > 0) {
body += '\n**Warnings:**\n';
for (const [file, res] of Object.entries(result.files)) {
for (const w of res.warnings) {
body += `- \`${file}\`: ${w}\n`;
}
}
}
} else if (result.summary.failed > 0) {
if (!body) body += '### Thanks for your contribution!\n\n';
body += 'The following issues were found with your company profile(s). ';
body += 'Please fix them and push an update to this PR:\n\n';
for (const [file, res] of Object.entries(result.files)) {
if (res.errors.length === 0 && res.warnings.length === 0) continue;
body += `**\`${file}\`**\n`;
for (const e of res.errors) {
body += `- :x: ${e}\n`;
}
for (const w of res.warnings) {
body += `- :warning: ${w}\n`;
}
body += '\n';
}
body += 'See the [company profile format docs](https://github.com/remoteintech/remote-jobs/blob/main/CLAUDE.md#company-profiles) for reference.\n';
}
}
if (!body) return;
// Find and update existing bot comment, or create new one
const marker = '<!-- validate-companies -->';
body = marker + '\n' + body;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
env:
VALIDATION_JSON: ${{ steps.validate.outputs.json }}
EXIT_CODE: ${{ steps.validate.outputs.exit_code }}
- name: Fail if validation errors
if: steps.validate.outputs.exit_code == '1'
run: |
echo "Validation failed. See the PR comment for details."
exit 1