create-dokio 0.1.1
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 +137 -0
- package/dist/index.js +846 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# create-dokio
|
|
2
|
+
|
|
3
|
+
CLI scaffold for Dokio templates. Generates the correct file structure, `data.yaml`, HTML boilerplate, and SCSS partials for PDF, General, Email, and Video template types.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Interactive — prompts for everything
|
|
9
|
+
npx create-dokio
|
|
10
|
+
|
|
11
|
+
# Pre-fill the template name
|
|
12
|
+
npx create-dokio pool-care
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> Using pnpm? `pnpm dlx create-dokio`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Template types
|
|
20
|
+
|
|
21
|
+
### PDF
|
|
22
|
+
PrinceXML-rendered print documents. Supports multi-page, resizable layouts, orderable exports, and proof downloads.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
dokio-pool-care/
|
|
26
|
+
├── CHANGELOG.md
|
|
27
|
+
├── data.yaml
|
|
28
|
+
├── index.html
|
|
29
|
+
├── assets/
|
|
30
|
+
├── partials/
|
|
31
|
+
│ ├── page1-cover.html
|
|
32
|
+
│ ├── page2-content.html
|
|
33
|
+
│ └── page3-cta.html
|
|
34
|
+
└── scss/
|
|
35
|
+
├── style.scss.hbs
|
|
36
|
+
├── _fonts.scss
|
|
37
|
+
├── _mixins.scss
|
|
38
|
+
├── _variables.scss
|
|
39
|
+
└── pages/
|
|
40
|
+
├── _page1.scss
|
|
41
|
+
├── _page2.scss
|
|
42
|
+
└── _page3.scss
|
|
43
|
+
```
|
|
44
|
+
|
|
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)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### General (image)
|
|
56
|
+
Chrome-rendered image output — JPG and/or PNG. Single or multi-section layouts.
|
|
57
|
+
|
|
58
|
+
**Prompts:**
|
|
59
|
+
- Dimensions (px)
|
|
60
|
+
- Page/section count
|
|
61
|
+
- Export formats (JPG, PNG, or both)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### Email
|
|
66
|
+
HTML email scaffold. Includes Outlook conditional comments, `{{{___assembled_css}}}` injection, `{{{subject}}}` title, and Gmail-safe table structure.
|
|
67
|
+
|
|
68
|
+
**Prompts:**
|
|
69
|
+
- Subdomain only — email structure is fixed at 600px
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### Video *(WIP)*
|
|
74
|
+
1920×1080 video template scaffold. `data.yaml` includes duration and highlight point fields.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## What gets generated
|
|
79
|
+
|
|
80
|
+
| File | Description |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `data.yaml` | Dokio template config — set your template ID and subdomain before upload |
|
|
83
|
+
| `index.html` | Entry HTML with Handlebars partial includes |
|
|
84
|
+
| `partials/*.html` | Per-page/section HTML fragments |
|
|
85
|
+
| `scss/style.scss.hbs` | Main SCSS entry — supports Handlebars syntax |
|
|
86
|
+
| `scss/_fonts.scss` | Empty with commented `@font-face` example |
|
|
87
|
+
| `scss/_variables.scss` | Dimension, colour, and typography variables |
|
|
88
|
+
| `scss/_mixins.scss` | `absolute()`, `flex()`, `truncate()` mixins |
|
|
89
|
+
| `scss/pages/_pageN.scss` | Per-page style partials |
|
|
90
|
+
| `assets/` | Drop fonts, images, and local assets here |
|
|
91
|
+
| `CHANGELOG.md` | Template-level changelog |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## After scaffolding
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd dokio-<name>
|
|
99
|
+
|
|
100
|
+
# 1. Set your template ID in data.yaml
|
|
101
|
+
# name: TEMPLATEID - dokio-<name> ← replace TEMPLATEID
|
|
102
|
+
|
|
103
|
+
# 2. Change subdomain from 'test' before uploading
|
|
104
|
+
# subdomain: your-hub-subdomain
|
|
105
|
+
|
|
106
|
+
# 3. Add fonts/images to assets/
|
|
107
|
+
# Declare fonts in scss/_fonts.scss
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git clone <repo>
|
|
116
|
+
pnpm install
|
|
117
|
+
pnpm dev # watch mode
|
|
118
|
+
pnpm build # compile to dist/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Commit format
|
|
122
|
+
|
|
123
|
+
This repo uses [Conventional Commits](https://www.conventionalcommits.org/):
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
feat: add resizable PDF option
|
|
127
|
+
fix: correct page count in data.yaml
|
|
128
|
+
chore: update dependencies
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`CHANGELOG.md` updates automatically on `git push`.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import kleur3 from "kleur";
|
|
5
|
+
|
|
6
|
+
// src/prompts.ts
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import kleur from "kleur";
|
|
9
|
+
|
|
10
|
+
// src/utils.ts
|
|
11
|
+
function toKebab(str) {
|
|
12
|
+
return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
13
|
+
}
|
|
14
|
+
function getPageName(index, total) {
|
|
15
|
+
if (total === 1) return "page1";
|
|
16
|
+
if (index === 0) return "page1-cover";
|
|
17
|
+
if (index === total - 1) return `page${index + 1}-cta`;
|
|
18
|
+
return `page${index + 1}-content`;
|
|
19
|
+
}
|
|
20
|
+
function buildPages(pageCount) {
|
|
21
|
+
return Array.from({ length: pageCount }, (_, i) => getPageName(i, pageCount));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/prompts.ts
|
|
25
|
+
var onCancel = () => {
|
|
26
|
+
console.log(kleur.yellow("\n Cancelled.\n"));
|
|
27
|
+
process.exit(0);
|
|
28
|
+
};
|
|
29
|
+
async function runPrompts(nameArg) {
|
|
30
|
+
const base = await prompts(
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
type: nameArg ? null : "text",
|
|
34
|
+
name: "name",
|
|
35
|
+
message: 'Template name (kebab-case, no "dokio-" prefix)',
|
|
36
|
+
initial: "my-template",
|
|
37
|
+
validate: (v) => /^[a-z0-9-]+$/.test(v) || "Lowercase letters, numbers, hyphens only"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "select",
|
|
41
|
+
name: "mode",
|
|
42
|
+
message: "Template type",
|
|
43
|
+
choices: [
|
|
44
|
+
{ title: "PDF", value: "pdf" },
|
|
45
|
+
{ title: "General (image/JPG/PNG)", value: "general" },
|
|
46
|
+
{ title: kleur.dim("Video [WIP]"), value: "video" },
|
|
47
|
+
{ title: "Email", value: "email" }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
name: "subdomain",
|
|
53
|
+
message: "Subdomain",
|
|
54
|
+
initial: "test"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
{ onCancel }
|
|
58
|
+
);
|
|
59
|
+
const name = nameArg ? toKebab(nameArg) : base.name;
|
|
60
|
+
const fullName = `dokio-${name}`;
|
|
61
|
+
const subdomain = base.subdomain;
|
|
62
|
+
if (base.mode === "pdf") {
|
|
63
|
+
const pdf = await prompts(
|
|
64
|
+
[
|
|
65
|
+
{ type: "number", name: "width", message: "Page width (mm)", initial: 210, min: 1 },
|
|
66
|
+
{ type: "number", name: "height", message: "Page height (mm)", initial: 297, min: 1 },
|
|
67
|
+
{ type: "number", name: "pageCount", message: "Number of pages", initial: 1, min: 1 },
|
|
68
|
+
{
|
|
69
|
+
type: "select",
|
|
70
|
+
name: "princeVersion",
|
|
71
|
+
message: "PrinceXML version",
|
|
72
|
+
choices: [
|
|
73
|
+
{ title: "15 (flexbox support \u2014 recommended)", value: 15 },
|
|
74
|
+
{ title: "11 (legacy)", value: 11 }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "toggle",
|
|
79
|
+
name: "resizable",
|
|
80
|
+
message: "Resizable template?",
|
|
81
|
+
initial: false,
|
|
82
|
+
active: "yes",
|
|
83
|
+
inactive: "no"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: "toggle",
|
|
87
|
+
name: "proofable",
|
|
88
|
+
message: "Enable proof downloads (draft_proofable)?",
|
|
89
|
+
initial: true,
|
|
90
|
+
active: "yes",
|
|
91
|
+
inactive: "no"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "toggle",
|
|
95
|
+
name: "orderable",
|
|
96
|
+
message: "Include orderable export (Print from Snap)?",
|
|
97
|
+
initial: false,
|
|
98
|
+
active: "yes",
|
|
99
|
+
inactive: "no"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
{ onCancel }
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
mode: "pdf",
|
|
106
|
+
name,
|
|
107
|
+
fullName,
|
|
108
|
+
subdomain,
|
|
109
|
+
width: pdf.width,
|
|
110
|
+
height: pdf.height,
|
|
111
|
+
pageCount: pdf.pageCount,
|
|
112
|
+
princeVersion: pdf.princeVersion,
|
|
113
|
+
resizable: pdf.resizable,
|
|
114
|
+
proofable: pdf.proofable,
|
|
115
|
+
orderable: pdf.orderable
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (base.mode === "general") {
|
|
119
|
+
const gen = await prompts(
|
|
120
|
+
[
|
|
121
|
+
{ type: "number", name: "width", message: "Width (px)", initial: 400, min: 1 },
|
|
122
|
+
{ type: "number", name: "height", message: "Height (px)", initial: 400, min: 1 },
|
|
123
|
+
{
|
|
124
|
+
type: "number",
|
|
125
|
+
name: "pageCount",
|
|
126
|
+
message: "Number of pages / sections",
|
|
127
|
+
initial: 1,
|
|
128
|
+
min: 1
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: "multiselect",
|
|
132
|
+
name: "exportFormats",
|
|
133
|
+
message: "Export formats",
|
|
134
|
+
choices: [
|
|
135
|
+
{ title: "JPG", value: "jpg", selected: true },
|
|
136
|
+
{ title: "PNG", value: "png", selected: true }
|
|
137
|
+
],
|
|
138
|
+
min: 1
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
{ onCancel }
|
|
142
|
+
);
|
|
143
|
+
return {
|
|
144
|
+
mode: "general",
|
|
145
|
+
name,
|
|
146
|
+
fullName,
|
|
147
|
+
subdomain,
|
|
148
|
+
width: gen.width,
|
|
149
|
+
height: gen.height,
|
|
150
|
+
pageCount: gen.pageCount,
|
|
151
|
+
exportFormats: gen.exportFormats
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (base.mode === "video") {
|
|
155
|
+
return { mode: "video", name, fullName, subdomain };
|
|
156
|
+
}
|
|
157
|
+
return { mode: "email", name, fullName, subdomain };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/scaffold.ts
|
|
161
|
+
import { join } from "path";
|
|
162
|
+
import fse from "fs-extra";
|
|
163
|
+
import kleur2 from "kleur";
|
|
164
|
+
|
|
165
|
+
// src/templates/shared.ts
|
|
166
|
+
function changelog(fullName) {
|
|
167
|
+
return `# Changelog
|
|
168
|
+
|
|
169
|
+
All notable changes to **${fullName}** will be documented here.
|
|
170
|
+
|
|
171
|
+
## [Unreleased]
|
|
172
|
+
|
|
173
|
+
### Added
|
|
174
|
+
- Initial scaffold
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
function gitkeep() {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
function fontsScss() {
|
|
181
|
+
const weights = [
|
|
182
|
+
["Regular", 400],
|
|
183
|
+
["Light", 300],
|
|
184
|
+
["Medium", 500],
|
|
185
|
+
["SemiBold", 600],
|
|
186
|
+
["Bold", 700],
|
|
187
|
+
["Black", 900]
|
|
188
|
+
];
|
|
189
|
+
return weights.map(
|
|
190
|
+
([variant, weight]) => `@font-face {
|
|
191
|
+
font-family: 'Montserrat';
|
|
192
|
+
src: url('../assets/Montserrat-${variant}.ttf') format('truetype');
|
|
193
|
+
font-weight: ${weight};
|
|
194
|
+
font-style: normal;
|
|
195
|
+
}`
|
|
196
|
+
).join("\n\n") + "\n";
|
|
197
|
+
}
|
|
198
|
+
function fontsScssEmpty() {
|
|
199
|
+
return `// Add custom fonts here.
|
|
200
|
+
// Place font files in assets/ and declare them below.
|
|
201
|
+
//
|
|
202
|
+
// Example:
|
|
203
|
+
//
|
|
204
|
+
// @font-face {
|
|
205
|
+
// font-family: 'Montserrat';
|
|
206
|
+
// src: url('../assets/Montserrat-Regular.ttf') format('truetype');
|
|
207
|
+
// font-weight: 400;
|
|
208
|
+
// font-style: normal;
|
|
209
|
+
// }
|
|
210
|
+
//
|
|
211
|
+
// @font-face {
|
|
212
|
+
// font-family: 'Montserrat';
|
|
213
|
+
// src: url('../assets/Montserrat-Bold.ttf') format('truetype');
|
|
214
|
+
// font-weight: 700;
|
|
215
|
+
// font-style: normal;
|
|
216
|
+
// }
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
function mixinsScss() {
|
|
220
|
+
return `@mixin absolute($top: auto, $right: auto, $bottom: auto, $left: auto) {
|
|
221
|
+
position: absolute;
|
|
222
|
+
top: $top;
|
|
223
|
+
right: $right;
|
|
224
|
+
bottom: $bottom;
|
|
225
|
+
left: $left;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@mixin flex($align: center, $justify: center, $direction: row) {
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: $align;
|
|
231
|
+
justify-content: $justify;
|
|
232
|
+
flex-direction: $direction;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@mixin truncate($lines: 1) {
|
|
236
|
+
@if $lines == 1 {
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
text-overflow: ellipsis;
|
|
239
|
+
white-space: nowrap;
|
|
240
|
+
} @else {
|
|
241
|
+
display: -webkit-box;
|
|
242
|
+
-webkit-line-clamp: $lines;
|
|
243
|
+
-webkit-box-orient: vertical;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/templates/pdf.ts
|
|
251
|
+
function pdfFiles(config, pages) {
|
|
252
|
+
return {
|
|
253
|
+
"CHANGELOG.md": changelog(config.fullName),
|
|
254
|
+
"data.yaml": pdfYaml(config),
|
|
255
|
+
"index.html": pdfHtml(pages),
|
|
256
|
+
"assets/.gitkeep": gitkeep(),
|
|
257
|
+
...pdfPartials(pages),
|
|
258
|
+
"scss/style.scss.hbs": pdfStyleSass(config, pages),
|
|
259
|
+
"scss/_fonts.scss": fontsScssEmpty(),
|
|
260
|
+
"scss/_variables.scss": pdfVariables(config),
|
|
261
|
+
"scss/_mixins.scss": mixinsScss(),
|
|
262
|
+
...pdfPageScss(pages)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function pdfYaml(config) {
|
|
266
|
+
const { fullName, subdomain, width, height, pageCount, princeVersion, resizable, proofable, orderable } = config;
|
|
267
|
+
const lines = [
|
|
268
|
+
`name: TEMPLATEID - ${fullName}`,
|
|
269
|
+
`mode: pdf`,
|
|
270
|
+
`prince_version: ${princeVersion}`,
|
|
271
|
+
`status: 0`,
|
|
272
|
+
`subdomain: ${subdomain}`,
|
|
273
|
+
`dimension_mode: mm`,
|
|
274
|
+
`page_count: ${pageCount}`,
|
|
275
|
+
`dimension_width: ${width}`,
|
|
276
|
+
`dimension_height: ${height}`,
|
|
277
|
+
`compositing_data: {}`
|
|
278
|
+
];
|
|
279
|
+
if (proofable) lines.push(`draft_proofable: true`);
|
|
280
|
+
if (resizable) lines.push(`resizable: true`);
|
|
281
|
+
lines.push(`export_options:`);
|
|
282
|
+
if (orderable) {
|
|
283
|
+
lines.push(
|
|
284
|
+
` orderable:`,
|
|
285
|
+
` title: Print order`,
|
|
286
|
+
` description: Order from Snap`,
|
|
287
|
+
` description_complete: Ordered from Snap`,
|
|
288
|
+
` title_download: Download for your own printer`,
|
|
289
|
+
` description_download: Download for your own printer`,
|
|
290
|
+
` description_download_complete: Downloaded for your own printer`,
|
|
291
|
+
` marks: true`,
|
|
292
|
+
` bleed: true`,
|
|
293
|
+
` supplier_choice: true`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
lines.push(
|
|
297
|
+
` downloadables:`,
|
|
298
|
+
` hi_res:`,
|
|
299
|
+
` title: Hi-res Download`,
|
|
300
|
+
` renderer: pdf`,
|
|
301
|
+
` marks: true`,
|
|
302
|
+
` bleed: true`,
|
|
303
|
+
` slug: false`,
|
|
304
|
+
` approval_required: false`,
|
|
305
|
+
` disable_printing: false`,
|
|
306
|
+
` low-res-pdf:`,
|
|
307
|
+
` title: Local Printer PDF - Use this version when emailing or printing in-house.`,
|
|
308
|
+
` renderer: pdf`,
|
|
309
|
+
` bleed: false`,
|
|
310
|
+
` slug: false`,
|
|
311
|
+
` marks: false`,
|
|
312
|
+
` approval_required: false`,
|
|
313
|
+
` disable_printing: false`,
|
|
314
|
+
` resolution: 200`,
|
|
315
|
+
`fields_data:`
|
|
316
|
+
);
|
|
317
|
+
return lines.join("\n") + "\n";
|
|
318
|
+
}
|
|
319
|
+
function pdfHtml(pages) {
|
|
320
|
+
const partialIncludes = pages.map((p) => ` {{> ${p}}}`).join("\n");
|
|
321
|
+
return `{{ pragma "disable-legacy-nesting" }}
|
|
322
|
+
<!DOCTYPE html>
|
|
323
|
+
<html>
|
|
324
|
+
<head>
|
|
325
|
+
<meta charset="UTF-8">
|
|
326
|
+
</head>
|
|
327
|
+
<body>
|
|
328
|
+
${partialIncludes}
|
|
329
|
+
</body>
|
|
330
|
+
</html>
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
function pdfPartials(pages) {
|
|
334
|
+
return Object.fromEntries(
|
|
335
|
+
pages.map((pageName, i) => {
|
|
336
|
+
const label = pageName.includes("-") ? pageName.split("-").slice(1).join(" ") : `page ${i + 1}`;
|
|
337
|
+
return [
|
|
338
|
+
`partials/${pageName}.html`,
|
|
339
|
+
`<div class="page ${pageName}">
|
|
340
|
+
{{! ${label} }}
|
|
341
|
+
</div>
|
|
342
|
+
`
|
|
343
|
+
];
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
function pdfStyleSass(config, pages) {
|
|
348
|
+
const { width, height, resizable } = config;
|
|
349
|
+
const pageImports = pages.map((_, i) => `@import 'pages/page${i + 1}';`).join("\n");
|
|
350
|
+
if (resizable) {
|
|
351
|
+
return `@import 'fonts';
|
|
352
|
+
@import 'variables';
|
|
353
|
+
@import 'mixins';
|
|
354
|
+
|
|
355
|
+
${pageImports}
|
|
356
|
+
|
|
357
|
+
{{! Add resizable page sizes below \u2014 update data.yaml select field to match }}
|
|
358
|
+
{{#if_eq page-size 'A4'}}
|
|
359
|
+
@page { size: ${width}mm ${height}mm; }
|
|
360
|
+
$multiplier: 1;
|
|
361
|
+
{{/if_eq}}
|
|
362
|
+
|
|
363
|
+
* {
|
|
364
|
+
box-sizing: border-box;
|
|
365
|
+
margin: 0;
|
|
366
|
+
padding: 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.page {
|
|
370
|
+
width: ${width}mm * $multiplier;
|
|
371
|
+
height: ${height}mm * $multiplier;
|
|
372
|
+
position: relative;
|
|
373
|
+
overflow: hidden;
|
|
374
|
+
page-break-after: always;
|
|
375
|
+
|
|
376
|
+
&:last-child {
|
|
377
|
+
page-break-after: avoid;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
return `@import 'fonts';
|
|
383
|
+
@import 'variables';
|
|
384
|
+
@import 'mixins';
|
|
385
|
+
|
|
386
|
+
${pageImports}
|
|
387
|
+
|
|
388
|
+
@page {
|
|
389
|
+
size: ${width}mm ${height}mm;
|
|
390
|
+
padding: 0;
|
|
391
|
+
margin: 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
* {
|
|
395
|
+
box-sizing: border-box;
|
|
396
|
+
margin: 0;
|
|
397
|
+
padding: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
body {
|
|
401
|
+
width: ${width}mm;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.page {
|
|
405
|
+
width: ${width}mm;
|
|
406
|
+
height: ${height}mm;
|
|
407
|
+
position: relative;
|
|
408
|
+
overflow: hidden;
|
|
409
|
+
page-break-after: always;
|
|
410
|
+
|
|
411
|
+
&:last-child {
|
|
412
|
+
page-break-after: avoid;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
`;
|
|
416
|
+
}
|
|
417
|
+
function pdfVariables(config) {
|
|
418
|
+
return `// Dimensions
|
|
419
|
+
$page-width: ${config.width}mm;
|
|
420
|
+
$page-height: ${config.height}mm;
|
|
421
|
+
|
|
422
|
+
// Typography
|
|
423
|
+
$font-primary: 'Montserrat', Arial, sans-serif;
|
|
424
|
+
|
|
425
|
+
// Colors \u2014 update to match brand
|
|
426
|
+
$color-primary: #000000;
|
|
427
|
+
$color-secondary: #ffffff;
|
|
428
|
+
$color-accent: #0079C8;
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
function pdfPageScss(pages) {
|
|
432
|
+
return Object.fromEntries(
|
|
433
|
+
pages.map((pageName, i) => {
|
|
434
|
+
const label = pageName.includes("-") ? pageName.split("-").slice(1).join(" ") : `page ${i + 1}`;
|
|
435
|
+
return [`scss/pages/_page${i + 1}.scss`, `.${pageName} {
|
|
436
|
+
// ${label} styles
|
|
437
|
+
}
|
|
438
|
+
`];
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/templates/general.ts
|
|
444
|
+
function generalFiles(config, pages) {
|
|
445
|
+
return {
|
|
446
|
+
"CHANGELOG.md": changelog(config.fullName),
|
|
447
|
+
"data.yaml": generalYaml(config),
|
|
448
|
+
"index.html": generalHtml(pages),
|
|
449
|
+
"assets/.gitkeep": gitkeep(),
|
|
450
|
+
...generalPartials(pages),
|
|
451
|
+
"scss/style.scss.hbs": generalStyleSass(config, pages),
|
|
452
|
+
"scss/_fonts.scss": fontsScssEmpty(),
|
|
453
|
+
"scss/_variables.scss": generalVariables(config),
|
|
454
|
+
"scss/_mixins.scss": mixinsScss(),
|
|
455
|
+
...generalPageScss(pages)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function generalYaml(config) {
|
|
459
|
+
const { fullName, subdomain, width, height, pageCount, exportFormats } = config;
|
|
460
|
+
const lines = [
|
|
461
|
+
`name: TEMPLATEID - ${fullName}`,
|
|
462
|
+
`mode: general`,
|
|
463
|
+
`subdomain: ${subdomain}`,
|
|
464
|
+
`dimension_mode: px`,
|
|
465
|
+
`page_count: ${pageCount}`,
|
|
466
|
+
`dimension_width: ${width}`,
|
|
467
|
+
`dimension_height: ${height}`,
|
|
468
|
+
`compositing_data: {}`,
|
|
469
|
+
`export_options:`,
|
|
470
|
+
` downloadables:`
|
|
471
|
+
];
|
|
472
|
+
if (exportFormats.includes("jpg")) {
|
|
473
|
+
lines.push(
|
|
474
|
+
` jpg_chrome:`,
|
|
475
|
+
` title: JPG`,
|
|
476
|
+
` renderer: image`,
|
|
477
|
+
` format: jpg`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (exportFormats.includes("png")) {
|
|
481
|
+
lines.push(
|
|
482
|
+
` png_chrome:`,
|
|
483
|
+
` title: PNG`,
|
|
484
|
+
` renderer: image`,
|
|
485
|
+
` format: png`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
lines.push(`fields_data:`);
|
|
489
|
+
return lines.join("\n") + "\n";
|
|
490
|
+
}
|
|
491
|
+
function generalHtml(pages) {
|
|
492
|
+
if (pages.length > 1) {
|
|
493
|
+
const partialIncludes = pages.map((p) => ` {{> ${p}}}`).join("\n");
|
|
494
|
+
return `{{ pragma "disable-legacy-nesting" }}
|
|
495
|
+
<!DOCTYPE html>
|
|
496
|
+
<html>
|
|
497
|
+
<head>
|
|
498
|
+
<meta charset="UTF-8">
|
|
499
|
+
</head>
|
|
500
|
+
<body>
|
|
501
|
+
${partialIncludes}
|
|
502
|
+
</body>
|
|
503
|
+
</html>
|
|
504
|
+
`;
|
|
505
|
+
}
|
|
506
|
+
return `{{ pragma "disable-legacy-nesting" }}
|
|
507
|
+
<!DOCTYPE html>
|
|
508
|
+
<html>
|
|
509
|
+
<head>
|
|
510
|
+
<meta charset="UTF-8">
|
|
511
|
+
</head>
|
|
512
|
+
<body>
|
|
513
|
+
<div class="page">
|
|
514
|
+
{{! Template content here }}
|
|
515
|
+
</div>
|
|
516
|
+
</body>
|
|
517
|
+
</html>
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
function generalPartials(pages) {
|
|
521
|
+
if (pages.length <= 1) return {};
|
|
522
|
+
return Object.fromEntries(
|
|
523
|
+
pages.map((pageName, i) => {
|
|
524
|
+
const label = pageName.includes("-") ? pageName.split("-").slice(1).join(" ") : `section ${i + 1}`;
|
|
525
|
+
return [
|
|
526
|
+
`partials/${pageName}.html`,
|
|
527
|
+
`<div class="page ${pageName}">
|
|
528
|
+
{{! ${label} }}
|
|
529
|
+
</div>
|
|
530
|
+
`
|
|
531
|
+
];
|
|
532
|
+
})
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
function generalStyleSass(config, pages) {
|
|
536
|
+
const { width, height } = config;
|
|
537
|
+
const pageImports = pages.map((_, i) => `@import 'pages/page${i + 1}';`).join("\n");
|
|
538
|
+
return `@import 'fonts';
|
|
539
|
+
@import 'variables';
|
|
540
|
+
@import 'mixins';
|
|
541
|
+
|
|
542
|
+
${pageImports}
|
|
543
|
+
|
|
544
|
+
* {
|
|
545
|
+
box-sizing: border-box;
|
|
546
|
+
margin: 0;
|
|
547
|
+
padding: 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
body {
|
|
551
|
+
width: ${width}px;
|
|
552
|
+
height: ${height}px;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.page {
|
|
556
|
+
width: ${width}px;
|
|
557
|
+
height: ${height}px;
|
|
558
|
+
position: relative;
|
|
559
|
+
overflow: hidden;
|
|
560
|
+
}
|
|
561
|
+
`;
|
|
562
|
+
}
|
|
563
|
+
function generalVariables(config) {
|
|
564
|
+
return `// Dimensions
|
|
565
|
+
$page-width: ${config.width}px;
|
|
566
|
+
$page-height: ${config.height}px;
|
|
567
|
+
|
|
568
|
+
// Typography
|
|
569
|
+
$font-primary: 'Montserrat', Arial, sans-serif;
|
|
570
|
+
|
|
571
|
+
// Colors \u2014 use hex or rgb only (no CMYK in general templates)
|
|
572
|
+
$color-primary: #000000;
|
|
573
|
+
$color-secondary: #ffffff;
|
|
574
|
+
$color-accent: #0079C8;
|
|
575
|
+
`;
|
|
576
|
+
}
|
|
577
|
+
function generalPageScss(pages) {
|
|
578
|
+
return Object.fromEntries(
|
|
579
|
+
pages.map((pageName, i) => {
|
|
580
|
+
const label = pageName.includes("-") ? pageName.split("-").slice(1).join(" ") : `section ${i + 1}`;
|
|
581
|
+
return [`scss/pages/_page${i + 1}.scss`, `.${pageName} {
|
|
582
|
+
// ${label} styles
|
|
583
|
+
}
|
|
584
|
+
`];
|
|
585
|
+
})
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/templates/video.ts
|
|
590
|
+
function videoFiles(config) {
|
|
591
|
+
return {
|
|
592
|
+
"CHANGELOG.md": changelog(config.fullName),
|
|
593
|
+
"data.yaml": videoYaml(config),
|
|
594
|
+
"index.html": videoHtml(),
|
|
595
|
+
"assets/.gitkeep": gitkeep(),
|
|
596
|
+
"scss/style.scss.hbs": videoStyleSass(),
|
|
597
|
+
"scss/_fonts.scss": fontsScss(),
|
|
598
|
+
"scss/_variables.scss": videoVariables(),
|
|
599
|
+
"scss/_mixins.scss": mixinsScss(),
|
|
600
|
+
"scss/pages/_page1.scss": `.page {
|
|
601
|
+
// video frame styles
|
|
602
|
+
}
|
|
603
|
+
`
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function videoYaml(config) {
|
|
607
|
+
return `# TODO: Video template support is WIP
|
|
608
|
+
name: TEMPLATEID - ${config.fullName}
|
|
609
|
+
mode: video
|
|
610
|
+
subdomain: ${config.subdomain}
|
|
611
|
+
duration: '30'
|
|
612
|
+
highlight_point: '27'
|
|
613
|
+
dimension_height: 1080
|
|
614
|
+
dimension_width: 1920
|
|
615
|
+
export_options:
|
|
616
|
+
downloadables:
|
|
617
|
+
hi-res:
|
|
618
|
+
title: 1080p
|
|
619
|
+
fields_data:
|
|
620
|
+
`;
|
|
621
|
+
}
|
|
622
|
+
function videoHtml() {
|
|
623
|
+
return `{{ pragma "disable-legacy-nesting" }}
|
|
624
|
+
<!DOCTYPE html>
|
|
625
|
+
<html>
|
|
626
|
+
<head>
|
|
627
|
+
<meta charset="UTF-8">
|
|
628
|
+
</head>
|
|
629
|
+
<body>
|
|
630
|
+
<div class="page">
|
|
631
|
+
{{! TODO: Video template content }}
|
|
632
|
+
</div>
|
|
633
|
+
</body>
|
|
634
|
+
</html>
|
|
635
|
+
`;
|
|
636
|
+
}
|
|
637
|
+
function videoStyleSass() {
|
|
638
|
+
return `@import 'fonts';
|
|
639
|
+
@import 'variables';
|
|
640
|
+
@import 'mixins';
|
|
641
|
+
@import 'pages/page1';
|
|
642
|
+
|
|
643
|
+
* {
|
|
644
|
+
box-sizing: border-box;
|
|
645
|
+
margin: 0;
|
|
646
|
+
padding: 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
body {
|
|
650
|
+
width: 1920px;
|
|
651
|
+
height: 1080px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.page {
|
|
655
|
+
width: 1920px;
|
|
656
|
+
height: 1080px;
|
|
657
|
+
position: relative;
|
|
658
|
+
overflow: hidden;
|
|
659
|
+
}
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
function videoVariables() {
|
|
663
|
+
return `$page-width: 1920px;
|
|
664
|
+
$page-height: 1080px;
|
|
665
|
+
|
|
666
|
+
$font-primary: 'Montserrat', Arial, sans-serif;
|
|
667
|
+
|
|
668
|
+
$color-primary: #000000;
|
|
669
|
+
$color-secondary: #ffffff;
|
|
670
|
+
$color-accent: #0079C8;
|
|
671
|
+
`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/templates/email.ts
|
|
675
|
+
function emailFiles(config) {
|
|
676
|
+
return {
|
|
677
|
+
"CHANGELOG.md": changelog(config.fullName),
|
|
678
|
+
"data.yaml": emailYaml(config),
|
|
679
|
+
"index.html": emailHtml(),
|
|
680
|
+
"assets/.gitkeep": gitkeep(),
|
|
681
|
+
"scss/style.scss.hbs": emailStyleSass(),
|
|
682
|
+
"scss/_fonts.scss": fontsScss(),
|
|
683
|
+
"scss/_variables.scss": emailVariables(),
|
|
684
|
+
"scss/_mixins.scss": mixinsScss(),
|
|
685
|
+
"scss/pages/_page1.scss": `.email-body {
|
|
686
|
+
// email body styles
|
|
687
|
+
}
|
|
688
|
+
`
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function emailYaml(config) {
|
|
692
|
+
return `name: TEMPLATEID - ${config.fullName}
|
|
693
|
+
mode: email
|
|
694
|
+
status: 0
|
|
695
|
+
subdomain: ${config.subdomain}
|
|
696
|
+
export_options:
|
|
697
|
+
downloadables:
|
|
698
|
+
a:
|
|
699
|
+
local_zip:
|
|
700
|
+
title: Standalone Download (local assets)
|
|
701
|
+
host_files: false
|
|
702
|
+
output: zip
|
|
703
|
+
b:
|
|
704
|
+
hosted_download:
|
|
705
|
+
title: HTML Download (hosted assets)
|
|
706
|
+
host_files: true
|
|
707
|
+
output: zip
|
|
708
|
+
fields_data:
|
|
709
|
+
`;
|
|
710
|
+
}
|
|
711
|
+
function emailHtml() {
|
|
712
|
+
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
|
713
|
+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
714
|
+
<head>
|
|
715
|
+
<!--[if gte mso 9]>
|
|
716
|
+
<xml>
|
|
717
|
+
<o:OfficeDocumentSettings>
|
|
718
|
+
<o:AllowPNG/>
|
|
719
|
+
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
720
|
+
</o:OfficeDocumentSettings>
|
|
721
|
+
</xml>
|
|
722
|
+
<![endif]-->
|
|
723
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
724
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
725
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
726
|
+
<title>{{{subject}}}</title>
|
|
727
|
+
<style type="text/css">{{{___assembled_css}}}</style>
|
|
728
|
+
</head>
|
|
729
|
+
<body style="margin: 0 0 0 0; padding: 0 0 0 0;" bgcolor="#ededed">
|
|
730
|
+
<table role="none" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
731
|
+
<tr>
|
|
732
|
+
<td>
|
|
733
|
+
<center>
|
|
734
|
+
<table role="none" width="600" class="full-wd" cellpadding="0" cellspacing="0" border="0">
|
|
735
|
+
<tr>
|
|
736
|
+
<td>
|
|
737
|
+
{{! TODO: Email content here }}
|
|
738
|
+
</td>
|
|
739
|
+
</tr>
|
|
740
|
+
</table>
|
|
741
|
+
</center>
|
|
742
|
+
</td>
|
|
743
|
+
</tr>
|
|
744
|
+
</table>
|
|
745
|
+
</body>
|
|
746
|
+
</html>
|
|
747
|
+
`;
|
|
748
|
+
}
|
|
749
|
+
function emailStyleSass() {
|
|
750
|
+
return `@import 'fonts';
|
|
751
|
+
@import 'variables';
|
|
752
|
+
@import 'mixins';
|
|
753
|
+
@import 'pages/page1';
|
|
754
|
+
|
|
755
|
+
// Use hex/rgb only \u2014 no CMYK in email
|
|
756
|
+
|
|
757
|
+
* {
|
|
758
|
+
box-sizing: border-box;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
body {
|
|
762
|
+
margin: 0;
|
|
763
|
+
padding: 0;
|
|
764
|
+
background-color: #f4f4f4;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
img {
|
|
768
|
+
display: block;
|
|
769
|
+
border: 0;
|
|
770
|
+
outline: none;
|
|
771
|
+
text-decoration: none;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
@media screen and (max-width: 599px) {
|
|
775
|
+
.mobileOff {
|
|
776
|
+
width: 0px !important;
|
|
777
|
+
display: none !important;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
`;
|
|
781
|
+
}
|
|
782
|
+
function emailVariables() {
|
|
783
|
+
return `$email-width: 600px;
|
|
784
|
+
|
|
785
|
+
// Use hex/rgb only (no CMYK)
|
|
786
|
+
$font-primary: 'Montserrat', Arial, sans-serif;
|
|
787
|
+
|
|
788
|
+
$color-primary: #000000;
|
|
789
|
+
$color-secondary: #ffffff;
|
|
790
|
+
$color-background: #f4f4f4;
|
|
791
|
+
$color-accent: #0079C8;
|
|
792
|
+
`;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/scaffold.ts
|
|
796
|
+
async function scaffold(config) {
|
|
797
|
+
const outDir = join(process.cwd(), config.fullName);
|
|
798
|
+
if (await fse.pathExists(outDir)) {
|
|
799
|
+
console.error(kleur2.red(`
|
|
800
|
+
Error: directory "${config.fullName}" already exists.
|
|
801
|
+
`));
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
let files;
|
|
805
|
+
if (config.mode === "pdf") {
|
|
806
|
+
files = pdfFiles(config, buildPages(config.pageCount));
|
|
807
|
+
} else if (config.mode === "general") {
|
|
808
|
+
files = generalFiles(config, buildPages(config.pageCount));
|
|
809
|
+
} else if (config.mode === "video") {
|
|
810
|
+
files = videoFiles(config);
|
|
811
|
+
} else {
|
|
812
|
+
files = emailFiles(config);
|
|
813
|
+
}
|
|
814
|
+
console.log("");
|
|
815
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
816
|
+
const fullPath = join(outDir, rel);
|
|
817
|
+
await fse.ensureDir(join(fullPath, ".."));
|
|
818
|
+
await fse.writeFile(fullPath, content, "utf8");
|
|
819
|
+
console.log(kleur2.dim(` + ${rel}`));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/index.ts
|
|
824
|
+
async function run(nameArg) {
|
|
825
|
+
console.log(kleur3.bold().cyan("\n \u25C6 dokio create\n"));
|
|
826
|
+
const config = await runPrompts(nameArg);
|
|
827
|
+
await scaffold(config);
|
|
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
|
+
);
|
|
837
|
+
}
|
|
838
|
+
console.log(kleur3.dim(` Next steps:`));
|
|
839
|
+
console.log(kleur3.dim(` cd ${config.fullName}`));
|
|
840
|
+
console.log(kleur3.dim(` Edit data.yaml \u2014 set your template ID and subdomain`));
|
|
841
|
+
console.log(kleur3.dim(` Add assets to assets/`));
|
|
842
|
+
console.log("");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// bin/index.ts
|
|
846
|
+
run(process.argv[2]);
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-dokio",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI scaffold for Dokio templates",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-dokio": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fs-extra": "^11.2.0",
|
|
17
|
+
"kleur": "^4.1.5",
|
|
18
|
+
"prompts": "^2.4.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@commitlint/cli": "^19.0.0",
|
|
22
|
+
"@commitlint/config-conventional": "^19.0.0",
|
|
23
|
+
"@commitlint/types": "^19.0.0",
|
|
24
|
+
"@types/fs-extra": "^11.0.4",
|
|
25
|
+
"@types/node": "^20.0.0",
|
|
26
|
+
"@types/prompts": "^2.4.9",
|
|
27
|
+
"changelogen": "^0.6.2",
|
|
28
|
+
"husky": "^9.0.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
},
|
|
32
|
+
"tsup": {
|
|
33
|
+
"entry": {
|
|
34
|
+
"index": "bin/index.ts"
|
|
35
|
+
},
|
|
36
|
+
"format": [
|
|
37
|
+
"esm"
|
|
38
|
+
],
|
|
39
|
+
"clean": true,
|
|
40
|
+
"banner": {
|
|
41
|
+
"js": "#!/usr/bin/env node"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"dokio",
|
|
46
|
+
"scaffold",
|
|
47
|
+
"template",
|
|
48
|
+
"cli"
|
|
49
|
+
],
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"changelog": "changelogen --bump --output CHANGELOG.md"
|
|
55
|
+
}
|
|
56
|
+
}
|