@codepassion/skills 1.1.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/SKILLS.md +6 -1
- package/bin/c9n-skills.js +28 -3
- package/install.sh +25 -2
- package/package.json +1 -1
- package/skills/c9n/SKILL.md +344 -0
- package/skills/c9n-deliverables/REFERENCE.md +97 -0
- package/skills/c9n-deliverables/SKILL.md +63 -0
- package/skills/c9n-deliverables/scripts/data-dictionary.cjs +49 -0
- package/skills/c9n-deliverables/scripts/export-openapi.cjs +35 -0
- package/skills/c9n-deliverables/scripts/md-to-pdf.mjs +73 -0
- package/skills/c9n-deliverables/scripts/openapi-to-pdf.mjs +78 -0
- package/skills/c9n-deliverables/scripts/snapshot-source.sh +23 -0
- package/skills/c9n-pipeline/SKILL.md +128 -0
- package/skills/c9n-pipeline/templates/Dockerfile.api +49 -0
- package/skills/c9n-pipeline/templates/Dockerfile.service +45 -0
- package/skills/c9n-pipeline/templates/Dockerfile.web +50 -0
- package/skills/c9n-pipeline/templates/dockerignore +18 -0
- package/skills/c9n-pipeline/templates/workflows/_build-job.partial.yml +13 -0
- package/skills/c9n-pipeline/templates/workflows/_deploy-job.partial.yml +17 -0
- package/skills/c9n-pipeline/templates/workflows/_migration-check.partial.yml +16 -0
- package/skills/c9n-pipeline/templates/workflows/ci.yml +38 -0
- package/skills/c9n-pipeline/templates/workflows/deployment.yml +21 -0
- package/skills/c9n-sentry/REFERENCE.md +172 -0
- package/skills/c9n-sentry/SKILL.md +53 -0
- package/skills/c9n-spec/SKILL.md +92 -0
- package/skills/c9n-spec/templates/.github/pull_request_template.md +29 -0
- package/skills/c9n-spec/templates/AGENTS.md +179 -0
- package/skills/c9n-spec/templates/CLAUDE.md +6 -0
- package/skills/c9n-spec/templates/CONTEXT.md +53 -0
- package/skills/c9n-spec/templates/DESIGN.md +73 -0
- package/skills/c9n-spec/templates/docs/SRS.md +37 -0
- package/skills/c9n-spec/templates/docs/adr/0001-spec-driven-iso29110-docs.md +44 -0
- package/skills/c9n-spec/templates/docs/adr/_TEMPLATE.md +26 -0
- package/skills/c9n-spec/templates/docs/agents/domain.md +43 -0
- package/skills/c9n-spec/templates/docs/agents/issue-tracker.md +22 -0
- package/skills/c9n-spec/templates/docs/agents/triage-labels.md +27 -0
- package/skills/c9n-spec/templates/docs/architecture.md +50 -0
- package/skills/c9n-spec/templates/docs/features/_TEMPLATE.md +34 -0
- package/skills/c9n-spec/templates/docs/test-plan.md +30 -0
- package/skills/c9n-spec/templates/docs/traceability.md +27 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Generate a data dictionary (Markdown) from a live MySQL/MariaDB INFORMATION_SCHEMA.
|
|
2
|
+
// Schema only — no row data. Requires `mysql2` resolvable (run from a project that has it).
|
|
3
|
+
//
|
|
4
|
+
// Usage: node data-dictionary.cjs <output.md>
|
|
5
|
+
// Env (or a .env loaded via dotenv): SQL_HOST SQL_PORT SQL_USERNAME SQL_PASSWORD SQL_DATABASE_1
|
|
6
|
+
// Adjust the env var names below to match the target project's config.
|
|
7
|
+
const mysql = require('mysql2/promise')
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
|
|
10
|
+
const OUT = process.argv[2]
|
|
11
|
+
if (!OUT) { console.error('usage: node data-dictionary.cjs <output.md>'); process.exit(2) }
|
|
12
|
+
try { require('dotenv').config({ path: process.env.ENV_FILE || '.env' }) } catch {}
|
|
13
|
+
|
|
14
|
+
const cfg = {
|
|
15
|
+
host: process.env.SQL_HOST || process.env.DB_HOST,
|
|
16
|
+
port: +(process.env.SQL_PORT || process.env.DB_PORT || 3306),
|
|
17
|
+
user: process.env.SQL_USERNAME || process.env.DB_USER,
|
|
18
|
+
password: process.env.SQL_PASSWORD || process.env.DB_PASSWORD,
|
|
19
|
+
database: process.env.SQL_DATABASE_1 || process.env.DB_NAME,
|
|
20
|
+
}
|
|
21
|
+
const esc = (s) => String(s ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ')
|
|
22
|
+
|
|
23
|
+
;(async () => {
|
|
24
|
+
const c = await mysql.createConnection({ ...cfg, connectTimeout: 10000 })
|
|
25
|
+
const [tables] = await c.query(
|
|
26
|
+
`SELECT TABLE_NAME, TABLE_COMMENT,
|
|
27
|
+
(SELECT COUNT(*) FROM information_schema.columns col WHERE col.table_schema=t.table_schema AND col.table_name=t.table_name) COLS
|
|
28
|
+
FROM information_schema.tables t WHERE t.table_schema=? AND t.table_type='BASE TABLE' ORDER BY t.table_name`, [cfg.database])
|
|
29
|
+
const [cols] = await c.query(
|
|
30
|
+
`SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, EXTRA, COLUMN_COMMENT
|
|
31
|
+
FROM information_schema.columns WHERE table_schema=? ORDER BY TABLE_NAME, ORDINAL_POSITION`, [cfg.database])
|
|
32
|
+
const byTable = {}
|
|
33
|
+
for (const col of cols) (byTable[col.TABLE_NAME] ||= []).push(col)
|
|
34
|
+
|
|
35
|
+
let md = `# Data Dictionary\n\nGenerated from the live database schema (\`INFORMATION_SCHEMA\`). **${tables.length} tables.**\n\n## Tables\n\n`
|
|
36
|
+
for (const t of tables) md += `- [${t.TABLE_NAME}](#${t.TABLE_NAME.toLowerCase()}) (${t.COLS} columns)\n`
|
|
37
|
+
md += `\n---\n\n`
|
|
38
|
+
for (const t of tables) {
|
|
39
|
+
md += `### ${t.TABLE_NAME}\n\n`
|
|
40
|
+
if (t.TABLE_COMMENT) md += `${esc(t.TABLE_COMMENT)}\n\n`
|
|
41
|
+
md += `| Column | Type | Null | Key | Default | Extra | Comment |\n|--------|------|------|-----|---------|-------|--------|\n`
|
|
42
|
+
for (const col of byTable[t.TABLE_NAME] || [])
|
|
43
|
+
md += `| ${esc(col.COLUMN_NAME)} | \`${esc(col.COLUMN_TYPE)}\` | ${col.IS_NULLABLE} | ${esc(col.COLUMN_KEY)} | ${col.COLUMN_DEFAULT === null ? '' : esc(col.COLUMN_DEFAULT)} | ${esc(col.EXTRA)} | ${esc(col.COLUMN_COMMENT)} |\n`
|
|
44
|
+
md += `\n`
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(OUT, md)
|
|
47
|
+
console.log(`wrote ${OUT} — ${tables.length} tables, ${cols.length} columns`)
|
|
48
|
+
await c.end()
|
|
49
|
+
})().catch((e) => { console.error('FAILED:', e.code || e.message); process.exit(1) })
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Export the OpenAPI/Swagger spec from a NestJS app WITHOUT starting the HTTP server.
|
|
2
|
+
// This is a TEMPLATE — adapt the DocumentBuilder block to match the project's main.ts.
|
|
3
|
+
//
|
|
4
|
+
// Usage: node export-openapi.cjs <output.json>
|
|
5
|
+
//
|
|
6
|
+
// IMPORTANT notes learned the hard way:
|
|
7
|
+
// - Run against the COMPILED build (`nest build` -> dist), not ts-node. ts-node
|
|
8
|
+
// transpile-only mis-emits decorator metadata and TypeORM entity validation fails.
|
|
9
|
+
// - App bootstrap may require real resources (DB reachable, credential files present).
|
|
10
|
+
// Booting connects to the DB; use a dev DB, never production.
|
|
11
|
+
// - Match enableVersioning() and the DocumentBuilder config to the project's main.ts.
|
|
12
|
+
const { NestFactory } = require('@nestjs/core')
|
|
13
|
+
const { SwaggerModule, DocumentBuilder } = require('@nestjs/swagger')
|
|
14
|
+
const { VersioningType } = require('@nestjs/common')
|
|
15
|
+
const fs = require('fs')
|
|
16
|
+
const path = require('path')
|
|
17
|
+
require('reflect-metadata')
|
|
18
|
+
try { require('dotenv').config({ path: process.env.ENV_FILE || '.env' }) } catch {}
|
|
19
|
+
|
|
20
|
+
const OUT = process.argv[2] || 'openapi.json'
|
|
21
|
+
// ADAPT: path to the compiled AppModule
|
|
22
|
+
const { AppModule } = require(path.resolve(process.cwd(), 'dist/src/app.module'))
|
|
23
|
+
|
|
24
|
+
;(async () => {
|
|
25
|
+
const app = await NestFactory.create(AppModule, { logger: ['error', 'warn'] })
|
|
26
|
+
// ADAPT: copy enableVersioning() from the project's main.ts if it uses versioning
|
|
27
|
+
app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1', prefix: 'api/v' })
|
|
28
|
+
// ADAPT: mirror the project's DocumentBuilder (title, version, auth schemes)
|
|
29
|
+
const config = new DocumentBuilder().setTitle('API').setVersion('1.0').build()
|
|
30
|
+
const document = SwaggerModule.createDocument(app, config)
|
|
31
|
+
fs.writeFileSync(path.resolve(process.cwd(), OUT), JSON.stringify(document, null, 2))
|
|
32
|
+
console.log(`wrote ${OUT} — ${Object.keys(document.paths || {}).length} paths`)
|
|
33
|
+
await app.close()
|
|
34
|
+
process.exit(0)
|
|
35
|
+
})().catch((e) => { console.error('FAILED:', e?.message || e); process.exit(1) })
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Render a Markdown file to PDF via a headless Chromium browser.
|
|
2
|
+
// Handles Mermaid code fences (```mermaid) by rendering them to SVG client-side.
|
|
3
|
+
//
|
|
4
|
+
// Usage: node md-to-pdf.mjs <input.md> <output.pdf>
|
|
5
|
+
//
|
|
6
|
+
// No install required if a Chromium-family browser is present (Brave/Chrome/Edge/Chromium).
|
|
7
|
+
// Mermaid + marked load from CDN, so the machine needs internet for diagram rendering.
|
|
8
|
+
//
|
|
9
|
+
// SECURITY: input is expected to be FIRST-PARTY documentation you generated for the
|
|
10
|
+
// delivery (the project's own docs), not untrusted third-party Markdown. Mermaid runs with
|
|
11
|
+
// securityLevel:'strict' (no click handlers / inline HTML in diagrams). Markdown is still
|
|
12
|
+
// rendered with raw HTML allowed — do not feed this untrusted Markdown.
|
|
13
|
+
//
|
|
14
|
+
// NOTE: For very WIDE diagrams (many nodes in one row), prefer `flowchart TB` / vertical
|
|
15
|
+
// ER layouts in the source — wide diagrams scale down to an illegible strip at page width.
|
|
16
|
+
import { readFileSync, writeFileSync, mkdtempSync, existsSync } from 'node:fs'
|
|
17
|
+
import { tmpdir } from 'node:os'
|
|
18
|
+
import { join, basename } from 'node:path'
|
|
19
|
+
import { execFileSync } from 'node:child_process'
|
|
20
|
+
|
|
21
|
+
const [src, out] = process.argv.slice(2)
|
|
22
|
+
if (!src || !out) { console.error('usage: node md-to-pdf.mjs <input.md> <output.pdf>'); process.exit(2) }
|
|
23
|
+
|
|
24
|
+
const BROWSERS = [
|
|
25
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
26
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
27
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
28
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
29
|
+
'/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
30
|
+
]
|
|
31
|
+
const browser = BROWSERS.find(existsSync)
|
|
32
|
+
if (!browser) { console.error('No Chromium-family browser found. Install Chrome/Brave/Chromium.'); process.exit(1) }
|
|
33
|
+
|
|
34
|
+
const md = readFileSync(src, 'utf8')
|
|
35
|
+
const html = `<!doctype html><html><head><meta charset="utf-8">
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
38
|
+
<style>
|
|
39
|
+
@page { margin: 18mm 16mm; }
|
|
40
|
+
body { font: 14px/1.6 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; color:#1a1a1a; max-width:820px; margin:0 auto; }
|
|
41
|
+
h1 { font-size:26px; border-bottom:2px solid #222; padding-bottom:6px; }
|
|
42
|
+
h2 { font-size:20px; margin-top:28px; border-bottom:1px solid #ddd; padding-bottom:4px; }
|
|
43
|
+
h3 { font-size:16px; margin-top:20px; }
|
|
44
|
+
table { border-collapse:collapse; width:100%; margin:12px 0; font-size:13px; }
|
|
45
|
+
th,td { border:1px solid #ccc; padding:6px 10px; text-align:left; vertical-align:top; }
|
|
46
|
+
th { background:#f4f4f4; }
|
|
47
|
+
code { background:#f0f0f0; padding:1px 5px; border-radius:3px; font-size:12px; }
|
|
48
|
+
pre { background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto; }
|
|
49
|
+
pre code { background:none; padding:0; }
|
|
50
|
+
.mermaid { text-align:center; margin:16px 0; }
|
|
51
|
+
blockquote { border-left:4px solid #ccc; margin:12px 0; padding:4px 14px; color:#555; }
|
|
52
|
+
</style></head><body>
|
|
53
|
+
<div id="out"></div>
|
|
54
|
+
<script type="text/markdown" id="src">${md.replace(/<\/script>/g, '<\\/script>')}</script>
|
|
55
|
+
<script>
|
|
56
|
+
document.getElementById('out').innerHTML = marked.parse(document.getElementById('src').textContent);
|
|
57
|
+
document.querySelectorAll('code.language-mermaid').forEach(c => {
|
|
58
|
+
const d = document.createElement('div'); d.className='mermaid'; d.textContent=c.textContent;
|
|
59
|
+
c.closest('pre').replaceWith(d);
|
|
60
|
+
});
|
|
61
|
+
mermaid.initialize({ startOnLoad:false, securityLevel:'strict' });
|
|
62
|
+
mermaid.run();
|
|
63
|
+
</script></body></html>`
|
|
64
|
+
|
|
65
|
+
const tmp = mkdtempSync(join(tmpdir(), 'md2pdf-'))
|
|
66
|
+
const htmlPath = join(tmp, basename(src) + '.html')
|
|
67
|
+
writeFileSync(htmlPath, html)
|
|
68
|
+
execFileSync(browser, [
|
|
69
|
+
'--headless', '--disable-gpu', '--no-pdf-header-footer',
|
|
70
|
+
'--run-all-compositor-stages-before-draw', '--virtual-time-budget=20000',
|
|
71
|
+
`--print-to-pdf=${out}`, `file://${htmlPath}`,
|
|
72
|
+
], { stdio: 'inherit' })
|
|
73
|
+
console.log('wrote', out)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Render an OpenAPI 3 spec to a print-friendly PDF (no lazy loading — full content,
|
|
2
|
+
// unlike Redoc/Swagger UI which lazy-render and truncate when printed).
|
|
3
|
+
//
|
|
4
|
+
// Usage: node openapi-to-pdf.mjs <openapi.json> <output.pdf> [guide.md]
|
|
5
|
+
// guide.md (optional) is prepended as an intro section before the endpoint reference.
|
|
6
|
+
//
|
|
7
|
+
// SECURITY: the spec and guide.md are expected to be FIRST-PARTY artifacts you generated
|
|
8
|
+
// for the delivery, not untrusted input. The guide Markdown is rendered with raw HTML
|
|
9
|
+
// allowed — do not pass an untrusted guide file.
|
|
10
|
+
import { readFileSync, writeFileSync, mkdtempSync, existsSync } from 'node:fs'
|
|
11
|
+
import { tmpdir } from 'node:os'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import { execFileSync } from 'node:child_process'
|
|
14
|
+
|
|
15
|
+
const [specPath, out, guidePath] = process.argv.slice(2)
|
|
16
|
+
if (!specPath || !out) { console.error('usage: node openapi-to-pdf.mjs <openapi.json> <output.pdf> [guide.md]'); process.exit(2) }
|
|
17
|
+
|
|
18
|
+
const BROWSERS = [
|
|
19
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
20
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
21
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
22
|
+
'/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
23
|
+
]
|
|
24
|
+
const browser = BROWSERS.find(existsSync)
|
|
25
|
+
if (!browser) { console.error('No Chromium-family browser found.'); process.exit(1) }
|
|
26
|
+
|
|
27
|
+
const spec = JSON.parse(readFileSync(specPath, 'utf8'))
|
|
28
|
+
const guideMd = guidePath && existsSync(guidePath) ? readFileSync(guidePath, 'utf8').replace(/^# .*\r?\n/, '') : ''
|
|
29
|
+
const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]))
|
|
30
|
+
const refName = (r) => (r?.$ref ? r.$ref.split('/').pop() : null)
|
|
31
|
+
const schemaType = (s) => !s ? '' : s.$ref ? refName(s) : s.type === 'array' ? `${schemaType(s.items)}[]` : s.enum ? `enum(${s.enum.map(esc).join(', ')})` : s.format ? `${s.type}<${s.format}>` : (s.type || 'object')
|
|
32
|
+
|
|
33
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete']
|
|
34
|
+
const groups = {}
|
|
35
|
+
for (const [p, item] of Object.entries(spec.paths || {}))
|
|
36
|
+
for (const m of methods) { const op = item[m]; if (!op) continue; const tag = (op.tags && op.tags[0]) || 'default'; (groups[tag] ||= []).push({ m, p, op }) }
|
|
37
|
+
|
|
38
|
+
const pTable = (ps = []) => !ps.length ? '' : `<table class="p"><thead><tr><th>Name</th><th>In</th><th>Req</th><th>Type</th></tr></thead><tbody>${ps.map((pa) => `<tr><td><code>${esc(pa.name)}</code></td><td>${esc(pa.in)}</td><td>${pa.required ? 'yes' : 'no'}</td><td>${esc(schemaType(pa.schema))}</td></tr>`).join('')}</tbody></table>`
|
|
39
|
+
const bBlock = (rb) => { if (!rb) return ''; const j = rb.content && (rb.content['application/json'] || Object.values(rb.content)[0]); return `<div class="body"><span class="lbl">Request body${rb.required ? ' (required)' : ''}:</span> <code>${esc(j ? schemaType(j.schema) : 'object')}</code></div>` }
|
|
40
|
+
const rBlock = (r = {}) => { const rows = Object.entries(r).map(([c, v]) => `<tr><td><code>${esc(c)}</code></td><td>${esc(v.description || '')}</td></tr>`).join(''); return rows ? `<table class="p"><thead><tr><th>Status</th><th>Description</th></tr></thead><tbody>${rows}</tbody></table>` : '' }
|
|
41
|
+
|
|
42
|
+
let body = ''
|
|
43
|
+
for (const tag of Object.keys(groups).sort()) {
|
|
44
|
+
body += `<h2>${esc(tag)}</h2>`
|
|
45
|
+
for (const { m, p, op } of groups[tag])
|
|
46
|
+
body += `<div class="op"><div class="line"><span class="m ${m}">${m.toUpperCase()}</span><code class="path">${esc(p)}</code></div>${op.summary ? `<div class="sum">${esc(op.summary)}</div>` : ''}${op.description ? `<div class="desc">${esc(op.description)}</div>` : ''}${pTable(op.parameters)}${bBlock(op.requestBody)}${rBlock(op.responses)}</div>`
|
|
47
|
+
}
|
|
48
|
+
const opCount = Object.values(spec.paths || {}).reduce((n, it) => n + methods.filter((m) => it[m]).length, 0)
|
|
49
|
+
|
|
50
|
+
const html = `<!doctype html><html><head><meta charset="utf-8"><style>
|
|
51
|
+
@page { margin: 16mm 14mm; }
|
|
52
|
+
body { font: 12px/1.5 -apple-system,Segoe UI,Roboto,Arial,sans-serif; color:#1a1a1a; max-width:860px; margin:0 auto; }
|
|
53
|
+
h1 { font-size:24px; border-bottom:2px solid #222; padding-bottom:6px; }
|
|
54
|
+
h2 { font-size:18px; margin:26px 0 10px; border-bottom:1px solid #ddd; padding-bottom:4px; break-after:avoid; }
|
|
55
|
+
.op { border:1px solid #e3e3e3; border-radius:6px; padding:10px 12px; margin:10px 0; break-inside:avoid; }
|
|
56
|
+
.line { display:flex; align-items:center; gap:8px; }
|
|
57
|
+
.m { color:#fff; font-weight:700; font-size:11px; padding:2px 8px; border-radius:4px; }
|
|
58
|
+
.m.get{background:#2e8b57}.m.post{background:#1f6feb}.m.put{background:#b8860b}.m.patch{background:#8250df}.m.delete{background:#cf222e}
|
|
59
|
+
.sum { font-weight:600; margin:6px 0 2px; } .desc { color:#555; margin:2px 0; }
|
|
60
|
+
table.p { border-collapse:collapse; width:100%; margin:8px 0; font-size:11px; }
|
|
61
|
+
table.p th, table.p td { border:1px solid #ddd; padding:4px 8px; text-align:left; } table.p th { background:#f5f5f5; }
|
|
62
|
+
.body { margin:6px 0; } .lbl { color:#555; } code { background:#f0f0f0; padding:1px 4px; border-radius:3px; }
|
|
63
|
+
.meta { color:#555; margin-bottom:18px; } .section-break { break-before:page; }
|
|
64
|
+
.guide table { border-collapse:collapse; width:100%; margin:8px 0; font-size:12px; }
|
|
65
|
+
.guide th, .guide td { border:1px solid #ccc; padding:5px 9px; text-align:left; } .guide th { background:#f4f4f4; }
|
|
66
|
+
.guide blockquote { border-left:4px solid #ccc; margin:10px 0; padding:4px 14px; color:#555; }
|
|
67
|
+
</style>${guideMd ? '<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>' : ''}</head><body>
|
|
68
|
+
<h1>${esc(spec.info?.title || 'API')} — API Documentation</h1>
|
|
69
|
+
<div class="meta">Version ${esc(spec.info?.version || '')} · OpenAPI ${esc(spec.openapi || '')} · ${Object.keys(spec.paths || {}).length} paths · ${opCount} operations</div>
|
|
70
|
+
${guideMd ? `<div class="guide" id="guide"></div><script type="text/markdown" id="g">${guideMd.replace(/<\/script>/g, '<\\/script>')}</script><script>document.getElementById('guide').innerHTML=marked.parse(document.getElementById('g').textContent)</script><h1 class="section-break">Endpoint Reference</h1>` : ''}
|
|
71
|
+
${body}
|
|
72
|
+
</body></html>`
|
|
73
|
+
|
|
74
|
+
const tmp = mkdtempSync(join(tmpdir(), 'oa2pdf-'))
|
|
75
|
+
const htmlPath = join(tmp, 'api.html')
|
|
76
|
+
writeFileSync(htmlPath, html)
|
|
77
|
+
execFileSync(browser, ['--headless', '--disable-gpu', '--no-pdf-header-footer', '--run-all-compositor-stages-before-draw', '--virtual-time-budget=15000', `--print-to-pdf=${out}`, `file://${htmlPath}`], { stdio: 'inherit' })
|
|
78
|
+
console.log('wrote', out)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Produce a clean source-code snapshot using git archive.
|
|
3
|
+
# git archive only includes tracked files — node_modules, .env, dist, secrets are
|
|
4
|
+
# excluded automatically (assuming they are gitignored / untracked).
|
|
5
|
+
#
|
|
6
|
+
# Usage: snapshot-source.sh <repo-root> <output.zip>
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
REPO_ROOT="${1:-$PWD}"
|
|
10
|
+
OUT="${2:?usage: snapshot-source.sh <repo-root> <output.zip>}"
|
|
11
|
+
|
|
12
|
+
# Resolve a relative <output.zip> against the caller's cwd, then use `git -C` so we
|
|
13
|
+
# never change the caller's working directory. (A bare relative --output under
|
|
14
|
+
# `git -C`/`cd` would surprisingly land inside the repo root.)
|
|
15
|
+
case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac
|
|
16
|
+
|
|
17
|
+
if [ ! -d "$REPO_ROOT/.git" ]; then
|
|
18
|
+
echo "Error: $REPO_ROOT is not a git repository." >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
git -C "$REPO_ROOT" archive --format=zip --output "$OUT" HEAD
|
|
23
|
+
echo "Source snapshot written to: $OUT"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: c9n-pipeline
|
|
3
|
+
description: Bootstrap the C9N CI/CD pipeline INTO a Turborepo/Bun monorepo — generates the per-repo GitHub Actions (CI + tag-driven Deployment that call the org codepassion-team/cicd reusable workflows), per-app Dockerfiles (Elysia→bun-compile, Next.js standalone+runtime-env, generic service), and .dockerignore; then walks the developer through the out-of-repo ops (GitHub Environments, Coolify resources, ghcr pull, the PORT=3000 web footgun) and verifies the live deploy. Use when setting up CI/CD on a new C9N project, "add github actions", "set up deployment", "wire up Coolify", or porting the Smiley pipeline to another repo. Complements c9n-semver-deployment (owns the tag->env mapping) and c9n-spec (scaffolds docs, not pipelines).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Bootstraps the Code Passion (C9N) CI/CD pipeline into a target repo so a new project deploys the
|
|
7
|
+
same way as the reference implementation (Smiley): **one image per app, built once at the Alpha
|
|
8
|
+
tag, redeployed to Beta/Production by base tag**, env injected at runtime by Coolify. The heavy
|
|
9
|
+
lifting lives in the org reusable workflows `codepassion-team/cicd/.github/workflows/{build,deploy}.yml`
|
|
10
|
+
— the per-repo files this skill generates just call them. The skill's real value is **Phase 3**:
|
|
11
|
+
the interactive ops checklist that encodes every footgun the Smiley setup hit.
|
|
12
|
+
|
|
13
|
+
Bundled `templates/` are the source skeleton. **Copy them, then substitute tokens** — never edit
|
|
14
|
+
the templates in place for a specific project. The Next.js wiring is an **edit** of files the dev
|
|
15
|
+
already owns, not a file drop (see Phase 2).
|
|
16
|
+
|
|
17
|
+
## What gets generated
|
|
18
|
+
|
|
19
|
+
| Path | Template | When |
|
|
20
|
+
|------|----------|------|
|
|
21
|
+
| `<app>/Dockerfile` | `Dockerfile.api` | app has `elysia` |
|
|
22
|
+
| `<app>/Dockerfile` | `Dockerfile.web` | app has `next` |
|
|
23
|
+
| `<app>/Dockerfile` | `Dockerfile.service` | any other deployable app (worker, plain Bun) |
|
|
24
|
+
| `.dockerignore` | `dockerignore` | always (repo root) |
|
|
25
|
+
| `.github/workflows/ci.yml` | `workflows/ci.yml` (+ `_migration-check.partial` if DB) | always |
|
|
26
|
+
| `.github/workflows/deployment.yml` | `workflows/deployment.yml` (+ `_build-job`/`_deploy-job` partials) | always |
|
|
27
|
+
| `<web>/next.config.js` | **edit, not copy** | per web app |
|
|
28
|
+
| `<web>/app/layout.tsx` | **edit, not copy** | per web app |
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
Run in order; this is a table of contents — rules are below.
|
|
33
|
+
|
|
34
|
+
1. **Detect (Phase 1).** Scan `apps/*/package.json`. Classify each: `next`→web, `elysia`→api, else
|
|
35
|
+
→service. Detect DB by **real artifacts** — a migration-generate script in some `package.json`
|
|
36
|
+
AND a migrations dir on disk — capture their actual names; never assume `drizzle:generate` /
|
|
37
|
+
`packages/supabase/migrations`. Read root `package.json` for the Bun version / lockfile name.
|
|
38
|
+
Ask which environments exist (Alpha / Beta / Production). **Print the detected plan and confirm
|
|
39
|
+
before writing anything.**
|
|
40
|
+
2. **Generate (Phase 2).** Copy templates with tokens substituted. **Diff/confirm per file** — if a
|
|
41
|
+
target exists, show the diff and ask overwrite/skip/merge; never blind-clobber; leave unrelated
|
|
42
|
+
files (a hand-kept `staging.yaml`) alone. Then do the **web edits** (below). Run the web build
|
|
43
|
+
once to prove nothing is baked.
|
|
44
|
+
3. **Checklist (Phase 3).** Walk the dev interactively through the out-of-repo ops (below). This is
|
|
45
|
+
the deliverable.
|
|
46
|
+
4. **Verify (Phase 4).** After the dev confirms the ops are done, push a tag (or reuse one), watch
|
|
47
|
+
the Actions run, then `curl` each deployed URL per environment and report pass/fail per app.
|
|
48
|
+
|
|
49
|
+
## Token substitution
|
|
50
|
+
|
|
51
|
+
| Token | Source |
|
|
52
|
+
|-------|--------|
|
|
53
|
+
| `{{PACKAGE_NAME}}` | `<project>-<appkey>`, e.g. `smiley-web`. Lowercase. This is the ghcr image name and the `package_name` workflow input. |
|
|
54
|
+
| `{{APP_DIR}}` | The app's path, e.g. `apps/web`. |
|
|
55
|
+
| `{{APP_KEY}}` | Short lowercase id unique per app: `api`, `web`, `worker`. Used in job names. |
|
|
56
|
+
| `{{APP_PORT}}` | `3001` api · `3000` web · service's listen port (drop `EXPOSE` for a portless worker). |
|
|
57
|
+
| `{{BUN_VERSION}}` | Pin from root toolchain, e.g. `1.3.10`. Same across all Dockerfiles. |
|
|
58
|
+
| `{{WORKSPACE_MANIFEST_COPIES}}` | **web template only:** one `COPY <app>/package.json ./<app>/package.json` line **per app** — the `deps` stage runs `bun install` before `COPY . .`, so it needs every workspace manifest to resolve the graph. The api/service compile templates copy only the built app's manifest (matches Smiley's api Dockerfile; avoids pulling a sibling app's postinstall without its src). |
|
|
59
|
+
| `{{MIGRATION_CHECK}}` | `_migration-check.partial.yml` with `{{MIGRATION_SCRIPT}}`/`{{MIGRATIONS_DIR}}` filled, **or empty string** if no DB. |
|
|
60
|
+
| `{{ORG_REF}}` | The org reusable-workflow ref — currently `turborepo`. One place; bump here if the org repo retags. |
|
|
61
|
+
| `{{BUILD_JOBS}}` / `{{DEPLOY_JOBS}}` | Stamp `_build-job.partial` once per app (Alpha only) and `_deploy-job.partial` once per app per environment, pruning unused envs. |
|
|
62
|
+
| `{{DEPLOY_NAME}}` | The org `deploy.yml` `name` input → uppercased to `<NAME>_UUID`. **Only `API` and `WEB` are wired** in the org deploy.yml (see Gotchas). |
|
|
63
|
+
|
|
64
|
+
## Web edits (surgical — not a file drop)
|
|
65
|
+
|
|
66
|
+
For each **web** app, edit the files the dev owns; fail loudly if they can't be parsed rather than
|
|
67
|
+
overwrite:
|
|
68
|
+
|
|
69
|
+
- `next.config.js` — add `output: "standalone"`, `outputFileTracingRoot` (monorepo root), and
|
|
70
|
+
`transpilePackages` for the workspace packages the app imports.
|
|
71
|
+
- `app/layout.tsx` — add `<head><PublicEnvScript /></head>` (`import { PublicEnvScript } from
|
|
72
|
+
"next-runtime-env"`). Client code reads public config with `env("NEXT_PUBLIC_X")`, **not**
|
|
73
|
+
`process.env` (which bakes at build → undefined at runtime).
|
|
74
|
+
- Flag `next-runtime-env` as a dependency to add — **do not run `bun add` yourself**; tell the dev.
|
|
75
|
+
- **Caveat:** runtime values only reach the browser on non-statically-prerendered pages. A page that
|
|
76
|
+
reads runtime public env must opt out of static caching (`export const dynamic = "force-dynamic"`)
|
|
77
|
+
or rely on `<PublicEnvScript>` keeping the document dynamic. Note this so future feature work
|
|
78
|
+
doesn't silently bake a static page.
|
|
79
|
+
|
|
80
|
+
## Phase 3 — interactive ops checklist (the deliverable)
|
|
81
|
+
|
|
82
|
+
Derived from the actual `codepassion-team/cicd` reusable workflows. Walk these one at a time:
|
|
83
|
+
|
|
84
|
+
**Repo prerequisites**
|
|
85
|
+
- A **`develop` branch must exist** — `build.yml` commits the version bump and does
|
|
86
|
+
`git pull --rebase origin develop` / `push HEAD:develop`. No develop branch → build fails.
|
|
87
|
+
- Tags drive deploys; the exact tag→env mapping is **c9n-semver-deployment** (don't restate it).
|
|
88
|
+
|
|
89
|
+
**Per environment** — create a GitHub **Environment** named exactly `Alpha` / `Beta` / `Production`:
|
|
90
|
+
- Variables: `COOLIFY_RESOURCE_API_UUID`, `COOLIFY_RESOURCE_WEB_UUID` (the Coolify app UUIDs;
|
|
91
|
+
`deploy.yml` resolves `${NAME}_UUID`), and `TURBO_TEAM`.
|
|
92
|
+
- Secrets: `COOLIFY_TOKEN` (Coolify API bearer) and `TURBO_TOKEN`. `GITHUB_TOKEN` is automatic
|
|
93
|
+
(ghcr push uses it; the build job already declares `packages: write`).
|
|
94
|
+
|
|
95
|
+
**Coolify** (one resource per app per env): source = Docker image
|
|
96
|
+
`ghcr.io/codepassion-team/{{PACKAGE_NAME}}:<base_tag>`; copy each resource's UUID into the matching
|
|
97
|
+
env variable above. Caddy `reverse_proxy <app>:<port>` (api `:3001`, web `:3000`).
|
|
98
|
+
|
|
99
|
+
**ghcr** — packages are **private**: give Coolify a registry pull credential (or set package
|
|
100
|
+
visibility), or the image pull silently fails.
|
|
101
|
+
|
|
102
|
+
**Web runtime env in Coolify** (the scars):
|
|
103
|
+
- `PORT=3000` — Next standalone obeys `PORT`; Coolify defaults it to `80`, which 502s behind a
|
|
104
|
+
Caddy `:3000` proxy. This is the single most common web-deploy failure.
|
|
105
|
+
- `HOSTNAME=0.0.0.0`.
|
|
106
|
+
- The app's `NEXT_PUBLIC_*` values — injected here at runtime, not baked into the image.
|
|
107
|
+
|
|
108
|
+
## Phase 4 — verify
|
|
109
|
+
|
|
110
|
+
Push a `v*.*.*` tag (or reuse one), watch the run (`gh run watch`), then for each app per env
|
|
111
|
+
`curl -so /dev/null -w "%{http_code}"` the deployed URL (e.g. `https://api.<proj>.alpha.c9n.co`,
|
|
112
|
+
`https://<proj>.alpha.c9n.co`) and confirm `200`. Report a clean pass/fail table. Don't call it done
|
|
113
|
+
on a green Actions run alone — the 502 class only shows at the live URL.
|
|
114
|
+
|
|
115
|
+
## Gotchas
|
|
116
|
+
|
|
117
|
+
- **Combined install mode does not carry `templates/`.** `install.sh combined` copies only the
|
|
118
|
+
generated `skills/c9n` SKILL.md body. Install via `install.sh claude` (or `project`/`codex`) so the
|
|
119
|
+
templates come along — same limitation as `c9n-spec`/`c9n-deliverables`.
|
|
120
|
+
- **Org deploy.yml wires only `API_UUID` + `WEB_UUID`.** A third app type (worker) needs a PR to
|
|
121
|
+
`codepassion-team/cicd` adding `COOLIFY_RESOURCE_<NAME>_UUID` to deploy.yml's `env:` block. Flag
|
|
122
|
+
this; don't generate a deploy job that silently can't resolve its UUID.
|
|
123
|
+
- **Build once, deploy by base tag.** build.yml strips `-alpha`/`-rc.*`; deploy.yml also strips
|
|
124
|
+
`-beta.*`. So `v1.2.3` (Alpha) and a later `v1.2.3-beta.1` release both resolve base tag `v1.2.3`
|
|
125
|
+
and redeploy the same image. Beta/Prod must have an Alpha build of that base version in ghcr first.
|
|
126
|
+
- **`.dockerignore` matters.** Without it, the host's `node_modules` (wrong platform) leaks into the
|
|
127
|
+
build context and clobbers the linux deps → "failed to load next.config". The template ships it.
|
|
128
|
+
- **Don't touch `.env*`.** Generate `.dockerignore` to exclude them; never read or write env files.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# {{PACKAGE_NAME}} (Elysia BFF) — compiled to a single Bun binary, run on
|
|
3
|
+
# debian slim. Generated by c9n-pipeline; tune freely after.
|
|
4
|
+
# -----------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
FROM oven/bun:{{BUN_VERSION}} AS build
|
|
7
|
+
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
|
|
10
|
+
# Root manifests + lockfile
|
|
11
|
+
COPY package.json bun.lock ./
|
|
12
|
+
|
|
13
|
+
# This app's manifest + shared packages (compile needs only this member's graph)
|
|
14
|
+
COPY {{APP_DIR}}/package.json ./{{APP_DIR}}/package.json
|
|
15
|
+
COPY packages/ ./packages/
|
|
16
|
+
|
|
17
|
+
# Source needed to compile (copied before install so workspace links resolve)
|
|
18
|
+
COPY {{APP_DIR}}/src ./{{APP_DIR}}/src
|
|
19
|
+
|
|
20
|
+
RUN bun install
|
|
21
|
+
|
|
22
|
+
ENV NODE_ENV=production
|
|
23
|
+
|
|
24
|
+
# Compile the API to a standalone binary from the repo root
|
|
25
|
+
RUN bun build \
|
|
26
|
+
--compile \
|
|
27
|
+
--minify-whitespace \
|
|
28
|
+
--minify-syntax \
|
|
29
|
+
--target=bun \
|
|
30
|
+
--outfile /app/server \
|
|
31
|
+
./{{APP_DIR}}/src/index.ts
|
|
32
|
+
|
|
33
|
+
# Runtime image — debian slim for compiled-Bun-binary compatibility
|
|
34
|
+
FROM debian:bookworm-slim
|
|
35
|
+
|
|
36
|
+
WORKDIR /app
|
|
37
|
+
|
|
38
|
+
RUN apt-get update && \
|
|
39
|
+
apt-get install -y --no-install-recommends ca-certificates && \
|
|
40
|
+
rm -rf /var/lib/apt/lists/*
|
|
41
|
+
|
|
42
|
+
COPY --from=build /app/server ./server
|
|
43
|
+
RUN chmod +x ./server
|
|
44
|
+
|
|
45
|
+
ENV NODE_ENV=production
|
|
46
|
+
|
|
47
|
+
EXPOSE {{APP_PORT}}
|
|
48
|
+
|
|
49
|
+
CMD ["./server"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# {{PACKAGE_NAME}} (generic Bun service / worker) — compiled to a single Bun
|
|
3
|
+
# binary, run on debian slim. Generated by c9n-pipeline.
|
|
4
|
+
# A worker with no HTTP port: drop the EXPOSE line and skip its Coolify domain.
|
|
5
|
+
# NOTE: the org deploy.yml only wires API_UUID + WEB_UUID — a third app type
|
|
6
|
+
# needs a PR to codepassion-team/cicd to add its COOLIFY_RESOURCE_<NAME>_UUID.
|
|
7
|
+
# -----------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
FROM oven/bun:{{BUN_VERSION}} AS build
|
|
10
|
+
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
|
|
13
|
+
COPY package.json bun.lock ./
|
|
14
|
+
COPY {{APP_DIR}}/package.json ./{{APP_DIR}}/package.json
|
|
15
|
+
COPY packages/ ./packages/
|
|
16
|
+
COPY {{APP_DIR}}/src ./{{APP_DIR}}/src
|
|
17
|
+
|
|
18
|
+
RUN bun install
|
|
19
|
+
|
|
20
|
+
ENV NODE_ENV=production
|
|
21
|
+
|
|
22
|
+
RUN bun build \
|
|
23
|
+
--compile \
|
|
24
|
+
--minify-whitespace \
|
|
25
|
+
--minify-syntax \
|
|
26
|
+
--target=bun \
|
|
27
|
+
--outfile /app/server \
|
|
28
|
+
./{{APP_DIR}}/src/index.ts
|
|
29
|
+
|
|
30
|
+
FROM debian:bookworm-slim
|
|
31
|
+
|
|
32
|
+
WORKDIR /app
|
|
33
|
+
|
|
34
|
+
RUN apt-get update && \
|
|
35
|
+
apt-get install -y --no-install-recommends ca-certificates && \
|
|
36
|
+
rm -rf /var/lib/apt/lists/*
|
|
37
|
+
|
|
38
|
+
COPY --from=build /app/server ./server
|
|
39
|
+
RUN chmod +x ./server
|
|
40
|
+
|
|
41
|
+
ENV NODE_ENV=production
|
|
42
|
+
|
|
43
|
+
EXPOSE {{APP_PORT}}
|
|
44
|
+
|
|
45
|
+
CMD ["./server"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# {{PACKAGE_NAME}} (Next.js app) — Bun multi-stage build.
|
|
3
|
+
# Public env is read at RUNTIME via next-runtime-env (<PublicEnvScript>), so this
|
|
4
|
+
# image bakes no NEXT_PUBLIC_* — Coolify injects them at run, like the API image.
|
|
5
|
+
# Generated by c9n-pipeline; see the ADR this skill points you to.
|
|
6
|
+
# -----------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
FROM oven/bun:{{BUN_VERSION}} AS base
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
|
|
11
|
+
# Install dependencies with bun
|
|
12
|
+
FROM base AS deps
|
|
13
|
+
COPY package.json bun.lock ./
|
|
14
|
+
{{WORKSPACE_MANIFEST_COPIES}}
|
|
15
|
+
COPY packages/ ./packages/
|
|
16
|
+
RUN bun install
|
|
17
|
+
|
|
18
|
+
# Rebuild the source code only when needed
|
|
19
|
+
FROM base AS builder
|
|
20
|
+
WORKDIR /app
|
|
21
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
22
|
+
COPY . .
|
|
23
|
+
|
|
24
|
+
# Next.js expects {{APP_DIR}}/.env to exist; intentionally empty — no public env
|
|
25
|
+
# is baked at build (it is injected at runtime via next-runtime-env).
|
|
26
|
+
RUN touch {{APP_DIR}}/.env
|
|
27
|
+
|
|
28
|
+
RUN cd {{APP_DIR}} && bun run build
|
|
29
|
+
|
|
30
|
+
# Production image — serve the Next.js standalone output
|
|
31
|
+
FROM base AS runner
|
|
32
|
+
WORKDIR /app
|
|
33
|
+
|
|
34
|
+
ENV NODE_ENV=production \
|
|
35
|
+
PORT={{APP_PORT}} \
|
|
36
|
+
HOSTNAME="0.0.0.0"
|
|
37
|
+
|
|
38
|
+
RUN groupadd --system --gid 1001 nodejs && \
|
|
39
|
+
useradd --system --uid 1001 nextjs
|
|
40
|
+
|
|
41
|
+
# Leverage Next.js output file tracing to keep the image small
|
|
42
|
+
COPY --from=builder --chown=nextjs:nodejs /app/{{APP_DIR}}/.next/standalone ./
|
|
43
|
+
COPY --from=builder --chown=nextjs:nodejs /app/{{APP_DIR}}/.next/static ./{{APP_DIR}}/.next/static
|
|
44
|
+
COPY --from=builder --chown=nextjs:nodejs /app/{{APP_DIR}}/public ./{{APP_DIR}}/public
|
|
45
|
+
|
|
46
|
+
USER nextjs
|
|
47
|
+
|
|
48
|
+
EXPOSE {{APP_PORT}}
|
|
49
|
+
|
|
50
|
+
CMD ["bun", "{{APP_DIR}}/server.js"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# One BUILD job per app. Alpha only — the image is built once at the tag push
|
|
2
|
+
# and redeployed to Beta/Production by base tag (no rebuild). {{APP_KEY}} is a
|
|
3
|
+
# lowercase id unique per app (e.g. api, web). {{ORG_REF}} is the org workflow
|
|
4
|
+
# ref (e.g. turborepo) — keep it identical across every job in this file.
|
|
5
|
+
build-{{APP_KEY}}-alpha:
|
|
6
|
+
name: Build
|
|
7
|
+
if: github.event_name != 'release' && startsWith(github.ref, 'refs/tags/v')
|
|
8
|
+
uses: codepassion-team/cicd/.github/workflows/build.yml@{{ORG_REF}}
|
|
9
|
+
with:
|
|
10
|
+
environment: Alpha
|
|
11
|
+
build_path: ./{{APP_DIR}}
|
|
12
|
+
package_name: {{PACKAGE_NAME}}
|
|
13
|
+
secrets: inherit
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# One DEPLOY job per app per environment. {{DEPLOY_NAME}} is the org deploy.yml
|
|
2
|
+
# name input that resolves the Coolify UUID var: it is uppercased to
|
|
3
|
+
# ${{DEPLOY_NAME_UPPER}}_UUID, so it MUST be a key the org deploy.yml wires
|
|
4
|
+
# (currently only API and WEB). Stamp the right {{IF_CONDITION}} per environment
|
|
5
|
+
# from c9n-semver-deployment:
|
|
6
|
+
# Alpha -> needs: build-{{APP_KEY}}-alpha ; if: github.event_name != 'release' && startsWith(github.ref, 'refs/tags/v')
|
|
7
|
+
# Beta -> (no needs) ; if: github.event_name == 'release' && github.event.release.prerelease == true && startsWith(github.event.release.tag_name, 'v')
|
|
8
|
+
# Production -> (no needs) ; if: github.event_name == 'release' && github.event.release.prerelease == false && startsWith(github.event.release.tag_name, 'v')
|
|
9
|
+
deploy-{{APP_KEY}}-{{ENV_KEY}}:
|
|
10
|
+
name: Deploy
|
|
11
|
+
if: {{IF_CONDITION}}
|
|
12
|
+
{{NEEDS_LINE}}
|
|
13
|
+
uses: codepassion-team/cicd/.github/workflows/deploy.yml@{{ORG_REF}}
|
|
14
|
+
with:
|
|
15
|
+
name: {{DEPLOY_NAME}}
|
|
16
|
+
environment: {{ENVIRONMENT}}
|
|
17
|
+
secrets: inherit
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Inject in place of {{MIGRATION_CHECK}} in ci.yml ONLY when a DB is detected
|
|
2
|
+
# (a migration-generate script exists in some package.json AND a migrations dir
|
|
3
|
+
# exists). Substitute {{MIGRATION_SCRIPT}} and {{MIGRATIONS_DIR}} with the real
|
|
4
|
+
# detected names — do not assume drizzle:generate / packages/supabase/migrations.
|
|
5
|
+
# If no DB, {{MIGRATION_CHECK}} is the empty string and ci.yml ends at Build.
|
|
6
|
+
- name: Verify migrations are up to date
|
|
7
|
+
run: bun run {{MIGRATION_SCRIPT}} -- --name check
|
|
8
|
+
- name: Check for uncommitted migration changes
|
|
9
|
+
run: |
|
|
10
|
+
if git diff --name-only --exit-code {{MIGRATIONS_DIR}}; then
|
|
11
|
+
echo "Migrations are up to date"
|
|
12
|
+
else
|
|
13
|
+
echo "Detected uncommitted migration changes. Run 'bun run {{MIGRATION_SCRIPT}}' locally and commit the result."
|
|
14
|
+
git diff {{MIGRATIONS_DIR}}
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: ['main', 'develop']
|
|
5
|
+
pull_request:
|
|
6
|
+
types: [opened, synchronize]
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Check
|
|
10
|
+
timeout-minutes: 15
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
env:
|
|
13
|
+
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
14
|
+
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
|
15
|
+
steps:
|
|
16
|
+
- name: Check out code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 2
|
|
20
|
+
- name: Cache turbo build setup
|
|
21
|
+
uses: actions/cache@v4
|
|
22
|
+
with:
|
|
23
|
+
path: .turbo
|
|
24
|
+
key: ${{ runner.os }}-turbo-${{ github.sha }}
|
|
25
|
+
restore-keys: |
|
|
26
|
+
${{ runner.os }}-turbo-
|
|
27
|
+
- uses: oven-sh/setup-bun@v2
|
|
28
|
+
- name: Setup Node.js environment
|
|
29
|
+
uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 24
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: bun install
|
|
34
|
+
- name: Check types
|
|
35
|
+
run: bun run check-types
|
|
36
|
+
- name: Build
|
|
37
|
+
run: bun run build
|
|
38
|
+
{{MIGRATION_CHECK}}
|