@envodium/envodium-cli 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.
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.capturePageErrors = capturePageErrors;
7
+ const puppeteer_1 = __importDefault(require("puppeteer"));
8
+ /**
9
+ * Opens a URL in a visible Chrome window. The user can interact with the page.
10
+ * All JS errors/console output are captured in the background.
11
+ * When the user closes the browser window, results are returned.
12
+ */
13
+ async function capturePageErrors(url, opts) {
14
+ const isHeadless = opts?.headless ?? false;
15
+ const consoleLogs = [];
16
+ const errors = [];
17
+ const browser = await puppeteer_1.default.launch({
18
+ headless: isHeadless,
19
+ defaultViewport: isHeadless ? { width: 1920, height: 1080 } : null,
20
+ args: ["--no-sandbox", "--disable-setuid-sandbox", ...(isHeadless ? [] : ["--start-maximized"])]
21
+ });
22
+ try {
23
+ const page = await browser.newPage();
24
+ // Auto-dismiss alert/confirm/prompt dialogs so they don't block
25
+ page.on("dialog", async (dialog) => {
26
+ consoleLogs.push({ type: "dialog", text: `${dialog.type()}: ${dialog.message()}` });
27
+ await dialog.dismiss();
28
+ });
29
+ // Capture console.log / console.error / console.warn etc.
30
+ page.on("console", (msg) => {
31
+ consoleLogs.push({ type: msg.type(), text: msg.text() });
32
+ });
33
+ // Capture uncaught exceptions
34
+ page.on("pageerror", (err) => {
35
+ errors.push(err.message || String(err));
36
+ });
37
+ // Capture failed requests (like 404 scripts)
38
+ page.on("requestfailed", (req) => {
39
+ errors.push(`Request failed: ${req.url()} (${req.failure()?.errorText ?? "unknown"})`);
40
+ });
41
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 });
42
+ if (isHeadless) {
43
+ // In headless mode, wait a few seconds for deferred JS then close
44
+ await new Promise((r) => setTimeout(r, 3000));
45
+ await browser.close();
46
+ }
47
+ else {
48
+ // In visible mode, wait until the user closes the browser
49
+ await new Promise((resolve) => {
50
+ browser.on("disconnected", () => resolve());
51
+ });
52
+ }
53
+ }
54
+ catch (err) {
55
+ // If browser was closed during goto, that's fine
56
+ if (!String(err).includes("Target closed") && !String(err).includes("Protocol error")) {
57
+ throw err;
58
+ }
59
+ }
60
+ return { consoleLogs, errors };
61
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const core_1 = require("./core");
5
+ async function main() {
6
+ const cmd = (process.argv[2] || "").toLowerCase();
7
+ const cwd = process.cwd();
8
+ if (cmd === "-v" || cmd === "--version" || cmd === "-version") {
9
+ const pkg = require("../package.json");
10
+ console.log(pkg.version);
11
+ return;
12
+ }
13
+ try {
14
+ if (cmd === "build") {
15
+ await (0, core_1.build)({ cwd, addTestParam: false, headless: false });
16
+ return;
17
+ }
18
+ if (cmd === "test") {
19
+ const headless = process.argv.includes("-headless") || process.argv.includes("--headless");
20
+ const noBrowser = process.argv.includes("-no") || process.argv.includes("--no");
21
+ await (0, core_1.build)({ cwd, addTestParam: true, headless, noBrowser });
22
+ return;
23
+ }
24
+ if (cmd === "meta") {
25
+ const input = process.argv[3] || "";
26
+ if (!input) {
27
+ console.log("Usage: envodium meta <input>");
28
+ return;
29
+ }
30
+ await (0, core_1.meta)({ cwd, input });
31
+ return;
32
+ }
33
+ if (cmd === "sync") {
34
+ await (0, core_1.sync)({ cwd });
35
+ return;
36
+ }
37
+ console.log("Usage: envodium build | test | sync | meta <input>");
38
+ }
39
+ catch (err) {
40
+ const e = err;
41
+ console.error("Fel:", e?.message ?? String(err));
42
+ process.exit(1);
43
+ }
44
+ }
45
+ main();
package/dist/config.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readProjectConfig = readProjectConfig;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function readProjectConfig(cwd) {
10
+ const vscodePath = path_1.default.join(cwd, "vscode.js");
11
+ let endpoint;
12
+ let bearer;
13
+ if (fs_1.default.existsSync(vscodePath)) {
14
+ // CommonJS require
15
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
16
+ const mod = require(vscodePath);
17
+ endpoint = mod.endpoint;
18
+ bearer = mod.bearer;
19
+ }
20
+ // env vars override (nice for CI/Claude/Codex)
21
+ endpoint = process.env.ENVODIUM_ENDPOINT || endpoint;
22
+ bearer = process.env.ENVODIUM_BEARER || bearer;
23
+ return { endpoint, bearer };
24
+ }
package/dist/core.js ADDED
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.build = build;
7
+ exports.meta = meta;
8
+ exports.sync = sync;
9
+ const path_1 = __importDefault(require("path"));
10
+ const url_1 = require("url");
11
+ const config_1 = require("./config");
12
+ const http_1 = require("./http");
13
+ const files_1 = require("./files");
14
+ const util_1 = require("./util");
15
+ const browser_1 = require("./browser");
16
+ async function build(opts) {
17
+ const { cwd, addTestParam, headless, noBrowser } = opts;
18
+ const cfg = await (0, config_1.readProjectConfig)(cwd);
19
+ if (!cfg.endpoint)
20
+ throw new Error("Ingen endpoint hittad (vscode.js saknas eller saknar endpoint).");
21
+ let endpoint = cfg.endpoint;
22
+ if (addTestParam) {
23
+ endpoint = (0, util_1.addOrReplaceQueryParam)(endpoint, "test", (0, util_1.newGuid)());
24
+ }
25
+ const files = await (0, files_1.collectFiles)(cwd);
26
+ const items = [];
27
+ for (const abs of files) {
28
+ const title = path_1.default.basename(abs);
29
+ const value = await files_1.writeFileUtf8.read(abs);
30
+ items.push({ title, value, lang: getLangFromFileName(title), openintab: false, deleted: false });
31
+ }
32
+ const body = JSON.stringify(items);
33
+ const headers = {
34
+ "Content-Type": "application/json",
35
+ "Content-Length": String(Buffer.byteLength(body, "utf8"))
36
+ };
37
+ if (cfg.bearer)
38
+ headers["Authorization"] = `Bearer ${cfg.bearer}`;
39
+ const res = await (0, http_1.httpSend)(endpoint, "POST", headers, body);
40
+ console.log("Status:", res.statusCode);
41
+ if (res.statusCode < 200 || res.statusCode >= 300) {
42
+ console.log(res.body);
43
+ throw new Error(`HTTP ${res.statusCode}`);
44
+ }
45
+ // Print preview URL (same logic as VS Code extension's buildPreviewUrlFromEndpoint)
46
+ const previewUrl = buildPreviewUrl(endpoint, cfg.bearer);
47
+ if (previewUrl) {
48
+ console.log(previewUrl);
49
+ }
50
+ // If test mode, open Chrome to capture JS errors
51
+ if (addTestParam && previewUrl && !noBrowser) {
52
+ if (headless) {
53
+ console.log("\nOpening headless Chrome to capture JS errors...");
54
+ }
55
+ else {
56
+ console.log("\nOpening Chrome — close the browser window when done testing...");
57
+ }
58
+ const result = await (0, browser_1.capturePageErrors)(previewUrl, { headless: !!headless });
59
+ if (result.errors.length > 0) {
60
+ console.log(`\n--- JS Errors (${result.errors.length}) ---`);
61
+ for (const err of result.errors) {
62
+ console.error(" ERROR:", err);
63
+ }
64
+ }
65
+ const consoleErrors = result.consoleLogs.filter((l) => l.type === "error");
66
+ const consoleWarnings = result.consoleLogs.filter((l) => l.type === "warning");
67
+ const consoleMsgs = result.consoleLogs.filter((l) => l.type !== "error" && l.type !== "warning");
68
+ if (consoleErrors.length > 0) {
69
+ console.log(`\n--- Console Errors (${consoleErrors.length}) ---`);
70
+ for (const l of consoleErrors)
71
+ console.error(" console.error:", l.text);
72
+ }
73
+ if (consoleWarnings.length > 0) {
74
+ console.log(`\n--- Console Warnings (${consoleWarnings.length}) ---`);
75
+ for (const l of consoleWarnings)
76
+ console.warn(" console.warn:", l.text);
77
+ }
78
+ if (consoleMsgs.length > 0) {
79
+ console.log(`\n--- Console Output (${consoleMsgs.length}) ---`);
80
+ for (const l of consoleMsgs)
81
+ console.log(` console.${l.type}:`, l.text);
82
+ }
83
+ const totalIssues = result.errors.length + consoleErrors.length;
84
+ if (totalIssues === 0) {
85
+ console.log("\nNo JS errors detected.");
86
+ }
87
+ else {
88
+ console.log(`\nTotal issues: ${totalIssues}`);
89
+ }
90
+ }
91
+ }
92
+ function buildPreviewUrl(endpoint, bearer) {
93
+ try {
94
+ const u = new url_1.URL(endpoint);
95
+ const guid = u.searchParams.get("guid");
96
+ if (!guid)
97
+ return null;
98
+ u.pathname = u.pathname.replace(/vscode\.aspx$/i, "vscode_view.aspx");
99
+ const params = new URLSearchParams();
100
+ params.set("guid", guid);
101
+ const test = u.searchParams.get("test");
102
+ if (test)
103
+ params.set("test", test);
104
+ if (bearer)
105
+ params.set("bearer", bearer);
106
+ u.search = `?${params.toString()}`;
107
+ return u.toString();
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ async function meta(opts) {
114
+ const { cwd, input } = opts;
115
+ const cfg = await (0, config_1.readProjectConfig)(cwd);
116
+ if (!cfg.endpoint)
117
+ throw new Error("Ingen endpoint hittad (vscode.js saknas eller saknar endpoint).");
118
+ // Replace vscode.aspx with meta.aspx
119
+ let endpoint = cfg.endpoint.replace(/vscode\.aspx/i, "meta.aspx");
120
+ endpoint = (0, util_1.addOrReplaceQueryParam)(endpoint, "input", input);
121
+ if (cfg.bearer)
122
+ endpoint = (0, util_1.addOrReplaceQueryParam)(endpoint, "bearer", cfg.bearer);
123
+ const headers = {};
124
+ if (cfg.bearer)
125
+ headers["Authorization"] = `Bearer ${cfg.bearer}`;
126
+ const res = await (0, http_1.httpSend)(endpoint, "POST", headers, "");
127
+ console.log("Status:", res.statusCode);
128
+ if (res.body)
129
+ console.log(res.body);
130
+ if (res.statusCode < 200 || res.statusCode >= 300) {
131
+ throw new Error(`HTTP ${res.statusCode}`);
132
+ }
133
+ }
134
+ function getLangFromFileName(name) {
135
+ const lower = name.toLowerCase();
136
+ if (lower.endsWith(".html") || lower.endsWith(".htm"))
137
+ return "html";
138
+ if (lower.endsWith(".js"))
139
+ return "javascript";
140
+ if (lower.endsWith(".css"))
141
+ return "css";
142
+ return "text";
143
+ }
144
+ function buildSyncUrl(endpoint, bearer) {
145
+ try {
146
+ const u = new url_1.URL(endpoint);
147
+ const guid = u.searchParams.get("guid");
148
+ if (!guid)
149
+ return null;
150
+ u.pathname = u.pathname.replace(/vscode\.aspx$/i, "vscode_sync.aspx");
151
+ const params = new URLSearchParams();
152
+ params.set("guid", guid);
153
+ if (bearer)
154
+ params.set("bearer", bearer);
155
+ u.search = `?${params.toString()}`;
156
+ return u.toString();
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ function ensureExtension(name, fallbackExt = ".html") {
163
+ if (!name)
164
+ return "untitled" + fallbackExt;
165
+ if (/\.[a-z0-9]+$/i.test(name))
166
+ return name;
167
+ return name + (fallbackExt.startsWith(".") ? fallbackExt : "." + fallbackExt);
168
+ }
169
+ async function sync(opts) {
170
+ const { cwd } = opts;
171
+ const cfg = await (0, config_1.readProjectConfig)(cwd);
172
+ if (!cfg.endpoint)
173
+ throw new Error("Ingen endpoint hittad (vscode.js saknas eller saknar endpoint).");
174
+ // Build sync URL: vscode.aspx -> vscode_sync.aspx, add guid + bearer as query params
175
+ const syncUrl = buildSyncUrl(cfg.endpoint, cfg.bearer);
176
+ if (!syncUrl)
177
+ throw new Error("Kunde inte bygga sync-URL (saknar guid i endpoint?).");
178
+ const headers = {};
179
+ if (cfg.bearer)
180
+ headers["Authorization"] = `Bearer ${cfg.bearer}`;
181
+ console.log("Syncing from server...");
182
+ const res = await (0, http_1.httpSend)(syncUrl, "GET", headers, "");
183
+ if (res.statusCode < 200 || res.statusCode >= 300) {
184
+ console.log(res.body);
185
+ throw new Error(`Sync misslyckades: HTTP ${res.statusCode}`);
186
+ }
187
+ let payload;
188
+ try {
189
+ payload = JSON.parse(res.body);
190
+ }
191
+ catch {
192
+ throw new Error("Svar var inte giltig JSON-array.");
193
+ }
194
+ if (!Array.isArray(payload))
195
+ throw new Error("Svar är inte en array.");
196
+ // Build a map of existing local files by basename
197
+ const existingFiles = await (0, files_1.collectFiles)(cwd);
198
+ const byBase = new Map();
199
+ for (const abs of existingFiles) {
200
+ const base = path_1.default.basename(abs).toLowerCase();
201
+ const list = byBase.get(base) ?? [];
202
+ list.push(abs);
203
+ byBase.set(base, list);
204
+ }
205
+ let updated = 0;
206
+ let created = 0;
207
+ for (const item of payload) {
208
+ if (!item?.title)
209
+ continue;
210
+ // Ensure file has an extension
211
+ const desiredTitle = ensureExtension(item.title, ".html");
212
+ const baseKey = desiredTitle.toLowerCase();
213
+ // Skip vscode.js
214
+ if (baseKey === "vscode.js")
215
+ continue;
216
+ let targetPath;
217
+ const candidates = byBase.get(baseKey);
218
+ if (candidates && candidates.length > 0) {
219
+ // Pick the one with the shortest relative path
220
+ targetPath = candidates.sort((a, b) => {
221
+ const ra = path_1.default.relative(cwd, a);
222
+ const rb = path_1.default.relative(cwd, b);
223
+ if (ra.length !== rb.length)
224
+ return ra.length - rb.length;
225
+ return ra.localeCompare(rb);
226
+ })[0];
227
+ updated++;
228
+ }
229
+ else {
230
+ // Create in project root
231
+ targetPath = path_1.default.join(cwd, desiredTitle);
232
+ created++;
233
+ }
234
+ await files_1.writeFileUtf8.write(targetPath, item.value ?? "");
235
+ console.log(` ${candidates ? "Updated" : "Created"}: ${path_1.default.relative(cwd, targetPath)}`);
236
+ }
237
+ console.log(`\nSync klar – uppdaterade ${updated}, skapade ${created}.`);
238
+ }
package/dist/files.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeFileUtf8 = void 0;
7
+ exports.collectFiles = collectFiles;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ async function collectFiles(cwd) {
11
+ const result = [];
12
+ // If a dist/ folder exists, only collect files from there
13
+ const distDir = path_1.default.join(cwd, "dist");
14
+ let useDistOnly = false;
15
+ try {
16
+ const stat = await promises_1.default.stat(distDir);
17
+ useDistOnly = stat.isDirectory();
18
+ }
19
+ catch {
20
+ // dist doesn't exist, collect from cwd as normal
21
+ }
22
+ const rootDir = useDistOnly ? distDir : cwd;
23
+ async function walk(dir) {
24
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
25
+ for (const e of entries) {
26
+ const abs = path_1.default.join(dir, e.name);
27
+ if (e.isDirectory()) {
28
+ if (["node_modules", ".git", "dist", "out", "bin", "obj", "coverage"].includes(e.name))
29
+ continue;
30
+ await walk(abs);
31
+ }
32
+ else if (e.isFile()) {
33
+ const lower = e.name.toLowerCase();
34
+ // Skip vscode.js (config file, not part of the project payload)
35
+ if (lower === "vscode.js")
36
+ continue;
37
+ if (lower.endsWith(".html") || lower.endsWith(".htm") || lower.endsWith(".js") || lower.endsWith(".css")) {
38
+ result.push(abs);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ await walk(rootDir);
44
+ return result;
45
+ }
46
+ exports.writeFileUtf8 = {
47
+ async read(abs) {
48
+ const buf = await promises_1.default.readFile(abs);
49
+ return buf.toString("utf8");
50
+ },
51
+ async write(abs, content) {
52
+ await promises_1.default.writeFile(abs, content, "utf8");
53
+ }
54
+ };
package/dist/http.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.httpSend = httpSend;
37
+ const http = __importStar(require("http"));
38
+ const https = __importStar(require("https"));
39
+ const dns = __importStar(require("dns"));
40
+ const url_1 = require("url");
41
+ function httpSend(endpoint, method, headers, body) {
42
+ return new Promise((resolve, reject) => {
43
+ const url = new url_1.URL(endpoint);
44
+ const lib = url.protocol === "https:" ? https : http;
45
+ const req = lib.request({
46
+ method,
47
+ hostname: url.hostname,
48
+ port: url.port ? Number(url.port) : undefined,
49
+ path: url.pathname + url.search,
50
+ headers,
51
+ // Force IPv4 for localhost to avoid ECONNREFUSED on ::1
52
+ lookup: (hostname, options, callback) => {
53
+ dns.lookup(hostname, { ...options, family: 4 }, callback);
54
+ }
55
+ }, (res) => {
56
+ let data = "";
57
+ res.setEncoding("utf8");
58
+ res.on("data", (chunk) => (data += chunk));
59
+ res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data }));
60
+ });
61
+ req.on("error", reject);
62
+ req.setTimeout(30000, () => req.destroy(new Error("Timeout (30s)")));
63
+ if (method !== "GET" && body)
64
+ req.write(body);
65
+ req.end();
66
+ });
67
+ }
package/dist/util.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addOrReplaceQueryParam = addOrReplaceQueryParam;
7
+ exports.newGuid = newGuid;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const url_1 = require("url");
10
+ function addOrReplaceQueryParam(urlStr, key, value) {
11
+ const u = new url_1.URL(urlStr);
12
+ u.searchParams.set(key, value);
13
+ return u.toString();
14
+ }
15
+ function newGuid() {
16
+ const anyCrypto = crypto_1.default;
17
+ if (typeof anyCrypto.randomUUID === "function")
18
+ return anyCrypto.randomUUID();
19
+ const b = crypto_1.default.randomBytes(16);
20
+ b[6] = (b[6] & 0x0f) | 0x40;
21
+ b[8] = (b[8] & 0x3f) | 0x80;
22
+ const hex = [...b].map((n) => n.toString(16).padStart(2, "0"));
23
+ return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex
24
+ .slice(8, 10)
25
+ .join("")}-${hex.slice(10).join("")}`;
26
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@envodium/envodium-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Envodium build, test, sync and meta commands",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "envodium": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/**/*.js",
11
+ "dist/**/*.d.ts"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "envodium",
19
+ "cli",
20
+ "build",
21
+ "deploy"
22
+ ],
23
+ "license": "MIT",
24
+ "devDependencies": {
25
+ "@types/node": "^25.3.0",
26
+ "typescript": "^5.5.4"
27
+ },
28
+ "dependencies": {
29
+ "puppeteer": "^24.37.4"
30
+ }
31
+ }