@lovo/matter-cli 0.5.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @lovo/matter-cli
2
2
 
3
+ ## 1.0.0
4
+
5
+ ## 0.6.0
6
+
3
7
  ## 0.5.0
4
8
 
5
9
  ### Minor Changes
package/README.md CHANGED
@@ -75,6 +75,7 @@ npx matter-cli poster --source <file> --output <path> [options]
75
75
  | `--capture-delay <seconds>` | `0` | Wait this long after the first non-blank frame before snapshotting. |
76
76
  | `--width <px>` | `1280` | Render width. |
77
77
  | `--height <px>` | `720` | Render height. |
78
+ | `--device-scale-factor <n>` | `2` | Capture device pixel ratio. Default matches the live renderer's DPR cap for crisp posters on retina. |
78
79
 
79
80
  #### Which format should I pick?
80
81
 
@@ -84,7 +85,7 @@ The default (JPEG q80) handles most shaders well. PNG wins on shaders with large
84
85
  | --------------------------------- | ------------------------------------------ |
85
86
  | `LinearGradient` with hard stops | `Aurora` and similar gradient-heavy scenes |
86
87
  | `SimplexNoise` with contour bands | `MeshGradient` (smooth color flow) |
87
- | Anything with < ~20 unique colors | `FilmGrain` (high-entropy noise) |
88
+ | Anything with < ~20 unique colors | `Grain` (high-entropy noise) |
88
89
 
89
90
  If unsure, run both — the difference can be 3–7× either direction.
90
91
 
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-TBB7CTPP.js";
8
8
  import {
9
9
  launchAndScreenshot
10
- } from "./chunk-RRL35RGP.js";
10
+ } from "./chunk-S4X3CIUX.js";
11
11
  import {
12
12
  findProjectRoot
13
13
  } from "./chunk-Q7CQW6AH.js";
@@ -17,6 +17,13 @@ import { mkdir, stat } from "fs/promises";
17
17
  import { dirname, extname, resolve } from "path";
18
18
  var READY_TIMEOUT_MS = 1e4;
19
19
  var DEFAULT_JPEG_QUALITY = 80;
20
+ var DEFAULT_DEVICE_SCALE_FACTOR = 2;
21
+ function validateDeviceScaleFactor(value) {
22
+ if (!(value > 0)) {
23
+ throw new Error(`--device-scale-factor must be greater than 0, received: ${value}`);
24
+ }
25
+ return value;
26
+ }
20
27
  function normalizeType(rawType) {
21
28
  const normalizedType = rawType?.toLowerCase();
22
29
  if (normalizedType === void 0 || normalizedType === "jpg" || normalizedType === "jpeg")
@@ -73,6 +80,9 @@ async function runPoster(opts, io = { cwd: process.cwd(), log: console.log }) {
73
80
  exportName: opts.exportName,
74
81
  projectRoot
75
82
  });
83
+ const deviceScaleFactor = validateDeviceScaleFactor(
84
+ opts.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR
85
+ );
76
86
  const server = await createPosterServer({
77
87
  bundle,
78
88
  config: { width: opts.width, height: opts.height }
@@ -88,7 +98,8 @@ async function runPoster(opts, io = { cwd: process.cwd(), log: console.log }) {
88
98
  outPath: outAbs,
89
99
  projectRoot,
90
100
  format,
91
- quality
101
+ quality,
102
+ deviceScaleFactor
92
103
  });
93
104
  io.log(`Wrote poster: ${resolvedOut} (${opts.width}\xD7${opts.height}, ${formatBytes(bytes)})`);
94
105
  io.log("");
@@ -113,7 +124,8 @@ function posterPublicSrc(outPath) {
113
124
  }
114
125
 
115
126
  export {
127
+ validateDeviceScaleFactor,
116
128
  resolveOutPath,
117
129
  runPoster
118
130
  };
119
- //# sourceMappingURL=chunk-YSFTOGZX.js.map
131
+ //# sourceMappingURL=chunk-3ACBRJOS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/poster.ts"],"sourcesContent":["import { mkdir, stat } from 'node:fs/promises';\nimport { dirname, extname, resolve } from 'node:path';\n\nimport { bundlePoster } from '../poster/bundle.js';\nimport { launchAndScreenshot } from '../poster/playwright.js';\nimport { findProjectRoot } from '../poster/projectRoot.js';\nimport { createPosterServer } from '../poster/server.js';\n\nexport type PosterFormat = 'jpeg' | 'png';\n\nexport interface PosterOptions {\n from: string;\n out: string;\n exportName: string;\n timeSeconds: number;\n width: number;\n height: number;\n type?: string;\n quality?: number;\n deviceScaleFactor?: number;\n}\n\nexport interface PosterIO {\n cwd: string;\n log: (line: string) => void;\n}\n\nconst READY_TIMEOUT_MS = 10_000;\nconst DEFAULT_JPEG_QUALITY = 80;\nconst DEFAULT_DEVICE_SCALE_FACTOR = 2;\n\nexport function validateDeviceScaleFactor(value: number): number {\n if (!(value > 0)) {\n throw new Error(`--device-scale-factor must be greater than 0, received: ${value}`);\n }\n\n return value;\n}\n\nfunction normalizeType(rawType: string | undefined): PosterFormat {\n const normalizedType = rawType?.toLowerCase();\n\n if (normalizedType === undefined || normalizedType === 'jpg' || normalizedType === 'jpeg')\n return 'jpeg';\n if (normalizedType === 'png') return 'png';\n\n throw new Error(`--type must be 'png' or 'jpg' (got ${String(rawType)})`);\n}\n\nfunction extensionFor(format: PosterFormat): string {\n return format === 'png' ? '.png' : '.jpg';\n}\n\nexport function resolveOutPath(out: string, format: PosterFormat): string {\n const ext = extname(out).toLowerCase();\n const expected = extensionFor(format);\n\n if (ext === expected) return out;\n if (format === 'jpeg' && ext === '.jpeg') return out;\n if (ext === '.png' || ext === '.jpg' || ext === '.jpeg') {\n throw new Error(\n `--out extension '${ext}' doesn't match --type '${format === 'jpeg' ? 'jpg' : 'png'}'`,\n );\n }\n\n return `${out}${expected}`;\n}\n\nexport async function runPoster(\n opts: PosterOptions,\n io: PosterIO = { cwd: process.cwd(), log: console.log },\n): Promise<void> {\n if (!Number.isInteger(opts.width) || opts.width <= 0 || opts.width > 4096) {\n throw new Error(`--width must be a positive integer ≤ 4096 (got ${opts.width})`);\n }\n if (!Number.isInteger(opts.height) || opts.height <= 0 || opts.height > 4096) {\n throw new Error(`--height must be a positive integer ≤ 4096 (got ${opts.height})`);\n }\n if (!Number.isFinite(opts.timeSeconds) || opts.timeSeconds < 0) {\n throw new Error(`--time must be ≥ 0 (got ${opts.timeSeconds})`);\n }\n if (opts.quality !== undefined) {\n if (!Number.isInteger(opts.quality) || opts.quality < 1 || opts.quality > 100) {\n throw new Error(`--quality must be an integer 1–100 (got ${opts.quality})`);\n }\n }\n\n const format = normalizeType(opts.type);\n const resolvedOut = resolveOutPath(opts.out, format);\n\n if (format === 'png' && opts.quality !== undefined) {\n io.log(`warn: --quality is ignored for PNG output (lossless)`);\n }\n\n const quality = format === 'jpeg' ? (opts.quality ?? DEFAULT_JPEG_QUALITY) : undefined;\n\n const fromAbs = resolve(io.cwd, opts.from);\n const outAbs = resolve(io.cwd, resolvedOut);\n\n try {\n await stat(fromAbs);\n } catch {\n throw new Error(`--from ${opts.from}: file not found`);\n }\n\n const projectRoot = await findProjectRoot(fromAbs);\n const bundle = await bundlePoster({\n from: fromAbs,\n exportName: opts.exportName,\n projectRoot,\n });\n\n const deviceScaleFactor = validateDeviceScaleFactor(\n opts.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR,\n );\n\n const server = await createPosterServer({\n bundle,\n config: { width: opts.width, height: opts.height },\n });\n\n try {\n await mkdir(dirname(outAbs), { recursive: true });\n const { bytes } = await launchAndScreenshot({\n url: server.url,\n width: opts.width,\n height: opts.height,\n timeSeconds: opts.timeSeconds,\n readyTimeoutMs: READY_TIMEOUT_MS,\n outPath: outAbs,\n projectRoot,\n format,\n quality,\n deviceScaleFactor,\n });\n\n io.log(`Wrote poster: ${resolvedOut} (${opts.width}×${opts.height}, ${formatBytes(bytes)})`);\n io.log('');\n io.log(`Wire it up inside ${opts.from}:`);\n io.log(' <ShaderScene fallback={<img src=\"' + posterPublicSrc(resolvedOut) + '\" alt=\"\" />}>');\n io.log(' ...');\n io.log(' </ShaderScene>');\n } finally {\n await server.close();\n }\n}\n\nfunction formatBytes(byteCount: number): string {\n if (byteCount < 1024) return `${byteCount} B`;\n if (byteCount < 1024 * 1024) return `${(byteCount / 1024).toFixed(1)} KB`;\n\n return `${(byteCount / 1024 / 1024).toFixed(2)} MB`;\n}\n\nfunction posterPublicSrc(outPath: string): string {\n // Best-effort hint: if the path goes through `/public/`, suggest the served form.\n const publicSegmentIndex = outPath.replace(/\\\\/g, '/').indexOf('/public/');\n\n if (publicSegmentIndex >= 0)\n return outPath.replace(/\\\\/g, '/').slice(publicSegmentIndex + '/public'.length);\n\n return outPath;\n}\n"],"mappings":"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","names":[]}
@@ -61,7 +61,7 @@ async function launchAndScreenshot(opts) {
61
61
  try {
62
62
  const browserContext = await browser.newContext({
63
63
  viewport: { width: opts.width, height: opts.height },
64
- deviceScaleFactor: 1
64
+ deviceScaleFactor: opts.deviceScaleFactor
65
65
  });
66
66
  const page = await browserContext.newPage();
67
67
  const consoleErrors = [];
@@ -101,4 +101,4 @@ export {
101
101
  resolvePlaywright,
102
102
  launchAndScreenshot
103
103
  };
104
- //# sourceMappingURL=chunk-RRL35RGP.js.map
104
+ //# sourceMappingURL=chunk-S4X3CIUX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/poster/playwright.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport { writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type * as Playwright from 'playwright';\n\n/**\n * Walk up the directory tree from `startDir` looking for a `playwright`\n * package directory. Avoids NODE_PATH leakage from the host process.\n */\nasync function findPlaywrightDir(startDir: string): Promise<string | null> {\n let dir = startDir;\n\n for (;;) {\n const candidate = join(dir, 'node_modules', 'playwright');\n\n try {\n await access(join(candidate, 'package.json'));\n\n return candidate;\n } catch {\n // not here; keep walking up\n }\n const parent = dirname(dir);\n\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isPlaywrightNamespace(value: unknown): value is typeof Playwright {\n if (!isRecord(value)) return false;\n\n const chromium = value.chromium;\n\n if (!isRecord(chromium)) return false;\n\n return typeof chromium.launch === 'function';\n}\n\nfunction getDefaultExport(value: unknown): unknown {\n if (!isRecord(value)) return undefined;\n\n return value.default;\n}\n\nexport async function resolvePlaywright(projectRoot: string): Promise<typeof Playwright> {\n const playwrightDir = await findPlaywrightDir(projectRoot);\n\n if (playwrightDir === null) {\n throw new Error(\n `Install playwright to use this command: pnpm add -D playwright && pnpm exec playwright install chromium`,\n );\n }\n // Prefer ESM entry; fall back to CJS with .default unwrap.\n for (const entryFilename of ['index.mjs', 'index.js']) {\n const filePath = join(playwrightDir, entryFilename);\n\n try {\n await access(filePath);\n\n const rawModule: unknown = await import(pathToFileURL(filePath).href);\n const playwrightNamespace = isPlaywrightNamespace(rawModule)\n ? rawModule\n : getDefaultExport(rawModule);\n\n if (!isPlaywrightNamespace(playwrightNamespace)) {\n throw new Error(`Resolved ${filePath} but it does not expose chromium.launch`);\n }\n\n return playwrightNamespace;\n } catch (caughtError) {\n if (entryFilename === 'index.js') throw caughtError;\n // else: try the next entry\n }\n }\n throw new Error(`Unable to import playwright from ${playwrightDir}`);\n}\n\nexport interface ScreenshotOpts {\n url: string;\n width: number;\n height: number;\n timeSeconds: number;\n readyTimeoutMs: number;\n outPath: string;\n projectRoot: string;\n format: 'jpeg' | 'png';\n quality: number | undefined;\n deviceScaleFactor: number;\n}\n\nexport async function launchAndScreenshot(opts: ScreenshotOpts): Promise<{ bytes: number }> {\n const playwright = await resolvePlaywright(opts.projectRoot);\n const browser = await playwright.chromium.launch({ headless: true });\n\n try {\n const browserContext = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n deviceScaleFactor: opts.deviceScaleFactor,\n });\n const page = await browserContext.newPage();\n const consoleErrors: string[] = [];\n\n page.on('pageerror', (pageError) => consoleErrors.push(`pageerror: ${pageError.message}`));\n page.on('console', (msg) => {\n if (msg.type() === 'error') consoleErrors.push(`console: ${msg.text()}`);\n });\n await page.goto(opts.url, { waitUntil: 'load' });\n try {\n await page.waitForFunction(() => Reflect.get(globalThis, '__matterReady') === true, {\n timeout: opts.readyTimeoutMs,\n });\n } catch {\n if (consoleErrors.length > 0) {\n throw new Error(\n `Poster render failed before producing a frame:\\n ${consoleErrors.join('\\n ')}`,\n );\n }\n throw new Error(\n `no canvas content detected within ${\n opts.readyTimeoutMs / 1000\n }s; does your component render a ShaderScene with a visible base layer?`,\n );\n }\n if (opts.timeSeconds > 0) {\n await page.waitForTimeout(opts.timeSeconds * 1000);\n }\n const canvas = page.locator('canvas').first();\n\n const imageBuffer =\n opts.format === 'jpeg'\n ? await canvas.screenshot({ type: 'jpeg', quality: opts.quality })\n : await canvas.screenshot({ type: 'png' });\n\n await writeFile(opts.outPath, imageBuffer);\n\n return { bytes: imageBuffer.length };\n } finally {\n await browser.close();\n }\n}\n"],"mappings":"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","names":[]}
@@ -8,12 +8,14 @@ interface PosterOptions {
8
8
  height: number;
9
9
  type?: string;
10
10
  quality?: number;
11
+ deviceScaleFactor?: number;
11
12
  }
12
13
  interface PosterIO {
13
14
  cwd: string;
14
15
  log: (line: string) => void;
15
16
  }
17
+ declare function validateDeviceScaleFactor(value: number): number;
16
18
  declare function resolveOutPath(out: string, format: PosterFormat): string;
17
19
  declare function runPoster(opts: PosterOptions, io?: PosterIO): Promise<void>;
18
20
 
19
- export { type PosterFormat, type PosterIO, type PosterOptions, resolveOutPath, runPoster };
21
+ export { type PosterFormat, type PosterIO, type PosterOptions, resolveOutPath, runPoster, validateDeviceScaleFactor };
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveOutPath,
4
- runPoster
5
- } from "../chunk-YSFTOGZX.js";
4
+ runPoster,
5
+ validateDeviceScaleFactor
6
+ } from "../chunk-3ACBRJOS.js";
6
7
  import "../chunk-YAWX2IU3.js";
7
8
  import "../chunk-TBB7CTPP.js";
8
- import "../chunk-RRL35RGP.js";
9
+ import "../chunk-S4X3CIUX.js";
9
10
  import "../chunk-Q7CQW6AH.js";
10
11
  import "../chunk-D4HDZEJT.js";
11
12
  export {
12
13
  resolveOutPath,
13
- runPoster
14
+ runPoster,
15
+ validateDeviceScaleFactor
14
16
  };
15
17
  //# sourceMappingURL=poster.js.map
@@ -12,22 +12,23 @@ function isRecord(value: unknown): value is Record<string, unknown> {
12
12
  return typeof value === 'object' && value !== null;
13
13
  }
14
14
 
15
- // Dynamic import returns `any`; assign to `unknown` first via explicit typing.
15
+ function isReactComponent(value: unknown): value is React.ComponentType<unknown> {
16
+ return typeof value === 'function';
17
+ }
18
+
16
19
  const rawModule: unknown = await import(/* @vite-ignore */ __MATTER_USER_MODULE_PATH);
17
20
  const userModule: Record<string, unknown> = isRecord(rawModule) ? rawModule : {};
18
21
 
19
22
  const rawExport: unknown = userModule[__MATTER_EXPORT_NAME];
20
23
 
21
- if (typeof rawExport !== 'function') {
24
+ if (!isReactComponent(rawExport)) {
22
25
  document.body.innerHTML = `<pre style="color:#fff;padding:1rem">matter poster: export "${__MATTER_EXPORT_NAME}" is not a React component (got ${typeof rawExport}). Available exports: ${Object.keys(
23
26
  userModule,
24
27
  ).join(', ')}</pre>`;
25
28
  throw new Error(`export "${__MATTER_EXPORT_NAME}" is not a component`);
26
29
  }
27
30
 
28
- // After the typeof guard above, rawExport is a function — safe to use as a React component.
29
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
30
- const Component = rawExport as React.ComponentType<unknown>;
31
+ const Component = rawExport;
31
32
 
32
33
  const rootEl = document.getElementById('root');
33
34
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ function fail(caughtError) {
9
9
  process.exit(1);
10
10
  }
11
11
  var program = new Command();
12
- program.name("matter-cli").description("CLI for Matter \u2014 copy-paste components from the registry into your project").version("0.5.0");
12
+ program.name("matter-cli").description("CLI for Matter \u2014 copy-paste components from the registry into your project").version("1.0.0");
13
13
  program.command("init").description("one-time project setup \u2014 writes matter.config.json").option("--force", "overwrite an existing matter.config.json").action(async (opts) => {
14
14
  try {
15
15
  const { runInit } = await import("./init-NB5EOU5H.js");
@@ -21,7 +21,7 @@ program.command("init").description("one-time project setup \u2014 writes matter
21
21
  program.command("list").description("show available components in the registry").option("--registry <url>", "override the registryUrl from matter.config.json").option("--reference <ref>", "tag, branch, or commit (defaults to the CLI version)").action(async (opts) => {
22
22
  try {
23
23
  const { runList } = await import("./list-L725RQM3.js");
24
- await runList({ registry: opts.registry, ref: opts.reference, cliVersion: "0.5.0" });
24
+ await runList({ registry: opts.registry, ref: opts.reference, cliVersion: "1.0.0" });
25
25
  } catch (caughtError) {
26
26
  fail(caughtError);
27
27
  }
@@ -34,7 +34,7 @@ program.command("add").description("copy one or more components from the registr
34
34
  registry: opts.registry,
35
35
  ref: opts.reference,
36
36
  force: opts.force,
37
- cliVersion: "0.5.0"
37
+ cliVersion: "1.0.0"
38
38
  });
39
39
  } catch (caughtError) {
40
40
  fail(caughtError);
@@ -49,14 +49,14 @@ program.command("update").description("re-fetch a previously-added component (or
49
49
  registry: opts.registry,
50
50
  ref: opts.reference,
51
51
  force: opts.force,
52
- cliVersion: "0.5.0"
52
+ cliVersion: "1.0.0"
53
53
  });
54
54
  } catch (caughtError) {
55
55
  fail(caughtError);
56
56
  }
57
57
  }
58
58
  );
59
- program.command("poster").description("render a Matter component tree to a static image for use as <ShaderScene fallback>").requiredOption("--source <file>", "path to a .tsx/.ts file exporting the component to render").requiredOption("--output <path>", "where to write the image (extension optional; --format wins)").option("--format <format>", "output format: png or jpg", "jpg").option("--quality <n>", "JPEG quality 1\u2013100 (default 80, ignored for PNG)").option("--export-name <name>", "named export to render", "default").option("--capture-delay <seconds>", "wait this long after first non-blank frame", "0").option("--width <px>", "render width", "1280").option("--height <px>", "render height", "720").action(
59
+ program.command("poster").description("render a Matter component tree to a static image for use as <ShaderScene fallback>").requiredOption("--source <file>", "path to a .tsx/.ts file exporting the component to render").requiredOption("--output <path>", "where to write the image (extension optional; --format wins)").option("--format <format>", "output format: png or jpg", "jpg").option("--quality <n>", "JPEG quality 1\u2013100 (default 80, ignored for PNG)").option("--export-name <name>", "named export to render", "default").option("--capture-delay <seconds>", "wait this long after first non-blank frame", "0").option("--width <px>", "render width", "1280").option("--height <px>", "render height", "720").option("--device-scale-factor <n>", "capture DPR; defaults to 2 to match the live renderer", "2").action(
60
60
  async (opts) => {
61
61
  try {
62
62
  const { runPoster } = await import("./commands/poster.js");
@@ -68,7 +68,8 @@ program.command("poster").description("render a Matter component tree to a stati
68
68
  exportName: opts.exportName,
69
69
  timeSeconds: Number.parseFloat(opts.captureDelay),
70
70
  width: Number.parseInt(opts.width, 10),
71
- height: Number.parseInt(opts.height, 10)
71
+ height: Number.parseInt(opts.height, 10),
72
+ deviceScaleFactor: Number.parseFloat(opts.deviceScaleFactor)
72
73
  });
73
74
  } catch (caughtError) {
74
75
  fail(caughtError);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\ndeclare const __VERSION__: string;\n\nfunction fail(caughtError: unknown): never {\n const message = caughtError instanceof Error ? caughtError.message : String(caughtError);\n\n process.stderr.write(`error: ${message}\\n`);\n process.exit(1);\n}\n\nconst program = new Command();\n\nprogram\n .name('matter-cli')\n .description('CLI for Matter — copy-paste components from the registry into your project')\n .version(__VERSION__);\n\nprogram\n .command('init')\n .description('one-time project setup — writes matter.config.json')\n .option('--force', 'overwrite an existing matter.config.json')\n .action(async (opts: { force?: boolean }) => {\n try {\n const { runInit } = await import('./commands/init.js');\n\n await runInit(opts);\n } catch (caughtError) {\n fail(caughtError);\n }\n });\n\nprogram\n .command('list')\n .description('show available components in the registry')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .action(async (opts: { registry?: string; reference?: string }) => {\n try {\n const { runList } = await import('./commands/list.js');\n\n await runList({ registry: opts.registry, ref: opts.reference, cliVersion: __VERSION__ });\n } catch (caughtError) {\n fail(caughtError);\n }\n });\n\nprogram\n .command('add')\n .description('copy one or more components from the registry into componentsDir')\n .argument('<components...>', 'component slugs (e.g. \"linear-gradient\")')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .option('--force', 'overwrite existing files in componentsDir')\n .action(\n async (\n components: string[],\n opts: { registry?: string; reference?: string; force?: boolean },\n ) => {\n try {\n const { runAdd } = await import('./commands/add.js');\n\n await runAdd(components, {\n registry: opts.registry,\n ref: opts.reference,\n force: opts.force,\n cliVersion: __VERSION__,\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nprogram\n .command('update')\n .description('re-fetch a previously-added component (or all, if no name given)')\n .argument('[components...]', 'component slugs; omit to update every component in componentsDir')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .option('--force', 'overwrite files even if they have local edits')\n .action(\n async (\n components: string[],\n opts: { registry?: string; reference?: string; force?: boolean },\n ) => {\n try {\n const { runUpdate } = await import('./commands/update.js');\n\n await runUpdate(components, {\n registry: opts.registry,\n ref: opts.reference,\n force: opts.force,\n cliVersion: __VERSION__,\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nprogram\n .command('poster')\n .description('render a Matter component tree to a static image for use as <ShaderScene fallback>')\n .requiredOption('--source <file>', 'path to a .tsx/.ts file exporting the component to render')\n .requiredOption('--output <path>', 'where to write the image (extension optional; --format wins)')\n .option('--format <format>', 'output format: png or jpg', 'jpg')\n .option('--quality <n>', 'JPEG quality 1–100 (default 80, ignored for PNG)')\n .option('--export-name <name>', 'named export to render', 'default')\n .option('--capture-delay <seconds>', 'wait this long after first non-blank frame', '0')\n .option('--width <px>', 'render width', '1280')\n .option('--height <px>', 'render height', '720')\n .action(\n async (opts: {\n source: string;\n output: string;\n format: string;\n quality?: string;\n exportName: string;\n captureDelay: string;\n width: string;\n height: string;\n }) => {\n try {\n const { runPoster } = await import('./commands/poster.js');\n\n await runPoster({\n from: opts.source,\n out: opts.output,\n type: opts.format,\n quality: opts.quality === undefined ? undefined : Number.parseInt(opts.quality, 10),\n exportName: opts.exportName,\n timeSeconds: Number.parseFloat(opts.captureDelay),\n width: Number.parseInt(opts.width, 10),\n height: Number.parseInt(opts.height, 10),\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nawait program.parseAsync(process.argv);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from 'commander';\n\ndeclare const __VERSION__: string;\n\nfunction fail(caughtError: unknown): never {\n const message = caughtError instanceof Error ? caughtError.message : String(caughtError);\n\n process.stderr.write(`error: ${message}\\n`);\n process.exit(1);\n}\n\nconst program = new Command();\n\nprogram\n .name('matter-cli')\n .description('CLI for Matter — copy-paste components from the registry into your project')\n .version(__VERSION__);\n\nprogram\n .command('init')\n .description('one-time project setup — writes matter.config.json')\n .option('--force', 'overwrite an existing matter.config.json')\n .action(async (opts: { force?: boolean }) => {\n try {\n const { runInit } = await import('./commands/init.js');\n\n await runInit(opts);\n } catch (caughtError) {\n fail(caughtError);\n }\n });\n\nprogram\n .command('list')\n .description('show available components in the registry')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .action(async (opts: { registry?: string; reference?: string }) => {\n try {\n const { runList } = await import('./commands/list.js');\n\n await runList({ registry: opts.registry, ref: opts.reference, cliVersion: __VERSION__ });\n } catch (caughtError) {\n fail(caughtError);\n }\n });\n\nprogram\n .command('add')\n .description('copy one or more components from the registry into componentsDir')\n .argument('<components...>', 'component slugs (e.g. \"linear-gradient\")')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .option('--force', 'overwrite existing files in componentsDir')\n .action(\n async (\n components: string[],\n opts: { registry?: string; reference?: string; force?: boolean },\n ) => {\n try {\n const { runAdd } = await import('./commands/add.js');\n\n await runAdd(components, {\n registry: opts.registry,\n ref: opts.reference,\n force: opts.force,\n cliVersion: __VERSION__,\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nprogram\n .command('update')\n .description('re-fetch a previously-added component (or all, if no name given)')\n .argument('[components...]', 'component slugs; omit to update every component in componentsDir')\n .option('--registry <url>', 'override the registryUrl from matter.config.json')\n .option('--reference <ref>', 'tag, branch, or commit (defaults to the CLI version)')\n .option('--force', 'overwrite files even if they have local edits')\n .action(\n async (\n components: string[],\n opts: { registry?: string; reference?: string; force?: boolean },\n ) => {\n try {\n const { runUpdate } = await import('./commands/update.js');\n\n await runUpdate(components, {\n registry: opts.registry,\n ref: opts.reference,\n force: opts.force,\n cliVersion: __VERSION__,\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nprogram\n .command('poster')\n .description('render a Matter component tree to a static image for use as <ShaderScene fallback>')\n .requiredOption('--source <file>', 'path to a .tsx/.ts file exporting the component to render')\n .requiredOption('--output <path>', 'where to write the image (extension optional; --format wins)')\n .option('--format <format>', 'output format: png or jpg', 'jpg')\n .option('--quality <n>', 'JPEG quality 1–100 (default 80, ignored for PNG)')\n .option('--export-name <name>', 'named export to render', 'default')\n .option('--capture-delay <seconds>', 'wait this long after first non-blank frame', '0')\n .option('--width <px>', 'render width', '1280')\n .option('--height <px>', 'render height', '720')\n .option('--device-scale-factor <n>', 'capture DPR; defaults to 2 to match the live renderer', '2')\n .action(\n async (opts: {\n source: string;\n output: string;\n format: string;\n quality?: string;\n exportName: string;\n captureDelay: string;\n width: string;\n height: string;\n deviceScaleFactor: string;\n }) => {\n try {\n const { runPoster } = await import('./commands/poster.js');\n\n await runPoster({\n from: opts.source,\n out: opts.output,\n type: opts.format,\n quality: opts.quality === undefined ? undefined : Number.parseInt(opts.quality, 10),\n exportName: opts.exportName,\n timeSeconds: Number.parseFloat(opts.captureDelay),\n width: Number.parseInt(opts.width, 10),\n height: Number.parseInt(opts.height, 10),\n deviceScaleFactor: Number.parseFloat(opts.deviceScaleFactor),\n });\n } catch (caughtError) {\n fail(caughtError);\n }\n },\n );\n\nawait program.parseAsync(process.argv);\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runPoster
4
- } from "../chunk-YSFTOGZX.js";
4
+ } from "../chunk-3ACBRJOS.js";
5
5
  import "../chunk-YAWX2IU3.js";
6
6
  import "../chunk-TBB7CTPP.js";
7
- import "../chunk-RRL35RGP.js";
7
+ import "../chunk-S4X3CIUX.js";
8
8
  import "../chunk-Q7CQW6AH.js";
9
9
  import {
10
10
  afterEach,
@@ -75,6 +75,7 @@ describe.skipIf(!E2E_ENABLED)("runPoster \u2014 E2E (MATTER_E2E=1)", () => {
75
75
  timeSeconds: 0,
76
76
  width: 800,
77
77
  height: 600,
78
+ deviceScaleFactor: 1,
78
79
  ...c.extra
79
80
  },
80
81
  { cwd: process.cwd(), log: vi.fn() }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/poster/e2e.test.ts"],"sourcesContent":["import { mkdtemp, rm, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { runPoster } from '../commands/poster.js';\n\nconst E2E_ENABLED = process.env.MATTER_E2E === '1';\n\nconst FIXTURES = new URL('../test-fixtures/posters/', import.meta.url).pathname;\n\nconst cases = [\n {\n name: 'single-linear-gradient',\n file: 'single-linear-gradient.tsx',\n type: 'png',\n extra: {},\n },\n {\n name: 'gradient-plus-grain',\n file: 'gradient-plus-grain.tsx',\n type: 'png',\n extra: {},\n },\n {\n name: 'aurora-with-time',\n file: 'aurora-with-time.tsx',\n type: 'png',\n extra: { timeSeconds: 2 },\n },\n {\n name: 'named-export',\n file: 'named-export.tsx',\n type: 'png',\n extra: { exportName: 'NamedExport' },\n },\n // Exercises the default JPEG path and verifies the magic-byte header.\n {\n name: 'jpeg-default',\n file: 'single-linear-gradient.tsx',\n type: 'jpg',\n extra: {},\n },\n] as const;\n\ndescribe.skipIf(!E2E_ENABLED)('runPoster — E2E (MATTER_E2E=1)', () => {\n let outDir: string;\n\n beforeEach(async () => {\n outDir = await mkdtemp(join(tmpdir(), 'matter-poster-e2e-'));\n });\n\n afterEach(async () => {\n await rm(outDir, { recursive: true, force: true });\n });\n\n for (const c of cases) {\n it(`produces a ${c.type.toUpperCase()} for ${c.name}`, async () => {\n const out = join(outDir, `${c.name}.${c.type === 'jpg' ? 'jpg' : 'png'}`);\n\n await runPoster(\n {\n from: join(FIXTURES, c.file),\n out,\n type: c.type,\n exportName: 'default',\n timeSeconds: 0,\n width: 800,\n height: 600,\n ...c.extra,\n },\n { cwd: process.cwd(), log: vi.fn() },\n );\n const s = await stat(out);\n\n expect(s.size).toBeGreaterThan(1024); // > 1 KB\n expect(s.size).toBeLessThan(5 * 1024 * 1024); // < 5 MB\n\n const { open } = await import('node:fs/promises');\n const fh = await open(out, 'r');\n\n try {\n const head = Buffer.alloc(4);\n\n await fh.read(head, 0, 4, 0);\n if (c.type === 'png') {\n expect(head[0]).toBe(0x89);\n expect(head[1]).toBe(0x50);\n } else {\n expect(head[0]).toBe(0xff);\n expect(head[1]).toBe(0xd8);\n }\n } finally {\n await fh.close();\n }\n }, 30_000);\n }\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,IAAI,YAAY;AAClC,SAAS,cAAc;AACvB,SAAS,YAAY;AAKrB,IAAM,cAAc,QAAQ,IAAI,eAAe;AAE/C,IAAM,WAAW,IAAI,IAAI,6BAA6B,YAAY,GAAG,EAAE;AAEvE,IAAM,QAAQ;AAAA,EACZ;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,EAAE,aAAa,EAAE;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,EAAE,YAAY,cAAc;AAAA,EACrC;AAAA;AAAA,EAEA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,OAAO,CAAC,WAAW,EAAE,uCAAkC,MAAM;AACpE,MAAI;AAEJ,aAAW,YAAY;AACrB,aAAS,MAAM,QAAQ,KAAK,OAAO,GAAG,oBAAoB,CAAC;AAAA,EAC7D,CAAC;AAED,YAAU,YAAY;AACpB,UAAM,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACnD,CAAC;AAED,aAAW,KAAK,OAAO;AACrB,OAAG,cAAc,EAAE,KAAK,YAAY,CAAC,QAAQ,EAAE,IAAI,IAAI,YAAY;AACjE,YAAM,MAAM,KAAK,QAAQ,GAAG,EAAE,IAAI,IAAI,EAAE,SAAS,QAAQ,QAAQ,KAAK,EAAE;AAExE,YAAM;AAAA,QACJ;AAAA,UACE,MAAM,KAAK,UAAU,EAAE,IAAI;AAAA,UAC3B;AAAA,UACA,MAAM,EAAE;AAAA,UACR,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,GAAG,EAAE;AAAA,QACP;AAAA,QACA,EAAE,KAAK,QAAQ,IAAI,GAAG,KAAK,GAAG,GAAG,EAAE;AAAA,MACrC;AACA,YAAM,IAAI,MAAM,KAAK,GAAG;AAExB,mBAAO,EAAE,IAAI,EAAE,gBAAgB,IAAI;AACnC,mBAAO,EAAE,IAAI,EAAE,aAAa,IAAI,OAAO,IAAI;AAE3C,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO,aAAkB;AAChD,YAAM,KAAK,MAAM,KAAK,KAAK,GAAG;AAE9B,UAAI;AACF,cAAM,OAAO,OAAO,MAAM,CAAC;AAE3B,cAAM,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC;AAC3B,YAAI,EAAE,SAAS,OAAO;AACpB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AACzB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,EAAI;AAAA,QAC3B,OAAO;AACL,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AACzB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AAAA,QAC3B;AAAA,MACF,UAAE;AACA,cAAM,GAAG,MAAM;AAAA,MACjB;AAAA,IACF,GAAG,GAAM;AAAA,EACX;AACF,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/poster/e2e.test.ts"],"sourcesContent":["import { mkdtemp, rm, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { runPoster } from '../commands/poster.js';\n\nconst E2E_ENABLED = process.env.MATTER_E2E === '1';\n\nconst FIXTURES = new URL('../test-fixtures/posters/', import.meta.url).pathname;\n\nconst cases = [\n {\n name: 'single-linear-gradient',\n file: 'single-linear-gradient.tsx',\n type: 'png',\n extra: {},\n },\n {\n name: 'gradient-plus-grain',\n file: 'gradient-plus-grain.tsx',\n type: 'png',\n extra: {},\n },\n {\n name: 'aurora-with-time',\n file: 'aurora-with-time.tsx',\n type: 'png',\n extra: { timeSeconds: 2 },\n },\n {\n name: 'named-export',\n file: 'named-export.tsx',\n type: 'png',\n extra: { exportName: 'NamedExport' },\n },\n // Exercises the default JPEG path and verifies the magic-byte header.\n {\n name: 'jpeg-default',\n file: 'single-linear-gradient.tsx',\n type: 'jpg',\n extra: {},\n },\n] as const;\n\ndescribe.skipIf(!E2E_ENABLED)('runPoster — E2E (MATTER_E2E=1)', () => {\n let outDir: string;\n\n beforeEach(async () => {\n outDir = await mkdtemp(join(tmpdir(), 'matter-poster-e2e-'));\n });\n\n afterEach(async () => {\n await rm(outDir, { recursive: true, force: true });\n });\n\n for (const c of cases) {\n it(`produces a ${c.type.toUpperCase()} for ${c.name}`, async () => {\n const out = join(outDir, `${c.name}.${c.type === 'jpg' ? 'jpg' : 'png'}`);\n\n await runPoster(\n {\n from: join(FIXTURES, c.file),\n out,\n type: c.type,\n exportName: 'default',\n timeSeconds: 0,\n width: 800,\n height: 600,\n deviceScaleFactor: 1,\n ...c.extra,\n },\n { cwd: process.cwd(), log: vi.fn() },\n );\n const s = await stat(out);\n\n expect(s.size).toBeGreaterThan(1024); // > 1 KB\n expect(s.size).toBeLessThan(5 * 1024 * 1024); // < 5 MB\n\n const { open } = await import('node:fs/promises');\n const fh = await open(out, 'r');\n\n try {\n const head = Buffer.alloc(4);\n\n await fh.read(head, 0, 4, 0);\n if (c.type === 'png') {\n expect(head[0]).toBe(0x89);\n expect(head[1]).toBe(0x50);\n } else {\n expect(head[0]).toBe(0xff);\n expect(head[1]).toBe(0xd8);\n }\n } finally {\n await fh.close();\n }\n }, 30_000);\n }\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,IAAI,YAAY;AAClC,SAAS,cAAc;AACvB,SAAS,YAAY;AAKrB,IAAM,cAAc,QAAQ,IAAI,eAAe;AAE/C,IAAM,WAAW,IAAI,IAAI,6BAA6B,YAAY,GAAG,EAAE;AAEvE,IAAM,QAAQ;AAAA,EACZ;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,EAAE,aAAa,EAAE;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,EAAE,YAAY,cAAc;AAAA,EACrC;AAAA;AAAA,EAEA;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,OAAO,CAAC,WAAW,EAAE,uCAAkC,MAAM;AACpE,MAAI;AAEJ,aAAW,YAAY;AACrB,aAAS,MAAM,QAAQ,KAAK,OAAO,GAAG,oBAAoB,CAAC;AAAA,EAC7D,CAAC;AAED,YAAU,YAAY;AACpB,UAAM,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACnD,CAAC;AAED,aAAW,KAAK,OAAO;AACrB,OAAG,cAAc,EAAE,KAAK,YAAY,CAAC,QAAQ,EAAE,IAAI,IAAI,YAAY;AACjE,YAAM,MAAM,KAAK,QAAQ,GAAG,EAAE,IAAI,IAAI,EAAE,SAAS,QAAQ,QAAQ,KAAK,EAAE;AAExE,YAAM;AAAA,QACJ;AAAA,UACE,MAAM,KAAK,UAAU,EAAE,IAAI;AAAA,UAC3B;AAAA,UACA,MAAM,EAAE;AAAA,UACR,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,mBAAmB;AAAA,UACnB,GAAG,EAAE;AAAA,QACP;AAAA,QACA,EAAE,KAAK,QAAQ,IAAI,GAAG,KAAK,GAAG,GAAG,EAAE;AAAA,MACrC;AACA,YAAM,IAAI,MAAM,KAAK,GAAG;AAExB,mBAAO,EAAE,IAAI,EAAE,gBAAgB,IAAI;AACnC,mBAAO,EAAE,IAAI,EAAE,aAAa,IAAI,OAAO,IAAI;AAE3C,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO,aAAkB;AAChD,YAAM,KAAK,MAAM,KAAK,KAAK,GAAG;AAE9B,UAAI;AACF,cAAM,OAAO,OAAO,MAAM,CAAC;AAE3B,cAAM,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC;AAC3B,YAAI,EAAE,SAAS,OAAO;AACpB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AACzB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,EAAI;AAAA,QAC3B,OAAO;AACL,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AACzB,uBAAO,KAAK,CAAC,CAAC,EAAE,KAAK,GAAI;AAAA,QAC3B;AAAA,MACF,UAAE;AACA,cAAM,GAAG,MAAM;AAAA,MACjB;AAAA,IACF,GAAG,GAAM;AAAA,EACX;AACF,CAAC;","names":[]}
@@ -11,6 +11,7 @@ interface ScreenshotOpts {
11
11
  projectRoot: string;
12
12
  format: 'jpeg' | 'png';
13
13
  quality: number | undefined;
14
+ deviceScaleFactor: number;
14
15
  }
15
16
  declare function launchAndScreenshot(opts: ScreenshotOpts): Promise<{
16
17
  bytes: number;
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  launchAndScreenshot,
4
4
  resolvePlaywright
5
- } from "../chunk-RRL35RGP.js";
5
+ } from "../chunk-S4X3CIUX.js";
6
6
  import "../chunk-D4HDZEJT.js";
7
7
  export {
8
8
  launchAndScreenshot,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolvePlaywright
4
- } from "../chunk-RRL35RGP.js";
4
+ } from "../chunk-S4X3CIUX.js";
5
5
  import {
6
6
  describe,
7
7
  globalExpect,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovo/matter-cli",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "CLI for Matter — copy-paste components from the registry into your project.",
5
5
  "keywords": [
6
6
  "cli",
@@ -48,9 +48,9 @@
48
48
  "typescript": "^5.6.0",
49
49
  "vite": "^8.0.14",
50
50
  "vitest": "^4.1.7",
51
- "@lovo/matter-react": "0.5.0",
52
- "@matter/registry": "0.0.0",
53
- "@matter/tsconfig": "0.0.0"
51
+ "@lovo/matter-react": "1.0.0",
52
+ "@matter/tsconfig": "0.0.0",
53
+ "@matter/registry": "0.0.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "playwright": "*"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/poster/playwright.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport { writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type * as Playwright from 'playwright';\n\n/**\n * Walk up the directory tree from `startDir` looking for a `playwright`\n * package directory. Avoids NODE_PATH leakage from the host process.\n */\nasync function findPlaywrightDir(startDir: string): Promise<string | null> {\n let dir = startDir;\n\n for (;;) {\n const candidate = join(dir, 'node_modules', 'playwright');\n\n try {\n await access(join(candidate, 'package.json'));\n\n return candidate;\n } catch {\n // not here; keep walking up\n }\n const parent = dirname(dir);\n\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isPlaywrightNamespace(value: unknown): value is typeof Playwright {\n if (!isRecord(value)) return false;\n\n const chromium = value.chromium;\n\n if (!isRecord(chromium)) return false;\n\n return typeof chromium.launch === 'function';\n}\n\nfunction getDefaultExport(value: unknown): unknown {\n if (!isRecord(value)) return undefined;\n\n return value.default;\n}\n\nexport async function resolvePlaywright(projectRoot: string): Promise<typeof Playwright> {\n const playwrightDir = await findPlaywrightDir(projectRoot);\n\n if (playwrightDir === null) {\n throw new Error(\n `Install playwright to use this command: pnpm add -D playwright && pnpm exec playwright install chromium`,\n );\n }\n // Prefer ESM entry; fall back to CJS with .default unwrap.\n for (const entryFilename of ['index.mjs', 'index.js']) {\n const filePath = join(playwrightDir, entryFilename);\n\n try {\n await access(filePath);\n\n const rawModule: unknown = await import(pathToFileURL(filePath).href);\n const playwrightNamespace = isPlaywrightNamespace(rawModule)\n ? rawModule\n : getDefaultExport(rawModule);\n\n if (!isPlaywrightNamespace(playwrightNamespace)) {\n throw new Error(`Resolved ${filePath} but it does not expose chromium.launch`);\n }\n\n return playwrightNamespace;\n } catch (caughtError) {\n if (entryFilename === 'index.js') throw caughtError;\n // else: try the next entry\n }\n }\n throw new Error(`Unable to import playwright from ${playwrightDir}`);\n}\n\nexport interface ScreenshotOpts {\n url: string;\n width: number;\n height: number;\n timeSeconds: number;\n readyTimeoutMs: number;\n outPath: string;\n projectRoot: string;\n format: 'jpeg' | 'png';\n quality: number | undefined;\n}\n\nexport async function launchAndScreenshot(opts: ScreenshotOpts): Promise<{ bytes: number }> {\n const playwright = await resolvePlaywright(opts.projectRoot);\n const browser = await playwright.chromium.launch({ headless: true });\n\n try {\n const browserContext = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n deviceScaleFactor: 1,\n });\n const page = await browserContext.newPage();\n const consoleErrors: string[] = [];\n\n page.on('pageerror', (pageError) => consoleErrors.push(`pageerror: ${pageError.message}`));\n page.on('console', (msg) => {\n if (msg.type() === 'error') consoleErrors.push(`console: ${msg.text()}`);\n });\n await page.goto(opts.url, { waitUntil: 'load' });\n try {\n await page.waitForFunction(() => Reflect.get(globalThis, '__matterReady') === true, {\n timeout: opts.readyTimeoutMs,\n });\n } catch {\n if (consoleErrors.length > 0) {\n throw new Error(\n `Poster render failed before producing a frame:\\n ${consoleErrors.join('\\n ')}`,\n );\n }\n throw new Error(\n `no canvas content detected within ${\n opts.readyTimeoutMs / 1000\n }s; does your component render a ShaderScene with a visible base layer?`,\n );\n }\n if (opts.timeSeconds > 0) {\n await page.waitForTimeout(opts.timeSeconds * 1000);\n }\n const canvas = page.locator('canvas').first();\n const imageBuffer =\n opts.format === 'jpeg'\n ? await canvas.screenshot({ type: 'jpeg', quality: opts.quality })\n : await canvas.screenshot({ type: 'png' });\n\n await writeFile(opts.outPath, imageBuffer);\n\n return { bytes: imageBuffer.length };\n } finally {\n await browser.close();\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/poster.ts"],"sourcesContent":["import { mkdir, stat } from 'node:fs/promises';\nimport { dirname, extname, resolve } from 'node:path';\n\nimport { bundlePoster } from '../poster/bundle.js';\nimport { launchAndScreenshot } from '../poster/playwright.js';\nimport { findProjectRoot } from '../poster/projectRoot.js';\nimport { createPosterServer } from '../poster/server.js';\n\nexport type PosterFormat = 'jpeg' | 'png';\n\nexport interface PosterOptions {\n from: string;\n out: string;\n exportName: string;\n timeSeconds: number;\n width: number;\n height: number;\n type?: string;\n quality?: number;\n}\n\nexport interface PosterIO {\n cwd: string;\n log: (line: string) => void;\n}\n\nconst READY_TIMEOUT_MS = 10_000;\nconst DEFAULT_JPEG_QUALITY = 80;\n\nfunction normalizeType(rawType: string | undefined): PosterFormat {\n const normalizedType = rawType?.toLowerCase();\n\n if (normalizedType === undefined || normalizedType === 'jpg' || normalizedType === 'jpeg')\n return 'jpeg';\n if (normalizedType === 'png') return 'png';\n\n throw new Error(`--type must be 'png' or 'jpg' (got ${String(rawType)})`);\n}\n\nfunction extensionFor(format: PosterFormat): string {\n return format === 'png' ? '.png' : '.jpg';\n}\n\nexport function resolveOutPath(out: string, format: PosterFormat): string {\n const ext = extname(out).toLowerCase();\n const expected = extensionFor(format);\n\n if (ext === expected) return out;\n if (format === 'jpeg' && ext === '.jpeg') return out;\n if (ext === '.png' || ext === '.jpg' || ext === '.jpeg') {\n throw new Error(\n `--out extension '${ext}' doesn't match --type '${format === 'jpeg' ? 'jpg' : 'png'}'`,\n );\n }\n\n return `${out}${expected}`;\n}\n\nexport async function runPoster(\n opts: PosterOptions,\n io: PosterIO = { cwd: process.cwd(), log: console.log },\n): Promise<void> {\n if (!Number.isInteger(opts.width) || opts.width <= 0 || opts.width > 4096) {\n throw new Error(`--width must be a positive integer ≤ 4096 (got ${opts.width})`);\n }\n if (!Number.isInteger(opts.height) || opts.height <= 0 || opts.height > 4096) {\n throw new Error(`--height must be a positive integer ≤ 4096 (got ${opts.height})`);\n }\n if (!Number.isFinite(opts.timeSeconds) || opts.timeSeconds < 0) {\n throw new Error(`--time must be ≥ 0 (got ${opts.timeSeconds})`);\n }\n if (opts.quality !== undefined) {\n if (!Number.isInteger(opts.quality) || opts.quality < 1 || opts.quality > 100) {\n throw new Error(`--quality must be an integer 1–100 (got ${opts.quality})`);\n }\n }\n\n const format = normalizeType(opts.type);\n const resolvedOut = resolveOutPath(opts.out, format);\n\n if (format === 'png' && opts.quality !== undefined) {\n io.log(`warn: --quality is ignored for PNG output (lossless)`);\n }\n\n const quality = format === 'jpeg' ? (opts.quality ?? DEFAULT_JPEG_QUALITY) : undefined;\n\n const fromAbs = resolve(io.cwd, opts.from);\n const outAbs = resolve(io.cwd, resolvedOut);\n\n try {\n await stat(fromAbs);\n } catch {\n throw new Error(`--from ${opts.from}: file not found`);\n }\n\n const projectRoot = await findProjectRoot(fromAbs);\n const bundle = await bundlePoster({\n from: fromAbs,\n exportName: opts.exportName,\n projectRoot,\n });\n\n const server = await createPosterServer({\n bundle,\n config: { width: opts.width, height: opts.height },\n });\n\n try {\n await mkdir(dirname(outAbs), { recursive: true });\n const { bytes } = await launchAndScreenshot({\n url: server.url,\n width: opts.width,\n height: opts.height,\n timeSeconds: opts.timeSeconds,\n readyTimeoutMs: READY_TIMEOUT_MS,\n outPath: outAbs,\n projectRoot,\n format,\n quality,\n });\n\n io.log(`Wrote poster: ${resolvedOut} (${opts.width}×${opts.height}, ${formatBytes(bytes)})`);\n io.log('');\n io.log(`Wire it up inside ${opts.from}:`);\n io.log(' <ShaderScene fallback={<img src=\"' + posterPublicSrc(resolvedOut) + '\" alt=\"\" />}>');\n io.log(' ...');\n io.log(' </ShaderScene>');\n } finally {\n await server.close();\n }\n}\n\nfunction formatBytes(byteCount: number): string {\n if (byteCount < 1024) return `${byteCount} B`;\n if (byteCount < 1024 * 1024) return `${(byteCount / 1024).toFixed(1)} KB`;\n\n return `${(byteCount / 1024 / 1024).toFixed(2)} MB`;\n}\n\nfunction posterPublicSrc(outPath: string): string {\n // Best-effort hint: if the path goes through `/public/`, suggest the served form.\n const publicSegmentIndex = outPath.replace(/\\\\/g, '/').indexOf('/public/');\n\n if (publicSegmentIndex >= 0)\n return outPath.replace(/\\\\/g, '/').slice(publicSegmentIndex + '/public'.length);\n\n return outPath;\n}\n"],"mappings":"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","names":[]}