@appmockup/node-render 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @appmockup/node-render
2
+
3
+ Node adapter for [AppMockup](https://github.com/yakupbulbul/AppMockup). Wraps
4
+ [`@appmockup/core`](https://www.npmjs.com/package/@appmockup/core) with a
5
+ [`@napi-rs/canvas`](https://github.com/Brooooooklyn/canvas) context (Skia, prebuilt
6
+ binaries — no system libraries required), bundled fonts, image decode/encode, and batch
7
+ generation. Backs both the CLI and the MCP server.
8
+
9
+ ```bash
10
+ npm i @appmockup/node-render
11
+ ```
12
+
13
+ ## API
14
+
15
+ - `loadConfigFile(path)` → `{ config, configDir }`.
16
+ - `validateConfig({ config, configDir })` → `string[]` of problems (headlines + files).
17
+ - `renderOneToPng(opts)` → `{ buffer, size, entry }` for one screenshot × language × size.
18
+ - `renderSceneToPng(scene)` → PNG `Buffer`.
19
+ - `generate(opts)` → writes `<outputDir>/<size>/<lang>/<prefix>_<lang>_<id>.png`, returns paths.
20
+ - `decodeImageBuffer(bytes)` → a drawable `ScreenshotImage` (for uploaded/base64 input).
21
+ - `ensureFontsRegistered()` / `fontsDir()`.
22
+
23
+ Screenshots come from disk (`configDir` + each entry's `file`) or from a pre-decoded
24
+ `images` map keyed by screenshot id, which takes priority.
25
+
26
+ ## Fonts
27
+
28
+ Ships Inter + Noto Sans + Noto Sans Arabic in `fonts/`, auto-registered on first render
29
+ so Node output matches the browser. Drop additional `.ttf`/`.otf` files in that dir to
30
+ extend language coverage.
31
+
32
+ ## License
33
+
34
+ MIT © Yakup Bülbül
@@ -0,0 +1,64 @@
1
+ import { MockupConfig, ScreenshotImage, OutputSize, ScreenshotEntry, RenderScene } from '@appmockup/core';
2
+
3
+ /** Absolute path to the bundled fonts directory (sibling of dist/, shipped in the package). */
4
+ declare function fontsDir(): string;
5
+ /**
6
+ * Register every bundled font once, so Node renders match the web studio. Drop a
7
+ * new .ttf/.otf into the fonts/ dir to add script coverage — it gets picked up
8
+ * automatically under its own family name.
9
+ */
10
+ declare function ensureFontsRegistered(): number;
11
+
12
+ interface LoadedConfig {
13
+ config: MockupConfig;
14
+ /** Directory the config was loaded from; screenshot paths resolve against it. */
15
+ configDir: string;
16
+ }
17
+ /** Load + validate a config JSON file from disk (mirrors Swift `MockupConfig.load`). */
18
+ declare function loadConfigFile(configPath: string): Promise<LoadedConfig>;
19
+ /**
20
+ * Full validation: schema (already done at load), per-language headlines, and
21
+ * on-disk existence of every screenshot file. Returns human-readable problems.
22
+ */
23
+ declare function validateConfig(loaded: LoadedConfig): string[];
24
+ /** Decode raw image bytes (e.g. an uploaded/base64 screenshot) into a drawable image. */
25
+ declare function decodeImageBuffer(buf: Buffer | Uint8Array): Promise<ScreenshotImage>;
26
+ /** Render a resolved scene to a PNG buffer. */
27
+ declare function renderSceneToPng(scene: RenderScene): Promise<Buffer>;
28
+ interface RenderOneOptions {
29
+ config: MockupConfig;
30
+ configDir: string;
31
+ screenshotId: string;
32
+ language: string;
33
+ /** Output size name; defaults to the first size in the config. */
34
+ sizeName?: string;
35
+ /** Pre-decoded images keyed by screenshot id; takes priority over reading from disk. */
36
+ images?: Record<string, ScreenshotImage>;
37
+ }
38
+ interface RenderOneResult {
39
+ buffer: Buffer;
40
+ size: OutputSize;
41
+ entry: ScreenshotEntry;
42
+ }
43
+ /** Render a single mockup to a PNG buffer (mirrors the Swift `preview` path). */
44
+ declare function renderOneToPng(opts: RenderOneOptions): Promise<RenderOneResult>;
45
+ interface GenerateOptions {
46
+ config: MockupConfig;
47
+ configDir: string;
48
+ outputDir: string;
49
+ languages?: string[];
50
+ screenshots?: string[];
51
+ sizes?: string[];
52
+ /** Pre-decoded images keyed by screenshot id; takes priority over reading from disk. */
53
+ images?: Record<string, ScreenshotImage>;
54
+ onProgress?: (pct: number, outputPath: string) => void;
55
+ }
56
+ /**
57
+ * Batch-render all (screenshot × language × size) combinations to disk, in the
58
+ * same iteration order, folder layout (`<out>/<size>/<lang>/`) and filename
59
+ * scheme (`<prefix>_<lang>_<id>.png`) as the Swift `generate` command.
60
+ * Returns the list of written file paths.
61
+ */
62
+ declare function generate(opts: GenerateOptions): Promise<string[]>;
63
+
64
+ export { type GenerateOptions, type LoadedConfig, type RenderOneOptions, type RenderOneResult, decodeImageBuffer, ensureFontsRegistered, fontsDir, generate, loadConfigFile, renderOneToPng, renderSceneToPng, validateConfig };
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ // src/index.ts
2
+ import { createCanvas, loadImage } from "@napi-rs/canvas";
3
+ import fs2 from "fs/promises";
4
+ import fssync from "fs";
5
+ import path2 from "path";
6
+ import {
7
+ parseConfig,
8
+ resolveScene,
9
+ renderMockup,
10
+ validateHeadlines
11
+ } from "@appmockup/core";
12
+
13
+ // src/fonts.ts
14
+ import { GlobalFonts } from "@napi-rs/canvas";
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import { fileURLToPath } from "url";
18
+ var registered = false;
19
+ function fontsDir() {
20
+ const here = path.dirname(fileURLToPath(import.meta.url));
21
+ return path.resolve(here, "../fonts");
22
+ }
23
+ function ensureFontsRegistered() {
24
+ if (registered) return 0;
25
+ registered = true;
26
+ const dir = fontsDir();
27
+ if (!fs.existsSync(dir)) return 0;
28
+ let count = 0;
29
+ for (const file of fs.readdirSync(dir)) {
30
+ if (/\.(ttf|otf|ttc|woff2?)$/i.test(file)) {
31
+ if (GlobalFonts.registerFromPath(path.join(dir, file))) count++;
32
+ }
33
+ }
34
+ return count;
35
+ }
36
+
37
+ // src/index.ts
38
+ async function loadConfigFile(configPath) {
39
+ const abs = path2.resolve(configPath);
40
+ const raw = await fs2.readFile(abs, "utf8");
41
+ const config = parseConfig(JSON.parse(raw));
42
+ return { config, configDir: path2.dirname(abs) };
43
+ }
44
+ function validateConfig(loaded) {
45
+ const errors = validateHeadlines(loaded.config);
46
+ for (const entry of loaded.config.screenshots) {
47
+ const filePath = path2.resolve(loaded.configDir, entry.file);
48
+ if (!fssync.existsSync(filePath)) {
49
+ errors.push(`Screenshot not found: ${entry.file}`);
50
+ }
51
+ }
52
+ return errors;
53
+ }
54
+ async function decodeScreenshot(filePath) {
55
+ if (!fssync.existsSync(filePath)) return null;
56
+ const image = await loadImage(filePath);
57
+ return { source: image, width: image.width, height: image.height };
58
+ }
59
+ async function decodeImageBuffer(buf) {
60
+ const image = await loadImage(Buffer.from(buf));
61
+ return { source: image, width: image.width, height: image.height };
62
+ }
63
+ async function renderSceneToPng(scene) {
64
+ ensureFontsRegistered();
65
+ const canvas = createCanvas(scene.width, scene.height);
66
+ const ctx = canvas.getContext("2d");
67
+ renderMockup(ctx, scene);
68
+ return canvas.encode("png");
69
+ }
70
+ async function renderOneToPng(opts) {
71
+ const entry = opts.config.screenshots.find((s) => s.id === opts.screenshotId);
72
+ if (!entry) {
73
+ throw new Error(`Screenshot ID '${opts.screenshotId}' not found in config.`);
74
+ }
75
+ const size = opts.sizeName ? opts.config.outputSizes.find((s) => s.name === opts.sizeName) : opts.config.outputSizes[0];
76
+ if (!size) {
77
+ throw new Error(
78
+ opts.sizeName ? `Size '${opts.sizeName}' not found in config.` : "Config has no output sizes."
79
+ );
80
+ }
81
+ const screenshot = opts.images?.[entry.id] ?? await decodeScreenshot(path2.resolve(opts.configDir, entry.file));
82
+ const scene = resolveScene(opts.config, entry, opts.language, size, screenshot);
83
+ const buffer = await renderSceneToPng(scene);
84
+ return { buffer, size, entry };
85
+ }
86
+ async function generate(opts) {
87
+ ensureFontsRegistered();
88
+ const { config } = opts;
89
+ const languages = opts.languages ?? config.languages;
90
+ const sizeFilter = opts.sizes;
91
+ const sizes = sizeFilter ? config.outputSizes.filter((s) => sizeFilter.includes(s.name)) : config.outputSizes;
92
+ const ssFilter = opts.screenshots;
93
+ const entries = ssFilter ? config.screenshots.filter((s) => ssFilter.includes(s.id)) : config.screenshots;
94
+ const total = entries.length * languages.length * sizes.length;
95
+ const paths = [];
96
+ let count = 0;
97
+ const imageCache = /* @__PURE__ */ new Map();
98
+ for (const entry of entries) {
99
+ for (const lang of languages) {
100
+ for (const size of sizes) {
101
+ count++;
102
+ let image = imageCache.get(entry.id);
103
+ if (image === void 0) {
104
+ image = opts.images?.[entry.id] ?? await decodeScreenshot(path2.resolve(opts.configDir, entry.file));
105
+ imageCache.set(entry.id, image);
106
+ }
107
+ const scene = resolveScene(config, entry, lang, size, image);
108
+ const buffer = await renderSceneToPng(scene);
109
+ const langDir = path2.join(opts.outputDir, size.name, lang);
110
+ await fs2.mkdir(langDir, { recursive: true });
111
+ const filename = `${config.outputPrefix}_${lang}_${entry.id}.png`;
112
+ const outputPath = path2.join(langDir, filename);
113
+ await fs2.writeFile(outputPath, buffer);
114
+ paths.push(outputPath);
115
+ const pct = total > 0 ? Math.floor(count / total * 100) : 100;
116
+ opts.onProgress?.(pct, outputPath);
117
+ }
118
+ }
119
+ }
120
+ return paths;
121
+ }
122
+ export {
123
+ decodeImageBuffer,
124
+ ensureFontsRegistered,
125
+ fontsDir,
126
+ generate,
127
+ loadConfigFile,
128
+ renderOneToPng,
129
+ renderSceneToPng,
130
+ validateConfig
131
+ };
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/fonts.ts"],"sourcesContent":["import { createCanvas, loadImage } from \"@napi-rs/canvas\";\nimport fs from \"node:fs/promises\";\nimport fssync from \"node:fs\";\nimport path from \"node:path\";\nimport {\n parseConfig,\n resolveScene,\n renderMockup,\n validateHeadlines,\n type Ctx2D,\n type MockupConfig,\n type OutputSize,\n type RenderScene,\n type ScreenshotEntry,\n type ScreenshotImage,\n} from \"@appmockup/core\";\nimport { ensureFontsRegistered } from \"./fonts.js\";\n\nexport { ensureFontsRegistered, fontsDir } from \"./fonts.js\";\n\nexport interface LoadedConfig {\n config: MockupConfig;\n /** Directory the config was loaded from; screenshot paths resolve against it. */\n configDir: string;\n}\n\n/** Load + validate a config JSON file from disk (mirrors Swift `MockupConfig.load`). */\nexport async function loadConfigFile(configPath: string): Promise<LoadedConfig> {\n const abs = path.resolve(configPath);\n const raw = await fs.readFile(abs, \"utf8\");\n const config = parseConfig(JSON.parse(raw));\n return { config, configDir: path.dirname(abs) };\n}\n\n/**\n * Full validation: schema (already done at load), per-language headlines, and\n * on-disk existence of every screenshot file. Returns human-readable problems.\n */\nexport function validateConfig(loaded: LoadedConfig): string[] {\n const errors = validateHeadlines(loaded.config);\n for (const entry of loaded.config.screenshots) {\n const filePath = path.resolve(loaded.configDir, entry.file);\n if (!fssync.existsSync(filePath)) {\n errors.push(`Screenshot not found: ${entry.file}`);\n }\n }\n return errors;\n}\n\nasync function decodeScreenshot(filePath: string): Promise<ScreenshotImage | null> {\n if (!fssync.existsSync(filePath)) return null;\n const image = await loadImage(filePath);\n return { source: image, width: image.width, height: image.height };\n}\n\n/** Decode raw image bytes (e.g. an uploaded/base64 screenshot) into a drawable image. */\nexport async function decodeImageBuffer(\n buf: Buffer | Uint8Array,\n): Promise<ScreenshotImage> {\n const image = await loadImage(Buffer.from(buf));\n return { source: image, width: image.width, height: image.height };\n}\n\n/** Render a resolved scene to a PNG buffer. */\nexport async function renderSceneToPng(scene: RenderScene): Promise<Buffer> {\n ensureFontsRegistered();\n const canvas = createCanvas(scene.width, scene.height);\n const ctx = canvas.getContext(\"2d\") as unknown as Ctx2D;\n renderMockup(ctx, scene);\n return canvas.encode(\"png\");\n}\n\nexport interface RenderOneOptions {\n config: MockupConfig;\n configDir: string;\n screenshotId: string;\n language: string;\n /** Output size name; defaults to the first size in the config. */\n sizeName?: string;\n /** Pre-decoded images keyed by screenshot id; takes priority over reading from disk. */\n images?: Record<string, ScreenshotImage>;\n}\n\nexport interface RenderOneResult {\n buffer: Buffer;\n size: OutputSize;\n entry: ScreenshotEntry;\n}\n\n/** Render a single mockup to a PNG buffer (mirrors the Swift `preview` path). */\nexport async function renderOneToPng(opts: RenderOneOptions): Promise<RenderOneResult> {\n const entry = opts.config.screenshots.find((s) => s.id === opts.screenshotId);\n if (!entry) {\n throw new Error(`Screenshot ID '${opts.screenshotId}' not found in config.`);\n }\n const size = opts.sizeName\n ? opts.config.outputSizes.find((s) => s.name === opts.sizeName)\n : opts.config.outputSizes[0];\n if (!size) {\n throw new Error(\n opts.sizeName\n ? `Size '${opts.sizeName}' not found in config.`\n : \"Config has no output sizes.\",\n );\n }\n const screenshot =\n opts.images?.[entry.id] ??\n (await decodeScreenshot(path.resolve(opts.configDir, entry.file)));\n const scene = resolveScene(opts.config, entry, opts.language, size, screenshot);\n const buffer = await renderSceneToPng(scene);\n return { buffer, size, entry };\n}\n\nexport interface GenerateOptions {\n config: MockupConfig;\n configDir: string;\n outputDir: string;\n languages?: string[];\n screenshots?: string[];\n sizes?: string[];\n /** Pre-decoded images keyed by screenshot id; takes priority over reading from disk. */\n images?: Record<string, ScreenshotImage>;\n onProgress?: (pct: number, outputPath: string) => void;\n}\n\n/**\n * Batch-render all (screenshot × language × size) combinations to disk, in the\n * same iteration order, folder layout (`<out>/<size>/<lang>/`) and filename\n * scheme (`<prefix>_<lang>_<id>.png`) as the Swift `generate` command.\n * Returns the list of written file paths.\n */\nexport async function generate(opts: GenerateOptions): Promise<string[]> {\n ensureFontsRegistered();\n const { config } = opts;\n\n const languages = opts.languages ?? config.languages;\n const sizeFilter = opts.sizes;\n const sizes = sizeFilter\n ? config.outputSizes.filter((s) => sizeFilter.includes(s.name))\n : config.outputSizes;\n const ssFilter = opts.screenshots;\n const entries = ssFilter\n ? config.screenshots.filter((s) => ssFilter.includes(s.id))\n : config.screenshots;\n\n const total = entries.length * languages.length * sizes.length;\n const paths: string[] = [];\n let count = 0;\n\n // Decode each screenshot once and reuse across languages/sizes.\n const imageCache = new Map<string, ScreenshotImage | null>();\n\n for (const entry of entries) {\n for (const lang of languages) {\n for (const size of sizes) {\n count++;\n let image = imageCache.get(entry.id);\n if (image === undefined) {\n image =\n opts.images?.[entry.id] ??\n (await decodeScreenshot(path.resolve(opts.configDir, entry.file)));\n imageCache.set(entry.id, image);\n }\n const scene = resolveScene(config, entry, lang, size, image);\n const buffer = await renderSceneToPng(scene);\n\n const langDir = path.join(opts.outputDir, size.name, lang);\n await fs.mkdir(langDir, { recursive: true });\n const filename = `${config.outputPrefix}_${lang}_${entry.id}.png`;\n const outputPath = path.join(langDir, filename);\n await fs.writeFile(outputPath, buffer);\n\n paths.push(outputPath);\n const pct = total > 0 ? Math.floor((count / total) * 100) : 100;\n opts.onProgress?.(pct, outputPath);\n }\n }\n }\n\n return paths;\n}\n","import { GlobalFonts } from \"@napi-rs/canvas\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet registered = false;\n\n/** Absolute path to the bundled fonts directory (sibling of dist/, shipped in the package). */\nexport function fontsDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n return path.resolve(here, \"../fonts\");\n}\n\n/**\n * Register every bundled font once, so Node renders match the web studio. Drop a\n * new .ttf/.otf into the fonts/ dir to add script coverage — it gets picked up\n * automatically under its own family name.\n */\nexport function ensureFontsRegistered(): number {\n if (registered) return 0;\n registered = true;\n const dir = fontsDir();\n if (!fs.existsSync(dir)) return 0;\n let count = 0;\n for (const file of fs.readdirSync(dir)) {\n if (/\\.(ttf|otf|ttc|woff2?)$/i.test(file)) {\n if (GlobalFonts.registerFromPath(path.join(dir, file))) count++;\n }\n }\n return count;\n}\n"],"mappings":"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","names":["fs","path","path","fs"]}
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@appmockup/node-render",
3
+ "version": "0.2.0",
4
+ "description": "Node adapter for @appmockup/core: fonts, image decode, PNG encode, batch generation via @napi-rs/canvas.",
5
+ "license": "MIT",
6
+ "author": "Yakup Bülbül",
7
+ "keywords": ["app-store", "screenshots", "mockup", "canvas", "napi-rs"],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/yakupbulbul/AppMockup.git",
11
+ "directory": "packages/node-render"
12
+ },
13
+ "homepage": "https://github.com/yakupbulbul/AppMockup#readme",
14
+ "bugs": "https://github.com/yakupbulbul/AppMockup/issues",
15
+ "publishConfig": { "access": "public" },
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": ["dist", "fonts"],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "typecheck": "tsc --noEmit"
31
+ },
32
+ "dependencies": {
33
+ "@appmockup/core": "workspace:*",
34
+ "@napi-rs/canvas": "^0.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.10.2",
38
+ "tsup": "^8.3.5",
39
+ "typescript": "^5.7.2"
40
+ }
41
+ }