mechanica 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/browser.js +2 -0
- package/dist/chunks/browser-BV2iB_xD.js +810 -0
- package/dist/cli.js +353 -460
- package/dist/types/cli/args.d.ts +12 -1
- package/dist/types/cli/browser.d.ts +130 -0
- package/dist/types/cli/dev-server.d.ts +13 -0
- package/dist/types/cli/headless.d.ts +69 -18
- package/dist/types/cli/section-query.d.ts +49 -0
- package/dist/types/cli/shot.d.ts +110 -8
- package/package.json +6 -2
package/dist/types/cli/args.d.ts
CHANGED
|
@@ -2,7 +2,18 @@ export interface ParsedArgs {
|
|
|
2
2
|
command?: string;
|
|
3
3
|
/** Positional arguments after the command (excluding `--flag` values). */
|
|
4
4
|
args: string[];
|
|
5
|
+
/** Flag values; a flag given several times keeps its last value (see {@link flagValues}). */
|
|
5
6
|
flags: Record<string, string | boolean>;
|
|
6
7
|
}
|
|
8
|
+
export interface ParseOptions {
|
|
9
|
+
/** Flags that never take a value, so `--full hero` keeps `hero` positional. */
|
|
10
|
+
booleans?: string[];
|
|
11
|
+
}
|
|
7
12
|
/** Parse `argv` (without node/script) into a command, positionals and a `--flag[=value]` map. */
|
|
8
|
-
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
13
|
+
export declare function parseArgs(argv: string[], options?: ParseOptions): ParsedArgs;
|
|
14
|
+
/**
|
|
15
|
+
* Every value a repeatable flag was given, in order — `--section a --section b`
|
|
16
|
+
* → `['a', 'b']`; a bare `--section` contributes `true`. Takes the same `argv`
|
|
17
|
+
* as {@link parseArgs} (command first).
|
|
18
|
+
*/
|
|
19
|
+
export declare function flagValues(argv: string[], name: string, options?: ParseOptions): Array<string | boolean>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mechanica/browser` — the headless browser behind `mechanica shot` and
|
|
3
|
+
* `mechanica thumbs`, for a site's own scripts: open a page (a dev-server URL,
|
|
4
|
+
* a design handoff's `file://` URL, any site), resize it, scroll it, measure it
|
|
5
|
+
* with `evaluate`, and screenshot it or one section of it.
|
|
6
|
+
*
|
|
7
|
+
* Drives a Chrome/Edge already installed on the machine over raw CDP
|
|
8
|
+
* (`headless.ts`), so there is nothing to download. Needs a global `WebSocket`
|
|
9
|
+
* (Node 22+ or Bun) and never imports Vite.
|
|
10
|
+
*/
|
|
11
|
+
import { type Clip } from './headless';
|
|
12
|
+
export { findDevServer, MAX_SHOT_HEIGHT, type Clip } from './headless';
|
|
13
|
+
/** Readiness of an ordinary page: the load event has fired. */
|
|
14
|
+
export declare const PAGE_LOADED = "document.readyState === 'complete'";
|
|
15
|
+
export interface GotoOptions {
|
|
16
|
+
/** JS expression polled until it is `true`. Default: {@link PAGE_LOADED}. */
|
|
17
|
+
ready?: string;
|
|
18
|
+
/** Then wait for web fonts and images (see {@link BrowserPage.settle}). Default `true`. */
|
|
19
|
+
settle?: boolean;
|
|
20
|
+
/** How long `ready` may take, in ms. Default 15 000. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface OpenPageOptions extends GotoOptions {
|
|
24
|
+
/** Viewport width in CSS px. Default 1440. */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** Viewport height in CSS px. Default 900. */
|
|
27
|
+
height?: number;
|
|
28
|
+
/**
|
|
29
|
+
* A Chromium-based executable. Default: the first installed browser that
|
|
30
|
+
* starts, from the usual install paths (on Windows Chrome, then Edge).
|
|
31
|
+
*/
|
|
32
|
+
browser?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Emulate `prefers-reduced-motion: reduce`, as `mechanica shot` does: pages
|
|
35
|
+
* that honor it skip their entrance animations. Default `true`.
|
|
36
|
+
*/
|
|
37
|
+
reducedMotion?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ScreenshotOptions {
|
|
40
|
+
/** An explicit region in document coordinates. Wins over everything else. */
|
|
41
|
+
clip?: Clip;
|
|
42
|
+
/** A CSS selector: the bounding box of the first matching element (see {@link BrowserPage.sectionClip}). */
|
|
43
|
+
section?: string;
|
|
44
|
+
/** The whole document (height capped at {@link MAX_SHOT_HEIGHT}). */
|
|
45
|
+
full?: boolean;
|
|
46
|
+
/** Output scale: `0.5` writes a half-size PNG. Default 1. */
|
|
47
|
+
scale?: number;
|
|
48
|
+
}
|
|
49
|
+
/** `goto` could not open the URL at all (DNS, refused connection, missing file…). */
|
|
50
|
+
export declare class NavigationError extends Error {
|
|
51
|
+
readonly url: string;
|
|
52
|
+
readonly reason: string;
|
|
53
|
+
constructor(url: string, reason: string);
|
|
54
|
+
}
|
|
55
|
+
/** One headless tab in its own browser process. */
|
|
56
|
+
export interface BrowserPage {
|
|
57
|
+
/** Console errors/warnings and uncaught exceptions, as `[console.error] …` / `[pageerror] …` lines. */
|
|
58
|
+
readonly messages: string[];
|
|
59
|
+
/** Navigate, then wait for `ready` and (by default) fonts + images. */
|
|
60
|
+
goto(url: string, options?: GotoOptions): Promise<void>;
|
|
61
|
+
/** Evaluate an expression in the page (promises are awaited) and return its JSON value. Throws on a page exception. */
|
|
62
|
+
evaluate<T = unknown>(expression: string): Promise<T>;
|
|
63
|
+
/** Poll `expression` until it is `true`. */
|
|
64
|
+
waitFor(expression: string, timeout?: number): Promise<void>;
|
|
65
|
+
/** Resize the viewport (height defaults to the current one). */
|
|
66
|
+
setViewport(width: number, height?: number): Promise<void>;
|
|
67
|
+
/** Scroll the window to `y` instantly (ignoring `scroll-behavior: smooth`), wait two frames, return the reached `scrollY`. */
|
|
68
|
+
scrollTo(y: number): Promise<number>;
|
|
69
|
+
/** Wait two animation frames: one full style → layout → paint cycle. */
|
|
70
|
+
nextFrame(): Promise<void>;
|
|
71
|
+
/** Wait for web fonts and images; lazy images are switched to eager. Gives up after 10 s. */
|
|
72
|
+
settle(): Promise<void>;
|
|
73
|
+
/** Turn off CSS animations and transitions (running transitions jump to their end). Idempotent. */
|
|
74
|
+
freezeAnimations(): Promise<void>;
|
|
75
|
+
/** Undo {@link freezeAnimations}. */
|
|
76
|
+
unfreezeAnimations(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* The document-coordinate box of the first element matching `selector`.
|
|
79
|
+
* Looks in the document, then in every open shadow root (`host >>> inner`
|
|
80
|
+
* crosses into `host`'s shadow root explicitly). A bare `#name` or `name`
|
|
81
|
+
* that matches nothing falls back to `[data-block-id="name"]`, a Mechanica
|
|
82
|
+
* block. Throws when nothing matches or the box is empty.
|
|
83
|
+
*/
|
|
84
|
+
sectionClip(selector: string): Promise<Clip>;
|
|
85
|
+
/**
|
|
86
|
+
* A PNG of the page: `clip`, else `section`, else `full`, else what is in
|
|
87
|
+
* the viewport right now (at the current scroll position).
|
|
88
|
+
*/
|
|
89
|
+
screenshot(options?: ScreenshotOptions): Promise<Buffer>;
|
|
90
|
+
/** A raw CDP command on this tab's session, for anything not covered above. */
|
|
91
|
+
send(method: string, params?: object): Promise<any>;
|
|
92
|
+
/** Close the browser and remove its temporary profile. */
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Launch a headless browser with one tab, sized and media-emulated like
|
|
97
|
+
* `mechanica shot`, and open `url` in it (omit `url` for a blank tab to
|
|
98
|
+
* `goto` later). Always `close()` the page — it owns the browser process.
|
|
99
|
+
*
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { openPage } from 'mechanica/browser'
|
|
102
|
+
*
|
|
103
|
+
* const page = await openPage('file:///…/design/index.html', { width: 390 })
|
|
104
|
+
* try {
|
|
105
|
+
* const size = await page.evaluate<string>(`getComputedStyle(document.querySelector('h1')).fontSize`)
|
|
106
|
+
* fs.writeFileSync('hero.png', await page.screenshot({ section: '#hero' }))
|
|
107
|
+
* } finally {
|
|
108
|
+
* await page.close()
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function openPage(url?: string, options?: OpenPageOptions): Promise<BrowserPage>;
|
|
113
|
+
type SectionBox = Clip | {
|
|
114
|
+
error: 'invalid';
|
|
115
|
+
message: string;
|
|
116
|
+
} | {
|
|
117
|
+
error: 'missing';
|
|
118
|
+
/** Readable ids on the page (`#name`), shadow roots included. */
|
|
119
|
+
ids: string[];
|
|
120
|
+
/** `data-block-id` values on the page. */
|
|
121
|
+
blockIds?: string[];
|
|
122
|
+
/** The block-id selector that was tried as well. */
|
|
123
|
+
fallback?: string | null;
|
|
124
|
+
} | {
|
|
125
|
+
error: 'empty';
|
|
126
|
+
width: number;
|
|
127
|
+
height: number;
|
|
128
|
+
};
|
|
129
|
+
/** The message for a `sectionClip` that found nothing usable. */
|
|
130
|
+
export declare function describeSectionError(selector: string, box: Exclude<SectionBox, Clip>): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ResolvedServer {
|
|
2
|
+
origin: string;
|
|
3
|
+
close: () => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Find a dev server to render against: an explicit origin, a running local
|
|
7
|
+
* Mechanica dev server, or an ephemeral one booted from the project's
|
|
8
|
+
* `vite.config.ts` and torn down afterwards.
|
|
9
|
+
*
|
|
10
|
+
* The only browser-side helper that needs Vite, kept out of `headless.ts` so
|
|
11
|
+
* the public `mechanica/browser` entry never pulls it in.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveServer(flag?: string): Promise<ResolvedServer>;
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Find a dev server to render against: an explicit origin, a running local
|
|
7
|
-
* Mechanica dev server, or an ephemeral one booted from the project's
|
|
8
|
-
* `vite.config.ts` and torn down afterwards.
|
|
9
|
-
*/
|
|
10
|
-
export declare function resolveServer(flag?: string): Promise<ResolvedServer>;
|
|
1
|
+
/** Where `mechanica shot`/`thumbs` look for a running dev server first. */
|
|
2
|
+
export declare const DEV_SERVER_ORIGINS: string[];
|
|
3
|
+
/** The origin of a running Mechanica dev server on the default port, or null. */
|
|
4
|
+
export declare function findDevServer(origins?: string[]): Promise<string | null>;
|
|
11
5
|
export declare function setViewport(cdp: CdpClient, sessionId: string, width: number, height?: number): Promise<void>;
|
|
12
6
|
export declare function evaluateValue<T>(cdp: CdpClient, sessionId: string, expression: string): Promise<T>;
|
|
13
7
|
/**
|
|
@@ -16,29 +10,86 @@ export declare function evaluateValue<T>(cdp: CdpClient, sessionId: string, expr
|
|
|
16
10
|
* ready yet" rather than failing the wait.
|
|
17
11
|
*/
|
|
18
12
|
export declare function waitForCondition(cdp: CdpClient, sessionId: string, expression: string, timeout: number): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
export declare
|
|
21
|
-
/**
|
|
13
|
+
/** Upper bound for {@link settlePageMedia}: a stalled image must not hang a shot. */
|
|
14
|
+
export declare const SETTLE_TIMEOUT = 10000;
|
|
15
|
+
/**
|
|
16
|
+
* Wait for web fonts and all images — normal pages have no injected ready flag.
|
|
17
|
+
* Lazy images are switched to eager first: below the fold they would never
|
|
18
|
+
* start loading in a headless tab (the wait used to hang on them), and a
|
|
19
|
+
* full-page capture should show them anyway. Gives up after `timeout` ms.
|
|
20
|
+
*/
|
|
21
|
+
export declare function settlePageMedia(cdp: CdpClient, sessionId: string, timeout?: number): Promise<void>;
|
|
22
|
+
/** Freeze animations/transitions so captures are deterministic across runs. Idempotent. */
|
|
22
23
|
export declare function freezeAnimations(cdp: CdpClient, sessionId: string): Promise<void>;
|
|
24
|
+
/** Undo {@link freezeAnimations}: transitions and animations run again. */
|
|
25
|
+
export declare function unfreezeAnimations(cdp: CdpClient, sessionId: string): Promise<void>;
|
|
26
|
+
/** Wait two animation frames: a full style → layout → paint cycle has run. */
|
|
27
|
+
export declare function nextFrames(cdp: CdpClient, sessionId: string): Promise<void>;
|
|
23
28
|
/** Chromium refuses to encode arbitrarily tall captures — cap and say so. */
|
|
24
29
|
export declare const MAX_SHOT_HEIGHT = 12000;
|
|
25
|
-
/**
|
|
26
|
-
export
|
|
30
|
+
/** A screenshot region in CSS pixels, relative to the document (not the viewport). */
|
|
31
|
+
export interface Clip {
|
|
27
32
|
x: number;
|
|
28
33
|
y: number;
|
|
29
34
|
width: number;
|
|
30
35
|
height: number;
|
|
31
|
-
}
|
|
36
|
+
}
|
|
37
|
+
/** The full document size as a screenshot clip. */
|
|
38
|
+
export declare function fullPageClip(cdp: CdpClient, sessionId: string): Promise<Clip>;
|
|
39
|
+
/** What is on screen right now (the visual viewport at its scroll offset), as a clip. */
|
|
40
|
+
export declare function viewportClip(cdp: CdpClient, sessionId: string): Promise<Clip>;
|
|
32
41
|
export interface LaunchedBrowser {
|
|
33
42
|
client: CdpClient;
|
|
34
43
|
close: () => Promise<void>;
|
|
35
44
|
}
|
|
45
|
+
/** A Chromium-based browser executable to try. */
|
|
46
|
+
export interface BrowserExecutable {
|
|
47
|
+
label: string;
|
|
48
|
+
path: string;
|
|
49
|
+
}
|
|
36
50
|
/**
|
|
37
51
|
* Spawn a headless Chromium-based browser with a DevTools endpoint and connect
|
|
38
|
-
* to it.
|
|
39
|
-
*
|
|
52
|
+
* to it. With `explicitPath` (`--browser`) only that executable is tried.
|
|
53
|
+
* Otherwise the installed browsers of {@link platformBrowserCandidates} are
|
|
54
|
+
* tried in order until one starts — a browser already on the machine, so
|
|
55
|
+
* nothing needs downloading — and the error names each one that failed.
|
|
40
56
|
*/
|
|
41
57
|
export declare function launchBrowser(explicitPath?: string): Promise<LaunchedBrowser>;
|
|
58
|
+
/**
|
|
59
|
+
* Try `start` on each candidate in order and return the first browser that
|
|
60
|
+
* comes up. When none does, the error lists every candidate and why it failed.
|
|
61
|
+
*/
|
|
62
|
+
export declare function startFirstBrowser<T>(candidates: BrowserExecutable[], start: (candidate: BrowserExecutable) => Promise<T>): Promise<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Spawn one browser on a fresh temporary profile and connect to it. When it
|
|
65
|
+
* does not come up (exits early, never prints its endpoint, refuses the
|
|
66
|
+
* connection), everything it started is ended and the profile removed before
|
|
67
|
+
* the error is rethrown.
|
|
68
|
+
*/
|
|
69
|
+
export declare function startBrowser(executable: BrowserExecutable): Promise<LaunchedBrowser>;
|
|
70
|
+
/**
|
|
71
|
+
* The environment a browser starts with. On Windows it leaves out
|
|
72
|
+
* `__COMPAT_LAYER` (Git Bash and some tool runners set it to `RunAsInvoker`):
|
|
73
|
+
* under a compatibility layer Edge relaunches itself without it and exits at
|
|
74
|
+
* once, so the process we spawned never prints its endpoint while the
|
|
75
|
+
* relaunched one keeps running.
|
|
76
|
+
*/
|
|
77
|
+
export declare function browserEnv(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): NodeJS.ProcessEnv;
|
|
78
|
+
/**
|
|
79
|
+
* The Windows commands that end a browser which failed to start, best effort:
|
|
80
|
+
* `taskkill /T /F` on its process tree while the spawned process still runs
|
|
81
|
+
* (`pid`), then — because a browser that relaunched itself has outlived the
|
|
82
|
+
* process we spawned — every process still running on its temporary profile,
|
|
83
|
+
* found by the profile's folder name in the command line.
|
|
84
|
+
*/
|
|
85
|
+
export declare function windowsKillCommands(pid: number | undefined, userDataDir: string): Array<[string, string[]]>;
|
|
86
|
+
/**
|
|
87
|
+
* Known install locations of Chromium-based browsers, in the order they are
|
|
88
|
+
* tried. On Windows Chrome comes first: Edge ships with Windows, so it is the
|
|
89
|
+
* fallback, and it relaunches itself under a compatibility layer (see
|
|
90
|
+
* {@link browserEnv}).
|
|
91
|
+
*/
|
|
92
|
+
export declare function platformBrowserCandidates(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, home?: string): BrowserExecutable[];
|
|
42
93
|
type CdpEventListener = (method: string, params: any, sessionId?: string) => void;
|
|
43
94
|
/**
|
|
44
95
|
* A minimal Chrome DevTools Protocol client over the platform `WebSocket`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding the element behind a section selector (`mechanica shot --section`,
|
|
3
|
+
* `BrowserPage.sectionClip`): the lookup that runs in the page, and the pure
|
|
4
|
+
* helpers that prepare its arguments.
|
|
5
|
+
*
|
|
6
|
+
* - **Shadow DOM.** Design handoffs render their sections inside custom
|
|
7
|
+
* elements with open shadow roots, where `document.querySelector` sees
|
|
8
|
+
* nothing. The lookup tries the document first, then every open shadow root
|
|
9
|
+
* in tree order (depth first), matching the selector within each root.
|
|
10
|
+
* Slotted content belongs to its host's tree, so that tree's query covers
|
|
11
|
+
* it. `host >>> inner` crosses into the shadow root of `host` explicitly.
|
|
12
|
+
* - **Block ids.** Mechanica renders a block with `data-block-id="<node id>"`
|
|
13
|
+
* and no `id`, so a bare `#hero` (or `hero`) that matches nothing falls back
|
|
14
|
+
* to `[data-block-id="hero"]`.
|
|
15
|
+
*/
|
|
16
|
+
/** Crosses into the shadow root of the elements matched by the part before it. */
|
|
17
|
+
export declare const SHADOW_COMBINATOR = ">>>";
|
|
18
|
+
/**
|
|
19
|
+
* Split a selector at `>>>` into trimmed parts (one part when there is none).
|
|
20
|
+
* A `>>>` inside quotes, brackets or parentheses, or after a backslash, is
|
|
21
|
+
* part of the selector. Throws when a part is empty (`>>> .a`, `.a >>>`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function splitShadowSelector(selector: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* The block-id selector to try when a bare `#name` or `name` matches nothing:
|
|
26
|
+
* `[data-block-id="name"]`. Null for any other selector. A name that is not a
|
|
27
|
+
* valid CSS id (`#1hero`) still gets one, so block ids starting with a digit
|
|
28
|
+
* are reachable too.
|
|
29
|
+
*/
|
|
30
|
+
export declare function blockIdFallback(selector: string): string | null;
|
|
31
|
+
/** Readable ids and block ids listed in a "no match" error, at most this many of each. */
|
|
32
|
+
export declare const MAX_LISTED_IDS = 20;
|
|
33
|
+
/**
|
|
34
|
+
* In-page source of `(parts, fallback) => result`, where `parts` is a selector
|
|
35
|
+
* split by {@link splitShadowSelector} and `fallback` is
|
|
36
|
+
* {@link blockIdFallback}'s result (or null). It finds the first element
|
|
37
|
+
* matching `parts`, else the first matching `fallback`, in the document and
|
|
38
|
+
* in every open shadow root, and returns:
|
|
39
|
+
*
|
|
40
|
+
* - `{ element }`;
|
|
41
|
+
* - `{ error: 'invalid', message }` when the selector does not parse (and the
|
|
42
|
+
* fallback finds nothing);
|
|
43
|
+
* - `{ error: 'missing', ids, blockIds, fallback }`: the readable ids
|
|
44
|
+
* (`#name`) and the `data-block-id` values on the page, shadow roots
|
|
45
|
+
* included, for the error message.
|
|
46
|
+
*
|
|
47
|
+
* Plain JS with no outside references: it is sent to the page as text.
|
|
48
|
+
*/
|
|
49
|
+
export declare const FIND_SECTION_SOURCE: string;
|
package/dist/types/cli/shot.d.ts
CHANGED
|
@@ -11,26 +11,128 @@ export interface ShotOptions {
|
|
|
11
11
|
server?: string;
|
|
12
12
|
/** Explicit Chromium-based browser executable path. */
|
|
13
13
|
browser?: string;
|
|
14
|
-
/** Screenshot the full page instead of clipping to the block element. */
|
|
14
|
+
/** Screenshot the full page instead of clipping to the block element (or to the viewport with `scroll`). */
|
|
15
15
|
full?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* CSS selectors (`--section`, repeatable): one PNG each, clipped to the first
|
|
18
|
+
* match — in the document or an open shadow root (`host >>> inner` crosses
|
|
19
|
+
* one explicitly); a bare `#name` that matches nothing finds
|
|
20
|
+
* `[data-block-id="name"]`. Page/URL/file shots only.
|
|
21
|
+
*/
|
|
22
|
+
section?: string[];
|
|
23
|
+
/** Scroll offset(s) in px, comma-separated: capture the viewport scrolled there. */
|
|
24
|
+
scroll?: string;
|
|
25
|
+
/** Leave CSS animations and transitions running (`--no-freeze`). */
|
|
26
|
+
noFreeze?: boolean;
|
|
16
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* What `mechanica shot` renders: a block through the preview route, a page of
|
|
30
|
+
* this project on the dev server, or a foreign document — any `http(s)://` URL
|
|
31
|
+
* or a local file (a design handoff's `index.html`) — opened as is.
|
|
32
|
+
*/
|
|
33
|
+
export type ShotTarget = {
|
|
34
|
+
kind: 'block';
|
|
35
|
+
blockId: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'page';
|
|
38
|
+
path: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'url';
|
|
41
|
+
url: string;
|
|
42
|
+
}
|
|
43
|
+
/** `file` is the path on disk when the URL maps to one (checked before launching). */
|
|
44
|
+
| {
|
|
45
|
+
kind: 'file';
|
|
46
|
+
url: string;
|
|
47
|
+
file?: string;
|
|
48
|
+
};
|
|
49
|
+
/** How one capture is framed. */
|
|
50
|
+
export type CaptureMode = 'block' | 'section' | 'full' | 'viewport';
|
|
51
|
+
/** Everything `runShot` needs, resolved and validated before any browser starts. */
|
|
52
|
+
export interface ShotPlan {
|
|
53
|
+
target: ShotTarget;
|
|
54
|
+
/** Base output name (without section/scroll/width suffixes). */
|
|
55
|
+
name: string;
|
|
56
|
+
widths: number[];
|
|
57
|
+
/** Scroll offsets to capture at, in order; undefined = no scrolling. */
|
|
58
|
+
scrolls?: number[];
|
|
59
|
+
sections: Array<{
|
|
60
|
+
selector: string;
|
|
61
|
+
slug: string;
|
|
62
|
+
}>;
|
|
63
|
+
data?: Record<string, unknown>;
|
|
64
|
+
mode: CaptureMode;
|
|
65
|
+
freeze: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Readiness flag of the standalone block preview route. */
|
|
68
|
+
export declare const PREVIEW_READY = "window.__MECHANICA_PREVIEW_READY__ === true";
|
|
17
69
|
/** Parse a `--width` value into a list of positive integers. Defaults to `[1440]`. */
|
|
18
70
|
export declare function parseWidths(value: string | undefined): number[];
|
|
71
|
+
/** Parse a `--scroll` value (px from the top, comma-separated). Undefined when absent. */
|
|
72
|
+
export declare function parseScrolls(value: string | undefined): number[] | undefined;
|
|
19
73
|
/** Resolve `--data`: inline JSON or `@file`, validated to a JSON object. */
|
|
20
74
|
export declare function resolveDataArg(value: string | undefined): Record<string, unknown> | undefined;
|
|
21
75
|
/** Build the preview-route URL for a block on a given server origin. */
|
|
22
76
|
export declare function previewUrl(origin: string, blockId: string, data?: Record<string, unknown>): string;
|
|
23
|
-
/**
|
|
24
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the output PNG path for one capture. `variant` (`-<section>`,
|
|
79
|
+
* `-y<scroll>`) is always part of the name; the `-w<width>` suffix only when
|
|
80
|
+
* shooting several widths. An `--out` ending in `.png` is a file name, any
|
|
81
|
+
* other value a directory.
|
|
82
|
+
*/
|
|
83
|
+
export declare function outputPath(out: string | undefined, name: string, width: number, multi: boolean, variant?: string): string;
|
|
84
|
+
/** The name suffix of one capture: `-<section slug>` and/or `-y<scroll>`. */
|
|
85
|
+
export declare function shotVariant(sectionSlug?: string, scroll?: number): string;
|
|
25
86
|
/** `/` → `index`, `/docs/getting-started` → `docs-getting-started`. */
|
|
26
87
|
export declare function pageSlug(path: string): string;
|
|
27
88
|
/** Normalize a page path: leading slash, no trailing slash (except the root). */
|
|
28
89
|
export declare function normalizePagePath(path: string): string;
|
|
90
|
+
/** Lowercase letters and digits (any script) joined by single dashes. */
|
|
91
|
+
export declare function slugify(value: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* One file-name slug per `--section` selector: `#hero` → `hero`,
|
|
94
|
+
* `.stage > .pin` → `stage-pin`. A selector with no letters or digits becomes
|
|
95
|
+
* `section`; repeats get `-2`, `-3`…
|
|
96
|
+
*/
|
|
97
|
+
export declare function sectionSlugs(selectors: string[]): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Classify a `shot` target, in this order:
|
|
100
|
+
* - `--page </path>` → that page;
|
|
101
|
+
* - `http://…` / `https://…` → a URL, opened as is;
|
|
102
|
+
* - `file://…`, a Windows drive path (`C:\…`), a `./` or `../` path, or any
|
|
103
|
+
* path ending in `.html`/`.htm` → a local file (relative to `cwd`);
|
|
104
|
+
* - anything else starting with `/` → a page of this project;
|
|
105
|
+
* - anything else → a block id.
|
|
106
|
+
*/
|
|
107
|
+
export declare function classifyTarget(target: string | undefined, page?: string, cwd?: string): ShotTarget;
|
|
108
|
+
/**
|
|
109
|
+
* Default output name of a URL or file shot:
|
|
110
|
+
* - a URL → its host (no `www.`, port appended) and path, extension dropped:
|
|
111
|
+
* `https://example.com/business/evolve` → `example-com-business-evolve`;
|
|
112
|
+
* - a file → its path relative to `cwd` (or its last three path segments when
|
|
113
|
+
* it lies outside), extension dropped: `design/evolve-level/index.html` →
|
|
114
|
+
* `design-evolve-level-index`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function foreignShotName(target: ShotTarget, cwd?: string): string;
|
|
117
|
+
/**
|
|
118
|
+
* How each capture is framed: `--section` boxes, else `--full` (whole
|
|
119
|
+
* document), else the viewport when scrolling (so sticky/pinned states are
|
|
120
|
+
* what's in the picture), else the block element for block shots and the
|
|
121
|
+
* whole document for pages, URLs and files.
|
|
122
|
+
*/
|
|
123
|
+
export declare function captureMode(kind: ShotTarget['kind'], flags: {
|
|
124
|
+
section?: boolean;
|
|
125
|
+
full?: boolean;
|
|
126
|
+
scroll?: boolean;
|
|
127
|
+
}): CaptureMode;
|
|
128
|
+
/** Resolve and validate a `shot` invocation without touching a browser or a server. */
|
|
129
|
+
export declare function planShot(target: string | undefined, options?: ShotOptions, cwd?: string): ShotPlan;
|
|
29
130
|
/**
|
|
30
|
-
* `mechanica shot <
|
|
31
|
-
*
|
|
32
|
-
* a
|
|
33
|
-
*
|
|
34
|
-
* the
|
|
131
|
+
* `mechanica shot <target>` — render one block through the dev preview route,
|
|
132
|
+
* a whole page (editor overlay stripped), or a foreign document (any URL, or
|
|
133
|
+
* a local `.html` file such as a design handoff) in a headless Chromium and
|
|
134
|
+
* write PNG screenshot(s): per width, per scroll offset, per `--section`.
|
|
135
|
+
* Made for agent loops: prints the image paths plus any console/render errors,
|
|
136
|
+
* and fails loudly when the block or page failed to render.
|
|
35
137
|
*/
|
|
36
138
|
export declare function runShot(target: string | undefined, options?: ShotOptions): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mechanica",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Build Vue 3 websites with a visual block editor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"types": "./dist/types/server/index.d.ts",
|
|
46
46
|
"import": "./dist/server.js"
|
|
47
47
|
},
|
|
48
|
+
"./browser": {
|
|
49
|
+
"types": "./dist/types/cli/browser.d.ts",
|
|
50
|
+
"import": "./dist/browser.js"
|
|
51
|
+
},
|
|
48
52
|
"./editor": {
|
|
49
53
|
"import": "./dist/editor.js"
|
|
50
54
|
},
|
|
@@ -66,7 +70,7 @@
|
|
|
66
70
|
"prepublishOnly": "bun ../../scripts/ensure-bun-publish.js && bun run build"
|
|
67
71
|
},
|
|
68
72
|
"dependencies": {
|
|
69
|
-
"mechanica-shared": "2.1
|
|
73
|
+
"mechanica-shared": "2.2.1",
|
|
70
74
|
"@vue/compiler-sfc": "^3.5.38",
|
|
71
75
|
"compact-json-schema": "^0.1.5",
|
|
72
76
|
"estree-walker": "^3.0.3",
|