tablefacts 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.
Files changed (98) hide show
  1. package/.env.example +14 -0
  2. package/CHANGELOG.md +12 -0
  3. package/LICENSE +21 -0
  4. package/README.md +313 -0
  5. package/bin/tablefacts.mjs +28 -0
  6. package/package.json +77 -0
  7. package/src/index.mjs +52 -0
  8. package/src/instagram/README.md +135 -0
  9. package/src/instagram/download.mjs +62 -0
  10. package/src/instagram/index.mjs +355 -0
  11. package/src/instagram/links.mjs +55 -0
  12. package/src/instagram/record.mjs +18 -0
  13. package/src/lib/edge.mjs +49 -0
  14. package/src/lib/env.mjs +35 -0
  15. package/src/lib/errors.mjs +39 -0
  16. package/src/lib/files.mjs +10 -0
  17. package/src/lib/images.mjs +20 -0
  18. package/src/lib/log.mjs +17 -0
  19. package/src/lib/photos.mjs +42 -0
  20. package/src/lib/playwright.mjs +13 -0
  21. package/src/lib/project.mjs +42 -0
  22. package/src/lib/text.mjs +7 -0
  23. package/src/lib/types.mjs +247 -0
  24. package/src/menu/README.md +97 -0
  25. package/src/menu/cluvi/config.mjs +33 -0
  26. package/src/menu/cluvi/extract.mjs +24 -0
  27. package/src/menu/cluvi/import.mjs +30 -0
  28. package/src/menu/cluvi/source.mjs +156 -0
  29. package/src/menu/index.mjs +9 -0
  30. package/src/menu/lib/db.mjs +119 -0
  31. package/src/menu/lib/import.mjs +126 -0
  32. package/src/menu/lib/menu.mjs +95 -0
  33. package/src/menu/lib/run.mjs +83 -0
  34. package/src/menu/raw/config.mjs +38 -0
  35. package/src/menu/raw/extract.mjs +72 -0
  36. package/src/menu/raw/import.mjs +99 -0
  37. package/src/menu/raw/normalize.mjs +120 -0
  38. package/src/menu/raw/source.mjs +116 -0
  39. package/src/menu/raw/vision.mjs +252 -0
  40. package/src/research/README.md +69 -0
  41. package/src/research/index.mjs +147 -0
  42. package/src/research/lib/google.mjs +93 -0
  43. package/src/research/lib/hours.mjs +109 -0
  44. package/src/research/lib/merge.mjs +119 -0
  45. package/src/research/lib/osm.mjs +49 -0
  46. package/src/research/lib/report.mjs +118 -0
  47. package/src/research/lib/social.mjs +49 -0
  48. package/src/research/lib/util.mjs +104 -0
  49. package/src/research/lib/website.mjs +285 -0
  50. package/src/research/research.mjs +67 -0
  51. package/src/tripadvisor/README.md +78 -0
  52. package/src/tripadvisor/index.mjs +178 -0
  53. package/src/tripadvisor/links.mjs +78 -0
  54. package/src/tripadvisor/photos.mjs +55 -0
  55. package/types/index.d.mts +65 -0
  56. package/types/instagram/download.d.mts +1 -0
  57. package/types/instagram/index.d.mts +13 -0
  58. package/types/instagram/links.d.mts +14 -0
  59. package/types/instagram/record.d.mts +1 -0
  60. package/types/lib/edge.d.mts +14 -0
  61. package/types/lib/env.d.mts +12 -0
  62. package/types/lib/errors.d.mts +25 -0
  63. package/types/lib/files.d.mts +1 -0
  64. package/types/lib/images.d.mts +6 -0
  65. package/types/lib/log.d.mts +5 -0
  66. package/types/lib/photos.d.mts +30 -0
  67. package/types/lib/playwright.d.mts +1677 -0
  68. package/types/lib/project.d.mts +32 -0
  69. package/types/lib/text.d.mts +4 -0
  70. package/types/lib/types.d.mts +668 -0
  71. package/types/menu/cluvi/config.d.mts +13 -0
  72. package/types/menu/cluvi/extract.d.mts +2 -0
  73. package/types/menu/cluvi/import.d.mts +35 -0
  74. package/types/menu/cluvi/source.d.mts +37 -0
  75. package/types/menu/index.d.mts +6 -0
  76. package/types/menu/lib/db.d.mts +23 -0
  77. package/types/menu/lib/import.d.mts +11 -0
  78. package/types/menu/lib/menu.d.mts +24 -0
  79. package/types/menu/lib/run.d.mts +27 -0
  80. package/types/menu/raw/config.d.mts +18 -0
  81. package/types/menu/raw/extract.d.mts +2 -0
  82. package/types/menu/raw/import.d.mts +64 -0
  83. package/types/menu/raw/normalize.d.mts +29 -0
  84. package/types/menu/raw/source.d.mts +19 -0
  85. package/types/menu/raw/vision.d.mts +13 -0
  86. package/types/research/index.d.mts +9 -0
  87. package/types/research/lib/google.d.mts +12 -0
  88. package/types/research/lib/hours.d.mts +22 -0
  89. package/types/research/lib/merge.d.mts +87 -0
  90. package/types/research/lib/osm.d.mts +36 -0
  91. package/types/research/lib/report.d.mts +6 -0
  92. package/types/research/lib/social.d.mts +118 -0
  93. package/types/research/lib/util.d.mts +45 -0
  94. package/types/research/lib/website.d.mts +283 -0
  95. package/types/research/research.d.mts +1 -0
  96. package/types/tripadvisor/index.d.mts +11 -0
  97. package/types/tripadvisor/links.d.mts +13 -0
  98. package/types/tripadvisor/photos.d.mts +1 -0
@@ -0,0 +1,78 @@
1
+ // The pure parts of photos.mjs: the command line, the restaurant link and the photo URLs.
2
+ // Split out so they can be tested without starting a browser.
3
+
4
+ import { usageError } from '../lib/errors.mjs'
5
+
6
+ export function parseArgs(argv) {
7
+ const opts = { links: [], dryRun: false, debug: false, max: Infinity }
8
+ for (let i = 0; i < argv.length; i++) {
9
+ const a = argv[i]
10
+ if (a === '--out') opts.out = argv[++i]
11
+ else if (a === '--cdp') opts.cdp = argv[++i]
12
+ else if (a === '--edge-dir') opts.edgeDir = argv[++i]
13
+ else if (a === '--max') {
14
+ opts.max = Number(argv[++i])
15
+ if (!(opts.max > 0)) throw usageError('--max needs a number above 0')
16
+ } else if (a === '--dry-run') opts.dryRun = true
17
+ else if (a === '--debug') opts.debug = true
18
+ else if (a === '--help' || a === '-h') opts.help = true
19
+ else if (a.startsWith('--')) throw usageError(`Unknown option ${a}`)
20
+ else opts.links.push(a)
21
+ }
22
+ return opts
23
+ }
24
+
25
+ // A restaurant page looks like /Restaurant_Review-g297478-d1234567-Reviews-Name-City.html on
26
+ // any country domain (tripadvisor.com, .co, .es...). The query and hash are dropped, so the same
27
+ // restaurant gives one URL.
28
+ export function normalizeRestaurant(raw) {
29
+ let url
30
+ try {
31
+ url = new URL(raw.trim())
32
+ } catch {
33
+ return null
34
+ }
35
+ if (!/(^|\.)tripadvisor\.[a-z.]+$/.test(url.hostname)) return null
36
+ const m = url.pathname.match(/\/Restaurant_Review-g\d+-(d\d+)[^/]*\.html$/)
37
+ if (!m) return null
38
+ return { id: m[1], url: `${url.origin}${url.pathname}` }
39
+ }
40
+
41
+ const MEDIA = /https?:\/\/(?:dynamic-media-cdn|media-cdn)\.tripadvisor\.com\/media\/photo-([a-z])\/(?:\d{3,}\/)?([\w-]+(?:\/[\w-]+){2,5}\/[^\s"'\\)?<>]+?\.(?:jpe?g|png|webp))/gi
42
+
43
+ // Page HTML often carries URLs inside JSON, with slashes escaped as \/ or \u002F.
44
+ function unescape(text) {
45
+ return text.replace(/\\u002F/gi, '/').replace(/\\\//g, '/').replace(/&amp;/g, '&')
46
+ }
47
+
48
+ // Every TripAdvisor photo URL in some text, once per photo (the same photo appears in several
49
+ // sizes: photo-s, photo-l, photo-o...). Reviewer avatars, maps and logos are left out.
50
+ // Returns [{ key, size, url }] in the order first seen; `key` is the path after the size.
51
+ export function findPhotos(text) {
52
+ const found = new Map()
53
+ for (const m of unescape(text).matchAll(MEDIA)) {
54
+ const [url, size, key] = m
55
+ if (/avatar|\/map|logo/i.test(key)) continue
56
+ if (!found.has(key)) found.set(key, { key, size, url })
57
+ }
58
+ return [...found.values()]
59
+ }
60
+
61
+ // Candidate URLs for one photo, biggest first: the original, then the next sizes, then the size the
62
+ // page itself showed. Not every size exists for every photo, so the caller tries them in turn.
63
+ export function sizeCandidates(photo) {
64
+ const base = 'https://dynamic-media-cdn.tripadvisor.com/media'
65
+ const urls = ['o', 'w', 'l'].map((size) => `${base}/photo-${size}/${photo.key}`)
66
+ const own = photo.url.replace(/\?.*$/, '')
67
+ if (!urls.includes(own)) urls.push(own)
68
+ return urls
69
+ }
70
+
71
+ // '1a/2b/3c/4d/name.jpg' -> '1a2b3c4d-name.jpg': unique per photo and stable between runs.
72
+ export function fileName(photo) {
73
+ const parts = photo.key.split('/')
74
+ const name = parts.pop()
75
+ const ext = (name.match(/\.(jpe?g|png|webp)$/i)?.[1] ?? 'jpg').toLowerCase().replace('jpeg', 'jpg')
76
+ const stem = name.replace(/\.\w+$/, '')
77
+ return `${parts.join('')}-${stem}.${ext}`.replace(/[^\w.-]/g, '_')
78
+ }
@@ -0,0 +1,55 @@
1
+ // Downloads the photos of a restaurant's TripAdvisor page, in the user's own Edge window.
2
+ // Usage: tablefacts photos tripadvisor --out <folder> [--cdp <url>] <restaurant link>...
3
+ // The work is in index.mjs (downloadTripadvisor); this file is only the command line.
4
+ import { TablefactsError, cliMessage, exitCodeFor } from '../lib/errors.mjs'
5
+ import { consoleLog } from '../lib/log.mjs'
6
+ import { downloadTripadvisor } from './index.mjs'
7
+ import { parseArgs } from './links.mjs'
8
+
9
+ const HELP = `Download the photos of a restaurant's TripAdvisor page.
10
+
11
+ tablefacts photos tripadvisor --out <folder> [options] <restaurant link>...
12
+
13
+ --out <folder> where the images are saved (required, created if missing)
14
+ --cdp <url> attach to Edge on this debugging address (default http://localhost:9222). If nothing
15
+ is listening there the script starts Edge itself
16
+ --edge-dir <dir> profile folder of that Edge (default C:\\ig-edge, shared with photos instagram)
17
+ --max <n> stop after n photos per restaurant (default: all it can reach)
18
+ --dry-run print the photo URLs found, save nothing
19
+ --debug save the page HTML into <folder>/_debug, to fix the selectors
20
+ --help this text
21
+
22
+ The link is the restaurant's page, like https://www.tripadvisor.com/Restaurant_Review-g297478-d123-Reviews-Name.html
23
+ TripAdvisor shows a "verify you are human" check to automated browsers: if it appears in the window,
24
+ tick it yourself. This script waits for it and never tries to get around it.
25
+
26
+ Only download photos the restaurant owns or has allowed you to use. Most photos on TripAdvisor were
27
+ uploaded by guests, who keep the rights to them.`
28
+
29
+ const FLAGS = {
30
+ out: '--out <folder>',
31
+ links: 'the arguments',
32
+ cdp: '--cdp',
33
+ debug: '--debug',
34
+ }
35
+
36
+ let summary
37
+ try {
38
+ const opts = parseArgs(process.argv.slice(2))
39
+ if (opts.help) {
40
+ console.log(HELP)
41
+ process.exit(0)
42
+ }
43
+ if (!opts.out) {
44
+ console.error('Missing --out <folder>.\n\n' + HELP)
45
+ process.exit(2)
46
+ }
47
+ summary = await downloadTripadvisor({ ...opts, log: consoleLog })
48
+ } catch (err) {
49
+ if (!(err instanceof TablefactsError)) throw err
50
+ console.error(cliMessage(err, FLAGS))
51
+ process.exit(exitCodeFor(err))
52
+ }
53
+ console.log(`\nSaved ${summary.saved}, already there ${summary.skipped}, failed ${summary.failed.length}.`)
54
+ for (const f of summary.failed) console.log(` ${f.item}: ${cliMessage({ message: f.reason }, FLAGS)}`)
55
+ process.exit(summary.failed.length ? 1 : 0)
@@ -0,0 +1,65 @@
1
+ /** @typedef {import('./lib/types.mjs').Log} Log */
2
+ /** @typedef {import('./lib/types.mjs').Env} Env */
3
+ /** @typedef {import('./lib/types.mjs').ResearchOptions} ResearchOptions */
4
+ /** @typedef {import('./lib/types.mjs').ResearchResult} ResearchResult */
5
+ /** @typedef {import('./lib/types.mjs').ResearchPhoto} ResearchPhoto */
6
+ /** @typedef {import('./lib/types.mjs').ResearchProfile} ResearchProfile */
7
+ /** @typedef {import('./lib/types.mjs').InstagramOptions} InstagramOptions */
8
+ /** @typedef {import('./lib/types.mjs').TripadvisorOptions} TripadvisorOptions */
9
+ /** @typedef {import('./lib/types.mjs').PhotoSummary} PhotoSummary */
10
+ /** @typedef {import('./lib/types.mjs').PhotoFailure} PhotoFailure */
11
+ /** @typedef {import('./lib/types.mjs').Menu} Menu */
12
+ /** @typedef {import('./lib/types.mjs').MenuCategory} MenuCategory */
13
+ /** @typedef {import('./lib/types.mjs').MenuSection} MenuSection */
14
+ /** @typedef {import('./lib/types.mjs').MenuProduct} MenuProduct */
15
+ /** @typedef {import('./lib/types.mjs').MenuTotals} MenuTotals */
16
+ /** @typedef {import('./lib/types.mjs').MenuImage} MenuImage */
17
+ /** @typedef {import('./lib/types.mjs').MenuSourceConfig} MenuSourceConfig */
18
+ /** @typedef {import('./lib/types.mjs').CluviConfig} CluviConfig */
19
+ /** @typedef {import('./lib/types.mjs').RawConfig} RawConfig */
20
+ /** @typedef {import('./lib/types.mjs').ImportOptions} ImportOptions */
21
+ /** @typedef {import('./lib/types.mjs').ImportMenuOptions} ImportMenuOptions */
22
+ /** @typedef {import('./lib/types.mjs').ImportCluviOptions} ImportCluviOptions */
23
+ /** @typedef {import('./lib/types.mjs').ImportImageMenuOptions} ImportImageMenuOptions */
24
+ /** @typedef {import('./lib/types.mjs').ListMenuImagesOptions} ListMenuImagesOptions */
25
+ /** @typedef {import('./lib/types.mjs').ImportResult} ImportResult */
26
+ /** @typedef {import('./lib/types.mjs').PriceFormat} PriceFormat */
27
+ /** @typedef {import('./lib/types.mjs').VisionProvider} VisionProvider */
28
+ /** @typedef {import('./lib/types.mjs').LoadEnvOptions} LoadEnvOptions */
29
+ /** @typedef {import('./lib/types.mjs').TablefactsErrorCode} TablefactsErrorCode */
30
+ export type Log = import('./lib/types.mjs').Log;
31
+ export type Env = import('./lib/types.mjs').Env;
32
+ export type ResearchOptions = import('./lib/types.mjs').ResearchOptions;
33
+ export type ResearchResult = import('./lib/types.mjs').ResearchResult;
34
+ export type ResearchPhoto = import('./lib/types.mjs').ResearchPhoto;
35
+ export type ResearchProfile = import('./lib/types.mjs').ResearchProfile;
36
+ export type InstagramOptions = import('./lib/types.mjs').InstagramOptions;
37
+ export type TripadvisorOptions = import('./lib/types.mjs').TripadvisorOptions;
38
+ export type PhotoSummary = import('./lib/types.mjs').PhotoSummary;
39
+ export type PhotoFailure = import('./lib/types.mjs').PhotoFailure;
40
+ export type Menu = import('./lib/types.mjs').Menu;
41
+ export type MenuCategory = import('./lib/types.mjs').MenuCategory;
42
+ export type MenuSection = import('./lib/types.mjs').MenuSection;
43
+ export type MenuProduct = import('./lib/types.mjs').MenuProduct;
44
+ export type MenuTotals = import('./lib/types.mjs').MenuTotals;
45
+ export type MenuImage = import('./lib/types.mjs').MenuImage;
46
+ export type MenuSourceConfig = import('./lib/types.mjs').MenuSourceConfig;
47
+ export type CluviConfig = import('./lib/types.mjs').CluviConfig;
48
+ export type RawConfig = import('./lib/types.mjs').RawConfig;
49
+ export type ImportOptions = import('./lib/types.mjs').ImportOptions;
50
+ export type ImportMenuOptions = import('./lib/types.mjs').ImportMenuOptions;
51
+ export type ImportCluviOptions = import('./lib/types.mjs').ImportCluviOptions;
52
+ export type ImportImageMenuOptions = import('./lib/types.mjs').ImportImageMenuOptions;
53
+ export type ListMenuImagesOptions = import('./lib/types.mjs').ListMenuImagesOptions;
54
+ export type ImportResult = import('./lib/types.mjs').ImportResult;
55
+ export type PriceFormat = import('./lib/types.mjs').PriceFormat;
56
+ export type VisionProvider = import('./lib/types.mjs').VisionProvider;
57
+ export type LoadEnvOptions = import('./lib/types.mjs').LoadEnvOptions;
58
+ export type TablefactsErrorCode = import('./lib/types.mjs').TablefactsErrorCode;
59
+ export { research } from './research/index.mjs';
60
+ export { downloadInstagram } from './instagram/index.mjs';
61
+ export { downloadTripadvisor } from './tripadvisor/index.mjs';
62
+ export { importMenu, importCluvi, importImageMenu, listMenuImages, validateMenu, countMenu, normalizePages, parsePrice, providers, defaultProvider, } from './menu/index.mjs';
63
+ export { projectRoot, workDir, workDirIn, envFiles } from './lib/project.mjs';
64
+ export { TablefactsError } from './lib/errors.mjs';
65
+ export { loadEnv } from './lib/env.mjs';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Downloads the photos of Instagram posts (and optionally a whole profile) through toolzu.com.
3
+ * At least one of `links`, `file` or `profile` is required. Resolves to a summary whose `failed`
4
+ * lists `{ item, reason }` and whose `found` is present only in a dry run. Throws a TablefactsError
5
+ * on invalid arguments (code 'EUSAGE', with `option`), a missing playwright ('EDEPENDENCY') or a
6
+ * browser that cannot be reached.
7
+ * `out`, `file` and `userDataDir` resolve against `projectDir` (default: TABLEFACTS_PROJECT or the
8
+ * current folder). `viaGoogle` reaches toolzu through a Google search. `log(message, level)` gets
9
+ * level 'info', 'warn' or 'error'.
10
+ * @param {import('../lib/types.mjs').InstagramOptions} options
11
+ * @returns {Promise<import('../lib/types.mjs').PhotoSummary>}
12
+ */
13
+ export declare function downloadInstagram({ links, file, out, profile, pages, cdp, edgeDir, viaGoogle, browser, userDataDir, projectDir, headed, dryRun, debug, log: logOption, }?: import('../lib/types.mjs').InstagramOptions): Promise<import('../lib/types.mjs').PhotoSummary>;
@@ -0,0 +1,14 @@
1
+ export declare function parseArgs(argv: any): {
2
+ links: never[];
3
+ headed: boolean;
4
+ dryRun: boolean;
5
+ debug: boolean;
6
+ };
7
+ export declare function normalize(raw: any): {
8
+ shortcode: string;
9
+ url: string;
10
+ } | null;
11
+ export declare function parseProfile(raw: any): {
12
+ user: any;
13
+ url: string;
14
+ } | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ export declare const DEFAULT_CDP = "http://localhost:9222";
2
+ export declare const DEFAULT_EDGE_DIR = "C:\\ig-edge";
3
+ /**
4
+ * Whether a browser answers on the DevTools address.
5
+ * @param {string} cdp e.g. http://localhost:9222
6
+ * @returns {Promise<boolean>}
7
+ */
8
+ export declare function cdpUp(cdp: string): Promise<boolean>;
9
+ /**
10
+ * @param {string} cdp DevTools address; only localhost can be started
11
+ * @param {string} [edgeDir] Edge profile folder. Default C:\ig-edge
12
+ * @returns {Promise<void>}
13
+ */
14
+ export declare function ensureEdge(cdp: string, edgeDir?: string): Promise<void>;
@@ -0,0 +1,12 @@
1
+ /** `env` when given, else process.env. */
2
+ export declare const resolveEnv: (env: any) => any;
3
+ /** Loads `files` (default: the project's .env files) into `env`. The first file that sets a variable wins. Returns the files it read. */
4
+ export declare function loadEnvFiles(files?: string[], env?: NodeJS.ProcessEnv): string[];
5
+ /**
6
+ * Loads the project's env files (`projectDir`, else TABLEFACTS_PROJECT or the current folder) or
7
+ * exactly `files` into `env` (default process.env). Never overrides a variable that is already set.
8
+ * Returns the files it read.
9
+ * @param {import('./types.mjs').LoadEnvOptions} [options]
10
+ * @returns {string[]}
11
+ */
12
+ export declare function loadEnv({ projectDir, files, env }?: import('./types.mjs').LoadEnvOptions): string[];
@@ -0,0 +1,25 @@
1
+ export declare class TablefactsError extends Error {
2
+ /** @type {import('./types.mjs').TablefactsErrorCode} */
3
+ code: import('./types.mjs').TablefactsErrorCode;
4
+ /** The library option the error is about, when it is about one. @type {string=} */
5
+ option: string | undefined;
6
+ /**
7
+ * @param {string} message
8
+ * @param {import('./types.mjs').TablefactsErrorCode} code
9
+ * @param {{ cause?: unknown, option?: string }} [options] `option` names the library option the message is about.
10
+ */
11
+ constructor(message: string, code: import('./types.mjs').TablefactsErrorCode, options?: {
12
+ cause?: unknown;
13
+ option?: string;
14
+ });
15
+ }
16
+ /** Bad or missing arguments: the CLIs exit with 2. */
17
+ export declare const usageError: (message: any) => TablefactsError;
18
+ /** The exit code a CLI uses for an error it reports: 2 for bad arguments (EUSAGE), 1 for anything else. */
19
+ export declare const exitCodeFor: (err: any) => 1 | 2;
20
+ /** "fooBar" -> "foo-bar". */
21
+ export declare const kebab: (name: any) => string;
22
+ /** An error about a library option. Write the option in backticks in the message, e.g. "`out` is required". */
23
+ export declare const optionError: (option: any, message: any, code?: string) => TablefactsError;
24
+ /** The message with `option` tokens replaced by CLI flag text from `flags` ({ out: '--out <folder>' }). */
25
+ export declare const cliMessage: (err: any, flags?: {}) => string;
@@ -0,0 +1 @@
1
+ export declare function exists(path: any): Promise<boolean>;
@@ -0,0 +1,6 @@
1
+ /** Throws EFAILED unless the response is a good image: ok, an image/* content type and at least `minBytes` bytes. */
2
+ export declare function assertImageResponse({ ok, status, contentType, bytes, minBytes, label }?: {
3
+ minBytes?: number | undefined;
4
+ }): void;
5
+ /** Writes the bytes to `file`, creating its folder first. */
6
+ export declare function writeImage(file: any, bytes: any): Promise<void>;
@@ -0,0 +1,5 @@
1
+ export declare const noopLog: () => void;
2
+ /** For CLIs: info goes to stdout, warn and error to stderr. */
3
+ export declare const consoleLog: (message: any, level?: string) => void;
4
+ /** Always returns a function that is called as log(message, level); tolerates a missing log or one that takes one argument. */
5
+ export declare function normalizeLog(log: any): (message: any, level?: string) => void;
@@ -0,0 +1,30 @@
1
+ export declare const DELAY_MS = 2500;
2
+ /** { saved, skipped, failed: [{ item, reason }], found? } where `found` ([{ name, url }]) exists only in a dry run. */
3
+ export declare function newSummary({ dryRun }?: {
4
+ dryRun?: boolean | undefined;
5
+ }): {
6
+ saved: number;
7
+ skipped: number;
8
+ failed: never[];
9
+ };
10
+ /** Resolves the output folder and creates it (not in a dry run), plus <out>/_debug when `debug`. */
11
+ export declare function prepareOut({ out, dryRun, debug, projectDir }?: {
12
+ debug?: boolean | undefined;
13
+ dryRun?: boolean | undefined;
14
+ }): Promise<{
15
+ outDir: string;
16
+ debugDir: string | null;
17
+ }>;
18
+ /**
19
+ * Dry run: records { name, url } in summary.found. File already there: counts it as skipped.
20
+ * Otherwise awaits `save({ file, name, url })` and counts it as saved. A save error is not caught.
21
+ */
22
+ export declare function storeFile({ outDir, name, url, dryRun, summary, log, save }: {
23
+ dryRun: any;
24
+ log: any;
25
+ name: any;
26
+ outDir: any;
27
+ save: any;
28
+ summary: any;
29
+ url: any;
30
+ }): Promise<void>;