@juangadm/pre-post 0.1.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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +388 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +82 -0
- package/dist/browser.js.map +1 -0
- package/dist/capture.d.ts +9 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +52 -0
- package/dist/capture.js.map +1 -0
- package/dist/clipboard.d.ts +9 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +26 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/filename.d.ts +24 -0
- package/dist/filename.d.ts.map +1 -0
- package/dist/filename.js +76 -0
- package/dist/filename.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/generic.d.ts +11 -0
- package/dist/routes/generic.d.ts.map +1 -0
- package/dist/routes/generic.js +106 -0
- package/dist/routes/generic.js.map +1 -0
- package/dist/routes/nextjs.d.ts +14 -0
- package/dist/routes/nextjs.d.ts.map +1 -0
- package/dist/routes/nextjs.js +196 -0
- package/dist/routes/nextjs.js.map +1 -0
- package/dist/routes.d.ts +21 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +174 -0
- package/dist/routes.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/upload.d.ts +35 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +116 -0
- package/dist/upload.js.map +1 -0
- package/dist/viewport.d.ts +3 -0
- package/dist/viewport.d.ts.map +1 -0
- package/dist/viewport.js +11 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +66 -0
- package/skill/SKILL.md +246 -0
- package/skill/scripts/adapters/0x0st.sh +36 -0
- package/skill/scripts/adapters/blob.sh +55 -0
- package/skill/scripts/adapters/gist.sh +59 -0
- package/skill/scripts/adapters/git-native.sh +75 -0
- package/skill/scripts/upload-and-copy.sh +183 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser automation via Playwright.
|
|
3
|
+
* Launches headless Chromium for screenshot capture.
|
|
4
|
+
*/
|
|
5
|
+
import { ViewportSize } from './types.js';
|
|
6
|
+
export interface ScreenshotOptions {
|
|
7
|
+
viewport: ViewportSize;
|
|
8
|
+
fullPage?: boolean;
|
|
9
|
+
selector?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Capture a screenshot using Playwright.
|
|
13
|
+
* Returns the screenshot as a Buffer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function captureScreenshot(url: string, options: ScreenshotOptions): Promise<Buffer>;
|
|
16
|
+
/**
|
|
17
|
+
* Close the browser session and clean up resources.
|
|
18
|
+
*/
|
|
19
|
+
export declare function closeBrowser(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Read a pre-captured screenshot from disk.
|
|
22
|
+
* Used in MCP mode where Playwright MCP saves files directly.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readScreenshot(filepath: string): Buffer;
|
|
25
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM1C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAyBD;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CASlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKvD"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser automation via Playwright.
|
|
3
|
+
* Launches headless Chromium for screenshot capture.
|
|
4
|
+
*/
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
let browser = null;
|
|
8
|
+
let page = null;
|
|
9
|
+
/**
|
|
10
|
+
* Get or create a Playwright page with the given viewport.
|
|
11
|
+
* Reuses the browser instance across calls for performance.
|
|
12
|
+
*/
|
|
13
|
+
async function getPage(viewport) {
|
|
14
|
+
if (!browser) {
|
|
15
|
+
try {
|
|
16
|
+
browser = await chromium.launch({ headless: true, channel: 'chrome' });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
browser = await chromium.launch({ headless: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (!page) {
|
|
23
|
+
page = await browser.newPage({
|
|
24
|
+
viewport,
|
|
25
|
+
deviceScaleFactor: 2,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await page.setViewportSize(viewport);
|
|
30
|
+
}
|
|
31
|
+
return page;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Capture a screenshot using Playwright.
|
|
35
|
+
* Returns the screenshot as a Buffer.
|
|
36
|
+
*/
|
|
37
|
+
export async function captureScreenshot(url, options) {
|
|
38
|
+
const pg = await getPage(options.viewport);
|
|
39
|
+
await pg.goto(url, { waitUntil: 'networkidle' });
|
|
40
|
+
// Disable animations and transitions for consistent captures
|
|
41
|
+
await pg.addStyleTag({
|
|
42
|
+
content: '*, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; }',
|
|
43
|
+
});
|
|
44
|
+
// Wait for web fonts to finish loading
|
|
45
|
+
await pg.evaluate(() => document.fonts.ready);
|
|
46
|
+
// If selector specified, scroll it into view
|
|
47
|
+
if (options.selector) {
|
|
48
|
+
const locator = pg.locator(options.selector);
|
|
49
|
+
const count = await locator.count();
|
|
50
|
+
if (count === 0) {
|
|
51
|
+
throw new Error(`Element not found: ${options.selector}`);
|
|
52
|
+
}
|
|
53
|
+
await locator.first().scrollIntoViewIfNeeded();
|
|
54
|
+
await pg.waitForTimeout(200);
|
|
55
|
+
}
|
|
56
|
+
const screenshot = await pg.screenshot({ fullPage: options.fullPage ?? false });
|
|
57
|
+
return Buffer.from(screenshot);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Close the browser session and clean up resources.
|
|
61
|
+
*/
|
|
62
|
+
export async function closeBrowser() {
|
|
63
|
+
if (page) {
|
|
64
|
+
await page.close();
|
|
65
|
+
page = null;
|
|
66
|
+
}
|
|
67
|
+
if (browser) {
|
|
68
|
+
await browser.close();
|
|
69
|
+
browser = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read a pre-captured screenshot from disk.
|
|
74
|
+
* Used in MCP mode where Playwright MCP saves files directly.
|
|
75
|
+
*/
|
|
76
|
+
export function readScreenshot(filepath) {
|
|
77
|
+
if (!fs.existsSync(filepath)) {
|
|
78
|
+
throw new Error(`Screenshot not found: ${filepath}`);
|
|
79
|
+
}
|
|
80
|
+
return fs.readFileSync(filepath);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CaptureOptions, CaptureResult, BeforeAfterCaptureOptions, BeforeAfterCaptureResult, ViewportConfig } from './types.js';
|
|
2
|
+
export declare function captureScreenshot(options: CaptureOptions): Promise<CaptureResult>;
|
|
3
|
+
export declare function captureBeforeAfter(options: BeforeAfterCaptureOptions): Promise<BeforeAfterCaptureResult>;
|
|
4
|
+
/**
|
|
5
|
+
* Capture a URL at multiple viewports (desktop + mobile by default).
|
|
6
|
+
* Returns a map of viewport label → CaptureResult.
|
|
7
|
+
*/
|
|
8
|
+
export declare function captureResponsive(url: string, viewports?: ViewportConfig[]): Promise<Map<string, CaptureResult>>;
|
|
9
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,aAAa,EACb,yBAAyB,EACzB,wBAAwB,EACxB,cAAc,EACf,MAAM,YAAY,CAAC;AAWpB,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAevF;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAmBnC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,cAAc,EAA0B,GAClD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAUrC"}
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { resolveViewport } from './viewport.js';
|
|
2
|
+
import { captureScreenshot as browserCapture } from './browser.js';
|
|
3
|
+
function normalizeOptions(input) {
|
|
4
|
+
if (typeof input === 'string') {
|
|
5
|
+
return { url: input };
|
|
6
|
+
}
|
|
7
|
+
return input;
|
|
8
|
+
}
|
|
9
|
+
export async function captureScreenshot(options) {
|
|
10
|
+
const viewport = resolveViewport(options.viewport);
|
|
11
|
+
const image = await browserCapture(options.url, {
|
|
12
|
+
viewport,
|
|
13
|
+
fullPage: options.fullPage ?? false,
|
|
14
|
+
selector: options.selector,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
image,
|
|
18
|
+
viewport,
|
|
19
|
+
url: options.url,
|
|
20
|
+
selector: options.selector,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export async function captureBeforeAfter(options) {
|
|
24
|
+
const sharedViewport = options.viewport;
|
|
25
|
+
const beforeOpts = normalizeOptions(options.before);
|
|
26
|
+
const afterOpts = normalizeOptions(options.after);
|
|
27
|
+
// Apply shared options if not overridden individually
|
|
28
|
+
if (sharedViewport && !beforeOpts.viewport) {
|
|
29
|
+
beforeOpts.viewport = sharedViewport;
|
|
30
|
+
}
|
|
31
|
+
if (sharedViewport && !afterOpts.viewport) {
|
|
32
|
+
afterOpts.viewport = sharedViewport;
|
|
33
|
+
}
|
|
34
|
+
// Capture sequentially to reuse browser session
|
|
35
|
+
const before = await captureScreenshot(beforeOpts);
|
|
36
|
+
const after = await captureScreenshot(afterOpts);
|
|
37
|
+
return { before, after };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Capture a URL at multiple viewports (desktop + mobile by default).
|
|
41
|
+
* Returns a map of viewport label → CaptureResult.
|
|
42
|
+
*/
|
|
43
|
+
export async function captureResponsive(url, viewports = ['desktop', 'mobile']) {
|
|
44
|
+
const results = new Map();
|
|
45
|
+
for (const vp of viewports) {
|
|
46
|
+
const label = typeof vp === 'string' ? vp : `${vp.width}x${vp.height}`;
|
|
47
|
+
const result = await captureScreenshot({ url, viewport: vp });
|
|
48
|
+
results.set(label, result);
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,IAAI,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnE,SAAS,gBAAgB,CAAC,KAA8B;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAuB;IAC7D,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE;QAC9C,QAAQ;QACR,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QACnC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;QACL,QAAQ;QACR,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAkC;IAElC,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC;IAExC,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAElD,sDAAsD;IACtD,IAAI,cAAc,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QAC3C,UAAU,CAAC,QAAQ,GAAG,cAAc,CAAC;IACvC,CAAC;IACD,IAAI,cAAc,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC1C,SAAS,CAAC,QAAQ,GAAG,cAAc,CAAC;IACtC,CAAC;IAED,gDAAgD;IAChD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAW,EACX,YAA8B,CAAC,SAAS,EAAE,QAAQ,CAAC;IAEnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEjD,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QACvE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcrD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform clipboard utilities
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
/**
|
|
6
|
+
* Copy text to system clipboard
|
|
7
|
+
* Supports macOS (pbcopy) and Linux (xclip)
|
|
8
|
+
*/
|
|
9
|
+
export function copyToClipboard(text) {
|
|
10
|
+
try {
|
|
11
|
+
if (process.platform === 'darwin') {
|
|
12
|
+
execSync('pbcopy', { input: text });
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
else if (process.platform === 'linux') {
|
|
16
|
+
execSync('xclip -selection clipboard', { input: text });
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// Windows or unsupported platform
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,QAAQ,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACxC,QAAQ,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,kCAAkC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface FilenameOptions {
|
|
2
|
+
/** Page title from <title> tag */
|
|
3
|
+
pageTitle?: string;
|
|
4
|
+
/** URL to extract slug from */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Element identifier (data-testid, id, or class) */
|
|
7
|
+
elementId?: string;
|
|
8
|
+
/** Timestamp for the filename */
|
|
9
|
+
timestamp?: Date;
|
|
10
|
+
/** Suffix (before/after/diff) */
|
|
11
|
+
suffix?: 'before' | 'after' | 'diff';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate a semantic filename for screenshots.
|
|
15
|
+
*
|
|
16
|
+
* Format: {page-name}[-{element-id}][-{suffix}]-{timestamp}.png
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* homepage-before-2026-01-26T15-30-45.png
|
|
20
|
+
* about-us-hero-after-2026-01-26T15-30-45.png
|
|
21
|
+
* pricing-card-diff-2026-01-26T15-30-45.png
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateFilename(options: FilenameOptions): string;
|
|
24
|
+
//# sourceMappingURL=filename.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,iCAAiC;IACjC,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CACtC;AAkDD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAyBjE"}
|
package/dist/filename.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify text for use in filenames.
|
|
3
|
+
* Converts to lowercase, replaces non-alphanumeric with hyphens, limits length.
|
|
4
|
+
*/
|
|
5
|
+
function slugify(text) {
|
|
6
|
+
return text
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
9
|
+
.replace(/^-|-$/g, '')
|
|
10
|
+
.slice(0, 50);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract a slug from a URL pathname.
|
|
14
|
+
* Examples:
|
|
15
|
+
* https://example.com/about → about
|
|
16
|
+
* https://example.com/products/shoes → products-shoes
|
|
17
|
+
* file:///path/to/page.html → page
|
|
18
|
+
*/
|
|
19
|
+
function extractSlugFromUrl(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
// For file:// URLs, use the filename without extension
|
|
23
|
+
if (parsed.protocol === 'file:') {
|
|
24
|
+
const parts = parsed.pathname.split('/');
|
|
25
|
+
const filename = parts[parts.length - 1];
|
|
26
|
+
return filename.replace(/\.[^.]+$/, ''); // Remove extension
|
|
27
|
+
}
|
|
28
|
+
// For http/https URLs, use the pathname
|
|
29
|
+
let pathname = parsed.pathname;
|
|
30
|
+
// Remove leading/trailing slashes
|
|
31
|
+
pathname = pathname.replace(/^\/+|\/+$/g, '');
|
|
32
|
+
// If empty (homepage), use domain
|
|
33
|
+
if (!pathname) {
|
|
34
|
+
return parsed.hostname.replace(/^www\./, '');
|
|
35
|
+
}
|
|
36
|
+
// Replace slashes with hyphens
|
|
37
|
+
return pathname.replace(/\//g, '-');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a semantic filename for screenshots.
|
|
45
|
+
*
|
|
46
|
+
* Format: {page-name}[-{element-id}][-{suffix}]-{timestamp}.png
|
|
47
|
+
*
|
|
48
|
+
* Examples:
|
|
49
|
+
* homepage-before-2026-01-26T15-30-45.png
|
|
50
|
+
* about-us-hero-after-2026-01-26T15-30-45.png
|
|
51
|
+
* pricing-card-diff-2026-01-26T15-30-45.png
|
|
52
|
+
*/
|
|
53
|
+
export function generateFilename(options) {
|
|
54
|
+
// 1. Page name: title → URL slug → "page"
|
|
55
|
+
let pageName = 'page';
|
|
56
|
+
if (options.pageTitle) {
|
|
57
|
+
pageName = slugify(options.pageTitle);
|
|
58
|
+
}
|
|
59
|
+
else if (options.url) {
|
|
60
|
+
const urlSlug = extractSlugFromUrl(options.url);
|
|
61
|
+
if (urlSlug) {
|
|
62
|
+
pageName = slugify(urlSlug);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 2. Element identifier (optional)
|
|
66
|
+
const elementPart = options.elementId ? `-${slugify(options.elementId)}` : '';
|
|
67
|
+
// 3. Suffix (before/after/diff)
|
|
68
|
+
const suffixPart = options.suffix ? `-${options.suffix}` : '';
|
|
69
|
+
// 4. Timestamp (ISO 8601 format, safe for filenames)
|
|
70
|
+
const timestamp = (options.timestamp || new Date())
|
|
71
|
+
.toISOString()
|
|
72
|
+
.replace(/[:.]/g, '-')
|
|
73
|
+
.slice(0, 19);
|
|
74
|
+
return `${pageName}${elementPart}${suffixPart}-${timestamp}.png`;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=filename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filename.js","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAE5B,uDAAuD;QACvD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACzC,OAAO,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,mBAAmB;QAC9D,CAAC;QAED,wCAAwC;QACxC,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAE/B,kCAAkC;QAClC,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAE9C,kCAAkC;QAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,+BAA+B;QAC/B,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAwB;IACvD,0CAA0C;IAC1C,IAAI,QAAQ,GAAG,MAAM,CAAC;IACtB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9E,gCAAgC;IAChC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9D,qDAAqD;IACrD,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC;SAChD,WAAW,EAAE;SACb,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEhB,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,IAAI,SAAS,MAAM,CAAC;AACnE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export { resolveViewport } from './viewport.js';
|
|
3
|
+
export { captureScreenshot, captureBeforeAfter, captureResponsive } from './capture.js';
|
|
4
|
+
export { generateFilename } from './filename.js';
|
|
5
|
+
export { closeBrowser, readScreenshot } from './browser.js';
|
|
6
|
+
export { detectRoutes, detectFramework, getChangedFiles } from './routes.js';
|
|
7
|
+
import { BeforeAndAfterOptions, CaptureOptions, CaptureResult, BeforeAfterCaptureOptions, BeforeAfterCaptureResult, ViewportSize, FromImagesOptions, FromImagesResult, ImageInput } from './types.js';
|
|
8
|
+
export declare class BeforeAndAfter {
|
|
9
|
+
private options;
|
|
10
|
+
constructor(options?: BeforeAndAfterOptions);
|
|
11
|
+
get viewport(): ViewportSize;
|
|
12
|
+
capture(options: CaptureOptions): Promise<CaptureResult>;
|
|
13
|
+
captureBeforeAfter(options: BeforeAfterCaptureOptions): Promise<BeforeAfterCaptureResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Create output from existing images (no browser capture needed).
|
|
16
|
+
* Outputs GitHub-flavored markdown table.
|
|
17
|
+
*/
|
|
18
|
+
fromImages(options: FromImagesOptions): Promise<FromImagesResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Generate GitHub-flavored markdown table for before/after images.
|
|
21
|
+
*/
|
|
22
|
+
generateMarkdown(beforeLabel: string, afterLabel: string, beforePath: ImageInput, afterPath: ImageInput): string;
|
|
23
|
+
private resolveImageInput;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI7E,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,aAAa,EACb,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACX,MAAM,YAAY,CAAC;AAKpB,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,GAAE,qBAA0B;IAI/C,IAAI,QAAQ,IAAI,YAAY,CAE3B;IAEK,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAOxD,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAO/F;;;OAGG;IACG,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAevE;;OAEG;IACH,gBAAgB,CACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,UAAU,GACpB,MAAM;IAYT,OAAO,CAAC,iBAAiB;CAQ1B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export { resolveViewport } from './viewport.js';
|
|
3
|
+
export { captureScreenshot, captureBeforeAfter, captureResponsive } from './capture.js';
|
|
4
|
+
export { generateFilename } from './filename.js';
|
|
5
|
+
export { closeBrowser, readScreenshot } from './browser.js';
|
|
6
|
+
export { detectRoutes, detectFramework, getChangedFiles } from './routes.js';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { resolveViewport } from './viewport.js';
|
|
10
|
+
import { captureScreenshot, captureBeforeAfter } from './capture.js';
|
|
11
|
+
export class BeforeAndAfter {
|
|
12
|
+
options;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
get viewport() {
|
|
17
|
+
return resolveViewport(this.options.viewport);
|
|
18
|
+
}
|
|
19
|
+
async capture(options) {
|
|
20
|
+
return captureScreenshot({
|
|
21
|
+
viewport: this.options.viewport,
|
|
22
|
+
...options,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async captureBeforeAfter(options) {
|
|
26
|
+
return captureBeforeAfter({
|
|
27
|
+
viewport: this.options.viewport,
|
|
28
|
+
...options,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create output from existing images (no browser capture needed).
|
|
33
|
+
* Outputs GitHub-flavored markdown table.
|
|
34
|
+
*/
|
|
35
|
+
async fromImages(options) {
|
|
36
|
+
const beforeImage = this.resolveImageInput(options.before);
|
|
37
|
+
const afterImage = this.resolveImageInput(options.after);
|
|
38
|
+
const beforeLabel = options.labels?.before || 'Before';
|
|
39
|
+
const afterLabel = options.labels?.after || 'After';
|
|
40
|
+
const markdown = this.generateMarkdown(beforeLabel, afterLabel, options.before, options.after);
|
|
41
|
+
return {
|
|
42
|
+
markdown,
|
|
43
|
+
beforeImage,
|
|
44
|
+
afterImage,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate GitHub-flavored markdown table for before/after images.
|
|
49
|
+
*/
|
|
50
|
+
generateMarkdown(beforeLabel, afterLabel, beforePath, afterPath) {
|
|
51
|
+
// For markdown, we use the path if it's a string, otherwise a placeholder
|
|
52
|
+
const beforeSrc = typeof beforePath === 'string' ? beforePath : 'before.png';
|
|
53
|
+
const afterSrc = typeof afterPath === 'string' ? afterPath : 'after.png';
|
|
54
|
+
return [
|
|
55
|
+
`| ${beforeLabel} | ${afterLabel} |`,
|
|
56
|
+
`|:------:|:-----:|`,
|
|
57
|
+
`|  |  |`,
|
|
58
|
+
].join('\n');
|
|
59
|
+
}
|
|
60
|
+
resolveImageInput(input) {
|
|
61
|
+
if (Buffer.isBuffer(input)) {
|
|
62
|
+
return input;
|
|
63
|
+
}
|
|
64
|
+
// It's a file path
|
|
65
|
+
const resolvedPath = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input);
|
|
66
|
+
return fs.readFileSync(resolvedPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic route detection for non-Next.js frameworks.
|
|
3
|
+
* Handles common patterns from Remix, SolidStart, SvelteKit, etc.
|
|
4
|
+
*/
|
|
5
|
+
import { DetectedRoute } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Detect routes from changed files using generic heuristics.
|
|
8
|
+
* Works as a fallback when framework is unknown.
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectGenericRoutes(files: string[]): DetectedRoute[];
|
|
11
|
+
//# sourceMappingURL=generic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/routes/generic.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsB5C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAuEpE"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic route detection for non-Next.js frameworks.
|
|
3
|
+
* Handles common patterns from Remix, SolidStart, SvelteKit, etc.
|
|
4
|
+
*/
|
|
5
|
+
/** Files that should be skipped (no visual output) */
|
|
6
|
+
const SKIP_PATTERNS = [
|
|
7
|
+
/\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
8
|
+
/^__tests__\//,
|
|
9
|
+
/^(package|tsconfig|vite\.config|vitest\.config)\./,
|
|
10
|
+
/^\.env/,
|
|
11
|
+
/^pnpm-lock\.yaml$/,
|
|
12
|
+
/^yarn\.lock$/,
|
|
13
|
+
/^package-lock\.json$/,
|
|
14
|
+
];
|
|
15
|
+
/** Global style files */
|
|
16
|
+
const GLOBAL_STYLE_PATTERNS = [
|
|
17
|
+
/globals?\.css$/,
|
|
18
|
+
/global\.(scss|less)$/,
|
|
19
|
+
/tailwind\.config\./,
|
|
20
|
+
/postcss\.config\./,
|
|
21
|
+
/theme\.(ts|js|css)$/,
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Detect routes from changed files using generic heuristics.
|
|
25
|
+
* Works as a fallback when framework is unknown.
|
|
26
|
+
*/
|
|
27
|
+
export function detectGenericRoutes(files) {
|
|
28
|
+
const routes = [];
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
if (SKIP_PATTERNS.some(p => p.test(file)))
|
|
31
|
+
continue;
|
|
32
|
+
// Global styles
|
|
33
|
+
if (GLOBAL_STYLE_PATTERNS.some(p => p.test(file))) {
|
|
34
|
+
routes.push({
|
|
35
|
+
path: '/',
|
|
36
|
+
sourceFile: file,
|
|
37
|
+
confidence: 'low',
|
|
38
|
+
reason: 'Global style file affects all pages',
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Remix-style: src/routes/*.tsx or routes/*.tsx
|
|
43
|
+
const remixMatch = file.match(/^(?:src\/)?routes\/(.+)\.(tsx|ts|jsx|js)$/);
|
|
44
|
+
if (remixMatch) {
|
|
45
|
+
const routePath = remixRouteToPath(remixMatch[1]);
|
|
46
|
+
routes.push({
|
|
47
|
+
path: routePath,
|
|
48
|
+
sourceFile: file,
|
|
49
|
+
confidence: 'high',
|
|
50
|
+
reason: 'Route file change',
|
|
51
|
+
});
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// SvelteKit: src/routes/**/+page.svelte
|
|
55
|
+
const svelteMatch = file.match(/^src\/routes\/(.+\/)?(\+page)\.(svelte|ts|js)$/);
|
|
56
|
+
if (svelteMatch) {
|
|
57
|
+
const dir = (svelteMatch[1] || '').replace(/\/$/, '');
|
|
58
|
+
routes.push({
|
|
59
|
+
path: dir ? '/' + dir : '/',
|
|
60
|
+
sourceFile: file,
|
|
61
|
+
confidence: 'high',
|
|
62
|
+
reason: 'SvelteKit page file change',
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Generic pages directory: src/pages/*.tsx
|
|
67
|
+
const pagesMatch = file.match(/^src\/pages\/(.+)\.(tsx|ts|jsx|js)$/);
|
|
68
|
+
if (pagesMatch) {
|
|
69
|
+
const name = pagesMatch[1];
|
|
70
|
+
const routePath = name === 'index' ? '/' : '/' + name.replace(/\/index$/, '');
|
|
71
|
+
routes.push({
|
|
72
|
+
path: routePath,
|
|
73
|
+
sourceFile: file,
|
|
74
|
+
confidence: 'medium',
|
|
75
|
+
reason: 'Page file change',
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// Component files in src/components — low confidence, can't map to route
|
|
80
|
+
const componentMatch = file.match(/^src\/components?\/.+\.(tsx|ts|jsx|js|svelte|vue)$/);
|
|
81
|
+
if (componentMatch) {
|
|
82
|
+
routes.push({
|
|
83
|
+
path: '/',
|
|
84
|
+
sourceFile: file,
|
|
85
|
+
confidence: 'low',
|
|
86
|
+
reason: 'Shared component change — may affect any page',
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return routes;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convert a Remix-style route filename to a URL path.
|
|
95
|
+
* Handles dot-delimiters for nested routes and $ for dynamic segments.
|
|
96
|
+
*/
|
|
97
|
+
function remixRouteToPath(filename) {
|
|
98
|
+
// Remove _index suffix
|
|
99
|
+
if (filename === '_index' || filename === 'index')
|
|
100
|
+
return '/';
|
|
101
|
+
return '/' + filename
|
|
102
|
+
.replace(/\._index$/, '') // trailing _index
|
|
103
|
+
.replace(/\./g, '/') // dots to slashes
|
|
104
|
+
.replace(/\$/g, ':'); // $ params to :params
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../src/routes/generic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js route detection from changed files.
|
|
3
|
+
* Supports App Router and Pages Router conventions.
|
|
4
|
+
*/
|
|
5
|
+
import { DetectedRoute } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Detect routes from changed files in a Next.js App Router project.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectAppRouterRoutes(files: string[]): DetectedRoute[];
|
|
10
|
+
/**
|
|
11
|
+
* Detect routes from changed files in a Next.js Pages Router project.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectPagesRouterRoutes(files: string[]): DetectedRoute[];
|
|
14
|
+
//# sourceMappingURL=nextjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/routes/nextjs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiC5C;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAsFtE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAwDxE"}
|