@opencraw/studio 0.0.8 → 0.0.9
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 +31 -5
- package/dist/index.esm.js +88 -6
- package/dist/src/browser-provisioning/ensure-browser-launch.use-case.d.ts +12 -0
- package/dist/src/browser-provisioning/index.d.ts +2 -0
- package/dist/src/browser-provisioning/install-chromium.use-case.d.ts +2 -0
- package/dist/src/browser-provisioning/missing-browser.policy.d.ts +3 -0
- package/dist/ui/assets/index-CiZ4i-q5.js +34 -0
- package/dist/ui/assets/{pdf-lqXfRfkR.js → pdf-De9LBx2x.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +3 -3
- package/dist/ui/assets/index-DdiGdaTN.js +0 -13
package/README.md
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/russoedu/open.craw/main/docs/assets/opencraw-logo.svg" alt="OpenCraw" width="360">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# @opencraw/studio
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
A visual editor for [OpenCraw](https://github.com/russoedu/open.craw) recipes: open a site or a document,
|
|
8
|
+
click the data you want, and get a recipe that runs — the same engine, the same JSON files, no hidden state.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install -g @opencraw/cli @opencraw/studio
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Start
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
opencraw studio recipes/ # the cli delegates to this package when it's installed
|
|
20
|
+
npx @opencraw/studio recipes/ # or run it directly, without the cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Either opens a local server (`127.0.0.1` only, behind a random access token) and prints the URL to open:
|
|
4
24
|
|
|
5
|
-
|
|
25
|
+
```text
|
|
26
|
+
OpenCraw Studio: http://127.0.0.1:52341/?token=8f2a1c9e4b7d3f6a0e5c8b1d2a4f7e9c
|
|
27
|
+
Workspace: /home/you/project/recipes
|
|
28
|
+
```
|
|
6
29
|
|
|
7
|
-
|
|
30
|
+
`recipes/` can already have `.input.json`/`.output.json` files in it, or be empty — the studio's own
|
|
31
|
+
**+ New recipe** writes a minimal, valid pair to start from.
|
|
8
32
|
|
|
9
|
-
##
|
|
33
|
+
## Learn more
|
|
10
34
|
|
|
11
|
-
|
|
35
|
+
[**The OpenCraw Studio manual**](https://github.com/russoedu/open.craw/blob/main/docs/studio/manual.md)
|
|
36
|
+
builds one real recipe end to end, tab by tab: picking on a live page, the Record tab's mapping, the
|
|
37
|
+
document canvases (JSON, PDF, Excel/CSV, PowerPoint), recording a login, and the JSON editor.
|
package/dist/index.esm.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { join, normalize, sep, extname,
|
|
1
|
+
import { join, dirname, normalize, sep, extname, resolve } from 'node:path';
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { BODY_KINDS,
|
|
5
|
+
import { BODY_KINDS, HttpClient, BrowserClient, NODE_ID_ATTRIBUTE, rowsOfCells, parseXml, findDeckTables, findGridTables, analyzeTables, isDeckDocument, readRecipeSource, parseInputRecipe, parseOutputRecipe, bindRecipeSet, memorySink, createCrawler, traceLine, resolveMissingPolicy, isWorkbookDocument, countMatches, matchesOf, tryParseJson, RecipeBindingError, RecipeValidationError, isPdfDocument, BrowserProfiles } from '@opencraw/core';
|
|
6
6
|
import { randomBytes, createHash } from 'node:crypto';
|
|
7
|
+
import { execFile } from 'node:child_process';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
7
10
|
import { load } from 'cheerio';
|
|
8
11
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
9
12
|
import { describeJson } from '@opencraw/probe';
|
|
@@ -786,6 +789,85 @@ function cookieToken(header) {
|
|
|
786
789
|
return undefined;
|
|
787
790
|
}
|
|
788
791
|
|
|
792
|
+
const execFileAsync = promisify(execFile);
|
|
793
|
+
/** `playwright`'s own `exports` map doesn't whitelist a `./cli` subpath, so its CLI is resolved off `playwright/package.json`'s real location — the same technique `@opencraw/captcha-tesseract`'s `bundledLangPath()` uses for a bundled data path. */
|
|
794
|
+
function playwrightCliPath() {
|
|
795
|
+
const require = createRequire(import.meta.url);
|
|
796
|
+
return join(dirname(require.resolve('playwright/package.json')), 'cli.js');
|
|
797
|
+
}
|
|
798
|
+
async function runPlaywrightInstall() {
|
|
799
|
+
await execFileAsync(process.execPath, [playwrightCliPath(), 'install', 'chromium']);
|
|
800
|
+
}
|
|
801
|
+
let installing;
|
|
802
|
+
/**
|
|
803
|
+
* Runs `playwright install chromium` at most once per process. A failed install clears the memo,
|
|
804
|
+
* so the next browser launch attempt retries instead of remembering failure forever.
|
|
805
|
+
*/
|
|
806
|
+
async function runOnce(run) {
|
|
807
|
+
try {
|
|
808
|
+
await run();
|
|
809
|
+
console.log('@opencraw/studio: Chromium installed.');
|
|
810
|
+
} catch (error) {
|
|
811
|
+
installing = undefined;
|
|
812
|
+
console.log('@opencraw/studio: "playwright install chromium" failed.');
|
|
813
|
+
throw error;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function installChromiumOnce(run = runPlaywrightInstall) {
|
|
817
|
+
if (installing === undefined) {
|
|
818
|
+
console.log('@opencraw/studio: Playwright\'s Chromium is not downloaded yet — running "playwright install chromium" once...');
|
|
819
|
+
installing = runOnce(run);
|
|
820
|
+
}
|
|
821
|
+
return installing;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Recognises Playwright's own "no browser downloaded" failure so a launch can be retried after installing one, instead of surfacing Playwright's raw ASCII-art error. */
|
|
825
|
+
function isMissingBrowserError(error) {
|
|
826
|
+
return error instanceof Error && /Executable doesn't exist at/.test(error.message);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Runs a browser-launching function, and — only when it fails with Playwright's own "no browser
|
|
831
|
+
* downloaded" error, only for a default chromium launch with no explicit binary configured — installs
|
|
832
|
+
* Chromium once and retries, instead of letting Playwright's raw error reach the studio's caller.
|
|
833
|
+
*
|
|
834
|
+
* An explicit `executablePath` or `OPENCRAW_CHROMIUM` means the person (or this repo's own sandbox)
|
|
835
|
+
* already chose a specific binary; a missing-browser failure there is their setup to fix, not
|
|
836
|
+
* something to silently install over.
|
|
837
|
+
*/
|
|
838
|
+
async function ensureBrowserLaunch(browser, launch, installChromium = installChromiumOnce) {
|
|
839
|
+
try {
|
|
840
|
+
return await launch();
|
|
841
|
+
} catch (error) {
|
|
842
|
+
if (!isMissingBrowserError(error)) throw error;
|
|
843
|
+
if (browser?.executablePath !== undefined || process.env.OPENCRAW_CHROMIUM !== undefined) {
|
|
844
|
+
throw new Error('@opencraw/studio: no browser found at the configured executable path. Check it, or unset it and let the studio install Chromium itself.', {
|
|
845
|
+
cause: error
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
if ((browser?.browserType ?? 'chromium') !== 'chromium') {
|
|
849
|
+
throw new Error(`@opencraw/studio: no ${browser?.browserType} browser found. Run "npx playwright install ${browser?.browserType}" and try again.`, {
|
|
850
|
+
cause: error
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
await installChromium();
|
|
855
|
+
} catch (installError) {
|
|
856
|
+
throw new Error('@opencraw/studio: could not install Chromium automatically. Run "npx playwright install chromium" yourself and try again.', {
|
|
857
|
+
cause: installError
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
try {
|
|
861
|
+
return await launch();
|
|
862
|
+
} catch (retryError) {
|
|
863
|
+
if (!isMissingBrowserError(retryError)) throw retryError;
|
|
864
|
+
throw new Error('@opencraw/studio: Chromium was installed but is still not found. Run "npx playwright install chromium" yourself and try again.', {
|
|
865
|
+
cause: retryError
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
789
871
|
/**
|
|
790
872
|
* Fetches an input recipe's start page as the engine would see it: a fresh
|
|
791
873
|
* Playwright page in web mode, core's HTTP client in api mode. Returns plain
|
|
@@ -809,7 +891,7 @@ async function fetchStartPage(input, browser) {
|
|
|
809
891
|
return input.mode === 'web' ? fetchWeb(point.url, browser) : fetchApi(point.url);
|
|
810
892
|
}
|
|
811
893
|
async function fetchWeb(url, browser) {
|
|
812
|
-
const client = await BrowserClient.launch(browser);
|
|
894
|
+
const client = await ensureBrowserLaunch(browser, () => BrowserClient.launch(browser));
|
|
813
895
|
try {
|
|
814
896
|
const session = await client.newSession();
|
|
815
897
|
try {
|
|
@@ -985,7 +1067,7 @@ async function takeSnapshot(input, stepPath, browser) {
|
|
|
985
1067
|
return input.mode === 'web' ? snapshotWeb(point.url, browser) : snapshotApi(point.url);
|
|
986
1068
|
}
|
|
987
1069
|
async function snapshotWeb(url, browser) {
|
|
988
|
-
const client = await BrowserClient.launch(browser);
|
|
1070
|
+
const client = await ensureBrowserLaunch(browser, () => BrowserClient.launch(browser));
|
|
989
1071
|
try {
|
|
990
1072
|
const session = await client.newSession();
|
|
991
1073
|
try {
|
|
@@ -2969,7 +3051,7 @@ async function responsesSeen(input, stepPath, browser) {
|
|
|
2969
3051
|
if (input.mode !== 'web') return [];
|
|
2970
3052
|
const point = input.start[0];
|
|
2971
3053
|
if (point === undefined) throw new Error(`recipe "${input.id}" has no start point`);
|
|
2972
|
-
const client = await BrowserClient.launch(browser);
|
|
3054
|
+
const client = await ensureBrowserLaunch(browser, () => BrowserClient.launch(browser));
|
|
2973
3055
|
try {
|
|
2974
3056
|
const session = await client.newSession();
|
|
2975
3057
|
try {
|
|
@@ -3957,7 +4039,7 @@ async function openRecorderSession(options, callbacks) {
|
|
|
3957
4039
|
...options.browser,
|
|
3958
4040
|
headless: false
|
|
3959
4041
|
});
|
|
3960
|
-
const session = await profiles.open(RECORDER_PROFILE_NAME, {}, {});
|
|
4042
|
+
const session = await ensureBrowserLaunch(options.browser, () => profiles.open(RECORDER_PROFILE_NAME, {}, {}));
|
|
3961
4043
|
const context = session.context;
|
|
3962
4044
|
const steps = [];
|
|
3963
4045
|
let closed = false;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BrowserSessionConfig } from '@opencraw/core';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a browser-launching function, and — only when it fails with Playwright's own "no browser
|
|
4
|
+
* downloaded" error, only for a default chromium launch with no explicit binary configured — installs
|
|
5
|
+
* Chromium once and retries, instead of letting Playwright's raw error reach the studio's caller.
|
|
6
|
+
*
|
|
7
|
+
* An explicit `executablePath` or `OPENCRAW_CHROMIUM` means the person (or this repo's own sandbox)
|
|
8
|
+
* already chose a specific binary; a missing-browser failure there is their setup to fix, not
|
|
9
|
+
* something to silently install over.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureBrowserLaunch<T>(browser: BrowserSessionConfig | undefined, launch: () => Promise<T>, installChromium?: (run?: () => Promise<void>) => Promise<void>): Promise<T>;
|
|
12
|
+
//# sourceMappingURL=ensure-browser-launch.use-case.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Recognises Playwright's own "no browser downloaded" failure so a launch can be retried after installing one, instead of surfacing Playwright's raw ASCII-art error. */
|
|
2
|
+
export declare function isMissingBrowserError(error: unknown): boolean;
|
|
3
|
+
//# sourceMappingURL=missing-browser.policy.d.ts.map
|