create-dokio 0.1.8 → 0.1.11
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 +109 -47
- package/dist/index.js +561 -65
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-dokio
|
|
2
2
|
|
|
3
|
-
CLI scaffold for Dokio templates. Generates
|
|
3
|
+
CLI scaffold for Dokio hub repos and templates. Generates correct file structure, `data.yaml`, HTML boilerplate, SCSS partials, and auto-wires changelog tooling.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -14,6 +14,77 @@ npx create-dokio pool-care
|
|
|
14
14
|
|
|
15
15
|
> Using pnpm? `pnpm dlx create-dokio`
|
|
16
16
|
|
|
17
|
+
Run from your **repos workspace folder** — the folder where all your hub repos live (e.g. `~/Projects/dokio/`).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
### `create-dokio hub`
|
|
24
|
+
|
|
25
|
+
Creates a new hub repository locally with full structure ready to push.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx create-dokio hub
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Prompts:**
|
|
32
|
+
- Hub ID (kebab-case, e.g. `bupa-sam`)
|
|
33
|
+
- Hub display name (e.g. `Bupa Sales And Marketing Hub`)
|
|
34
|
+
|
|
35
|
+
**Generates:**
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bupa-sam-templates/
|
|
39
|
+
├── .githooks/
|
|
40
|
+
│ ├── commit-msg # Enforces conventional commits
|
|
41
|
+
│ └── post-commit # Auto-updates template CHANGELOGs on commit
|
|
42
|
+
├── .vscode/
|
|
43
|
+
│ └── settings.json # scss.validate: false, css.validate: false
|
|
44
|
+
├── templates/ # All templates live here
|
|
45
|
+
├── tools/
|
|
46
|
+
│ └── changelog/
|
|
47
|
+
│ ├── update_changelog.py # Changelog generator script
|
|
48
|
+
│ └── .env.example # ANTHROPIC_API_KEY for AI descriptions
|
|
49
|
+
├── .gitignore
|
|
50
|
+
└── README.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Next steps after hub creation:**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd bupa-sam-templates
|
|
57
|
+
# Create GitHub repo at github.com/dokioco/bupa-sam-templates
|
|
58
|
+
git remote add origin https://github.com/dokioco/bupa-sam-templates
|
|
59
|
+
git push -u origin main
|
|
60
|
+
|
|
61
|
+
# Optional — enable AI changelog descriptions:
|
|
62
|
+
cp tools/changelog/.env.example tools/changelog/.env
|
|
63
|
+
# Add your ANTHROPIC_API_KEY to tools/changelog/.env
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### `create-dokio template`
|
|
69
|
+
|
|
70
|
+
Scaffolds a new template inside an existing hub repo. Clones the hub if not found locally.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx create-dokio template
|
|
74
|
+
# or just:
|
|
75
|
+
npx create-dokio
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Prompts:**
|
|
79
|
+
- Template ID (e.g. `HW485`)
|
|
80
|
+
- Template name (e.g. `My Cool Template`)
|
|
81
|
+
- Template type (PDF / General / Email / Video)
|
|
82
|
+
- Hub selection
|
|
83
|
+
|
|
84
|
+
If the hub repo exists locally, it pulls latest. If not, it clones it.
|
|
85
|
+
|
|
86
|
+
If the hub is missing any tooling (changelog script, hooks, `.vscode`, `.gitignore`, `README.md`), it adds them automatically.
|
|
87
|
+
|
|
17
88
|
---
|
|
18
89
|
|
|
19
90
|
## Template types
|
|
@@ -22,15 +93,14 @@ npx create-dokio pool-care
|
|
|
22
93
|
PrinceXML-rendered print documents. Supports multi-page, resizable layouts, orderable exports, and proof downloads.
|
|
23
94
|
|
|
24
95
|
```
|
|
25
|
-
|
|
96
|
+
HW485-my-cool-template/
|
|
26
97
|
├── CHANGELOG.md
|
|
27
98
|
├── data.yaml
|
|
28
99
|
├── index.html
|
|
29
100
|
├── assets/
|
|
30
101
|
├── partials/
|
|
31
102
|
│ ├── page1-cover.html
|
|
32
|
-
│
|
|
33
|
-
│ └── page3-cta.html
|
|
103
|
+
│ └── page2-content.html
|
|
34
104
|
└── scss/
|
|
35
105
|
├── style.scss.hbs
|
|
36
106
|
├── _fonts.scss
|
|
@@ -38,35 +108,24 @@ dokio-pool-care/
|
|
|
38
108
|
├── _variables.scss
|
|
39
109
|
└── pages/
|
|
40
110
|
├── _page1.scss
|
|
41
|
-
|
|
42
|
-
└── _page3.scss
|
|
111
|
+
└── _page2.scss
|
|
43
112
|
```
|
|
44
113
|
|
|
45
|
-
**Prompts:**
|
|
46
|
-
- Page dimensions (mm)
|
|
47
|
-
- Page count
|
|
48
|
-
- PrinceXML version (11 or 15 — use 15 for flexbox)
|
|
49
|
-
- Resizable template
|
|
50
|
-
- Proof downloads (`draft_proofable`)
|
|
51
|
-
- Orderable export (Print from Snap)
|
|
114
|
+
**Prompts:** page dimensions (mm), page count, PrinceXML version (11 or 15), resizable, proof downloads, orderable export.
|
|
52
115
|
|
|
53
116
|
---
|
|
54
117
|
|
|
55
118
|
### General (image)
|
|
56
119
|
Chrome-rendered image output — JPG and/or PNG. Single or multi-section layouts.
|
|
57
120
|
|
|
58
|
-
**Prompts:**
|
|
59
|
-
- Dimensions (px)
|
|
60
|
-
- Page/section count
|
|
61
|
-
- Export formats (JPG, PNG, or both)
|
|
121
|
+
**Prompts:** dimensions (px), page/section count, export formats (JPG, PNG, or both).
|
|
62
122
|
|
|
63
123
|
---
|
|
64
124
|
|
|
65
125
|
### Email
|
|
66
126
|
HTML email scaffold. Includes Outlook conditional comments, `{{{___assembled_css}}}` injection, `{{{subject}}}` title, and Gmail-safe table structure.
|
|
67
127
|
|
|
68
|
-
**Prompts:**
|
|
69
|
-
- Subdomain only — email structure is fixed at 600px
|
|
128
|
+
**Prompts:** subdomain only — email structure is fixed at 600px.
|
|
70
129
|
|
|
71
130
|
---
|
|
72
131
|
|
|
@@ -75,36 +134,34 @@ HTML email scaffold. Includes Outlook conditional comments, `{{{___assembled_css
|
|
|
75
134
|
|
|
76
135
|
---
|
|
77
136
|
|
|
78
|
-
##
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
| `scss/pages/_pageN.scss` | Per-page style partials |
|
|
90
|
-
| `assets/` | Drop fonts, images, and local assets here |
|
|
91
|
-
| `CHANGELOG.md` | Template-level changelog |
|
|
137
|
+
## Changelog generator
|
|
138
|
+
|
|
139
|
+
Each template has its own `CHANGELOG.md`. After every commit that touches a template folder, the `post-commit` hook automatically:
|
|
140
|
+
|
|
141
|
+
1. Detects which templates changed
|
|
142
|
+
2. Logs the commit to each affected template's `CHANGELOG.md` (author, date, branch, title, files with status labels)
|
|
143
|
+
3. Amends the commit silently to include the changelog update
|
|
144
|
+
|
|
145
|
+
**Description generation:**
|
|
146
|
+
- If `tools/changelog/.env` exists with a valid `ANTHROPIC_API_KEY` → Claude Haiku generates a description
|
|
147
|
+
- If no key → falls back to `TODO - INTEGRATE WITH AI (JAKE TASK)`
|
|
92
148
|
|
|
93
149
|
---
|
|
94
150
|
|
|
95
|
-
## After scaffolding
|
|
151
|
+
## After scaffolding a template
|
|
96
152
|
|
|
97
153
|
```bash
|
|
98
|
-
cd
|
|
99
|
-
|
|
100
|
-
# 1. Set your template ID in data.yaml
|
|
101
|
-
# name: TEMPLATEID - dokio-<name> ← replace TEMPLATEID
|
|
154
|
+
cd bupa-sam-templates/templates/HW485-my-cool-template
|
|
102
155
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
156
|
+
# 1. Check data.yaml — name and subdomain are auto-set
|
|
157
|
+
# 2. Add assets to assets/
|
|
158
|
+
# 3. Edit partials/
|
|
105
159
|
|
|
106
|
-
#
|
|
107
|
-
|
|
160
|
+
# When ready to commit (from hub root):
|
|
161
|
+
cd ../../
|
|
162
|
+
git add templates/HW485-my-cool-template/
|
|
163
|
+
git commit -m "feat: add My Cool Template"
|
|
164
|
+
git push
|
|
108
165
|
```
|
|
109
166
|
|
|
110
167
|
---
|
|
@@ -112,23 +169,28 @@ cd dokio-<name>
|
|
|
112
169
|
## Development
|
|
113
170
|
|
|
114
171
|
```bash
|
|
115
|
-
git clone
|
|
172
|
+
git clone https://github.com/dokioco/create-dokio
|
|
173
|
+
cd create-dokio
|
|
116
174
|
pnpm install
|
|
117
|
-
pnpm
|
|
118
|
-
pnpm
|
|
175
|
+
pnpm build # compile to dist/
|
|
176
|
+
pnpm link --global
|
|
119
177
|
```
|
|
120
178
|
|
|
121
179
|
### Commit format
|
|
122
180
|
|
|
123
|
-
|
|
181
|
+
Enforced by commitlint on every commit:
|
|
124
182
|
|
|
125
183
|
```
|
|
126
184
|
feat: add resizable PDF option
|
|
127
185
|
fix: correct page count in data.yaml
|
|
128
186
|
chore: update dependencies
|
|
187
|
+
docs: update README
|
|
129
188
|
```
|
|
130
189
|
|
|
131
|
-
|
|
190
|
+
Breaking changes:
|
|
191
|
+
```
|
|
192
|
+
feat!: redesign template API
|
|
193
|
+
```
|
|
132
194
|
|
|
133
195
|
---
|
|
134
196
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import prompts3 from "prompts";
|
|
5
|
+
import kleur6 from "kleur";
|
|
6
|
+
|
|
7
|
+
// src/template.ts
|
|
8
|
+
import { join as join3 } from "path";
|
|
9
|
+
import fse3 from "fs-extra";
|
|
10
|
+
import kleur4 from "kleur";
|
|
5
11
|
|
|
6
12
|
// src/prompts.ts
|
|
7
13
|
import prompts from "prompts";
|
|
@@ -21,6 +27,46 @@ function buildPages(pageCount) {
|
|
|
21
27
|
return Array.from({ length: pageCount }, (_, i) => getPageName(i, pageCount));
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
// src/hubs.ts
|
|
31
|
+
var HUBS = [
|
|
32
|
+
{ id: "meridianenergy", title: "Meridian Energy Hub" },
|
|
33
|
+
{ id: "aexp", title: "Amex Global" },
|
|
34
|
+
{ id: "ipa", title: "IPA LAM Hub" },
|
|
35
|
+
{ id: "bupa-sam", title: "Bupa Sales And Marketing Hub" },
|
|
36
|
+
{ id: "gwm", title: "GWM Advertising Studio" },
|
|
37
|
+
{ id: "knowledgebase", title: "KB Hub" },
|
|
38
|
+
{ id: "vidacorp", title: "VidaDesign Hub" },
|
|
39
|
+
{ id: "mi", title: "Measurable Impact" },
|
|
40
|
+
{ id: "bupa-hs", title: "Bupa Health Services" },
|
|
41
|
+
{ id: "originloopvpp-partnerships-hub", title: "Origin Loop VPP Partnerships Hub" },
|
|
42
|
+
{ id: "amazon-devices", title: "Amazon Devices" },
|
|
43
|
+
{ id: "belmond", title: "Belmond" },
|
|
44
|
+
{ id: "origin", title: "Origin" },
|
|
45
|
+
{ id: "shell-au", title: "Shell Australia Brand Templates" },
|
|
46
|
+
{ id: "nissan", title: "Nissan" },
|
|
47
|
+
{ id: "bupa-agedcare", title: "Bupa Aged Care" },
|
|
48
|
+
{ id: "australian-unity", title: "Australian Unity" },
|
|
49
|
+
{ id: "poolwerx", title: "Poolwerx Loop" },
|
|
50
|
+
{ id: "scimmer", title: "Scimmer" },
|
|
51
|
+
{ id: "sandbox", title: "Sandbox" },
|
|
52
|
+
{ id: "bupa-healthy-cities", title: "Bupa Healthy Cities" },
|
|
53
|
+
{ id: "eabrandhub", title: "EnergyAustralia's Brand Hub" },
|
|
54
|
+
{ id: "origin-fugu", title: "Origin Fugu" },
|
|
55
|
+
{ id: "fridas", title: "Frida's Luxe Sip n' Paint" },
|
|
56
|
+
{ id: "headspace", title: "Headspace" },
|
|
57
|
+
{ id: "iris-samsung", title: "Iris | Samsung" },
|
|
58
|
+
{ id: "bupa-marketing", title: "Bupa Marketing" },
|
|
59
|
+
{ id: "bupa-retail", title: "Bupa Retail" },
|
|
60
|
+
{ id: "designsystem", title: "Design System" },
|
|
61
|
+
{ id: "hellofresh", title: "HelloFresh" }
|
|
62
|
+
];
|
|
63
|
+
function hubRepoUrl(hubId) {
|
|
64
|
+
return `https://github.com/dokioco/${hubId}-templates`;
|
|
65
|
+
}
|
|
66
|
+
function hubRepoDirName(hubId) {
|
|
67
|
+
return `${hubId}-templates`;
|
|
68
|
+
}
|
|
69
|
+
|
|
24
70
|
// src/prompts.ts
|
|
25
71
|
var onCancel = () => {
|
|
26
72
|
console.log(kleur.yellow("\n Cancelled.\n"));
|
|
@@ -29,12 +75,18 @@ var onCancel = () => {
|
|
|
29
75
|
async function runPrompts(nameArg) {
|
|
30
76
|
const base = await prompts(
|
|
31
77
|
[
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
name: "templateId",
|
|
81
|
+
message: "Template ID (e.g. HW485)",
|
|
82
|
+
validate: (v) => /^[A-Za-z0-9]+$/.test(v.trim()) || "Alphanumeric only"
|
|
83
|
+
},
|
|
32
84
|
{
|
|
33
85
|
type: nameArg ? null : "text",
|
|
34
86
|
name: "name",
|
|
35
|
-
message:
|
|
36
|
-
initial: "
|
|
37
|
-
validate: (v) =>
|
|
87
|
+
message: "Template name (e.g. My Cool Template)",
|
|
88
|
+
initial: "My Template",
|
|
89
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
38
90
|
},
|
|
39
91
|
{
|
|
40
92
|
type: "select",
|
|
@@ -48,17 +100,19 @@ async function runPrompts(nameArg) {
|
|
|
48
100
|
]
|
|
49
101
|
},
|
|
50
102
|
{
|
|
51
|
-
type: "
|
|
52
|
-
name: "
|
|
53
|
-
message: "
|
|
54
|
-
|
|
103
|
+
type: "select",
|
|
104
|
+
name: "hubId",
|
|
105
|
+
message: "Hub",
|
|
106
|
+
choices: HUBS.map((h) => ({ title: h.title, value: h.id }))
|
|
55
107
|
}
|
|
56
108
|
],
|
|
57
109
|
{ onCancel }
|
|
58
110
|
);
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
111
|
+
const templateId = base.templateId.trim();
|
|
112
|
+
const name = nameArg ? nameArg.trim() : base.name.trim();
|
|
113
|
+
const fullName = `${templateId}-${toKebab(name)}`;
|
|
114
|
+
const hubId = base.hubId;
|
|
115
|
+
const subdomain = hubId;
|
|
62
116
|
if (base.mode === "pdf") {
|
|
63
117
|
const pdf = await prompts(
|
|
64
118
|
[
|
|
@@ -103,9 +157,11 @@ async function runPrompts(nameArg) {
|
|
|
103
157
|
);
|
|
104
158
|
return {
|
|
105
159
|
mode: "pdf",
|
|
160
|
+
templateId,
|
|
106
161
|
name,
|
|
107
162
|
fullName,
|
|
108
163
|
subdomain,
|
|
164
|
+
hubId,
|
|
109
165
|
width: pdf.width,
|
|
110
166
|
height: pdf.height,
|
|
111
167
|
pageCount: pdf.pageCount,
|
|
@@ -142,9 +198,11 @@ async function runPrompts(nameArg) {
|
|
|
142
198
|
);
|
|
143
199
|
return {
|
|
144
200
|
mode: "general",
|
|
201
|
+
templateId,
|
|
145
202
|
name,
|
|
146
203
|
fullName,
|
|
147
204
|
subdomain,
|
|
205
|
+
hubId,
|
|
148
206
|
width: gen.width,
|
|
149
207
|
height: gen.height,
|
|
150
208
|
pageCount: gen.pageCount,
|
|
@@ -152,16 +210,11 @@ async function runPrompts(nameArg) {
|
|
|
152
210
|
};
|
|
153
211
|
}
|
|
154
212
|
if (base.mode === "video") {
|
|
155
|
-
return { mode: "video", name, fullName, subdomain };
|
|
213
|
+
return { mode: "video", templateId, name, fullName, subdomain, hubId };
|
|
156
214
|
}
|
|
157
|
-
return { mode: "email", name, fullName, subdomain };
|
|
215
|
+
return { mode: "email", templateId, name, fullName, subdomain, hubId };
|
|
158
216
|
}
|
|
159
217
|
|
|
160
|
-
// src/scaffold.ts
|
|
161
|
-
import { join } from "path";
|
|
162
|
-
import fse from "fs-extra";
|
|
163
|
-
import kleur2 from "kleur";
|
|
164
|
-
|
|
165
218
|
// src/templates/shared.ts
|
|
166
219
|
function changelog(fullName) {
|
|
167
220
|
return `# Changelog
|
|
@@ -216,6 +269,29 @@ function fontsScssEmpty() {
|
|
|
216
269
|
// }
|
|
217
270
|
`;
|
|
218
271
|
}
|
|
272
|
+
function commitMsgHook() {
|
|
273
|
+
return `#!/usr/bin/env sh
|
|
274
|
+
|
|
275
|
+
commit_msg=$(cat "$1")
|
|
276
|
+
first_line=$(printf '%s' "$commit_msg" | head -n 1)
|
|
277
|
+
pattern='^(feat|fix|chore|docs|style|refactor|test|perf|ci|build|revert)(\\(.+\\))?: .+'
|
|
278
|
+
|
|
279
|
+
if ! printf '%s' "$first_line" | grep -qE "$pattern"; then
|
|
280
|
+
printf '\\n \u2717 Invalid commit message.\\n\\n'
|
|
281
|
+
printf ' Must follow Conventional Commits:\\n\\n'
|
|
282
|
+
printf ' feat: add new feature\\n'
|
|
283
|
+
printf ' fix: resolve a bug\\n'
|
|
284
|
+
printf ' chore: maintenance task\\n'
|
|
285
|
+
printf ' docs: update documentation\\n'
|
|
286
|
+
printf ' style: formatting only\\n'
|
|
287
|
+
printf ' refactor: code change, no feature/fix\\n'
|
|
288
|
+
printf ' test: add or update tests\\n'
|
|
289
|
+
printf ' perf: performance improvement\\n\\n'
|
|
290
|
+
printf ' Your message: "%s"\\n\\n' "$first_line"
|
|
291
|
+
exit 1
|
|
292
|
+
fi
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
219
295
|
function mixinsScss() {
|
|
220
296
|
return `@mixin absolute($top: auto, $right: auto, $bottom: auto, $left: auto) {
|
|
221
297
|
position: absolute;
|
|
@@ -250,7 +326,7 @@ function mixinsScss() {
|
|
|
250
326
|
// src/templates/pdf.ts
|
|
251
327
|
function pdfFiles(config, pages) {
|
|
252
328
|
return {
|
|
253
|
-
"CHANGELOG.md": changelog(config.
|
|
329
|
+
"CHANGELOG.md": changelog(`${config.templateId} - ${config.name}`),
|
|
254
330
|
"data.yaml": pdfYaml(config),
|
|
255
331
|
"index.html": pdfHtml(pages),
|
|
256
332
|
"assets/.gitkeep": gitkeep(),
|
|
@@ -263,9 +339,9 @@ function pdfFiles(config, pages) {
|
|
|
263
339
|
};
|
|
264
340
|
}
|
|
265
341
|
function pdfYaml(config) {
|
|
266
|
-
const {
|
|
342
|
+
const { templateId, name, subdomain, width, height, pageCount, princeVersion, resizable, proofable, orderable } = config;
|
|
267
343
|
const lines = [
|
|
268
|
-
`name:
|
|
344
|
+
`name: ${templateId} - ${name}`,
|
|
269
345
|
`mode: pdf`,
|
|
270
346
|
`prince_version: ${princeVersion}`,
|
|
271
347
|
`status: 0`,
|
|
@@ -443,7 +519,7 @@ function pdfPageScss(pages) {
|
|
|
443
519
|
// src/templates/general.ts
|
|
444
520
|
function generalFiles(config, pages) {
|
|
445
521
|
return {
|
|
446
|
-
"CHANGELOG.md": changelog(config.
|
|
522
|
+
"CHANGELOG.md": changelog(`${config.templateId} - ${config.name}`),
|
|
447
523
|
"data.yaml": generalYaml(config),
|
|
448
524
|
"index.html": generalHtml(pages),
|
|
449
525
|
"assets/.gitkeep": gitkeep(),
|
|
@@ -456,9 +532,9 @@ function generalFiles(config, pages) {
|
|
|
456
532
|
};
|
|
457
533
|
}
|
|
458
534
|
function generalYaml(config) {
|
|
459
|
-
const {
|
|
535
|
+
const { templateId, name, subdomain, width, height, pageCount, exportFormats } = config;
|
|
460
536
|
const lines = [
|
|
461
|
-
`name:
|
|
537
|
+
`name: ${templateId} - ${name}`,
|
|
462
538
|
`mode: general`,
|
|
463
539
|
`subdomain: ${subdomain}`,
|
|
464
540
|
`dimension_mode: px`,
|
|
@@ -589,7 +665,7 @@ function generalPageScss(pages) {
|
|
|
589
665
|
// src/templates/video.ts
|
|
590
666
|
function videoFiles(config) {
|
|
591
667
|
return {
|
|
592
|
-
"CHANGELOG.md": changelog(config.
|
|
668
|
+
"CHANGELOG.md": changelog(`${config.templateId} - ${config.name}`),
|
|
593
669
|
"data.yaml": videoYaml(config),
|
|
594
670
|
"index.html": videoHtml(),
|
|
595
671
|
"assets/.gitkeep": gitkeep(),
|
|
@@ -605,7 +681,7 @@ function videoFiles(config) {
|
|
|
605
681
|
}
|
|
606
682
|
function videoYaml(config) {
|
|
607
683
|
return `# TODO: Video template support is WIP
|
|
608
|
-
name:
|
|
684
|
+
name: ${config.templateId} - ${config.name}
|
|
609
685
|
mode: video
|
|
610
686
|
subdomain: ${config.subdomain}
|
|
611
687
|
duration: '30'
|
|
@@ -674,7 +750,7 @@ $color-accent: #0079C8;
|
|
|
674
750
|
// src/templates/email.ts
|
|
675
751
|
function emailFiles(config) {
|
|
676
752
|
return {
|
|
677
|
-
"CHANGELOG.md": changelog(config.
|
|
753
|
+
"CHANGELOG.md": changelog(`${config.templateId} - ${config.name}`),
|
|
678
754
|
"data.yaml": emailYaml(config),
|
|
679
755
|
"index.html": emailHtml(),
|
|
680
756
|
"assets/.gitkeep": gitkeep(),
|
|
@@ -689,7 +765,7 @@ function emailFiles(config) {
|
|
|
689
765
|
};
|
|
690
766
|
}
|
|
691
767
|
function emailYaml(config) {
|
|
692
|
-
return `name:
|
|
768
|
+
return `name: ${config.templateId} - ${config.name}
|
|
693
769
|
mode: email
|
|
694
770
|
status: 0
|
|
695
771
|
subdomain: ${config.subdomain}
|
|
@@ -792,55 +868,475 @@ $color-accent: #0079C8;
|
|
|
792
868
|
`;
|
|
793
869
|
}
|
|
794
870
|
|
|
871
|
+
// src/files.ts
|
|
872
|
+
function buildFiles(config) {
|
|
873
|
+
if (config.mode === "pdf") return pdfFiles(config, buildPages(config.pageCount));
|
|
874
|
+
if (config.mode === "general") return generalFiles(config, buildPages(config.pageCount));
|
|
875
|
+
if (config.mode === "video") return videoFiles(config);
|
|
876
|
+
return emailFiles(config);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/git.ts
|
|
880
|
+
import { join, basename } from "path";
|
|
881
|
+
import { execSync } from "child_process";
|
|
882
|
+
import fse from "fs-extra";
|
|
883
|
+
import kleur2 from "kleur";
|
|
884
|
+
|
|
885
|
+
// src/templates/changelog.ts
|
|
886
|
+
function changelogScript() {
|
|
887
|
+
return `#!/usr/bin/env python3
|
|
888
|
+
"""Prepend a new entry into a template's CHANGELOG.md."""
|
|
889
|
+
import sys
|
|
890
|
+
import re
|
|
891
|
+
import os
|
|
892
|
+
import datetime
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
def load_dotenv():
|
|
896
|
+
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
|
897
|
+
if not os.path.isfile(env_path):
|
|
898
|
+
return
|
|
899
|
+
with open(env_path) as f:
|
|
900
|
+
for line in f:
|
|
901
|
+
line = line.strip()
|
|
902
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
903
|
+
continue
|
|
904
|
+
key, _, val = line.partition("=")
|
|
905
|
+
key = key.strip()
|
|
906
|
+
val = val.strip().strip('"').strip("'")
|
|
907
|
+
if key and key not in os.environ:
|
|
908
|
+
os.environ[key] = val
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
load_dotenv()
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
STATUS_LABELS = {
|
|
915
|
+
"A": "Added",
|
|
916
|
+
"M": "Modified",
|
|
917
|
+
"D": "Deleted",
|
|
918
|
+
"R": "Renamed",
|
|
919
|
+
"C": "Copied",
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def format_date(today_str):
|
|
924
|
+
d = datetime.datetime.strptime(today_str, "%Y-%m-%d")
|
|
925
|
+
day = str(int(d.strftime("%d")))
|
|
926
|
+
return d.strftime(f"%B {day}, %Y")
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def generate_description(title, files, diff):
|
|
930
|
+
try:
|
|
931
|
+
import anthropic
|
|
932
|
+
except ImportError:
|
|
933
|
+
return None
|
|
934
|
+
|
|
935
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
936
|
+
if not api_key:
|
|
937
|
+
return None
|
|
938
|
+
|
|
939
|
+
try:
|
|
940
|
+
client = anthropic.Anthropic(api_key=api_key)
|
|
941
|
+
files_str = "\\n".join(f"- {label}: {path}" for label, path in files)
|
|
942
|
+
diff_section = f"\\n\\nDiff:\\n\`\`\`\\n{diff[:4000]}\\n\`\`\`" if diff.strip() else ""
|
|
943
|
+
|
|
944
|
+
prompt = (
|
|
945
|
+
f"You are writing a changelog description for a Dokio template.\\n\\n"
|
|
946
|
+
f"Commit title: {title}\\n"
|
|
947
|
+
f"Files changed:\\n{files_str}"
|
|
948
|
+
f"{diff_section}\\n\\n"
|
|
949
|
+
f"Write a clear, concise description (2-4 sentences) explaining what changed and why. "
|
|
950
|
+
f"Focus on impact and purpose. Be specific but brief. No markdown headers or bullet points."
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
message = client.messages.create(
|
|
954
|
+
model="claude-haiku-4-5-20251001",
|
|
955
|
+
max_tokens=256,
|
|
956
|
+
messages=[{"role": "user", "content": prompt}],
|
|
957
|
+
)
|
|
958
|
+
return message.content[0].text.strip()
|
|
959
|
+
except Exception:
|
|
960
|
+
return None
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def parse_files(files_raw, template_prefix):
|
|
964
|
+
files = []
|
|
965
|
+
for line in files_raw.strip().splitlines():
|
|
966
|
+
line = line.strip()
|
|
967
|
+
if not line:
|
|
968
|
+
continue
|
|
969
|
+
parts = line.split("\\t")
|
|
970
|
+
if len(parts) >= 2:
|
|
971
|
+
status_key = parts[0].strip()[0].upper()
|
|
972
|
+
label = STATUS_LABELS.get(status_key, "Modified")
|
|
973
|
+
path = parts[-1].strip()
|
|
974
|
+
else:
|
|
975
|
+
label = "Modified"
|
|
976
|
+
path = parts[0].strip()
|
|
977
|
+
if template_prefix and path.startswith(template_prefix + "/"):
|
|
978
|
+
path = path[len(template_prefix):].lstrip("/")
|
|
979
|
+
files.append((label, path))
|
|
980
|
+
return files
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def build_entry_content(branch, title, description, author, today_str, files):
|
|
984
|
+
date_str = format_date(today_str)
|
|
985
|
+
lines = [
|
|
986
|
+
f"**{author}** \u2022 *{date_str}*",
|
|
987
|
+
f"**branch:** \`{branch}\`",
|
|
988
|
+
"",
|
|
989
|
+
f"**title:** {title}",
|
|
990
|
+
"",
|
|
991
|
+
f"**description:** {description}",
|
|
992
|
+
"",
|
|
993
|
+
"### Files Changed",
|
|
994
|
+
]
|
|
995
|
+
for label, path in files:
|
|
996
|
+
lines.append(f"- **{label}** \`{path}\`")
|
|
997
|
+
return "\\n".join(lines)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def update_changelog(path, branch, today, title, author, files, diff):
|
|
1001
|
+
with open(path, "r") as f:
|
|
1002
|
+
content = f.read()
|
|
1003
|
+
|
|
1004
|
+
description = generate_description(title, files, diff) or "TODO - INTEGRATE WITH AI (JAKE TASK)"
|
|
1005
|
+
entry_content = build_entry_content(branch, title, description, author, today, files)
|
|
1006
|
+
|
|
1007
|
+
insert_match = re.search(r"^(---|## )", content, re.MULTILINE)
|
|
1008
|
+
|
|
1009
|
+
if insert_match:
|
|
1010
|
+
insert_pos = insert_match.start()
|
|
1011
|
+
if content[insert_pos:].startswith("---"):
|
|
1012
|
+
new_block = f"---\\n\\n{entry_content}\\n\\n"
|
|
1013
|
+
else:
|
|
1014
|
+
new_block = f"---\\n\\n{entry_content}\\n\\n---\\n\\n"
|
|
1015
|
+
content = content[:insert_pos] + new_block + content[insert_pos:]
|
|
1016
|
+
else:
|
|
1017
|
+
content = content.rstrip("\\n") + f"\\n\\n---\\n\\n{entry_content}\\n\\n---\\n"
|
|
1018
|
+
|
|
1019
|
+
with open(path, "w") as f:
|
|
1020
|
+
f.write(content)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
if __name__ == "__main__":
|
|
1024
|
+
path = sys.argv[1]
|
|
1025
|
+
today = sys.argv[2]
|
|
1026
|
+
branch = os.environ.get("CHANGELOG_BRANCH", "")
|
|
1027
|
+
message = os.environ.get("CHANGELOG_MESSAGE", "")
|
|
1028
|
+
author = os.environ.get("CHANGELOG_AUTHOR", "")
|
|
1029
|
+
files_raw = os.environ.get("CHANGELOG_FILES", "")
|
|
1030
|
+
template_prefix = os.environ.get("CHANGELOG_TEMPLATE_PREFIX", "")
|
|
1031
|
+
diff = os.environ.get("CHANGELOG_DIFF", "")
|
|
1032
|
+
|
|
1033
|
+
files = parse_files(files_raw, template_prefix)
|
|
1034
|
+
update_changelog(path, branch, today, message, author, files, diff)
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
function changelogHook() {
|
|
1038
|
+
return `#!/bin/bash
|
|
1039
|
+
# Auto-updates CHANGELOG.md in modified template folders after each commit.
|
|
1040
|
+
# Amends the commit silently to include changelog changes.
|
|
1041
|
+
|
|
1042
|
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
1043
|
+
LOCK="$REPO_ROOT/.git/changelog-hook-running"
|
|
1044
|
+
|
|
1045
|
+
[ -f "$LOCK" ] && exit 0
|
|
1046
|
+
|
|
1047
|
+
CHANGED=$(git diff-tree --no-commit-id -r --name-only HEAD | grep -v "CHANGELOG\\.md$")
|
|
1048
|
+
[ -z "$CHANGED" ] && exit 0
|
|
1049
|
+
|
|
1050
|
+
# Detect changed templates (expects templates/<template-id>/ structure)
|
|
1051
|
+
TEMPLATES=$(echo "$CHANGED" | grep "^templates/" | awk -F'/' '{print $1"/"$2}' | sort -u)
|
|
1052
|
+
[ -z "$TEMPLATES" ] && exit 0
|
|
1053
|
+
|
|
1054
|
+
COMMIT_MSG=$(git log -1 --pretty=%s)
|
|
1055
|
+
AUTHOR=$(git log -1 --pretty="%an")
|
|
1056
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
1057
|
+
TODAY=$(date +%Y-%m-%d)
|
|
1058
|
+
|
|
1059
|
+
touch "$LOCK"
|
|
1060
|
+
trap 'rm -f "$LOCK"' EXIT
|
|
1061
|
+
|
|
1062
|
+
AMENDED=0
|
|
1063
|
+
while IFS= read -r TEMPLATE; do
|
|
1064
|
+
[ -z "$TEMPLATE" ] && continue
|
|
1065
|
+
CHANGELOG="$REPO_ROOT/$TEMPLATE/CHANGELOG.md"
|
|
1066
|
+
[ ! -f "$CHANGELOG" ] && continue
|
|
1067
|
+
|
|
1068
|
+
TEMPLATE_FILES=$(git diff-tree --no-commit-id -r --name-status HEAD \\
|
|
1069
|
+
| grep -v "CHANGELOG\\.md$" \\
|
|
1070
|
+
| grep -E $'\\t'"$TEMPLATE/")
|
|
1071
|
+
|
|
1072
|
+
TEMPLATE_DIFF=$(git diff-tree --no-commit-id -r -p HEAD -- "$TEMPLATE/" 2>/dev/null | head -c 4000)
|
|
1073
|
+
|
|
1074
|
+
export CHANGELOG_BRANCH="$BRANCH"
|
|
1075
|
+
export CHANGELOG_MESSAGE="$COMMIT_MSG"
|
|
1076
|
+
export CHANGELOG_AUTHOR="$AUTHOR"
|
|
1077
|
+
export CHANGELOG_FILES="$TEMPLATE_FILES"
|
|
1078
|
+
export CHANGELOG_TEMPLATE_PREFIX="$TEMPLATE"
|
|
1079
|
+
export CHANGELOG_DIFF="$TEMPLATE_DIFF"
|
|
1080
|
+
python3 "$REPO_ROOT/tools/changelog/update_changelog.py" "$CHANGELOG" "$TODAY"
|
|
1081
|
+
|
|
1082
|
+
git add "$CHANGELOG"
|
|
1083
|
+
AMENDED=1
|
|
1084
|
+
done <<< "$TEMPLATES"
|
|
1085
|
+
|
|
1086
|
+
if [ "$AMENDED" = "1" ]; then
|
|
1087
|
+
git commit --amend --no-edit
|
|
1088
|
+
fi
|
|
1089
|
+
`;
|
|
1090
|
+
}
|
|
1091
|
+
function changelogEnvExample() {
|
|
1092
|
+
return `ANTHROPIC_API_KEY=sk-ant-...
|
|
1093
|
+
`;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/git.ts
|
|
1097
|
+
async function ensureHubRepo(hubId) {
|
|
1098
|
+
const cwd = process.cwd();
|
|
1099
|
+
const dirName = hubRepoDirName(hubId);
|
|
1100
|
+
const alreadyInside = basename(cwd) === dirName;
|
|
1101
|
+
const hubDir = alreadyInside ? cwd : join(cwd, dirName);
|
|
1102
|
+
if (alreadyInside || await fse.pathExists(hubDir)) {
|
|
1103
|
+
console.log(kleur2.dim(`
|
|
1104
|
+
\u21BB Pulling latest ${dirName}...`));
|
|
1105
|
+
execSync("git pull", { cwd: hubDir, stdio: "ignore" });
|
|
1106
|
+
} else {
|
|
1107
|
+
console.log(kleur2.dim(`
|
|
1108
|
+
\u2193 Cloning ${hubRepoUrl(hubId)}...`));
|
|
1109
|
+
execSync(`git clone ${hubRepoUrl(hubId)}`, { stdio: "inherit" });
|
|
1110
|
+
}
|
|
1111
|
+
return hubDir;
|
|
1112
|
+
}
|
|
1113
|
+
async function setupHooks(hubDir) {
|
|
1114
|
+
const commitMsgPath = join(hubDir, ".githooks", "commit-msg");
|
|
1115
|
+
if (!await fse.pathExists(commitMsgPath)) {
|
|
1116
|
+
await fse.ensureDir(join(commitMsgPath, ".."));
|
|
1117
|
+
await fse.writeFile(commitMsgPath, commitMsgHook(), "utf8");
|
|
1118
|
+
await fse.chmod(commitMsgPath, 493);
|
|
1119
|
+
console.log(kleur2.dim(` + .githooks/commit-msg`));
|
|
1120
|
+
}
|
|
1121
|
+
const postCommitPath = join(hubDir, ".githooks", "post-commit");
|
|
1122
|
+
const changelogScriptPath = join(hubDir, "tools", "changelog", "update_changelog.py");
|
|
1123
|
+
if (!await fse.pathExists(changelogScriptPath)) {
|
|
1124
|
+
await fse.ensureDir(join(changelogScriptPath, ".."));
|
|
1125
|
+
await fse.writeFile(changelogScriptPath, changelogScript(), "utf8");
|
|
1126
|
+
await fse.chmod(changelogScriptPath, 493);
|
|
1127
|
+
console.log(kleur2.dim(` + tools/changelog/update_changelog.py`));
|
|
1128
|
+
const envExamplePath = join(hubDir, "tools", "changelog", ".env.example");
|
|
1129
|
+
await fse.writeFile(envExamplePath, changelogEnvExample(), "utf8");
|
|
1130
|
+
console.log(kleur2.dim(` + tools/changelog/.env.example`));
|
|
1131
|
+
if (!await fse.pathExists(postCommitPath)) {
|
|
1132
|
+
await fse.writeFile(postCommitPath, changelogHook(), "utf8");
|
|
1133
|
+
await fse.chmod(postCommitPath, 493);
|
|
1134
|
+
console.log(kleur2.dim(` + .githooks/post-commit`));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
const vscodePath = join(hubDir, ".vscode", "settings.json");
|
|
1138
|
+
if (!await fse.pathExists(vscodePath)) {
|
|
1139
|
+
await fse.ensureDir(join(vscodePath, ".."));
|
|
1140
|
+
await fse.writeFile(vscodePath, JSON.stringify({ "scss.validate": false, "css.validate": false }, null, 2) + "\n", "utf8");
|
|
1141
|
+
console.log(kleur2.dim(` + .vscode/settings.json`));
|
|
1142
|
+
}
|
|
1143
|
+
const gitignorePath = join(hubDir, ".gitignore");
|
|
1144
|
+
const requiredIgnores = [".DS_Store", "node_modules/", "*.log", "tools/changelog/.env"];
|
|
1145
|
+
if (!await fse.pathExists(gitignorePath)) {
|
|
1146
|
+
await fse.writeFile(gitignorePath, requiredIgnores.join("\n") + "\n", "utf8");
|
|
1147
|
+
console.log(kleur2.dim(` + .gitignore`));
|
|
1148
|
+
} else {
|
|
1149
|
+
const existing = await fse.readFile(gitignorePath, "utf8");
|
|
1150
|
+
const missing = requiredIgnores.filter((e) => !existing.includes(e));
|
|
1151
|
+
if (missing.length) {
|
|
1152
|
+
await fse.appendFile(gitignorePath, missing.join("\n") + "\n");
|
|
1153
|
+
console.log(kleur2.dim(` ~ .gitignore (added: ${missing.join(", ")})`));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const readmePath = join(hubDir, "README.md");
|
|
1157
|
+
if (!await fse.pathExists(readmePath)) {
|
|
1158
|
+
const hubName = basename(hubDir);
|
|
1159
|
+
await fse.writeFile(readmePath, `# ${hubName}
|
|
1160
|
+
|
|
1161
|
+
Templates for ${hubName} on Dokio.
|
|
1162
|
+
|
|
1163
|
+
## Creating a new template
|
|
1164
|
+
|
|
1165
|
+
Run \`create-dokio template\` from inside this repo.
|
|
1166
|
+
`, "utf8");
|
|
1167
|
+
console.log(kleur2.dim(` + README.md`));
|
|
1168
|
+
}
|
|
1169
|
+
execSync("git config core.hooksPath .githooks", { cwd: hubDir, stdio: "ignore" });
|
|
1170
|
+
}
|
|
1171
|
+
|
|
795
1172
|
// src/scaffold.ts
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1173
|
+
import { join as join2 } from "path";
|
|
1174
|
+
import fse2 from "fs-extra";
|
|
1175
|
+
import kleur3 from "kleur";
|
|
1176
|
+
async function writeFiles(outDir, files, fullName) {
|
|
1177
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
1178
|
+
const fullPath = join2(outDir, rel);
|
|
1179
|
+
await fse2.ensureDir(join2(fullPath, ".."));
|
|
1180
|
+
await fse2.writeFile(fullPath, content, "utf8");
|
|
1181
|
+
console.log(kleur3.dim(` + ${fullName}/${rel}`));
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/template.ts
|
|
1186
|
+
async function runTemplate(nameArg) {
|
|
1187
|
+
console.log(kleur4.bold().cyan("\n \u25C6 dokio create template\n"));
|
|
1188
|
+
const config = await runPrompts(nameArg);
|
|
1189
|
+
const files = buildFiles(config);
|
|
1190
|
+
const hubDir = await ensureHubRepo(config.hubId);
|
|
1191
|
+
const hubDirName = hubRepoDirName(config.hubId);
|
|
1192
|
+
const outDir = join3(hubDir, "templates", config.fullName);
|
|
1193
|
+
if (await fse3.pathExists(outDir)) {
|
|
1194
|
+
console.error(kleur4.red(`
|
|
1195
|
+
Error: "${config.fullName}" already exists in ${hubDirName}/templates/.
|
|
801
1196
|
`));
|
|
802
1197
|
process.exit(1);
|
|
803
1198
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1199
|
+
console.log("");
|
|
1200
|
+
await writeFiles(outDir, files, config.fullName);
|
|
1201
|
+
await setupHooks(hubDir);
|
|
1202
|
+
const templatePath = `${hubDirName}/templates/${config.fullName}`;
|
|
1203
|
+
console.log(kleur4.green(`
|
|
1204
|
+
\u2713 Created ${kleur4.bold(templatePath)}
|
|
1205
|
+
`));
|
|
1206
|
+
if (config.mode === "video") {
|
|
1207
|
+
console.log(kleur4.yellow(` \u26A0 video support is WIP \u2014 check data.yaml for TODOs
|
|
1208
|
+
`));
|
|
813
1209
|
}
|
|
1210
|
+
console.log(kleur4.dim(` Next steps:`));
|
|
1211
|
+
console.log(kleur4.dim(` cd ${templatePath}`));
|
|
1212
|
+
console.log(kleur4.dim(` Edit data.yaml \u2014 set your template ID`));
|
|
1213
|
+
console.log(kleur4.dim(` Add assets to assets/`));
|
|
1214
|
+
console.log("");
|
|
1215
|
+
console.log(kleur4.dim(` When ready to commit:`));
|
|
1216
|
+
console.log(kleur4.dim(` cd ../../ (back to ${hubDirName}/)`));
|
|
1217
|
+
console.log(kleur4.dim(` git add templates/${config.fullName}/`));
|
|
1218
|
+
console.log(kleur4.dim(` git commit -m "feat: add ${config.name} template"`));
|
|
1219
|
+
console.log(kleur4.dim(` git push`));
|
|
1220
|
+
console.log("");
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/hub.ts
|
|
1224
|
+
import { join as join4 } from "path";
|
|
1225
|
+
import { execSync as execSync2 } from "child_process";
|
|
1226
|
+
import fse4 from "fs-extra";
|
|
1227
|
+
import kleur5 from "kleur";
|
|
1228
|
+
import prompts2 from "prompts";
|
|
1229
|
+
var onCancel2 = () => {
|
|
1230
|
+
console.log(kleur5.yellow("\n Cancelled.\n"));
|
|
1231
|
+
process.exit(0);
|
|
1232
|
+
};
|
|
1233
|
+
async function runHub() {
|
|
1234
|
+
console.log(kleur5.bold().cyan("\n \u25C6 dokio create hub\n"));
|
|
1235
|
+
const answers = await prompts2(
|
|
1236
|
+
[
|
|
1237
|
+
{
|
|
1238
|
+
type: "text",
|
|
1239
|
+
name: "hubId",
|
|
1240
|
+
message: "Hub ID (kebab-case, e.g. bupa-sam)",
|
|
1241
|
+
validate: (v) => /^[a-z0-9-]+$/.test(v.trim()) || "Lowercase letters, numbers, hyphens only"
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
type: "text",
|
|
1245
|
+
name: "hubName",
|
|
1246
|
+
message: "Hub display name (e.g. Bupa Sales And Marketing Hub)",
|
|
1247
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
1248
|
+
}
|
|
1249
|
+
],
|
|
1250
|
+
{ onCancel: onCancel2 }
|
|
1251
|
+
);
|
|
1252
|
+
const hubId = answers.hubId.trim();
|
|
1253
|
+
const hubName = answers.hubName.trim();
|
|
1254
|
+
const dirName = `${hubId}-templates`;
|
|
1255
|
+
const outDir = join4(process.cwd(), dirName);
|
|
1256
|
+
if (await fse4.pathExists(outDir)) {
|
|
1257
|
+
console.error(kleur5.red(`
|
|
1258
|
+
Error: "${dirName}" already exists.
|
|
1259
|
+
`));
|
|
1260
|
+
process.exit(1);
|
|
1261
|
+
}
|
|
1262
|
+
const files = {
|
|
1263
|
+
".githooks/commit-msg": commitMsgHook(),
|
|
1264
|
+
".githooks/post-commit": changelogHook(),
|
|
1265
|
+
".vscode/settings.json": JSON.stringify({ "scss.validate": false, "css.validate": false }, null, 2) + "\n",
|
|
1266
|
+
"templates/.gitkeep": "",
|
|
1267
|
+
"tools/changelog/update_changelog.py": changelogScript(),
|
|
1268
|
+
"tools/changelog/.env.example": changelogEnvExample(),
|
|
1269
|
+
".gitignore": `.DS_Store
|
|
1270
|
+
node_modules/
|
|
1271
|
+
*.log
|
|
1272
|
+
tools/changelog/.env
|
|
1273
|
+
`,
|
|
1274
|
+
"README.md": `# ${hubName}
|
|
1275
|
+
|
|
1276
|
+
Templates for ${hubName} on Dokio.
|
|
1277
|
+
|
|
1278
|
+
## Creating a new template
|
|
1279
|
+
|
|
1280
|
+
Run \`create-dokio template\` from inside this repo.
|
|
1281
|
+
`
|
|
1282
|
+
};
|
|
814
1283
|
console.log("");
|
|
815
1284
|
for (const [rel, content] of Object.entries(files)) {
|
|
816
|
-
const fullPath =
|
|
817
|
-
await
|
|
818
|
-
await
|
|
819
|
-
|
|
1285
|
+
const fullPath = join4(outDir, rel);
|
|
1286
|
+
await fse4.ensureDir(join4(fullPath, ".."));
|
|
1287
|
+
await fse4.writeFile(fullPath, content, "utf8");
|
|
1288
|
+
if (rel === ".githooks/commit-msg" || rel === ".githooks/post-commit" || rel === "tools/changelog/update_changelog.py") await fse4.chmod(fullPath, 493);
|
|
1289
|
+
console.log(kleur5.dim(` + ${rel}`));
|
|
820
1290
|
}
|
|
1291
|
+
execSync2("git init", { cwd: outDir, stdio: "ignore" });
|
|
1292
|
+
execSync2("git config core.hooksPath .githooks", { cwd: outDir, stdio: "ignore" });
|
|
1293
|
+
execSync2("git add .", { cwd: outDir, stdio: "ignore" });
|
|
1294
|
+
execSync2('git commit -m "chore: init Dokio Hub"', { cwd: outDir, stdio: "ignore" });
|
|
1295
|
+
console.log(kleur5.green(`
|
|
1296
|
+
\u2713 Created ${kleur5.bold(dirName)}
|
|
1297
|
+
`));
|
|
1298
|
+
console.log(kleur5.dim(` Next steps:`));
|
|
1299
|
+
console.log(kleur5.dim(` cd ${dirName}`));
|
|
1300
|
+
console.log(kleur5.dim(` Create a GitHub repo: github.com/dokioco/${dirName}`));
|
|
1301
|
+
console.log(kleur5.dim(` git remote add origin https://github.com/dokioco/${dirName}`));
|
|
1302
|
+
console.log(kleur5.dim(` git push -u origin main`));
|
|
1303
|
+
console.log("");
|
|
1304
|
+
console.log(kleur5.dim(` Changelog (optional \u2014 for AI descriptions):`));
|
|
1305
|
+
console.log(kleur5.dim(` cp tools/changelog/.env.example tools/changelog/.env`));
|
|
1306
|
+
console.log(kleur5.dim(` # Add your ANTHROPIC_API_KEY to tools/changelog/.env`));
|
|
1307
|
+
console.log("");
|
|
821
1308
|
}
|
|
822
1309
|
|
|
823
1310
|
// src/index.ts
|
|
824
|
-
async function
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const isWip = config.mode === "video";
|
|
829
|
-
console.log(kleur3.green(`
|
|
830
|
-
\u2713 Created ${kleur3.bold(config.fullName)}
|
|
831
|
-
`));
|
|
832
|
-
if (isWip) {
|
|
833
|
-
console.log(
|
|
834
|
-
kleur3.yellow(` \u26A0 ${config.mode} support is WIP \u2014 check data.yaml for TODOs
|
|
835
|
-
`)
|
|
836
|
-
);
|
|
1311
|
+
async function main(argv) {
|
|
1312
|
+
const subcommand = argv[0];
|
|
1313
|
+
if (subcommand === "template") {
|
|
1314
|
+
return runTemplate(argv[1]);
|
|
837
1315
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
console.log(
|
|
842
|
-
|
|
1316
|
+
if (subcommand === "hub") {
|
|
1317
|
+
return runHub();
|
|
1318
|
+
}
|
|
1319
|
+
console.log(kleur6.bold().cyan("\n \u25C6 dokio create\n"));
|
|
1320
|
+
const { action } = await prompts3(
|
|
1321
|
+
{
|
|
1322
|
+
type: "select",
|
|
1323
|
+
name: "action",
|
|
1324
|
+
message: "What are you creating?",
|
|
1325
|
+
choices: [
|
|
1326
|
+
{ title: "Template", value: "template", description: "Scaffold a new template inside a hub repo" },
|
|
1327
|
+
{ title: "Hub", value: "hub", description: "Create a new hub repository" }
|
|
1328
|
+
]
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
onCancel: () => {
|
|
1332
|
+
console.log(kleur6.yellow("\n Cancelled.\n"));
|
|
1333
|
+
process.exit(0);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
if (action === "hub") return runHub();
|
|
1338
|
+
return runTemplate();
|
|
843
1339
|
}
|
|
844
1340
|
|
|
845
1341
|
// bin/index.ts
|
|
846
|
-
|
|
1342
|
+
main(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-dokio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "CLI scaffold for Dokio templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
"@commitlint/cli": "^19.0.0",
|
|
22
22
|
"@commitlint/config-conventional": "^19.0.0",
|
|
23
23
|
"@commitlint/types": "^19.0.0",
|
|
24
|
+
"@eslint/js": "^10.0.1",
|
|
24
25
|
"@types/fs-extra": "^11.0.4",
|
|
25
26
|
"@types/node": "^20.0.0",
|
|
26
27
|
"@types/prompts": "^2.4.9",
|
|
27
28
|
"changelogen": "^0.6.2",
|
|
29
|
+
"eslint": "^10.4.1",
|
|
28
30
|
"husky": "^9.0.0",
|
|
29
31
|
"tsup": "^8.0.0",
|
|
30
32
|
"tsx": "^4.22.3",
|
|
31
|
-
"typescript": "^5.4.0"
|
|
33
|
+
"typescript": "^5.4.0",
|
|
34
|
+
"typescript-eslint": "^8.60.0"
|
|
32
35
|
},
|
|
33
36
|
"tsup": {
|
|
34
37
|
"entry": {
|
|
@@ -52,6 +55,8 @@
|
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "tsup",
|
|
54
57
|
"dev": "tsup --watch",
|
|
55
|
-
"
|
|
58
|
+
"lint": "eslint",
|
|
59
|
+
"changelog": "changelogen --bump && tsx scripts/changelog.ts",
|
|
60
|
+
"release": "changelogen --bump --output CHANGELOG.md && git add package.json CHANGELOG.md && git commit -m \"chore(release): v$(node -p 'require(\"./package.json\").version')\" && git push -u origin HEAD"
|
|
56
61
|
}
|
|
57
62
|
}
|