@sambitcreate/parsely-cli 2.2.0 → 2.3.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/dist/cli.js +4 -1
- package/dist/components/Banner.d.ts +1 -1
- package/dist/components/Footer.d.ts +2 -2
- package/dist/components/LandingScreen.js +1 -8
- package/dist/components/PhaseRail.d.ts +1 -1
- package/dist/components/RecipeCard.js +3 -11
- package/dist/services/scraper.js +4 -2
- package/dist/utils/helpers.d.ts +2 -0
- package/dist/utils/helpers.js +8 -0
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { render } from 'ink';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
4
5
|
import { App } from './app.js';
|
|
5
6
|
import { sanitizeTerminalText } from './utils/helpers.js';
|
|
6
7
|
import { createSynchronizedWriteProxy, resetDefaultTerminalBackground, shouldUseDisplayPalette, shouldUseSynchronizedOutput, } from './utils/terminal.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { version } = require('../package.json');
|
|
7
10
|
const ENTER_ALT_SCREEN = '\u001B[?1049h\u001B[2J\u001B[H';
|
|
8
11
|
const EXIT_ALT_SCREEN = '\u001B[?1049l';
|
|
9
12
|
// Simple arg parsing – accept an optional recipe URL as the first positional arg
|
|
@@ -32,7 +35,7 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
32
35
|
}
|
|
33
36
|
// Handle --version / -v
|
|
34
37
|
if (args.includes('--version') || args.includes('-v')) {
|
|
35
|
-
console.log(
|
|
38
|
+
console.log(`parsely-cli v${version}`);
|
|
36
39
|
process.exit(0);
|
|
37
40
|
}
|
|
38
41
|
async function main() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AppPhase } from '../utils/helpers.js';
|
|
2
|
+
export type { AppPhase };
|
|
2
3
|
interface FooterProps {
|
|
3
4
|
phase: AppPhase;
|
|
4
5
|
width: number;
|
|
5
6
|
}
|
|
6
7
|
export declare function Footer({ phase, width }: FooterProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export {};
|
|
@@ -5,6 +5,7 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { URLInput } from './URLInput.js';
|
|
7
7
|
import { theme } from '../theme.js';
|
|
8
|
+
import { buildOccurrenceKeys } from '../utils/helpers.js';
|
|
8
9
|
function readLogoSvg() {
|
|
9
10
|
try {
|
|
10
11
|
return readFileSync(fileURLToPath(new URL('../../public/parsely-logo.svg', import.meta.url)), 'utf8');
|
|
@@ -41,14 +42,6 @@ function stripCommonIndent(lines) {
|
|
|
41
42
|
}
|
|
42
43
|
return lines.map((line) => line.slice(indent));
|
|
43
44
|
}
|
|
44
|
-
function buildOccurrenceKeys(items) {
|
|
45
|
-
const counts = new Map();
|
|
46
|
-
return items.map((item) => {
|
|
47
|
-
const count = (counts.get(item) ?? 0) + 1;
|
|
48
|
-
counts.set(item, count);
|
|
49
|
-
return `${item}-${count}`;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
45
|
function buildLandingArt() {
|
|
53
46
|
const rendered = CFonts.render('Parsely', {
|
|
54
47
|
font: 'block',
|
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import BigText from 'ink-big-text';
|
|
5
5
|
import { theme } from '../theme.js';
|
|
6
|
-
import { formatMinutes, getUrlHost, isoToMinutes } from '../utils/helpers.js';
|
|
6
|
+
import { buildOccurrenceKeys, formatMinutes, getUrlHost, isoToMinutes } from '../utils/helpers.js';
|
|
7
7
|
import { wrapText } from '../utils/text-layout.js';
|
|
8
8
|
function extractInstructions(recipe) {
|
|
9
9
|
const raw = recipe.recipeInstructions;
|
|
@@ -33,14 +33,6 @@ function extractInstructions(recipe) {
|
|
|
33
33
|
}
|
|
34
34
|
return steps;
|
|
35
35
|
}
|
|
36
|
-
function buildOccurrenceKeys(items) {
|
|
37
|
-
const counts = new Map();
|
|
38
|
-
return items.map((item) => {
|
|
39
|
-
const count = (counts.get(item) ?? 0) + 1;
|
|
40
|
-
counts.set(item, count);
|
|
41
|
-
return `${item}-${count}`;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
36
|
function formatTimeValue(iso) {
|
|
45
37
|
if (!iso)
|
|
46
38
|
return 'Not listed';
|
|
@@ -101,7 +93,7 @@ function buildCompactHeaderLines(recipe, sourceHost, contentWidth) {
|
|
|
101
93
|
'',
|
|
102
94
|
];
|
|
103
95
|
}
|
|
104
|
-
function buildCompactBodyLines(
|
|
96
|
+
function buildCompactBodyLines(sourceHost, sourceLabel, sourceCopy, contentWidth, ingredients, instructions) {
|
|
105
97
|
const lines = [];
|
|
106
98
|
lines.push('Ingredients');
|
|
107
99
|
lines.push(buildRule(contentWidth, contentWidth));
|
|
@@ -160,7 +152,7 @@ export function RecipeCard({ recipe, width, height, sourceUrl }) {
|
|
|
160
152
|
const constrained = width < 110 || height < 34;
|
|
161
153
|
const compactContentWidth = Math.max(24, width - 4);
|
|
162
154
|
const compactHeaderLines = buildCompactHeaderLines(recipe, sourceHost, compactContentWidth);
|
|
163
|
-
const compactBodyLines = buildCompactBodyLines(
|
|
155
|
+
const compactBodyLines = buildCompactBodyLines(sourceHost, sourceLabel, sourceCopy, compactContentWidth, ingredients, instructions);
|
|
164
156
|
const compactBodyHeight = Math.max(4, height - compactHeaderLines.length - 2);
|
|
165
157
|
const maxScroll = Math.max(0, compactBodyLines.length - compactBodyHeight);
|
|
166
158
|
const visibleBodyLines = compactBodyLines.slice(scrollOffset, scrollOffset + compactBodyHeight);
|
package/dist/services/scraper.js
CHANGED
|
@@ -10,7 +10,7 @@ const BROWSER_ARGS = [
|
|
|
10
10
|
'--disable-blink-features=AutomationControlled',
|
|
11
11
|
];
|
|
12
12
|
const BROWSER_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
|
|
13
|
-
'(KHTML, like Gecko) Chrome/
|
|
13
|
+
'(KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36';
|
|
14
14
|
const PAGE_TIMEOUT_MS = 20_000;
|
|
15
15
|
const NETWORK_IDLE_TIMEOUT_MS = 5_000;
|
|
16
16
|
const AI_TIMEOUT_MS = 30_000;
|
|
@@ -190,7 +190,9 @@ async function configurePage(page) {
|
|
|
190
190
|
await page.evaluateOnNewDocument(() => {
|
|
191
191
|
Object.defineProperty(navigator, 'webdriver', { get: () => false });
|
|
192
192
|
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
193
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
193
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
194
|
+
get: () => Object.assign([{}, {}, {}], { length: 3, refresh: () => { } }),
|
|
195
|
+
});
|
|
194
196
|
});
|
|
195
197
|
}
|
|
196
198
|
function createTimedSignal(signal, timeoutMs) {
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -21,3 +21,5 @@ export declare function getUrlHost(url?: string): string;
|
|
|
21
21
|
* Basic URL validation.
|
|
22
22
|
*/
|
|
23
23
|
export declare function isValidUrl(url: string): boolean;
|
|
24
|
+
export type AppPhase = 'idle' | 'scraping' | 'display' | 'error';
|
|
25
|
+
export declare function buildOccurrenceKeys(items: string[]): string[];
|
package/dist/utils/helpers.js
CHANGED
|
@@ -79,3 +79,11 @@ export function isValidUrl(url) {
|
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
+
export function buildOccurrenceKeys(items) {
|
|
83
|
+
const counts = new Map();
|
|
84
|
+
return items.map((item) => {
|
|
85
|
+
const count = (counts.get(item) ?? 0) + 1;
|
|
86
|
+
counts.set(item, count);
|
|
87
|
+
return `${item}-${count}`;
|
|
88
|
+
});
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sambitcreate/parsely-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A smart recipe scraper CLI with interactive TUI built on Ink",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -43,20 +43,20 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/sambitcreate/parsely-cli#readme",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"cheerio": "^1.0.0",
|
|
47
46
|
"cfonts": "^3.1.1",
|
|
47
|
+
"cheerio": "^1.0.0",
|
|
48
48
|
"dotenv": "^16.4.7",
|
|
49
49
|
"ink": "^5.1.0",
|
|
50
50
|
"ink-big-text": "^2.0.0",
|
|
51
51
|
"ink-spinner": "^5.0.0",
|
|
52
52
|
"ink-text-input": "^6.0.0",
|
|
53
53
|
"openai": "^4.82.0",
|
|
54
|
-
"puppeteer-core": "^24.
|
|
54
|
+
"puppeteer-core": "^24.38.0",
|
|
55
55
|
"react": "^18.3.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@types/react": "^18.3.18",
|
|
59
58
|
"@types/ink-big-text": "^1.2.4",
|
|
59
|
+
"@types/react": "^18.3.28",
|
|
60
60
|
"tsx": "^4.19.2",
|
|
61
61
|
"typescript": "^5.7.3"
|
|
62
62
|
},
|