@opencraw/cli 0.1.7 → 0.1.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/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from 'node:util';
2
2
  import { writeFile } from 'node:fs/promises';
3
- import { readableKey, readRecordsFile, diffRecords, loadAccessConfig, htmlTableSheets, AccessBroker, HttpClient, parseXml, BrowserClient, readRecipeSource, parseOutputRecipe, parseInputRecipe, bindRecipeSet, memorySink, jsonLinesSink, createCrawler, RecipeSet, diffOptionsFor, RecipeValidationError, RecipeBindingError, traceLine } from '@opencraw/core';
3
+ import { readableKey, readRecordsFile, diffRecords, loadAccessConfig, htmlTableSheets, HostAllowlist, AccessBroker, HttpClient, parseXml, BrowserClient, readRecipeSource, parseOutputRecipe, parseInputRecipe, bindRecipeSet, memorySink, jsonLinesSink, createCrawler, RecipeSet, variantLabel, diffOptionsFor, RecipeValidationError, RecipeBindingError, traceLine } from '@opencraw/core';
4
4
  import { resolve } from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { readFileSync } from 'node:fs';
@@ -1046,8 +1046,10 @@ const OBSERVE_MS = 4000;
1046
1046
  * @throws Error when the fetch itself fails.
1047
1047
  */
1048
1048
  async function probeUrl(url, options) {
1049
- const plugins = options.plugins === undefined ? undefined : await loadPlugins(options.plugins);
1050
- const lease = await new AccessBroker(await resolveAccess(options), plugins?.accessPlugins).lease({
1049
+ const allowedHosts = HostAllowlist.of(options.allowedHosts);
1050
+ const plugins = options.accessPlugins === undefined && options.plugins !== undefined ? await loadPlugins(options.plugins) : undefined;
1051
+ const access = options.accessConfig === undefined ? await resolveAccess(options) : withDefault(options.accessConfig, options.accessProfile);
1052
+ const lease = await new AccessBroker(access, options.accessPlugins ?? plugins?.accessPlugins).lease({
1051
1053
  recipeId: 'probe'
1052
1054
  });
1053
1055
  if (lease.cdp !== undefined) throw new Error(`access profile "${lease.profile}" is a remote browser; probe fetches over HTTP and needs a proxy profile`);
@@ -1055,7 +1057,8 @@ async function probeUrl(url, options) {
1055
1057
  userAgent: options.userAgent ?? BROWSER_USER_AGENT,
1056
1058
  ignoreHTTPSErrors: options.insecureTls || lease.ignoreHTTPSErrors === true,
1057
1059
  proxy: lease.proxy,
1058
- headers: lease.headers
1060
+ headers: lease.headers,
1061
+ allowedHosts
1059
1062
  });
1060
1063
  try {
1061
1064
  const target = /^[a-z][\w+.-]+:/i.test(url) ? url : pathToFileURL(resolve(url)).href;
@@ -1106,7 +1109,7 @@ async function probeUrl(url, options) {
1106
1109
  xml: describeXml(parseXml(body.xml, response.url))
1107
1110
  };
1108
1111
  const text = body.kind === 'html' ? body.html : body.text;
1109
- const observed = options.browser && !target.startsWith('file:') ? await observeBrowserJson(url, options, lease) : [];
1112
+ const observed = options.browser && !target.startsWith('file:') ? await observeBrowserJson(url, options, lease, allowedHosts) : [];
1110
1113
  const html = body.kind === 'html' ? {
1111
1114
  html: describeHtml(body.html, response.format === 'markdown' || response.format === 'docx')
1112
1115
  } : {};
@@ -1139,7 +1142,7 @@ async function probePage(url, options, terminal) {
1139
1142
  return 1;
1140
1143
  }
1141
1144
  }
1142
- async function observeBrowserJson(url, options, lease) {
1145
+ async function observeBrowserJson(url, options, lease, allowedHosts) {
1143
1146
  const browser = await BrowserClient.launch({
1144
1147
  executablePath: options.browserPath,
1145
1148
  ignoreHTTPSErrors: options.insecureTls
@@ -1149,7 +1152,8 @@ async function observeBrowserJson(url, options, lease) {
1149
1152
  userAgent: options.userAgent,
1150
1153
  proxy: lease.proxy,
1151
1154
  headers: lease.headers,
1152
- ignoreHTTPSErrors: lease.ignoreHTTPSErrors
1155
+ ignoreHTTPSErrors: lease.ignoreHTTPSErrors,
1156
+ allowedHosts
1153
1157
  });
1154
1158
  const seen = [];
1155
1159
  session.page.on('response', response => {
@@ -1169,6 +1173,15 @@ async function observeBrowserJson(url, options, lease) {
1169
1173
  await browser.close();
1170
1174
  }
1171
1175
  }
1176
+ /** An access config with its default profile overridden, when one is named. */
1177
+ function withDefault(config, profile) {
1178
+ if (profile === undefined) return config;
1179
+ if (!Object.hasOwn(config.profiles, profile)) throw new Error(`access profile "${profile}" is not in the access config (profiles: ${Object.keys(config.profiles).join(', ') || 'none'})`);
1180
+ return {
1181
+ ...config,
1182
+ default: profile
1183
+ };
1184
+ }
1172
1185
  function reportOf(result) {
1173
1186
  if (result.pdf !== undefined) return pdfReport(result.url, result.pdf);
1174
1187
  if (result.workbook !== undefined) return workbookReport(result.url, result.workbook);
@@ -1299,7 +1312,7 @@ async function runRecipes(command, terminal) {
1299
1312
  const report = await crawler.run(new RecipeSet(set.output, inputs));
1300
1313
  if (command.out === undefined && !command.dryRun) for (const record of sink.records) terminal.out(JSON.stringify(record.data));
1301
1314
  for (const recipe of report.recipes) {
1302
- terminal.err(`${recipe.recipeId}: ${recipe.emitted} emitted, ${recipe.rejected} rejected, ${recipe.duplicates} duplicates, ${recipe.skipped} skipped, ${recipe.stepsSkipped > 0 ? `${recipe.stepsSkipped} steps skipped (see --trace), ` : ''}${recipe.pages} pages, ${recipe.durationMs} ms${recipe.error === undefined ? '' : `, stopped: ${recipe.error}`}`);
1315
+ terminal.err(`${recipe.recipeId}${recipe.variant === undefined ? '' : ` [${variantLabel(recipe.variant)}]`}: ${recipe.emitted} emitted, ${recipe.rejected} rejected, ${recipe.duplicates} duplicates, ${recipe.skipped} skipped, ${recipe.stepsSkipped > 0 ? `${recipe.stepsSkipped} steps skipped (see --trace), ` : ''}${recipe.pages} pages, ${recipe.durationMs} ms${recipe.error === undefined ? '' : `, stopped: ${recipe.error}`}`);
1303
1316
  }
1304
1317
  if (!command.dryRun) terminal.err(`${report.records} records${report.sink.location === undefined ? '' : ` written to ${report.sink.location}`}`);
1305
1318
  if (previous !== undefined) {
@@ -4,7 +4,7 @@ export type { Command } from './arguments/index.js';
4
4
  export { processTerminal } from './terminal/index.js';
5
5
  export type { Terminal } from './terminal/index.js';
6
6
  export { findData, probeReport, probeUrl } from './probe/index.js';
7
- export type { ProbeFindings, ProbeResult } from './probe/index.js';
7
+ export type { ProbeFindings, ProbeResult, ProbeOptions } from './probe/index.js';
8
8
  export { readRecipeFiles } from './recipe-files/index.js';
9
9
  export type { RecipeFiles } from './recipe-files/index.js';
10
10
  export { loadForRun } from './run/index.js';
@@ -1,5 +1,5 @@
1
1
  export { probePage, probeUrl } from './probe-page.use-case.js';
2
- export type { ProbeResult } from './probe-page.use-case.js';
2
+ export type { ProbeResult, ProbeOptions } from './probe-page.use-case.js';
3
3
  export { findData } from './find-data.algorithm.js';
4
4
  export type { ProbeFindings, JsonLdFinding, InlineJsonFinding } from './find-data.algorithm.js';
5
5
  export { probeReport, pdfReport, workbookReport, deckReport, jsonReport, xmlReport } from './probe-report.mapper.js';
@@ -1,3 +1,4 @@
1
+ import type { AccessConfig, AccessPlugin } from '@opencraw/core';
1
2
  import type { CommonOptions } from '../arguments/index.js';
2
3
  import type { Terminal } from '../terminal/index.js';
3
4
  import { findData } from './find-data.algorithm.js';
@@ -27,6 +28,17 @@ export interface ProbeResult {
27
28
  /** Present for XML: its namespaces, repeated elements and structure. */
28
29
  xml?: XmlFindings;
29
30
  }
31
+ /** How to probe: the cli's options, plus what a service probing for others sets. */
32
+ export interface ProbeOptions extends CommonOptions {
33
+ /** Also render the page and list the JSON it fetches. */
34
+ browser: boolean;
35
+ /** The only hosts the probe may reach, as `CrawlOptions.allowedHosts`; local paths and `file:` are refused. */
36
+ allowedHosts?: string[];
37
+ /** An access config already loaded, instead of `access` (a file). `accessProfile` still picks the default. */
38
+ accessConfig?: AccessConfig;
39
+ /** Access plugins already loaded, instead of those in `plugins` (a module). */
40
+ accessPlugins?: AccessPlugin[];
41
+ }
30
42
  /**
31
43
  * Fetches a page and finds where its data lives, through the access profile the
32
44
  * options name (direct without one). With `browser: true` it also
@@ -43,9 +55,7 @@ export interface ProbeResult {
43
55
  * @returns What was found.
44
56
  * @throws Error when the fetch itself fails.
45
57
  */
46
- export declare function probeUrl(url: string, options: {
47
- browser: boolean;
48
- } & CommonOptions): Promise<ProbeResult>;
58
+ export declare function probeUrl(url: string, options: ProbeOptions): Promise<ProbeResult>;
49
59
  /**
50
60
  * Fetches a page and reports where its data lives, to a `Terminal`.
51
61
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencraw/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.esm.js",
6
6
  "module": "./dist/index.esm.js",
@@ -24,7 +24,7 @@
24
24
  "!**/*.js.map"
25
25
  ],
26
26
  "dependencies": {
27
- "@opencraw/core": "^0.1.7",
27
+ "@opencraw/core": "^0.1.9",
28
28
  "playwright": "^1.63.0"
29
29
  },
30
30
  "publishConfig": {