@codepassion/skills 1.0.2 → 1.2.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 +13 -912
- package/SKILLS.md +4 -4
- package/package.json +6 -6
- package/skills/c9n/SKILL.md +65 -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.md
CHANGED
|
@@ -4,10 +4,10 @@ Agent skills (SKILL.md) based on the CodePassion (C9N) conventions.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
npx @codepassion/
|
|
8
|
-
npx @codepassion/
|
|
9
|
-
npx @codepassion/
|
|
10
|
-
npx @codepassion/
|
|
7
|
+
npx @codepassion/skills install claude # ~/.claude/skills
|
|
8
|
+
npx @codepassion/skills install codex # ~/.codex/skills
|
|
9
|
+
npx @codepassion/skills install project # ./.agents/skills
|
|
10
|
+
npx @codepassion/skills install combined # one combined skill
|
|
11
11
|
|
|
12
12
|
Or: ./install.sh {claude|codex|project|combined}
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codepassion/skills",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Agent skills (SKILL.md) for CodePassion (C9N) engineering conventions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"c9n-skills": "bin/c9n-skills.js"
|
|
9
|
-
"skills": "bin/c9n-skills.js"
|
|
8
|
+
"c9n-skills": "./bin/c9n-skills.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"bin",
|
|
@@ -15,13 +14,14 @@
|
|
|
15
14
|
"skills",
|
|
16
15
|
"install.sh",
|
|
17
16
|
"LICENSE",
|
|
18
|
-
"SKILLS.md"
|
|
17
|
+
"SKILLS.md",
|
|
18
|
+
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "node scripts/build-combined.mjs",
|
|
22
22
|
"lint": "node scripts/lint-skills.mjs",
|
|
23
23
|
"test": "npm run build && npm run lint",
|
|
24
|
-
"prepack": "npm run build && npm run lint",
|
|
24
|
+
"prepack": "cp SKILLS.md README.md && npm run build && npm run lint",
|
|
25
25
|
"prepare": "husky || true"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
],
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
50
|
-
"url": "
|
|
50
|
+
"url": "https://github.com/codepassion-team/c9n.git"
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/codepassion-team/c9n#readme",
|
|
53
53
|
"bugs": {
|
package/skills/c9n/SKILL.md
CHANGED
|
@@ -172,3 +172,68 @@ Alpha: tag from develop, `git tag -a vX.Y.Z -m "Release vX.Y.Z" && git push orig
|
|
|
172
172
|
Production: confirm Go → pull release branch → create release → CI deploys → monitor.
|
|
173
173
|
Reference: https://semver.org/spec/v2.0.0.html
|
|
174
174
|
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Deliverables
|
|
180
|
+
|
|
181
|
+
Repeatable customer project delivery for the Code Passion team. Turns a codebase into a
|
|
182
|
+
clean, customer-ready documentation package. Fully generic — adapt to whatever stack the
|
|
183
|
+
project uses. Bundled `scripts/` are proven references; read and adapt them per project.
|
|
184
|
+
|
|
185
|
+
## Four mandatory gates
|
|
186
|
+
|
|
187
|
+
These ALWAYS run, in order — never skip them:
|
|
188
|
+
|
|
189
|
+
1. **Clarify-first** — interview before generating anything (see Workflow step 1).
|
|
190
|
+
2. **Security/sanitize** — never ship secrets. Before packaging: scan for and exclude
|
|
191
|
+
`.env`, credentials, service-account JSON, `*.pem`/`*.key`; sanitize real env files to
|
|
192
|
+
`.env.example` (names + descriptions, no values); strip `.DS_Store`. If you copied any
|
|
193
|
+
credential into a build dir to make tooling work, delete it before finishing.
|
|
194
|
+
3. **Verify rendered output** — after creating PDFs, screenshot each (headless browser
|
|
195
|
+
`--screenshot`) and visually confirm. Catches illegible/empty diagrams and truncation.
|
|
196
|
+
Do not trust `file`'s page count — use `mdls -name kMDItemNumberOfPages` on macOS.
|
|
197
|
+
4. **Delivery email** — always draft one. Thai for Thai customer teams, Code Passion
|
|
198
|
+
framing (from Code Passion → client). Leave recipient/contact as fill-in blanks.
|
|
199
|
+
|
|
200
|
+
## Workflow
|
|
201
|
+
|
|
202
|
+
1. **Clarify** (ask, don't assume): audience (customer vs internal); which documents are
|
|
203
|
+
wanted; document language (default: English docs + Thai email); output format (PDF?);
|
|
204
|
+
package structure — **plain docs package (default)** or **ISO/IEC 29110 work-product
|
|
205
|
+
layout** (offer it, see [REFERENCE.md](../c9n-deliverables/REFERENCE.md)); any contractual scope.
|
|
206
|
+
2. **Inventory** the codebase — detect stack, framework, DB, API doc tooling; decide which
|
|
207
|
+
generators apply. Skip what doesn't fit the stack.
|
|
208
|
+
3. **Generate** the doc set (only what was requested). Common artifacts and recipes are in
|
|
209
|
+
[REFERENCE.md](../c9n-deliverables/REFERENCE.md): system/architecture overview (+ Mermaid diagrams),
|
|
210
|
+
database design (ER diagram + data dictionary), API reference (export spec → PDF),
|
|
211
|
+
deployment runbook, configuration reference, source snapshot.
|
|
212
|
+
4. **Render PDFs** with `scripts/md-to-pdf.mjs` (Markdown+Mermaid) and
|
|
213
|
+
`scripts/openapi-to-pdf.mjs` (OpenAPI). Then run gate 3 (verify).
|
|
214
|
+
5. **Organize** per the chosen structure. Master `README.md` index; for 29110 include a
|
|
215
|
+
supporting-artifact → work-product traceability map.
|
|
216
|
+
6. **Security gate** (gate 2), then **package**: zip the organized folder directly (or, if a
|
|
217
|
+
dedicated packaging skill such as `deliverables-packager` is installed, hand off to it for
|
|
218
|
+
final folder polish + zip).
|
|
219
|
+
7. **Delivery email** (gate 4).
|
|
220
|
+
|
|
221
|
+
## Scripts
|
|
222
|
+
|
|
223
|
+
| Script | Purpose |
|
|
224
|
+
|--------|---------|
|
|
225
|
+
| `scripts/md-to-pdf.mjs <in.md> <out.pdf>` | Markdown → PDF, renders Mermaid. Needs a Chromium-family browser. |
|
|
226
|
+
| `scripts/openapi-to-pdf.mjs <spec.json> <out.pdf> [guide.md]` | OpenAPI → print-friendly PDF (no lazy-load truncation). |
|
|
227
|
+
| `scripts/export-openapi.cjs <out.json>` | TEMPLATE: boot a NestJS app, dump Swagger spec. Adapt to project. |
|
|
228
|
+
| `scripts/data-dictionary.cjs <out.md>` | Data dictionary from live MySQL `INFORMATION_SCHEMA`. |
|
|
229
|
+
| `scripts/snapshot-source.sh <repo> <out.zip>` | Clean `git archive` source snapshot. |
|
|
230
|
+
|
|
231
|
+
## Gotchas
|
|
232
|
+
|
|
233
|
+
- **Wide Mermaid diagrams** render as an illegible strip at page width. Use `flowchart TB`
|
|
234
|
+
and split large ER diagrams into per-domain groups (portrait aspect ratio).
|
|
235
|
+
- **OpenAPI export**: build first (`nest build`); ts-node mis-emits decorator metadata.
|
|
236
|
+
Booting connects to the DB — use a dev DB, never production.
|
|
237
|
+
- **Redoc/Swagger UI → PDF**: they lazy-render; printing truncates. Use `openapi-to-pdf.mjs`.
|
|
238
|
+
- See [REFERENCE.md](../c9n-deliverables/REFERENCE.md) for the ISO/IEC 29110 layout and per-artifact recipes.
|
|
239
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# C9N Deliverables — Reference
|
|
2
|
+
|
|
3
|
+
## Document set (artifacts & recipes)
|
|
4
|
+
|
|
5
|
+
Generate only what the clarify step asked for. Each is a Markdown source rendered to PDF.
|
|
6
|
+
|
|
7
|
+
### System / Architecture Overview
|
|
8
|
+
- Content: tech stack table, module map, auth model, API versioning, **architecture
|
|
9
|
+
diagrams** (Mermaid `flowchart`): System Context, Layered Request Flow, Deployment.
|
|
10
|
+
- Source from README + code structure. Render with `md-to-pdf.mjs`.
|
|
11
|
+
- Keep flow diagrams `flowchart TB` so they stay legible at page width.
|
|
12
|
+
|
|
13
|
+
### Database Design
|
|
14
|
+
- ER diagram(s) as Mermaid `erDiagram`. **Split into per-domain groups** — one giant ER is
|
|
15
|
+
unreadable. Plus a domain-grouped entity catalog.
|
|
16
|
+
- **Data dictionary**: every table & column. Generate from the live DB with
|
|
17
|
+
`data-dictionary.cjs` (most accurate) — adjust the env var names to the project.
|
|
18
|
+
|
|
19
|
+
### API Reference
|
|
20
|
+
- Export the spec from the running/built app (`export-openapi.cjs` for NestJS/Swagger, or
|
|
21
|
+
`curl <host>/api-json`). Render with `openapi-to-pdf.mjs` (optionally pass a guide.md
|
|
22
|
+
intro covering auth, versioning, how to import the spec).
|
|
23
|
+
- Also ship `openapi.json` (Postman/codegen) and optionally a Redoc HTML for browsing.
|
|
24
|
+
|
|
25
|
+
### Deployment Runbook
|
|
26
|
+
- Prerequisites, env vars (point to `.env.example`), local dev, build, Docker, CI/CD
|
|
27
|
+
(e.g. Cloud Build → Cloud Run), production checklist, troubleshooting.
|
|
28
|
+
|
|
29
|
+
### Configuration Reference
|
|
30
|
+
- Sanitized `.env.example` — variable names + descriptions, **no values**. Plus notes on
|
|
31
|
+
Docker/compose/CI config files.
|
|
32
|
+
|
|
33
|
+
### Source Code Snapshot
|
|
34
|
+
- `snapshot-source.sh <repo> <out.zip>`. Run at final delivery so it isn't stale.
|
|
35
|
+
|
|
36
|
+
## Plain docs package layout (default)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
deliverables/
|
|
40
|
+
├── README.md # index
|
|
41
|
+
├── 01-documentation/
|
|
42
|
+
│ ├── markdown/ # editable sources
|
|
43
|
+
│ └── pdf/ # rendered PDFs
|
|
44
|
+
├── 02-database/ # data dictionary, ER
|
|
45
|
+
├── 03-configuration/ # .env.example, notes
|
|
46
|
+
├── 04-utilities/ # helper scripts
|
|
47
|
+
└── 05-source-code/ # snapshot (or placeholder)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## ISO/IEC 29110 layout (Basic Profile, offer when engagement needs it)
|
|
51
|
+
|
|
52
|
+
Two process groups. Formal work products at their slots; supporting engineering artifacts
|
|
53
|
+
filed under the SI work product they evidence, with a traceability map in the README.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
deliverables/
|
|
57
|
+
├── README.md # master index + traceability
|
|
58
|
+
├── 00-project-management/ # PM work products
|
|
59
|
+
│ ├── PM-01_Statement-of-Work.pdf
|
|
60
|
+
│ ├── PM-02_Project-Plan.pdf
|
|
61
|
+
│ ├── PM-03_Progress-Status-Record.pdf
|
|
62
|
+
│ ├── PM-04_Change-Request.pdf
|
|
63
|
+
│ ├── PM-05_Acceptance-Record.pdf
|
|
64
|
+
│ └── PM-06_Verification-Validation-Record.pdf
|
|
65
|
+
└── 01-software-implementation/ # SI work products
|
|
66
|
+
├── SI-01_Requirements-Specification.pdf
|
|
67
|
+
├── SI-02_Software-Design.pdf
|
|
68
|
+
├── SI-03_Traceability-Record.pdf
|
|
69
|
+
├── SI-04_Test-Cases-and-Procedures.pdf
|
|
70
|
+
├── SI-05_Test-Report.pdf
|
|
71
|
+
├── SI-06_Product-Operation-Guide.pdf
|
|
72
|
+
├── SI-07_Maintenance-Documentation.pdf
|
|
73
|
+
└── supporting/
|
|
74
|
+
├── architecture/ → SI.2
|
|
75
|
+
├── database/ → SI.2
|
|
76
|
+
├── api/ → SI.2 (interfaces)
|
|
77
|
+
├── operations/ → SI.6 / SI.7
|
|
78
|
+
└── source-code/ → SI Software Components
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Traceability: architecture/database/api → **SI.2 Software Design**; operations →
|
|
82
|
+
**SI.6 Product Operation Guide** + **SI.7 Maintenance**; source-code → **SI Software
|
|
83
|
+
Components**. PM acceptance/sign-off rows stay open for the customer.
|
|
84
|
+
|
|
85
|
+
## Delivery email shape (Thai default)
|
|
86
|
+
|
|
87
|
+
- Subject: `ส่งมอบชุด Deliverables โครงการ [PROJECT]`
|
|
88
|
+
- From Code Passion → customer team. One paragraph: package attached. One: contents
|
|
89
|
+
overview. One: PDFs for reading + Markdown sources retained. Note where the source
|
|
90
|
+
snapshot lives. Close with support availability. Leave `[ชื่อผู้รับ]` / `[ช่องทางติดต่อ]` blanks.
|
|
91
|
+
|
|
92
|
+
## Dependency notes
|
|
93
|
+
|
|
94
|
+
- PDF rendering needs a Chromium-family browser (Brave/Chrome/Edge/Chromium). Never auto-
|
|
95
|
+
install heavy deps (puppeteer, pandoc, LaTeX) without asking the user first.
|
|
96
|
+
- `data-dictionary.cjs` needs `mysql2` resolvable from a project that has it.
|
|
97
|
+
- Mermaid + marked load from CDN → diagram rendering needs internet.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: c9n-deliverables
|
|
3
|
+
description: Generate a customer-facing delivery/handover package FROM a codebase — architecture docs, ER/data-dictionary, API reference, all rendered to PDF, organized, security-scanned, zipped, with a delivery email. Use when handing a project off to a customer or another team, preparing deliverables, "delivery package", "handover", "ส่งมอบงาน", or when asked to document a codebase for an external recipient. Complements deliverables-packager (this generates docs from code + organizes; that packages existing docs).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Repeatable customer project delivery for the Code Passion team. Turns a codebase into a
|
|
7
|
+
clean, customer-ready documentation package. Fully generic — adapt to whatever stack the
|
|
8
|
+
project uses. Bundled `scripts/` are proven references; read and adapt them per project.
|
|
9
|
+
|
|
10
|
+
## Four mandatory gates
|
|
11
|
+
|
|
12
|
+
These ALWAYS run, in order — never skip them:
|
|
13
|
+
|
|
14
|
+
1. **Clarify-first** — interview before generating anything (see Workflow step 1).
|
|
15
|
+
2. **Security/sanitize** — never ship secrets. Before packaging: scan for and exclude
|
|
16
|
+
`.env`, credentials, service-account JSON, `*.pem`/`*.key`; sanitize real env files to
|
|
17
|
+
`.env.example` (names + descriptions, no values); strip `.DS_Store`. If you copied any
|
|
18
|
+
credential into a build dir to make tooling work, delete it before finishing.
|
|
19
|
+
3. **Verify rendered output** — after creating PDFs, screenshot each (headless browser
|
|
20
|
+
`--screenshot`) and visually confirm. Catches illegible/empty diagrams and truncation.
|
|
21
|
+
Do not trust `file`'s page count — use `mdls -name kMDItemNumberOfPages` on macOS.
|
|
22
|
+
4. **Delivery email** — always draft one. Thai for Thai customer teams, Code Passion
|
|
23
|
+
framing (from Code Passion → client). Leave recipient/contact as fill-in blanks.
|
|
24
|
+
|
|
25
|
+
## Workflow
|
|
26
|
+
|
|
27
|
+
1. **Clarify** (ask, don't assume): audience (customer vs internal); which documents are
|
|
28
|
+
wanted; document language (default: English docs + Thai email); output format (PDF?);
|
|
29
|
+
package structure — **plain docs package (default)** or **ISO/IEC 29110 work-product
|
|
30
|
+
layout** (offer it, see [REFERENCE.md](../c9n-deliverables/REFERENCE.md)); any contractual scope.
|
|
31
|
+
2. **Inventory** the codebase — detect stack, framework, DB, API doc tooling; decide which
|
|
32
|
+
generators apply. Skip what doesn't fit the stack.
|
|
33
|
+
3. **Generate** the doc set (only what was requested). Common artifacts and recipes are in
|
|
34
|
+
[REFERENCE.md](../c9n-deliverables/REFERENCE.md): system/architecture overview (+ Mermaid diagrams),
|
|
35
|
+
database design (ER diagram + data dictionary), API reference (export spec → PDF),
|
|
36
|
+
deployment runbook, configuration reference, source snapshot.
|
|
37
|
+
4. **Render PDFs** with `scripts/md-to-pdf.mjs` (Markdown+Mermaid) and
|
|
38
|
+
`scripts/openapi-to-pdf.mjs` (OpenAPI). Then run gate 3 (verify).
|
|
39
|
+
5. **Organize** per the chosen structure. Master `README.md` index; for 29110 include a
|
|
40
|
+
supporting-artifact → work-product traceability map.
|
|
41
|
+
6. **Security gate** (gate 2), then **package**: zip the organized folder directly (or, if a
|
|
42
|
+
dedicated packaging skill such as `deliverables-packager` is installed, hand off to it for
|
|
43
|
+
final folder polish + zip).
|
|
44
|
+
7. **Delivery email** (gate 4).
|
|
45
|
+
|
|
46
|
+
## Scripts
|
|
47
|
+
|
|
48
|
+
| Script | Purpose |
|
|
49
|
+
|--------|---------|
|
|
50
|
+
| `scripts/md-to-pdf.mjs <in.md> <out.pdf>` | Markdown → PDF, renders Mermaid. Needs a Chromium-family browser. |
|
|
51
|
+
| `scripts/openapi-to-pdf.mjs <spec.json> <out.pdf> [guide.md]` | OpenAPI → print-friendly PDF (no lazy-load truncation). |
|
|
52
|
+
| `scripts/export-openapi.cjs <out.json>` | TEMPLATE: boot a NestJS app, dump Swagger spec. Adapt to project. |
|
|
53
|
+
| `scripts/data-dictionary.cjs <out.md>` | Data dictionary from live MySQL `INFORMATION_SCHEMA`. |
|
|
54
|
+
| `scripts/snapshot-source.sh <repo> <out.zip>` | Clean `git archive` source snapshot. |
|
|
55
|
+
|
|
56
|
+
## Gotchas
|
|
57
|
+
|
|
58
|
+
- **Wide Mermaid diagrams** render as an illegible strip at page width. Use `flowchart TB`
|
|
59
|
+
and split large ER diagrams into per-domain groups (portrait aspect ratio).
|
|
60
|
+
- **OpenAPI export**: build first (`nest build`); ts-node mis-emits decorator metadata.
|
|
61
|
+
Booting connects to the DB — use a dev DB, never production.
|
|
62
|
+
- **Redoc/Swagger UI → PDF**: they lazy-render; printing truncates. Use `openapi-to-pdf.mjs`.
|
|
63
|
+
- See [REFERENCE.md](../c9n-deliverables/REFERENCE.md) for the ISO/IEC 29110 layout and per-artifact recipes.
|
|
@@ -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"
|