@open-assets/open-assets 0.1.2
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/LICENSE +21 -0
- package/README.md +564 -0
- package/bin/open-assets.mjs +476 -0
- package/lib/init.mjs +545 -0
- package/lib/lockfile.mjs +65 -0
- package/lib/renderer.mjs +81 -0
- package/lib/server.mjs +283 -0
- package/package.json +46 -0
- package/public/viewer.html +518 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ian MacCallum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# open-assets
|
|
2
|
+
|
|
3
|
+
Dev server and export tool for app marketing assets. Like Storybook, but for marketing assets.
|
|
4
|
+
|
|
5
|
+
Design your App Store screenshots, app icons, logos, OG images, favicons, and more in HTML/CSS/SVG, preview them live in the browser, and export pixel-perfect PNGs at any size — including directly into your Xcode project.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install
|
|
11
|
+
npm install --save-dev open-assets
|
|
12
|
+
|
|
13
|
+
# Scaffold a new project
|
|
14
|
+
npx open-assets init
|
|
15
|
+
|
|
16
|
+
# Start the dev server
|
|
17
|
+
npx open-assets dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Concepts
|
|
21
|
+
|
|
22
|
+
open-assets uses a simple, unified data model. Every asset type follows the same structure: **N templates × M export sizes**.
|
|
23
|
+
|
|
24
|
+
| Term | Definition |
|
|
25
|
+
|------|-----------|
|
|
26
|
+
| **Collection** | A named group of related assets sharing the same source size and export sizes. One tab in the dev UI. |
|
|
27
|
+
| **Template** | A single source file (HTML or SVG) that produces one image per export size. |
|
|
28
|
+
| **Export Size** | A named output dimension that templates are rendered at (e.g., "iPhone 6.9" → 1320×2868). |
|
|
29
|
+
| **Platform** | Optional grouping label for related export sizes (e.g., "App Store", "Play Store"). |
|
|
30
|
+
| **Source Size** | The dimensions the HTML template is authored at. Puppeteer scales from source → export size. |
|
|
31
|
+
| **Output** | An optional post-render action (e.g., write to Xcode `.appiconset`, copy SVG source). |
|
|
32
|
+
|
|
33
|
+
## End-to-End Screenshot Pipeline
|
|
34
|
+
|
|
35
|
+
### 1. Initialize
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx open-assets init
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Creates a `manifest.json`, sample HTML templates, and a `public/` directory for shared assets.
|
|
42
|
+
|
|
43
|
+
### 2. Design with Claude Code
|
|
44
|
+
|
|
45
|
+
Install the open-assets Claude skill, then prompt:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Now we want to make beautiful screenshots for this app. Install the open-assets
|
|
49
|
+
skill from https://github.com/ianmaccallum/open-assets. Look at the marketing
|
|
50
|
+
doc and demographics. Design 8 high-converting App Store screenshots that catch
|
|
51
|
+
your eye as you scroll. Use bold headlines with highlighted keywords, phone
|
|
52
|
+
mockups with real app screenshots, and close with reviews + a CTA.
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Claude reads your `manifest.json` and `publicDir` to understand the project structure, then creates screenshot HTML files using the design patterns in the skill.
|
|
56
|
+
|
|
57
|
+
### 3. Preview
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# With Tailwind
|
|
61
|
+
concurrently "npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --watch" "npx open-assets dev"
|
|
62
|
+
|
|
63
|
+
# Without Tailwind
|
|
64
|
+
npx open-assets dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Opens a live preview UI at `http://localhost:3200` with zoom/pan controls and export buttons.
|
|
68
|
+
|
|
69
|
+
### 4. Export
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx open-assets render --all
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Exports every template at every configured export size into `./exports/`, organized as `exports/{collection}/{size}/{template}.png`.
|
|
76
|
+
|
|
77
|
+
### 5. Upload to App Store Connect / Google Play
|
|
78
|
+
|
|
79
|
+
Upload the exported PNGs to App Store Connect or Google Play Console. Each size subdirectory maps to a device size required by the store.
|
|
80
|
+
|
|
81
|
+
## Generating Screenshots from UI Tests
|
|
82
|
+
|
|
83
|
+
Capture real app screenshots via Playwright or XCTest, then use them inside your marketing screenshot templates:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 1. Run UI tests to capture app screenshots into public/screenshots/
|
|
87
|
+
npx playwright test --project=screenshots
|
|
88
|
+
|
|
89
|
+
# 2. Export marketing screenshots with those captures embedded
|
|
90
|
+
npx open-assets render --all
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Reference captured screenshots in your HTML templates:
|
|
94
|
+
```html
|
|
95
|
+
<img src="../../public/screenshots/01-home.png" style="width: 100%; height: 100%; object-fit: cover;" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `manifest.json` `command` field stores the export command so automation tools know what to run:
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"command": "npx open-assets render --all"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## CLI Commands
|
|
106
|
+
|
|
107
|
+
### `open-assets dev [dir]`
|
|
108
|
+
|
|
109
|
+
Start the dev server with a live preview UI and export controls.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
open-assets dev # Use current directory
|
|
113
|
+
open-assets dev ./screenshots # Use a specific directory
|
|
114
|
+
open-assets dev --port 4000 # Custom port (default: 3200)
|
|
115
|
+
open-assets dev --host 0.0.0.0 # Bind to all interfaces (network access)
|
|
116
|
+
open-assets dev --no-open # Don't auto-open browser
|
|
117
|
+
open-assets dev --quiet # Suppress server logs
|
|
118
|
+
open-assets dev --ci # CI mode (quiet + no browser)
|
|
119
|
+
open-assets dev --static-dir ./shared # Serve additional static directories
|
|
120
|
+
open-assets dev --manifest config.json # Use a custom manifest filename
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Options:
|
|
124
|
+
| Flag | Env Var | Default | Description |
|
|
125
|
+
|------|---------|---------|-------------|
|
|
126
|
+
| `-p, --port <port>` | `OPEN_ASSETS_PORT` | `3200` | Port to listen on |
|
|
127
|
+
| `-H, --host <host>` | `OPEN_ASSETS_HOST` | `localhost` | Host to bind to |
|
|
128
|
+
| `--no-open` | `OPEN_ASSETS_NO_OPEN` | — | Don't auto-open the browser |
|
|
129
|
+
| `-q, --quiet` | `OPEN_ASSETS_QUIET` | `false` | Suppress server logs |
|
|
130
|
+
| `--ci` | `CI` | `false` | CI mode: quiet + no browser |
|
|
131
|
+
| `--manifest <path>` | `OPEN_ASSETS_MANIFEST` | `manifest.json` | Path to manifest file |
|
|
132
|
+
| `--static-dir <dirs...>` | — | — | Additional static directories to serve |
|
|
133
|
+
| `--render-timeout <ms>` | `OPEN_ASSETS_RENDER_TIMEOUT` | `30000` | Puppeteer render timeout |
|
|
134
|
+
|
|
135
|
+
### `open-assets render [dir]`
|
|
136
|
+
|
|
137
|
+
Render assets headlessly via the command line, without opening a browser.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
open-assets render # Render all collections at source size
|
|
141
|
+
open-assets render --all # Export at EVERY configured size
|
|
142
|
+
open-assets render --collection screenshots # Render a specific collection
|
|
143
|
+
open-assets render --template 01-hero # Render a single template
|
|
144
|
+
open-assets render --size iphone-6.9 # Use a named export size
|
|
145
|
+
open-assets render --platform "App Store" # Render all sizes for a platform
|
|
146
|
+
open-assets render --width 1320 --height 2868 # Custom size
|
|
147
|
+
open-assets render --force # Re-render everything, ignore cache
|
|
148
|
+
open-assets render -o ./build # Custom output directory
|
|
149
|
+
open-assets render --json # Output results as JSON (for CI)
|
|
150
|
+
open-assets render --quiet # Suppress progress logs
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Options:
|
|
154
|
+
| Flag | Env Var | Default | Description |
|
|
155
|
+
|------|---------|---------|-------------|
|
|
156
|
+
| `--collection <id>` | — | all | Render only the collection with this ID |
|
|
157
|
+
| `--template <name>` | — | all | Render only the template with this name |
|
|
158
|
+
| `--size <name>` | — | — | Use a named export size from manifest |
|
|
159
|
+
| `--platform <name>` | — | — | Render only sizes for this platform |
|
|
160
|
+
| `--width <px>` | — | — | Custom output width |
|
|
161
|
+
| `--height <px>` | — | — | Custom output height |
|
|
162
|
+
| `--all` | — | — | Export at every size defined in the manifest |
|
|
163
|
+
| `--force` | — | — | Re-render all assets even if unchanged |
|
|
164
|
+
| `-o, --output <dir>` | `OPEN_ASSETS_OUTPUT` | `./exports` | Output directory |
|
|
165
|
+
| `--manifest <path>` | `OPEN_ASSETS_MANIFEST` | `manifest.json` | Path to manifest file |
|
|
166
|
+
| `--parallel <count>` | `OPEN_ASSETS_PARALLEL` | `1` | Number of parallel renders |
|
|
167
|
+
| `--render-timeout <ms>` | `OPEN_ASSETS_RENDER_TIMEOUT` | `30000` | Puppeteer render timeout |
|
|
168
|
+
| `--json` | — | — | Output results as JSON |
|
|
169
|
+
| `-q, --quiet` | `OPEN_ASSETS_QUIET` | `false` | Suppress progress logs |
|
|
170
|
+
|
|
171
|
+
**Selective export** — flags compose naturally:
|
|
172
|
+
```bash
|
|
173
|
+
# Single template at a single device size
|
|
174
|
+
open-assets render --collection screenshots --template 01-hero --size iphone-6.9
|
|
175
|
+
|
|
176
|
+
# One template at all device sizes
|
|
177
|
+
open-assets render --collection screenshots --template 01-hero --all
|
|
178
|
+
|
|
179
|
+
# All templates at one device size
|
|
180
|
+
open-assets render --collection screenshots --size iphone-6.9
|
|
181
|
+
|
|
182
|
+
# All templates for a specific platform
|
|
183
|
+
open-assets render --collection screenshots --platform "App Store"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Output structure**: `exports/{collection}/{size}/{template}.png`
|
|
187
|
+
```
|
|
188
|
+
exports/
|
|
189
|
+
screenshots/
|
|
190
|
+
iphone-6.9/
|
|
191
|
+
01-hero.png
|
|
192
|
+
02-features.png
|
|
193
|
+
iphone-6.7/
|
|
194
|
+
01-hero.png
|
|
195
|
+
02-features.png
|
|
196
|
+
icon/
|
|
197
|
+
1024/
|
|
198
|
+
icon.png
|
|
199
|
+
180/
|
|
200
|
+
icon.png
|
|
201
|
+
logo/
|
|
202
|
+
svg/
|
|
203
|
+
logo.svg
|
|
204
|
+
logo-dark.svg
|
|
205
|
+
1024/
|
|
206
|
+
logo.png
|
|
207
|
+
logo-dark.png
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### `open-assets list [dir]`
|
|
211
|
+
|
|
212
|
+
List all collections and templates defined in the manifest.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
open-assets list # Pretty-print asset tree
|
|
216
|
+
open-assets list --json # Output as JSON
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `open-assets validate [dir]`
|
|
220
|
+
|
|
221
|
+
Validate the manifest and check that all referenced source files exist.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
open-assets validate # Check current directory
|
|
225
|
+
open-assets validate ./assets # Check specific directory
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Returns exit code 1 if any errors are found — useful in CI pipelines.
|
|
229
|
+
|
|
230
|
+
### `open-assets init [dir]`
|
|
231
|
+
|
|
232
|
+
Scaffold a new project with a `manifest.json`, sample HTML templates, and a `public/` directory.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
open-assets init # Current directory
|
|
236
|
+
open-assets init ./my-assets # Specific directory
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Manifest Format
|
|
240
|
+
|
|
241
|
+
Your project needs a `manifest.json` at its root. This file defines the collections shown in the viewer and all export options.
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"version": 1,
|
|
246
|
+
"name": "My App",
|
|
247
|
+
"publicDir": "public",
|
|
248
|
+
"command": "npx open-assets render --all",
|
|
249
|
+
"collections": [ ... ]
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Root Fields
|
|
254
|
+
|
|
255
|
+
| Field | Type | Required | Description |
|
|
256
|
+
|-------|------|----------|-------------|
|
|
257
|
+
| `version` | number | yes | Manifest schema version (`1`) |
|
|
258
|
+
| `name` | string | yes | App display name |
|
|
259
|
+
| `publicDir` | string | no | Directory for shared assets (auto-served by dev server) |
|
|
260
|
+
| `command` | string | no | Export command for automation tools and CI |
|
|
261
|
+
| `collections` | array | yes | Asset collection definitions |
|
|
262
|
+
|
|
263
|
+
### Collection Schema (Unified)
|
|
264
|
+
|
|
265
|
+
All collections follow the same structure. No `type` field needed.
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"id": "screenshots",
|
|
270
|
+
"label": "App Store Screenshots",
|
|
271
|
+
"sourceSize": { "width": 440, "height": 956 },
|
|
272
|
+
"borderRadius": 4,
|
|
273
|
+
"templates": [
|
|
274
|
+
{ "src": "src/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" },
|
|
275
|
+
{ "src": "src/screenshots/02-features.html", "name": "02-features", "label": "Features" }
|
|
276
|
+
],
|
|
277
|
+
"exportSizes": [
|
|
278
|
+
{
|
|
279
|
+
"platform": "App Store",
|
|
280
|
+
"sizes": [
|
|
281
|
+
{ "name": "iphone-6.9", "label": "iPhone 6.9\"", "width": 1320, "height": 2868 },
|
|
282
|
+
{ "name": "iphone-6.7", "label": "iPhone 6.7\"", "width": 1290, "height": 2796 }
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"outputs": [
|
|
287
|
+
{ "type": "xcode", "path": "../MyApp/Assets.xcassets/AppIcon.appiconset" }
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
| Field | Type | Description |
|
|
293
|
+
|-------|------|-------------|
|
|
294
|
+
| `id` | string | Unique collection identifier (used in CLI `--collection`) |
|
|
295
|
+
| `label` | string | Display name in the collection selector |
|
|
296
|
+
| `sourceSize` | object | `{ width, height }` — the dimensions templates are authored at |
|
|
297
|
+
| `borderRadius` | number | Border radius for preview frames (px) |
|
|
298
|
+
| `templates` | array | Source files to render |
|
|
299
|
+
| `templates[].src` | string | Path to HTML/SVG file (relative to project root) |
|
|
300
|
+
| `templates[].name` | string | Filename for exports (used by `--template` flag) |
|
|
301
|
+
| `templates[].label` | string | Display label |
|
|
302
|
+
| `exportSizes` | array | Grouped export size definitions |
|
|
303
|
+
| `exportSizes[].platform` | string | Optional grouping label |
|
|
304
|
+
| `exportSizes[].sizes[]` | object | `{ name, label, width, height }` |
|
|
305
|
+
| `outputs` | array | Optional post-render actions |
|
|
306
|
+
| `customExport` | object | Optional `{ defaultWidth, defaultHeight }` for custom size UI |
|
|
307
|
+
|
|
308
|
+
### Output Types
|
|
309
|
+
|
|
310
|
+
| Type | Config | Description |
|
|
311
|
+
|------|--------|-------------|
|
|
312
|
+
| `xcode` | `{ "type": "xcode", "path": "..." }` | Render icon and write to Xcode asset catalog |
|
|
313
|
+
| `copy-source` | `{ "type": "copy-source", "format": "svg" }` | Copy source files to export directory |
|
|
314
|
+
|
|
315
|
+
### Example Collections
|
|
316
|
+
|
|
317
|
+
**Screenshots** (8 templates × 4 iPhone sizes):
|
|
318
|
+
```json
|
|
319
|
+
{
|
|
320
|
+
"id": "screenshots",
|
|
321
|
+
"label": "App Store Screenshots",
|
|
322
|
+
"sourceSize": { "width": 440, "height": 956 },
|
|
323
|
+
"borderRadius": 4,
|
|
324
|
+
"templates": [
|
|
325
|
+
{ "src": "src/screenshots/01-hero.html", "name": "01-hero", "label": "Hero" },
|
|
326
|
+
{ "src": "src/screenshots/02-import.html", "name": "02-import", "label": "Import" }
|
|
327
|
+
],
|
|
328
|
+
"exportSizes": [
|
|
329
|
+
{
|
|
330
|
+
"platform": "App Store",
|
|
331
|
+
"sizes": [
|
|
332
|
+
{ "name": "iphone-6.9", "label": "iPhone 6.9\"", "width": 1320, "height": 2868 },
|
|
333
|
+
{ "name": "iphone-6.7", "label": "iPhone 6.7\"", "width": 1290, "height": 2796 },
|
|
334
|
+
{ "name": "iphone-6.5", "label": "iPhone 6.5\"", "width": 1284, "height": 2778 },
|
|
335
|
+
{ "name": "iphone-6.1", "label": "iPhone 6.1\"", "width": 1179, "height": 2556 }
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**App Icon** (1 template × many Apple sizes + Xcode output):
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"id": "icon",
|
|
346
|
+
"label": "App Icon",
|
|
347
|
+
"sourceSize": { "width": 1024, "height": 1024 },
|
|
348
|
+
"borderRadius": 224,
|
|
349
|
+
"templates": [
|
|
350
|
+
{ "src": "src/icon.html", "name": "icon", "label": "App Icon" }
|
|
351
|
+
],
|
|
352
|
+
"exportSizes": [
|
|
353
|
+
{
|
|
354
|
+
"platform": "Apple",
|
|
355
|
+
"sizes": [
|
|
356
|
+
{ "name": "1024", "label": "1024px", "width": 1024, "height": 1024 },
|
|
357
|
+
{ "name": "180", "label": "180px", "width": 180, "height": 180 },
|
|
358
|
+
{ "name": "120", "label": "120px", "width": 120, "height": 120 }
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
"outputs": [
|
|
363
|
+
{ "type": "xcode", "path": "../MyApp/Assets.xcassets/AppIcon.appiconset" }
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Icon Explorations** (3 concept variants × preview sizes):
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"id": "icon-explorations",
|
|
372
|
+
"label": "Icon Explorations",
|
|
373
|
+
"sourceSize": { "width": 1024, "height": 1024 },
|
|
374
|
+
"borderRadius": 224,
|
|
375
|
+
"templates": [
|
|
376
|
+
{ "src": "src/icons/concept-a.html", "name": "concept-a", "label": "Concept A" },
|
|
377
|
+
{ "src": "src/icons/concept-b.html", "name": "concept-b", "label": "Concept B" },
|
|
378
|
+
{ "src": "src/icons/seasonal-winter.html", "name": "seasonal-winter", "label": "Winter" }
|
|
379
|
+
],
|
|
380
|
+
"exportSizes": [
|
|
381
|
+
{
|
|
382
|
+
"sizes": [
|
|
383
|
+
{ "name": "1024", "label": "1024px", "width": 1024, "height": 1024 },
|
|
384
|
+
{ "name": "512", "label": "512px", "width": 512, "height": 512 }
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Logo** (3 variants + SVG source copy):
|
|
392
|
+
```json
|
|
393
|
+
{
|
|
394
|
+
"id": "logo",
|
|
395
|
+
"label": "Logo",
|
|
396
|
+
"sourceSize": { "width": 940, "height": 940 },
|
|
397
|
+
"templates": [
|
|
398
|
+
{ "src": "src/logo.svg", "name": "logo", "label": "Logo" },
|
|
399
|
+
{ "src": "src/logo-dark.svg", "name": "logo-dark", "label": "Dark" },
|
|
400
|
+
{ "src": "src/wordmark.svg", "name": "wordmark", "label": "Wordmark" }
|
|
401
|
+
],
|
|
402
|
+
"exportSizes": [
|
|
403
|
+
{
|
|
404
|
+
"sizes": [
|
|
405
|
+
{ "name": "2048", "label": "2048px", "width": 2048, "height": 2048 },
|
|
406
|
+
{ "name": "1024", "label": "1024px", "width": 1024, "height": 1024 },
|
|
407
|
+
{ "name": "512", "label": "512px", "width": 512, "height": 512 }
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
],
|
|
411
|
+
"outputs": [
|
|
412
|
+
{ "type": "copy-source", "format": "svg" }
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**OG Images** (social cards):
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"id": "og-images",
|
|
421
|
+
"label": "Social Cards",
|
|
422
|
+
"sourceSize": { "width": 1200, "height": 630 },
|
|
423
|
+
"templates": [
|
|
424
|
+
{ "src": "src/og/default.html", "name": "default", "label": "Default OG" }
|
|
425
|
+
],
|
|
426
|
+
"exportSizes": [
|
|
427
|
+
{ "sizes": [{ "name": "og", "label": "OG Image", "width": 1200, "height": 630 }] }
|
|
428
|
+
]
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Favicons** (1 template × many sizes):
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"id": "favicon",
|
|
436
|
+
"label": "Favicons",
|
|
437
|
+
"sourceSize": { "width": 512, "height": 512 },
|
|
438
|
+
"templates": [
|
|
439
|
+
{ "src": "src/favicon.html", "name": "favicon", "label": "Favicon" }
|
|
440
|
+
],
|
|
441
|
+
"exportSizes": [
|
|
442
|
+
{
|
|
443
|
+
"platform": "Web",
|
|
444
|
+
"sizes": [
|
|
445
|
+
{ "name": "512", "label": "512px", "width": 512, "height": 512 },
|
|
446
|
+
{ "name": "192", "label": "192px", "width": 192, "height": 192 },
|
|
447
|
+
{ "name": "32", "label": "32px", "width": 32, "height": 32 },
|
|
448
|
+
{ "name": "16", "label": "16px", "width": 16, "height": 16 }
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"platform": "Apple",
|
|
453
|
+
"sizes": [
|
|
454
|
+
{ "name": "apple-touch", "label": "Apple Touch Icon", "width": 180, "height": 180 }
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Incremental Builds with manifest.lock
|
|
462
|
+
|
|
463
|
+
The `render` command maintains a `manifest.lock` file that stores SHA256 checksums of each source HTML/SVG file. On subsequent renders, unchanged assets are skipped automatically:
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
$ open-assets render --all
|
|
467
|
+
Skipping 01-hero at 1320x2868 (unchanged)
|
|
468
|
+
Rendering 03-new-screen at 1320x2868...
|
|
469
|
+
→ exports/screenshots/iphone-6.9/03-new-screen.png
|
|
470
|
+
|
|
471
|
+
Done. 1 asset(s) rendered, 1 skipped (unchanged) in 1.2s.
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Use `--force` to re-render everything regardless of the cache.
|
|
475
|
+
|
|
476
|
+
**Limitation**: Only source HTML/SVG files are checksummed. Changes to referenced assets (images in `publicDir`, compiled Tailwind CSS) won't trigger re-renders — use `--force` when those change.
|
|
477
|
+
|
|
478
|
+
Add `manifest.lock` to your `.gitignore` (done automatically by `open-assets init`).
|
|
479
|
+
|
|
480
|
+
## CI/CD with GitHub Actions
|
|
481
|
+
|
|
482
|
+
Automatically export screenshots on push:
|
|
483
|
+
|
|
484
|
+
```yaml
|
|
485
|
+
name: Export Marketing Assets
|
|
486
|
+
on:
|
|
487
|
+
push:
|
|
488
|
+
paths:
|
|
489
|
+
- 'marketing/screenshots/**'
|
|
490
|
+
- 'marketing/screenshots/manifest.json'
|
|
491
|
+
jobs:
|
|
492
|
+
export:
|
|
493
|
+
runs-on: ubuntu-latest
|
|
494
|
+
steps:
|
|
495
|
+
- uses: actions/checkout@v4
|
|
496
|
+
- uses: actions/setup-node@v4
|
|
497
|
+
with:
|
|
498
|
+
node-version: 20
|
|
499
|
+
- run: npm ci
|
|
500
|
+
- run: npx open-assets validate
|
|
501
|
+
- run: npx open-assets render --all --json --quiet
|
|
502
|
+
- uses: actions/upload-artifact@v4
|
|
503
|
+
with:
|
|
504
|
+
name: screenshots
|
|
505
|
+
path: exports/
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Common Export Sizes
|
|
509
|
+
|
|
510
|
+
| Platform | Label | Width | Height |
|
|
511
|
+
|----------|-------|-------|--------|
|
|
512
|
+
| App Store | iPhone 6.9" | 1320 | 2868 |
|
|
513
|
+
| App Store | iPhone 6.7" | 1290 | 2796 |
|
|
514
|
+
| App Store | iPhone 6.5" | 1284 | 2778 |
|
|
515
|
+
| App Store | iPhone 6.1" | 1179 | 2556 |
|
|
516
|
+
| App Store | iPad 12.9" | 2048 | 2732 |
|
|
517
|
+
| Play Store | Phone | 1080 | 1920 |
|
|
518
|
+
| Play Store | Phone (tall) | 1080 | 2400 |
|
|
519
|
+
| Product Hunt | Gallery | 1270 | 760 |
|
|
520
|
+
| Web | OG Image | 1200 | 630 |
|
|
521
|
+
| Mac App Store | Retina | 2880 | 1800 |
|
|
522
|
+
|
|
523
|
+
## File Structure Convention
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
project/
|
|
527
|
+
manifest.json
|
|
528
|
+
manifest.lock # Auto-generated cache (gitignored)
|
|
529
|
+
public/ # Shared assets (configured via publicDir)
|
|
530
|
+
logo-round.png
|
|
531
|
+
social/
|
|
532
|
+
youtube.svg
|
|
533
|
+
screenshots/ # Real app screenshots from UI tests
|
|
534
|
+
01-home.png
|
|
535
|
+
src/
|
|
536
|
+
styles.css # Tailwind input
|
|
537
|
+
icon.html # App icon template
|
|
538
|
+
logo.svg # Vector logo template
|
|
539
|
+
icons/ # Icon concept explorations
|
|
540
|
+
concept-a.html
|
|
541
|
+
concept-b.html
|
|
542
|
+
screenshots/
|
|
543
|
+
01-hero.html
|
|
544
|
+
02-features.html
|
|
545
|
+
og/ # OG image templates
|
|
546
|
+
default.html
|
|
547
|
+
dist/
|
|
548
|
+
styles.css # Compiled Tailwind
|
|
549
|
+
exports/ # Rendered output (gitignored)
|
|
550
|
+
screenshots/
|
|
551
|
+
iphone-6.9/
|
|
552
|
+
01-hero.png
|
|
553
|
+
iphone-6.7/
|
|
554
|
+
icon/
|
|
555
|
+
1024/
|
|
556
|
+
icon.png
|
|
557
|
+
logo/
|
|
558
|
+
svg/
|
|
559
|
+
logo.svg
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## License
|
|
563
|
+
|
|
564
|
+
MIT
|