@lovo/matter-cli 0.4.1 → 0.5.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 +14 -0
- package/README.md +68 -4
- package/dist/add-T7IRU2NK.js +11 -0
- package/dist/{chunk-4XUN2PKU.js → chunk-53DI7V2J.js} +22 -21
- package/dist/chunk-53DI7V2J.js.map +1 -0
- package/dist/chunk-D4HDZEJT.js +37 -0
- package/dist/chunk-D4HDZEJT.js.map +1 -0
- package/dist/{chunk-OJWKSIZ5.js → chunk-FHRQIHCN.js} +32 -25
- package/dist/chunk-FHRQIHCN.js.map +1 -0
- package/dist/chunk-Q7CQW6AH.js +27 -0
- package/dist/chunk-Q7CQW6AH.js.map +1 -0
- package/dist/{chunk-M6S6HYHE.js → chunk-R7OX6KUP.js} +48 -44
- package/dist/chunk-R7OX6KUP.js.map +1 -0
- package/dist/chunk-RRL35RGP.js +104 -0
- package/dist/chunk-RRL35RGP.js.map +1 -0
- package/dist/chunk-TBB7CTPP.js +77 -0
- package/dist/chunk-TBB7CTPP.js.map +1 -0
- package/dist/chunk-YADIAD35.js +16322 -0
- package/dist/chunk-YADIAD35.js.map +1 -0
- package/dist/chunk-YAWX2IU3.js +46 -0
- package/dist/chunk-YAWX2IU3.js.map +1 -0
- package/dist/chunk-YSFTOGZX.js +119 -0
- package/dist/chunk-YSFTOGZX.js.map +1 -0
- package/dist/commands/poster.d.ts +19 -0
- package/dist/commands/poster.js +15 -0
- package/dist/commands/poster.js.map +1 -0
- package/dist/dist-I76TGDBX.js +547 -0
- package/dist/dist-I76TGDBX.js.map +1 -0
- package/dist/harness/frameReady.ts +35 -0
- package/dist/harness/index.html +20 -0
- package/dist/harness/index.tsx +40 -0
- package/dist/index.js +50 -21
- package/dist/index.js.map +1 -1
- package/dist/{init-W43YVGBI.js → init-NB5EOU5H.js} +3 -2
- package/dist/init-NB5EOU5H.js.map +1 -0
- package/dist/{list-S626E7NZ.js → list-L725RQM3.js} +6 -5
- package/dist/list-L725RQM3.js.map +1 -0
- package/dist/magic-string.es-DKS3S7WW.js +1310 -0
- package/dist/magic-string.es-DKS3S7WW.js.map +1 -0
- package/dist/poster/bundle.d.ts +12 -0
- package/dist/poster/bundle.js +9 -0
- package/dist/poster/bundle.js.map +1 -0
- package/dist/poster/bundle.test.d.ts +2 -0
- package/dist/poster/bundle.test.js +49 -0
- package/dist/poster/bundle.test.js.map +1 -0
- package/dist/poster/e2e.test.d.ts +2 -0
- package/dist/poster/e2e.test.js +103 -0
- package/dist/poster/e2e.test.js.map +1 -0
- package/dist/poster/playwright.d.ts +19 -0
- package/dist/poster/playwright.js +11 -0
- package/dist/poster/playwright.js.map +1 -0
- package/dist/poster/playwright.test.d.ts +2 -0
- package/dist/poster/playwright.test.js +28 -0
- package/dist/poster/playwright.test.js.map +1 -0
- package/dist/poster/projectRoot.d.ts +3 -0
- package/dist/poster/projectRoot.js +9 -0
- package/dist/poster/projectRoot.js.map +1 -0
- package/dist/poster/projectRoot.test.d.ts +2 -0
- package/dist/poster/projectRoot.test.js +47 -0
- package/dist/poster/projectRoot.test.js.map +1 -0
- package/dist/poster/server.d.ts +18 -0
- package/dist/poster/server.js +9 -0
- package/dist/poster/server.js.map +1 -0
- package/dist/poster/server.test.d.ts +2 -0
- package/dist/poster/server.test.js +54 -0
- package/dist/poster/server.test.js.map +1 -0
- package/dist/{update-3BHHOUCS.js → update-WK7CA42P.js} +26 -17
- package/dist/update-WK7CA42P.js.map +1 -0
- package/package.json +19 -3
- package/dist/add-ABC455QH.js +0 -10
- package/dist/chunk-4XUN2PKU.js.map +0 -1
- package/dist/chunk-M6S6HYHE.js.map +0 -1
- package/dist/chunk-OJWKSIZ5.js.map +0 -1
- package/dist/init-W43YVGBI.js.map +0 -1
- package/dist/list-S626E7NZ.js.map +0 -1
- package/dist/update-3BHHOUCS.js.map +0 -1
- /package/dist/{add-ABC455QH.js.map → add-T7IRU2NK.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @lovo/matter-cli
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0299ddb: Rename ambiguous CLI flags and the config key to spelled-out names (BREAKING, pre-1.0):
|
|
8
|
+
|
|
9
|
+
- `list`/`add`/`update`: `--ref` → `--reference`
|
|
10
|
+
- `poster`: `--from` → `--source`, `--out` → `--output`, `--type` → `--format`, `--export` → `--export-name`, `--time` → `--capture-delay`
|
|
11
|
+
- `matter.config.json`: removed the `tsx` boolean key (it was validated but never read by any command)
|
|
12
|
+
|
|
13
|
+
Kept: `--registry`, `--quality`, `--width`, `--height`, `--force`, and the config keys `componentsDir`, `registryUrl`, `aliases`.
|
|
14
|
+
|
|
15
|
+
Migration: update any scripts that pass the old flags. You can delete the `tsx` key from your `matter.config.json` if present — it is no longer used (unknown keys are ignored). Re-running `matter-cli init` regenerates a config without it.
|
|
16
|
+
|
|
3
17
|
## 0.4.1
|
|
4
18
|
|
|
5
19
|
## 0.4.0
|
package/README.md
CHANGED
|
@@ -25,12 +25,11 @@ Writes `matter.config.json` to your project root with sensible defaults:
|
|
|
25
25
|
{
|
|
26
26
|
"componentsDir": "src/components/matter",
|
|
27
27
|
"registryUrl": "https://raw.githubusercontent.com/lovo-hq/matter/${ref}/registry",
|
|
28
|
-
"aliases": { "@/": "src/" }
|
|
29
|
-
"tsx": true
|
|
28
|
+
"aliases": { "@/": "src/" }
|
|
30
29
|
}
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
The `${ref}` placeholder is auto-substituted with the CLI's published version tag (e.g., `v0.1.0`), so you get a stable snapshot. Override with `--
|
|
32
|
+
The `${ref}` placeholder is auto-substituted with the CLI's published version tag (e.g., `v0.1.0`), so you get a stable snapshot. Override with `--reference <tag|branch|sha>` if you want to track `main` or a specific commit.
|
|
34
33
|
|
|
35
34
|
### List available components
|
|
36
35
|
|
|
@@ -58,9 +57,74 @@ npx matter-cli update linear-gradient
|
|
|
58
57
|
npx matter-cli update --force
|
|
59
58
|
```
|
|
60
59
|
|
|
60
|
+
### Render a static fallback image
|
|
61
|
+
|
|
62
|
+
Render a Matter component tree to an image for use as a `<ShaderScene fallback>` — eliminates the visible blank canvas during WebGPU initialization.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx matter-cli poster --source <file> --output <path> [options]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Flag | Default | Description |
|
|
69
|
+
| ------------------ | ---------- | ---------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `--source <file>` | (required) | Path to a `.tsx`/`.ts` file whose chosen export renders the full tree (must include `<ShaderScene>`) |
|
|
71
|
+
| `--output <path>` | (required) | Where to write the image. Extension optional — `--format` decides. Parent directories auto-created. |
|
|
72
|
+
| `--format <format>` | `jpg` | Output format: `png` or `jpg`. Default is `jpg` — best size/quality for most shaders. |
|
|
73
|
+
| `--quality <n>` | `80` | JPEG quality 1–100. Ignored for PNG. |
|
|
74
|
+
| `--export-name <name>` | `default` | Named export to render. |
|
|
75
|
+
| `--capture-delay <seconds>` | `0` | Wait this long after the first non-blank frame before snapshotting. |
|
|
76
|
+
| `--width <px>` | `1280` | Render width. |
|
|
77
|
+
| `--height <px>` | `720` | Render height. |
|
|
78
|
+
|
|
79
|
+
#### Which format should I pick?
|
|
80
|
+
|
|
81
|
+
The default (JPEG q80) handles most shaders well. PNG wins on shaders with large flat-color regions where its lossless palette compression beats JPEG's DCT. As a rule of thumb:
|
|
82
|
+
|
|
83
|
+
| Use PNG (`--format png`) for… | Use the default JPEG for… |
|
|
84
|
+
| --------------------------------- | ------------------------------------------ |
|
|
85
|
+
| `LinearGradient` with hard stops | `Aurora` and similar gradient-heavy scenes |
|
|
86
|
+
| `SimplexNoise` with contour bands | `MeshGradient` (smooth color flow) |
|
|
87
|
+
| Anything with < ~20 unique colors | `FilmGrain` (high-entropy noise) |
|
|
88
|
+
|
|
89
|
+
If unsure, run both — the difference can be 3–7× either direction.
|
|
90
|
+
|
|
91
|
+
**Requires Playwright** as a peer dependency:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm add -D playwright
|
|
95
|
+
pnpm exec playwright install chromium
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Examples:**
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Default — writes ./public/hero.jpg (JPEG q80)
|
|
102
|
+
npx matter-cli poster --from ./src/components/matter/hero.tsx --out ./public/hero
|
|
103
|
+
|
|
104
|
+
# Posterized shader — PNG compresses smaller
|
|
105
|
+
npx matter-cli poster --from ./gradient.tsx --out ./public/gradient --type png
|
|
106
|
+
|
|
107
|
+
# Higher quality JPEG
|
|
108
|
+
npx matter-cli poster --from ./aurora.tsx --out ./public/aurora --quality 92
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Wire it up:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<ShaderScene fallback={<img src="/hero.jpg" alt="" />}>
|
|
115
|
+
<LinearGradient ... />
|
|
116
|
+
</ShaderScene>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Limitations:**
|
|
120
|
+
|
|
121
|
+
- The component you point at must render the entire tree (including `<ShaderScene>`); the CLI doesn't wrap.
|
|
122
|
+
- Components that depend on app-context hooks (`useTheme`, `useRouter`, etc.) won't render in the headless harness. Extract a presentational child.
|
|
123
|
+
- WebP and AVIF are not supported (would require an extra dependency for marginal savings over JPEG).
|
|
124
|
+
|
|
61
125
|
## v1 components
|
|
62
126
|
|
|
63
|
-
`linear-gradient`, `mesh-gradient`, `aurora`, `dot-field`, `noise
|
|
127
|
+
`linear-gradient`, `mesh-gradient`, `aurora`, `dot-field`, `simplex-noise`, `waves`.
|
|
64
128
|
|
|
65
129
|
Each component depends on `@lovo/matter` and `@lovo/matter-react`, which you install separately:
|
|
66
130
|
|
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
fetchComponentSource,
|
|
4
4
|
fetchRegistry,
|
|
5
5
|
resolveRef
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FHRQIHCN.js";
|
|
7
7
|
import {
|
|
8
|
-
readMatterConfig
|
|
9
|
-
|
|
8
|
+
readMatterConfig,
|
|
9
|
+
resolveRegistryUrl
|
|
10
|
+
} from "./chunk-R7OX6KUP.js";
|
|
10
11
|
|
|
11
12
|
// src/commands/add.ts
|
|
12
13
|
import { access, mkdir, writeFile } from "fs/promises";
|
|
@@ -32,34 +33,33 @@ async function runAdd(components, opts, io = { cwd: process.cwd(), log: console.
|
|
|
32
33
|
if (components.length === 0) {
|
|
33
34
|
throw new Error("add: at least one component name is required");
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
-
const baseUrl = opts.registry ?? cfg.registryUrl;
|
|
36
|
+
const matterConfig = await readMatterConfig(io.cwd);
|
|
37
37
|
const ref = resolveRef(opts.ref, opts.cliVersion);
|
|
38
|
-
const registryUrl =
|
|
38
|
+
const registryUrl = resolveRegistryUrl(matterConfig, { registry: opts.registry, ref });
|
|
39
39
|
const registry = await fetchRegistry(registryUrl);
|
|
40
40
|
const resolved = components.map((slug) => resolveComponent(slug, registry, registryUrl));
|
|
41
41
|
if (opts.force !== true) {
|
|
42
|
-
for (const
|
|
43
|
-
const targetPath = join(io.cwd,
|
|
42
|
+
for (const resolvedComponent of resolved) {
|
|
43
|
+
const targetPath = join(io.cwd, matterConfig.componentsDir, resolvedComponent.entry.file);
|
|
44
44
|
if (await fileExists(targetPath)) {
|
|
45
45
|
throw new Error(`${targetPath} already exists. Pass --force to overwrite.`);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
const fetched = await Promise.all(
|
|
50
|
-
resolved.map(async (
|
|
51
|
-
const source = await fetchComponentSource(registryUrl,
|
|
52
|
-
return { ...
|
|
50
|
+
resolved.map(async (resolvedComponent) => {
|
|
51
|
+
const source = await fetchComponentSource(registryUrl, resolvedComponent.entry.file);
|
|
52
|
+
return { ...resolvedComponent, source };
|
|
53
53
|
})
|
|
54
54
|
);
|
|
55
55
|
const allDeps = /* @__PURE__ */ new Set();
|
|
56
|
-
for (const
|
|
57
|
-
const targetPath = join(io.cwd,
|
|
58
|
-
const rewritten = rewriteImports(
|
|
56
|
+
for (const fetchedComponent of fetched) {
|
|
57
|
+
const targetPath = join(io.cwd, matterConfig.componentsDir, fetchedComponent.entry.file);
|
|
58
|
+
const rewritten = rewriteImports(fetchedComponent.source, matterConfig.aliases);
|
|
59
59
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
60
60
|
await writeFile(targetPath, rewritten, "utf-8");
|
|
61
61
|
io.log(`Wrote ${targetPath}`);
|
|
62
|
-
for (const dep of
|
|
62
|
+
for (const dep of fetchedComponent.entry.dependencies) allDeps.add(dep);
|
|
63
63
|
}
|
|
64
64
|
const sortedDeps = [...allDeps].sort();
|
|
65
65
|
io.log("");
|
|
@@ -76,17 +76,18 @@ function resolveComponent(slug, registry, registryUrl) {
|
|
|
76
76
|
}
|
|
77
77
|
return { slug, entry };
|
|
78
78
|
}
|
|
79
|
-
async function fileExists(
|
|
79
|
+
async function fileExists(filePath) {
|
|
80
80
|
try {
|
|
81
|
-
await access(
|
|
81
|
+
await access(filePath);
|
|
82
82
|
return true;
|
|
83
|
-
} catch (
|
|
84
|
-
if (
|
|
85
|
-
|
|
83
|
+
} catch (caughtError) {
|
|
84
|
+
if (caughtError instanceof Error && "code" in caughtError && caughtError.code === "ENOENT")
|
|
85
|
+
return false;
|
|
86
|
+
throw caughtError;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export {
|
|
90
91
|
runAdd
|
|
91
92
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
93
|
+
//# sourceMappingURL=chunk-53DI7V2J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/add.ts","../src/transforms/rewriteImports.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\n\nimport { readMatterConfig, resolveRegistryUrl } from '../config/matterConfig.js';\nimport {\n fetchComponentSource,\n fetchRegistry,\n type Registry,\n type RegistryEntry,\n} from '../registry/fetchRegistry.js';\nimport { resolveRef } from '../registry/ref.js';\nimport { rewriteImports } from '../transforms/rewriteImports.js';\n\nexport interface AddOptions {\n registry?: string;\n ref?: string;\n force?: boolean;\n cliVersion: string;\n}\n\nexport interface AddIO {\n cwd: string;\n log: (line: string) => void;\n}\n\nexport async function runAdd(\n components: string[],\n opts: AddOptions,\n io: AddIO = { cwd: process.cwd(), log: console.log },\n): Promise<void> {\n if (components.length === 0) {\n throw new Error('add: at least one component name is required');\n }\n\n const matterConfig = await readMatterConfig(io.cwd);\n const ref = resolveRef(opts.ref, opts.cliVersion);\n const registryUrl = resolveRegistryUrl(matterConfig, { registry: opts.registry, ref });\n const registry = await fetchRegistry(registryUrl);\n\n const resolved = components.map((slug) => resolveComponent(slug, registry, registryUrl));\n\n if (opts.force !== true) {\n for (const resolvedComponent of resolved) {\n const targetPath = join(io.cwd, matterConfig.componentsDir, resolvedComponent.entry.file);\n\n if (await fileExists(targetPath)) {\n throw new Error(`${targetPath} already exists. Pass --force to overwrite.`);\n }\n }\n }\n\n const fetched = await Promise.all(\n resolved.map(async (resolvedComponent) => {\n const source = await fetchComponentSource(registryUrl, resolvedComponent.entry.file);\n\n return { ...resolvedComponent, source };\n }),\n );\n\n const allDeps = new Set<string>();\n\n for (const fetchedComponent of fetched) {\n const targetPath = join(io.cwd, matterConfig.componentsDir, fetchedComponent.entry.file);\n const rewritten = rewriteImports(fetchedComponent.source, matterConfig.aliases);\n\n await mkdir(dirname(targetPath), { recursive: true });\n await writeFile(targetPath, rewritten, 'utf-8');\n io.log(`Wrote ${targetPath}`);\n for (const dep of fetchedComponent.entry.dependencies) allDeps.add(dep);\n }\n\n const sortedDeps = [...allDeps].sort();\n\n io.log('');\n io.log(`This component requires: ${sortedDeps.join(', ')}`);\n io.log('Install with your package manager, e.g.:');\n io.log(`npm install ${sortedDeps.join(' ')}`);\n}\n\nfunction resolveComponent(\n slug: string,\n registry: Registry,\n registryUrl: string,\n): { slug: string; entry: RegistryEntry } {\n const entry = registry.components[slug];\n\n if (!entry) {\n throw new Error(\n `Component \"${slug}\" not found in registry at ${registryUrl}. Run \\`matter-cli list\\` to see available components.`,\n );\n }\n\n return { slug, entry };\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n\n return true;\n } catch (caughtError) {\n if (caughtError instanceof Error && 'code' in caughtError && caughtError.code === 'ENOENT')\n return false;\n throw caughtError;\n }\n}\n","export function rewriteImports(source: string, aliases: Record<string, string>): string {\n const sortedAliases = Object.entries(aliases).sort(([a], [b]) => b.length - a.length);\n\n if (sortedAliases.length === 0) return source;\n\n const importRe = /(\\bfrom\\s+|\\bimport\\s*\\(\\s*)(['\"])([^'\"]+)\\2/g;\n\n return source.replace(importRe, (full, lead: string, quote: string, spec: string) => {\n for (const [key, value] of sortedAliases) {\n if (spec.startsWith(key)) {\n return `${lead}${quote}${value}${spec.slice(key.length)}${quote}`;\n }\n }\n\n return full;\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
__commonJS,
|
|
34
|
+
__export,
|
|
35
|
+
__toESM
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-D4HDZEJT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -3,30 +3,37 @@
|
|
|
3
3
|
// src/registry/readUrl.ts
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
+
async function readFileUrl(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return await readFile(filePath, "utf-8");
|
|
9
|
+
} catch (caughtError) {
|
|
10
|
+
if (caughtError instanceof Error && "code" in caughtError && caughtError.code === "ENOENT") {
|
|
11
|
+
throw new Error(`File not found: ${filePath}`);
|
|
12
|
+
}
|
|
13
|
+
throw caughtError;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function readHttpUrl(url) {
|
|
17
|
+
let response;
|
|
18
|
+
try {
|
|
19
|
+
response = await fetch(url);
|
|
20
|
+
} catch (caughtError) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Failed to fetch ${url}: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
return response.text();
|
|
29
|
+
}
|
|
6
30
|
async function readUrl(url) {
|
|
7
31
|
const parsed = new URL(url);
|
|
8
32
|
if (parsed.protocol === "file:") {
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
return await readFile(path, "utf-8");
|
|
12
|
-
} catch (err) {
|
|
13
|
-
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
14
|
-
throw new Error(`File not found: ${path}`);
|
|
15
|
-
}
|
|
16
|
-
throw err;
|
|
17
|
-
}
|
|
33
|
+
return readFileUrl(fileURLToPath(parsed));
|
|
18
34
|
}
|
|
19
35
|
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
res = await fetch(url);
|
|
23
|
-
} catch (err) {
|
|
24
|
-
throw new Error(`Failed to fetch ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
25
|
-
}
|
|
26
|
-
if (!res.ok) {
|
|
27
|
-
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
28
|
-
}
|
|
29
|
-
return await res.text();
|
|
36
|
+
return readHttpUrl(url);
|
|
30
37
|
}
|
|
31
38
|
throw new Error(
|
|
32
39
|
`Unsupported protocol: ${parsed.protocol} (only file://, http://, https:// are supported)`
|
|
@@ -44,9 +51,9 @@ async function fetchRegistry(baseUrl) {
|
|
|
44
51
|
let parsed;
|
|
45
52
|
try {
|
|
46
53
|
parsed = JSON.parse(json);
|
|
47
|
-
} catch (
|
|
54
|
+
} catch (caughtError) {
|
|
48
55
|
throw new Error(
|
|
49
|
-
`Registry at ${url} is not valid JSON: ${
|
|
56
|
+
`Registry at ${url} is not valid JSON: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
|
|
50
57
|
);
|
|
51
58
|
}
|
|
52
59
|
if (!looksLikeRegistry(parsed)) {
|
|
@@ -54,9 +61,9 @@ async function fetchRegistry(baseUrl) {
|
|
|
54
61
|
}
|
|
55
62
|
return parsed;
|
|
56
63
|
}
|
|
57
|
-
function looksLikeRegistry(
|
|
58
|
-
if (typeof
|
|
59
|
-
const components =
|
|
64
|
+
function looksLikeRegistry(value) {
|
|
65
|
+
if (typeof value !== "object" || value === null || !("components" in value)) return false;
|
|
66
|
+
const components = value.components;
|
|
60
67
|
return typeof components === "object" && components !== null && !Array.isArray(components);
|
|
61
68
|
}
|
|
62
69
|
async function fetchComponentSource(baseUrl, file) {
|
|
@@ -75,4 +82,4 @@ export {
|
|
|
75
82
|
fetchComponentSource,
|
|
76
83
|
resolveRef
|
|
77
84
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
85
|
+
//# sourceMappingURL=chunk-FHRQIHCN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry/readUrl.ts","../src/registry/fetchRegistry.ts","../src/registry/ref.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\n\nasync function readFileUrl(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, 'utf-8');\n } catch (caughtError) {\n if (caughtError instanceof Error && 'code' in caughtError && caughtError.code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`);\n }\n throw caughtError;\n }\n}\n\nasync function readHttpUrl(url: string): Promise<string> {\n let response: Response;\n\n try {\n response = await fetch(url);\n } catch (caughtError) {\n throw new Error(\n `Failed to fetch ${url}: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n\n return response.text();\n}\n\nexport async function readUrl(url: string): Promise<string> {\n const parsed = new URL(url);\n\n if (parsed.protocol === 'file:') {\n return readFileUrl(fileURLToPath(parsed));\n }\n\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {\n return readHttpUrl(url);\n }\n\n throw new Error(\n `Unsupported protocol: ${parsed.protocol} (only file://, http://, https:// are supported)`,\n );\n}\n","import { readUrl } from './readUrl.js';\n\nexport interface RegistryEntry {\n file: string;\n description?: string;\n dependencies: string[];\n uses_primitives?: string[];\n tier: 1 | 2 | 3;\n}\n\nexport interface Registry {\n version: string;\n components: Record<string, RegistryEntry>;\n}\n\nfunction joinUrl(base: string, file: string): string {\n const trimmed = base.endsWith('/') ? base.slice(0, -1) : base;\n\n return `${trimmed}/${file}`;\n}\n\nexport async function fetchRegistry(baseUrl: string): Promise<Registry> {\n const url = joinUrl(baseUrl, 'registry.json');\n const json = await readUrl(url);\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(json);\n } catch (caughtError) {\n throw new Error(\n `Registry at ${url} is not valid JSON: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`,\n );\n }\n if (!looksLikeRegistry(parsed)) {\n throw new Error(`Registry at ${url} is missing a \"components\" object`);\n }\n\n return parsed;\n}\n\nfunction looksLikeRegistry(value: unknown): value is Registry {\n if (typeof value !== 'object' || value === null || !('components' in value)) return false;\n const components = value.components;\n\n return typeof components === 'object' && components !== null && !Array.isArray(components);\n}\n\n/**\n * Fetch the raw source of a component file referenced by a registry entry.\n */\nexport async function fetchComponentSource(baseUrl: string, file: string): Promise<string> {\n return await readUrl(joinUrl(baseUrl, file));\n}\n","export function resolveRef(ref: string | undefined, cliVersion: string): string {\n if (ref !== undefined && ref !== '') return ref;\n if (cliVersion === '0.0.0') return 'main';\n\n return `v${cliVersion}`;\n}\n"],"mappings":";;;AAAA,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAE9B,eAAe,YAAY,UAAmC;AAC5D,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,SAAS,aAAa;AACpB,QAAI,uBAAuB,SAAS,UAAU,eAAe,YAAY,SAAS,UAAU;AAC1F,YAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,IAC/C;AACA,UAAM;AAAA,EACR;AACF;AAEA,eAAe,YAAY,KAA8B;AACvD,MAAI;AAEJ,MAAI;AACF,eAAW,MAAM,MAAM,GAAG;AAAA,EAC5B,SAAS,aAAa;AACpB,UAAM,IAAI;AAAA,MACR,mBAAmB,GAAG,KAAK,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW,CAAC;AAAA,IACrG;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,mBAAmB,GAAG,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACrF;AAEA,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,QAAQ,KAA8B;AAC1D,QAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,MAAI,OAAO,aAAa,SAAS;AAC/B,WAAO,YAAY,cAAc,MAAM,CAAC;AAAA,EAC1C;AAEA,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,WAAO,YAAY,GAAG;AAAA,EACxB;AAEA,QAAM,IAAI;AAAA,IACR,yBAAyB,OAAO,QAAQ;AAAA,EAC1C;AACF;;;AC/BA,SAAS,QAAQ,MAAc,MAAsB;AACnD,QAAM,UAAU,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AAEzD,SAAO,GAAG,OAAO,IAAI,IAAI;AAC3B;AAEA,eAAsB,cAAc,SAAoC;AACtE,QAAM,MAAM,QAAQ,SAAS,eAAe;AAC5C,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI;AAEJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,SAAS,aAAa;AACpB,UAAM,IAAI;AAAA,MACR,eAAe,GAAG,uBAAuB,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW,CAAC;AAAA,IACnH;AAAA,EACF;AACA,MAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B,UAAM,IAAI,MAAM,eAAe,GAAG,mCAAmC;AAAA,EACvE;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAmC;AAC5D,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,gBAAgB,OAAQ,QAAO;AACpF,QAAM,aAAa,MAAM;AAEzB,SAAO,OAAO,eAAe,YAAY,eAAe,QAAQ,CAAC,MAAM,QAAQ,UAAU;AAC3F;AAKA,eAAsB,qBAAqB,SAAiB,MAA+B;AACzF,SAAO,MAAM,QAAQ,QAAQ,SAAS,IAAI,CAAC;AAC7C;;;ACpDO,SAAS,WAAW,KAAyB,YAA4B;AAC9E,MAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;AAC5C,MAAI,eAAe,QAAS,QAAO;AAEnC,SAAO,IAAI,UAAU;AACvB;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/poster/projectRoot.ts
|
|
4
|
+
import { access } from "fs/promises";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
async function findProjectRoot(fromPath) {
|
|
7
|
+
let dir = dirname(resolve(fromPath));
|
|
8
|
+
for (; ; ) {
|
|
9
|
+
try {
|
|
10
|
+
await access(`${dir}/package.json`);
|
|
11
|
+
return dir;
|
|
12
|
+
} catch {
|
|
13
|
+
const parent = dirname(dir);
|
|
14
|
+
if (parent === dir) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Could not find a package.json walking up from ${fromPath}. Poster needs a project root to resolve dependencies against.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
findProjectRoot
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-Q7CQW6AH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/poster/projectRoot.ts"],"sourcesContent":["import { access } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\n\nexport async function findProjectRoot(fromPath: string): Promise<string> {\n let dir = dirname(resolve(fromPath));\n\n // Guard against infinite loops on root (dirname('/') === '/').\n for (;;) {\n try {\n await access(`${dir}/package.json`);\n\n return dir;\n } catch {\n const parent = dirname(dir);\n\n if (parent === dir) {\n throw new Error(\n `Could not find a package.json walking up from ${fromPath}. Poster needs a project root to resolve dependencies against.`,\n );\n }\n dir = parent;\n }\n }\n}\n"],"mappings":";;;AAAA,SAAS,cAAc;AACvB,SAAS,SAAS,eAAe;AAEjC,eAAsB,gBAAgB,UAAmC;AACvE,MAAI,MAAM,QAAQ,QAAQ,QAAQ,CAAC;AAGnC,aAAS;AACP,QAAI;AACF,YAAM,OAAO,GAAG,GAAG,eAAe;AAElC,aAAO;AAAA,IACT,QAAQ;AACN,YAAM,SAAS,QAAQ,GAAG;AAE1B,UAAI,WAAW,KAAK;AAClB,cAAM,IAAI;AAAA,UACR,iDAAiD,QAAQ;AAAA,QAC3D;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;","names":[]}
|
|
@@ -3,11 +3,44 @@
|
|
|
3
3
|
// src/config/matterConfig.ts
|
|
4
4
|
import { access, readFile, writeFile } from "fs/promises";
|
|
5
5
|
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
// src/config/validate.ts
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null;
|
|
10
|
+
}
|
|
11
|
+
function validateMatterConfig(parsed, path) {
|
|
12
|
+
if (!isRecord(parsed)) {
|
|
13
|
+
throw new Error(`${path}: expected an object`);
|
|
14
|
+
}
|
|
15
|
+
const obj = parsed;
|
|
16
|
+
if (typeof obj.componentsDir !== "string" || obj.componentsDir === "") {
|
|
17
|
+
throw new Error(`${path}: missing or empty "componentsDir" string`);
|
|
18
|
+
}
|
|
19
|
+
if (typeof obj.registryUrl !== "string" || obj.registryUrl === "") {
|
|
20
|
+
throw new Error(`${path}: missing or empty "registryUrl" string`);
|
|
21
|
+
}
|
|
22
|
+
if (!isRecord(obj.aliases)) {
|
|
23
|
+
throw new Error(`${path}: missing "aliases" object`);
|
|
24
|
+
}
|
|
25
|
+
const aliases = {};
|
|
26
|
+
for (const [aliasKey, aliasValue] of Object.entries(obj.aliases)) {
|
|
27
|
+
if (typeof aliasValue !== "string") {
|
|
28
|
+
throw new Error(`${path}: aliases.${aliasKey} must be a string`);
|
|
29
|
+
}
|
|
30
|
+
aliases[aliasKey] = aliasValue;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
componentsDir: obj.componentsDir,
|
|
34
|
+
registryUrl: obj.registryUrl,
|
|
35
|
+
aliases
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/config/matterConfig.ts
|
|
6
40
|
var DEFAULT_MATTER_CONFIG = {
|
|
7
41
|
componentsDir: "src/components/matter",
|
|
8
42
|
registryUrl: "https://raw.githubusercontent.com/lovo-hq/matter/${ref}/registry",
|
|
9
|
-
aliases: { "@/": "src/" }
|
|
10
|
-
tsx: true
|
|
43
|
+
aliases: { "@/": "src/" }
|
|
11
44
|
};
|
|
12
45
|
var CONFIG_FILENAME = "matter.config.json";
|
|
13
46
|
function configPath(projectRoot) {
|
|
@@ -26,70 +59,41 @@ async function readMatterConfig(projectRoot) {
|
|
|
26
59
|
let raw;
|
|
27
60
|
try {
|
|
28
61
|
raw = await readFile(path, "utf-8");
|
|
29
|
-
} catch (
|
|
30
|
-
if (
|
|
62
|
+
} catch (caughtError) {
|
|
63
|
+
if (caughtError instanceof Error && "code" in caughtError && caughtError.code === "ENOENT") {
|
|
31
64
|
throw new Error(
|
|
32
65
|
`matter.config.json not found in ${projectRoot}. Run \`matter-cli init\` first.`
|
|
33
66
|
);
|
|
34
67
|
}
|
|
35
|
-
throw
|
|
68
|
+
throw caughtError;
|
|
36
69
|
}
|
|
37
70
|
let parsed;
|
|
38
71
|
try {
|
|
39
72
|
parsed = JSON.parse(raw);
|
|
40
|
-
} catch (
|
|
73
|
+
} catch (caughtError) {
|
|
41
74
|
throw new Error(
|
|
42
|
-
`${path} is not valid JSON: ${
|
|
75
|
+
`${path} is not valid JSON: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
|
|
43
76
|
);
|
|
44
77
|
}
|
|
45
78
|
return validateMatterConfig(parsed, path);
|
|
46
79
|
}
|
|
47
|
-
|
|
80
|
+
function resolveRegistryUrl(matterConfig, opts) {
|
|
81
|
+
const baseUrl = opts.registry ?? matterConfig.registryUrl;
|
|
82
|
+
return baseUrl.replace("${ref}", opts.ref);
|
|
83
|
+
}
|
|
84
|
+
async function writeMatterConfig(projectRoot, matterConfig) {
|
|
48
85
|
const path = configPath(projectRoot);
|
|
49
|
-
const json = `${JSON.stringify(
|
|
86
|
+
const json = `${JSON.stringify(matterConfig, null, 2)}
|
|
50
87
|
`;
|
|
51
88
|
await writeFile(path, json, "utf-8");
|
|
52
89
|
}
|
|
53
|
-
function isRecord(x) {
|
|
54
|
-
return typeof x === "object" && x !== null;
|
|
55
|
-
}
|
|
56
|
-
function validateMatterConfig(parsed, path) {
|
|
57
|
-
if (!isRecord(parsed)) {
|
|
58
|
-
throw new Error(`${path}: expected an object`);
|
|
59
|
-
}
|
|
60
|
-
const obj = parsed;
|
|
61
|
-
if (typeof obj.componentsDir !== "string" || obj.componentsDir === "") {
|
|
62
|
-
throw new Error(`${path}: missing or empty "componentsDir" string`);
|
|
63
|
-
}
|
|
64
|
-
if (typeof obj.registryUrl !== "string" || obj.registryUrl === "") {
|
|
65
|
-
throw new Error(`${path}: missing or empty "registryUrl" string`);
|
|
66
|
-
}
|
|
67
|
-
if (!isRecord(obj.aliases)) {
|
|
68
|
-
throw new Error(`${path}: missing "aliases" object`);
|
|
69
|
-
}
|
|
70
|
-
if (typeof obj.tsx !== "boolean") {
|
|
71
|
-
throw new Error(`${path}: missing "tsx" boolean`);
|
|
72
|
-
}
|
|
73
|
-
const aliases = {};
|
|
74
|
-
for (const [k, v] of Object.entries(obj.aliases)) {
|
|
75
|
-
if (typeof v !== "string") {
|
|
76
|
-
throw new Error(`${path}: aliases.${k} must be a string`);
|
|
77
|
-
}
|
|
78
|
-
aliases[k] = v;
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
componentsDir: obj.componentsDir,
|
|
82
|
-
registryUrl: obj.registryUrl,
|
|
83
|
-
aliases,
|
|
84
|
-
tsx: obj.tsx
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
90
|
|
|
88
91
|
export {
|
|
89
92
|
DEFAULT_MATTER_CONFIG,
|
|
90
93
|
configPath,
|
|
91
94
|
configExists,
|
|
92
95
|
readMatterConfig,
|
|
96
|
+
resolveRegistryUrl,
|
|
93
97
|
writeMatterConfig
|
|
94
98
|
};
|
|
95
|
-
//# sourceMappingURL=chunk-
|
|
99
|
+
//# sourceMappingURL=chunk-R7OX6KUP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/matterConfig.ts","../src/config/validate.ts"],"sourcesContent":["import { access, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { validateMatterConfig } from './validate.js';\n\nexport interface MatterConfig {\n componentsDir: string;\n registryUrl: string;\n aliases: Record<string, string>;\n}\n\nexport const DEFAULT_MATTER_CONFIG: MatterConfig = {\n componentsDir: 'src/components/matter',\n registryUrl: 'https://raw.githubusercontent.com/lovo-hq/matter/${ref}/registry',\n aliases: { '@/': 'src/' },\n};\n\nconst CONFIG_FILENAME = 'matter.config.json';\n\nexport function configPath(projectRoot: string): string {\n return join(projectRoot, CONFIG_FILENAME);\n}\n\nexport async function configExists(projectRoot: string): Promise<boolean> {\n try {\n await access(configPath(projectRoot));\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readMatterConfig(projectRoot: string): Promise<MatterConfig> {\n const path = configPath(projectRoot);\n let raw: string;\n\n try {\n raw = await readFile(path, 'utf-8');\n } catch (caughtError) {\n if (caughtError instanceof Error && 'code' in caughtError && caughtError.code === 'ENOENT') {\n throw new Error(\n `matter.config.json not found in ${projectRoot}. Run \\`matter-cli init\\` first.`,\n );\n }\n throw caughtError;\n }\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (caughtError) {\n throw new Error(\n `${path} is not valid JSON: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`,\n );\n }\n\n return validateMatterConfig(parsed, path);\n}\n\nexport function resolveRegistryUrl(\n matterConfig: MatterConfig,\n opts: { registry?: string; ref: string },\n): string {\n const baseUrl = opts.registry ?? matterConfig.registryUrl;\n\n return baseUrl.replace('${ref}', opts.ref);\n}\n\nexport async function writeMatterConfig(\n projectRoot: string,\n matterConfig: MatterConfig,\n): Promise<void> {\n const path = configPath(projectRoot);\n const json = `${JSON.stringify(matterConfig, null, 2)}\\n`;\n\n await writeFile(path, json, 'utf-8');\n}\n","import type { MatterConfig } from './matterConfig.js';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function validateMatterConfig(parsed: unknown, path: string): MatterConfig {\n if (!isRecord(parsed)) {\n throw new Error(`${path}: expected an object`);\n }\n const obj = parsed;\n\n if (typeof obj.componentsDir !== 'string' || obj.componentsDir === '') {\n throw new Error(`${path}: missing or empty \"componentsDir\" string`);\n }\n if (typeof obj.registryUrl !== 'string' || obj.registryUrl === '') {\n throw new Error(`${path}: missing or empty \"registryUrl\" string`);\n }\n if (!isRecord(obj.aliases)) {\n throw new Error(`${path}: missing \"aliases\" object`);\n }\n const aliases: Record<string, string> = {};\n\n for (const [aliasKey, aliasValue] of Object.entries(obj.aliases)) {\n if (typeof aliasValue !== 'string') {\n throw new Error(`${path}: aliases.${aliasKey} must be a string`);\n }\n aliases[aliasKey] = aliasValue;\n }\n\n return {\n componentsDir: obj.componentsDir,\n registryUrl: obj.registryUrl,\n aliases,\n };\n}\n"],"mappings":";;;AAAA,SAAS,QAAQ,UAAU,iBAAiB;AAC5C,SAAS,YAAY;;;ACCrB,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEO,SAAS,qBAAqB,QAAiB,MAA4B;AAChF,MAAI,CAAC,SAAS,MAAM,GAAG;AACrB,UAAM,IAAI,MAAM,GAAG,IAAI,sBAAsB;AAAA,EAC/C;AACA,QAAM,MAAM;AAEZ,MAAI,OAAO,IAAI,kBAAkB,YAAY,IAAI,kBAAkB,IAAI;AACrE,UAAM,IAAI,MAAM,GAAG,IAAI,2CAA2C;AAAA,EACpE;AACA,MAAI,OAAO,IAAI,gBAAgB,YAAY,IAAI,gBAAgB,IAAI;AACjE,UAAM,IAAI,MAAM,GAAG,IAAI,yCAAyC;AAAA,EAClE;AACA,MAAI,CAAC,SAAS,IAAI,OAAO,GAAG;AAC1B,UAAM,IAAI,MAAM,GAAG,IAAI,4BAA4B;AAAA,EACrD;AACA,QAAM,UAAkC,CAAC;AAEzC,aAAW,CAAC,UAAU,UAAU,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AAChE,QAAI,OAAO,eAAe,UAAU;AAClC,YAAM,IAAI,MAAM,GAAG,IAAI,aAAa,QAAQ,mBAAmB;AAAA,IACjE;AACA,YAAQ,QAAQ,IAAI;AAAA,EACtB;AAEA,SAAO;AAAA,IACL,eAAe,IAAI;AAAA,IACnB,aAAa,IAAI;AAAA,IACjB;AAAA,EACF;AACF;;;ADxBO,IAAM,wBAAsC;AAAA,EACjD,eAAe;AAAA,EACf,aAAa;AAAA,EACb,SAAS,EAAE,MAAM,OAAO;AAC1B;AAEA,IAAM,kBAAkB;AAEjB,SAAS,WAAW,aAA6B;AACtD,SAAO,KAAK,aAAa,eAAe;AAC1C;AAEA,eAAsB,aAAa,aAAuC;AACxE,MAAI;AACF,UAAM,OAAO,WAAW,WAAW,CAAC;AAEpC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,iBAAiB,aAA4C;AACjF,QAAM,OAAO,WAAW,WAAW;AACnC,MAAI;AAEJ,MAAI;AACF,UAAM,MAAM,SAAS,MAAM,OAAO;AAAA,EACpC,SAAS,aAAa;AACpB,QAAI,uBAAuB,SAAS,UAAU,eAAe,YAAY,SAAS,UAAU;AAC1F,YAAM,IAAI;AAAA,QACR,mCAAmC,WAAW;AAAA,MAChD;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACA,MAAI;AAEJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,aAAa;AACpB,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,uBAAuB,uBAAuB,QAAQ,YAAY,UAAU,OAAO,WAAW,CAAC;AAAA,IACxG;AAAA,EACF;AAEA,SAAO,qBAAqB,QAAQ,IAAI;AAC1C;AAEO,SAAS,mBACd,cACA,MACQ;AACR,QAAM,UAAU,KAAK,YAAY,aAAa;AAE9C,SAAO,QAAQ,QAAQ,UAAU,KAAK,GAAG;AAC3C;AAEA,eAAsB,kBACpB,aACA,cACe;AACf,QAAM,OAAO,WAAW,WAAW;AACnC,QAAM,OAAO,GAAG,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AAAA;AAErD,QAAM,UAAU,MAAM,MAAM,OAAO;AACrC;","names":[]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/poster/playwright.ts
|
|
4
|
+
import { access } from "fs/promises";
|
|
5
|
+
import { writeFile } from "fs/promises";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { pathToFileURL } from "url";
|
|
8
|
+
async function findPlaywrightDir(startDir) {
|
|
9
|
+
let dir = startDir;
|
|
10
|
+
for (; ; ) {
|
|
11
|
+
const candidate = join(dir, "node_modules", "playwright");
|
|
12
|
+
try {
|
|
13
|
+
await access(join(candidate, "package.json"));
|
|
14
|
+
return candidate;
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir) return null;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null;
|
|
24
|
+
}
|
|
25
|
+
function isPlaywrightNamespace(value) {
|
|
26
|
+
if (!isRecord(value)) return false;
|
|
27
|
+
const chromium = value.chromium;
|
|
28
|
+
if (!isRecord(chromium)) return false;
|
|
29
|
+
return typeof chromium.launch === "function";
|
|
30
|
+
}
|
|
31
|
+
function getDefaultExport(value) {
|
|
32
|
+
if (!isRecord(value)) return void 0;
|
|
33
|
+
return value.default;
|
|
34
|
+
}
|
|
35
|
+
async function resolvePlaywright(projectRoot) {
|
|
36
|
+
const playwrightDir = await findPlaywrightDir(projectRoot);
|
|
37
|
+
if (playwrightDir === null) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Install playwright to use this command: pnpm add -D playwright && pnpm exec playwright install chromium`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
for (const entryFilename of ["index.mjs", "index.js"]) {
|
|
43
|
+
const filePath = join(playwrightDir, entryFilename);
|
|
44
|
+
try {
|
|
45
|
+
await access(filePath);
|
|
46
|
+
const rawModule = await import(pathToFileURL(filePath).href);
|
|
47
|
+
const playwrightNamespace = isPlaywrightNamespace(rawModule) ? rawModule : getDefaultExport(rawModule);
|
|
48
|
+
if (!isPlaywrightNamespace(playwrightNamespace)) {
|
|
49
|
+
throw new Error(`Resolved ${filePath} but it does not expose chromium.launch`);
|
|
50
|
+
}
|
|
51
|
+
return playwrightNamespace;
|
|
52
|
+
} catch (caughtError) {
|
|
53
|
+
if (entryFilename === "index.js") throw caughtError;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Unable to import playwright from ${playwrightDir}`);
|
|
57
|
+
}
|
|
58
|
+
async function launchAndScreenshot(opts) {
|
|
59
|
+
const playwright = await resolvePlaywright(opts.projectRoot);
|
|
60
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
61
|
+
try {
|
|
62
|
+
const browserContext = await browser.newContext({
|
|
63
|
+
viewport: { width: opts.width, height: opts.height },
|
|
64
|
+
deviceScaleFactor: 1
|
|
65
|
+
});
|
|
66
|
+
const page = await browserContext.newPage();
|
|
67
|
+
const consoleErrors = [];
|
|
68
|
+
page.on("pageerror", (pageError) => consoleErrors.push(`pageerror: ${pageError.message}`));
|
|
69
|
+
page.on("console", (msg) => {
|
|
70
|
+
if (msg.type() === "error") consoleErrors.push(`console: ${msg.text()}`);
|
|
71
|
+
});
|
|
72
|
+
await page.goto(opts.url, { waitUntil: "load" });
|
|
73
|
+
try {
|
|
74
|
+
await page.waitForFunction(() => Reflect.get(globalThis, "__matterReady") === true, {
|
|
75
|
+
timeout: opts.readyTimeoutMs
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
if (consoleErrors.length > 0) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Poster render failed before producing a frame:
|
|
81
|
+
${consoleErrors.join("\n ")}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
throw new Error(
|
|
85
|
+
`no canvas content detected within ${opts.readyTimeoutMs / 1e3}s; does your component render a ShaderScene with a visible base layer?`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (opts.timeSeconds > 0) {
|
|
89
|
+
await page.waitForTimeout(opts.timeSeconds * 1e3);
|
|
90
|
+
}
|
|
91
|
+
const canvas = page.locator("canvas").first();
|
|
92
|
+
const imageBuffer = opts.format === "jpeg" ? await canvas.screenshot({ type: "jpeg", quality: opts.quality }) : await canvas.screenshot({ type: "png" });
|
|
93
|
+
await writeFile(opts.outPath, imageBuffer);
|
|
94
|
+
return { bytes: imageBuffer.length };
|
|
95
|
+
} finally {
|
|
96
|
+
await browser.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
resolvePlaywright,
|
|
102
|
+
launchAndScreenshot
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=chunk-RRL35RGP.js.map
|