@caido-utils/backend 0.1.0 → 0.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/index.d.ts CHANGED
@@ -1 +1,5 @@
1
1
  export { Fetch, type FetchOptions } from "./fetcher";
2
+ export { createSignal, pausePool, resumePool, runPool, stopPool } from "./pool";
3
+ export type { PoolCallbacks, PoolConfig, PoolSignal } from "./pool";
4
+ export { spawnCommand } from "./process";
5
+ export { fileExists, readFile, storeFile } from "./storage";
package/dist/index.js CHANGED
@@ -1 +1,4 @@
1
1
  export { Fetch } from "./fetcher.js";
2
+ export { createSignal, pausePool, resumePool, runPool, stopPool } from "./pool.js";
3
+ export { spawnCommand } from "./process.js";
4
+ export { fileExists, readFile, storeFile } from "./storage.js";
package/dist/pool.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ export type PoolSignal = {
2
+ stopped: boolean;
3
+ paused: boolean;
4
+ };
5
+ export type PoolConfig = {
6
+ concurrency: number;
7
+ delayMs?: number;
8
+ };
9
+ export type PoolCallbacks<TItem> = {
10
+ onProgress?: (progress: {
11
+ completed: number;
12
+ total: number;
13
+ }) => void;
14
+ onError?: (item: TItem, error: string) => void;
15
+ };
16
+ export declare function createSignal(): PoolSignal;
17
+ export declare function stopPool(signal: PoolSignal): void;
18
+ export declare function pausePool(signal: PoolSignal): void;
19
+ export declare function resumePool(signal: PoolSignal): void;
20
+ export declare function runPool<TItem>(opts: {
21
+ items: TItem[] | AsyncIterable<TItem>;
22
+ config: PoolConfig;
23
+ signal: PoolSignal;
24
+ run: (item: TItem) => Promise<void>;
25
+ callbacks?: PoolCallbacks<TItem>;
26
+ }): Promise<{
27
+ completed: number;
28
+ }>;
package/dist/pool.js ADDED
@@ -0,0 +1,85 @@
1
+ export function createSignal() {
2
+ return { stopped: false, paused: false };
3
+ }
4
+ export function stopPool(signal) {
5
+ signal.stopped = true;
6
+ }
7
+ export function pausePool(signal) {
8
+ signal.paused = true;
9
+ }
10
+ export function resumePool(signal) {
11
+ signal.paused = false;
12
+ }
13
+ const PAUSE_POLL_MS = 200;
14
+ const YIELD_EVERY = 10;
15
+ function yieldTick() {
16
+ return new Promise((resolve) => setTimeout(resolve, 0));
17
+ }
18
+ function delay(ms) {
19
+ if (ms <= 0) return Promise.resolve();
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ function waitForUnpause(signal) {
23
+ if (!signal.paused || signal.stopped) return Promise.resolve();
24
+ return new Promise((resolve) => {
25
+ const poll = () => {
26
+ if (!signal.paused || signal.stopped) {
27
+ resolve();
28
+ return;
29
+ }
30
+ setTimeout(poll, PAUSE_POLL_MS);
31
+ };
32
+ setTimeout(poll, PAUSE_POLL_MS);
33
+ });
34
+ }
35
+ async function materializeItems(source) {
36
+ if (Array.isArray(source)) return source;
37
+ const items = [];
38
+ for await (const item of source) {
39
+ items.push(item);
40
+ }
41
+ return items;
42
+ }
43
+ export async function runPool(opts) {
44
+ const { config, signal, run, callbacks } = opts;
45
+ const items = await materializeItems(opts.items);
46
+ const total = items.length;
47
+ const delayMs = config.delayMs ?? 0;
48
+ let nextIndex = 0;
49
+ let completed = 0;
50
+ async function worker() {
51
+ let localCount = 0;
52
+ while (nextIndex < items.length) {
53
+ if (signal.stopped) return;
54
+ await waitForUnpause(signal);
55
+ if (signal.stopped) return;
56
+ const idx = nextIndex++;
57
+ const item = items[idx];
58
+ if (item === void 0) return;
59
+ try {
60
+ await run(item);
61
+ completed++;
62
+ } catch (error) {
63
+ completed++;
64
+ if (callbacks?.onError) {
65
+ const message = error instanceof Error ? error.message : "Unknown error";
66
+ callbacks.onError(item, message);
67
+ }
68
+ }
69
+ if (callbacks?.onProgress) {
70
+ callbacks.onProgress({ completed, total });
71
+ }
72
+ localCount++;
73
+ if (localCount % YIELD_EVERY === 0) {
74
+ await yieldTick();
75
+ }
76
+ if (delayMs > 0) {
77
+ await delay(delayMs);
78
+ }
79
+ }
80
+ }
81
+ const workerCount = Math.min(config.concurrency, items.length);
82
+ const workers = Array.from({ length: workerCount }, () => worker());
83
+ await Promise.all(workers);
84
+ return { completed };
85
+ }
@@ -0,0 +1,2 @@
1
+ import type { SDK } from "caido:plugin";
2
+ export declare function spawnCommand(sdk: SDK, binary: string, args: string[], stdin?: string): Promise<string>;
@@ -0,0 +1,58 @@
1
+ import {
2
+ spawn as nodeSpawn
3
+ } from "child_process";
4
+ import { access, constants } from "fs/promises";
5
+ import * as path from "path";
6
+ async function ensureBinaryExecutable(binaryPath, sdk) {
7
+ try {
8
+ await access(binaryPath, constants.F_OK | constants.X_OK);
9
+ } catch {
10
+ try {
11
+ await makeExecutable(binaryPath);
12
+ } catch (chmodError) {
13
+ sdk.console.error(
14
+ `Failed to make ${binaryPath} executable: ${chmodError}`
15
+ );
16
+ throw new Error(`Cannot make binary executable: ${chmodError}`);
17
+ }
18
+ }
19
+ }
20
+ async function makeExecutable(binaryPath) {
21
+ const child = nodeSpawn("chmod", ["+x", binaryPath], {
22
+ shell: true
23
+ });
24
+ await driveChild(child);
25
+ }
26
+ export async function spawnCommand(sdk, binary, args, stdin) {
27
+ try {
28
+ const binaryPath = path.join(sdk.meta.assetsPath(), binary);
29
+ await ensureBinaryExecutable(binaryPath, sdk);
30
+ const child = nodeSpawn(binaryPath, args);
31
+ if (stdin !== void 0) {
32
+ child.stdin.write(stdin);
33
+ child.stdin.end();
34
+ }
35
+ const output = await driveChild(child);
36
+ return output;
37
+ } catch (error) {
38
+ sdk.console.error(`Spawn command error (${binary}): ${error}`);
39
+ throw error;
40
+ }
41
+ }
42
+ async function driveChild(child, allowExitCode1 = false) {
43
+ let output = "";
44
+ child.stdout.on("data", (data) => {
45
+ output += data.toString();
46
+ });
47
+ let error = "";
48
+ child.stderr.on("data", (data) => {
49
+ error += data.toString();
50
+ });
51
+ const exitCode = await new Promise((resolve) => {
52
+ child.on("close", resolve);
53
+ });
54
+ if (exitCode !== void 0 && exitCode !== 0 && !(allowExitCode1 && exitCode === 1)) {
55
+ throw new Error(`subprocess error exit ${exitCode}, ${error}`);
56
+ }
57
+ return output;
58
+ }
@@ -0,0 +1,9 @@
1
+ import type { SDK } from "caido:plugin";
2
+ type StoreFileOptions = {
3
+ data: string;
4
+ contentType?: string;
5
+ };
6
+ export declare function storeFile(sdk: SDK, options: StoreFileOptions): Promise<string>;
7
+ export declare function readFile(fsPath: string): Promise<string>;
8
+ export declare function fileExists(fsPath: string): Promise<boolean>;
9
+ export {};
@@ -0,0 +1,45 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ const extensionMap = {
4
+ "application/javascript": ".js",
5
+ "application/x-javascript": ".js",
6
+ "text/javascript": ".js",
7
+ "text/html": ".html",
8
+ "application/json": ".json",
9
+ "application/xml": ".xml",
10
+ "text/xml": ".xml",
11
+ "text/css": ".css",
12
+ "text/plain": ".txt",
13
+ "application/typescript": ".ts"
14
+ };
15
+ function getExtensionFromContentType(contentType) {
16
+ if (contentType === void 0) return "";
17
+ return extensionMap[contentType.toLowerCase()] ?? "";
18
+ }
19
+ function generateFileName(contentType) {
20
+ const randomName = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
21
+ const extension = getExtensionFromContentType(contentType);
22
+ return randomName + extension;
23
+ }
24
+ export async function storeFile(sdk, options) {
25
+ const { data, contentType } = options;
26
+ const dataDir = sdk.meta.path();
27
+ const filesDir = path.join(dataDir, "files");
28
+ await fs.mkdir(filesDir, { recursive: true });
29
+ const fileName = generateFileName(contentType);
30
+ const filePath = path.join(filesDir, fileName);
31
+ await fs.writeFile(filePath, data);
32
+ return filePath;
33
+ }
34
+ export async function readFile(fsPath) {
35
+ const content = await fs.readFile(fsPath);
36
+ return content.toString();
37
+ }
38
+ export async function fileExists(fsPath) {
39
+ try {
40
+ await fs.access(fsPath);
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caido-utils/backend",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -11,6 +11,9 @@
11
11
  "import": "./dist/index.js"
12
12
  }
13
13
  },
14
+ "scripts": {
15
+ "build": "unbuild"
16
+ },
14
17
  "peerDependencies": {
15
18
  "@caido/sdk-backend": ">=0.46.0"
16
19
  },
@@ -18,8 +21,5 @@
18
21
  "@caido/sdk-backend": "^0.46.0",
19
22
  "typescript": "^5.5.4",
20
23
  "unbuild": "^3.6.1"
21
- },
22
- "scripts": {
23
- "build": "unbuild"
24
24
  }
25
- }
25
+ }