@raycast/utils
Advanced tools
Comparing version 1.4.1 to 1.4.2
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { Image } from "@raycast/api"; | ||
@@ -3,0 +2,0 @@ import { URL } from "url"; |
@@ -1,3 +0,1 @@ | ||
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { CachedPromiseOptions } from "./useCachedPromise"; | ||
@@ -4,0 +2,0 @@ import { UseCachedPromiseReturnType } from "./types"; |
@@ -11,10 +11,7 @@ "use strict"; | ||
const node_child_process_1 = __importDefault(require("node:child_process")); | ||
const node_buffer_1 = require("node:buffer"); | ||
const node_stream_1 = __importDefault(require("node:stream")); | ||
const node_util_1 = require("node:util"); | ||
const react_1 = require("react"); | ||
const signal_exit_1 = __importDefault(require("signal-exit")); | ||
const useDeepMemo_1 = require("./useDeepMemo"); | ||
const useCachedPromise_1 = require("./useCachedPromise"); | ||
const useLatest_1 = require("./useLatest"); | ||
const exec_utils_1 = require("./exec-utils"); | ||
const SPACES_REGEXP = / +/g; | ||
@@ -39,125 +36,2 @@ function parseCommand(command, args) { | ||
} | ||
function getSpawnedPromise(spawned) { | ||
return new Promise((resolve, reject) => { | ||
spawned.on("exit", (exitCode, signal) => { | ||
resolve({ exitCode, signal, timedOut: false }); | ||
}); | ||
spawned.on("error", (error) => { | ||
reject(error); | ||
}); | ||
if (spawned.stdin) { | ||
spawned.stdin.on("error", (error) => { | ||
reject(error); | ||
}); | ||
} | ||
}); | ||
} | ||
function setupTimeout(spawned, { timeout }, spawnedPromise) { | ||
if (timeout === 0 || timeout === undefined) { | ||
return spawnedPromise; | ||
} | ||
let timeoutId; | ||
const timeoutPromise = new Promise((_resolve, reject) => { | ||
timeoutId = setTimeout(() => { | ||
spawned.kill("SIGTERM"); | ||
reject(Object.assign(new Error("Timed out"), { timedOut: true, signal: "SIGTERM" })); | ||
}, timeout); | ||
}); | ||
const safeSpawnedPromise = spawnedPromise.finally(() => { | ||
clearTimeout(timeoutId); | ||
}); | ||
return Promise.race([timeoutPromise, safeSpawnedPromise]); | ||
} | ||
function setExitHandler(spawned, timedPromise) { | ||
const removeExitHandler = (0, signal_exit_1.default)(() => { | ||
spawned.kill(); | ||
}); | ||
return timedPromise.finally(() => { | ||
removeExitHandler(); | ||
}); | ||
} | ||
class MaxBufferError extends Error { | ||
constructor() { | ||
super("The output is too big"); | ||
this.name = "MaxBufferError"; | ||
} | ||
} | ||
const streamPipelinePromisified = (0, node_util_1.promisify)(node_stream_1.default.pipeline); | ||
function bufferStream(options) { | ||
const { encoding } = options; | ||
const isBuffer = encoding === "buffer"; | ||
// @ts-expect-error missing the methods we are adding below | ||
const stream = new node_stream_1.default.PassThrough({ objectMode: false }); | ||
if (encoding && encoding !== "buffer") { | ||
stream.setEncoding(encoding); | ||
} | ||
let length = 0; | ||
const chunks = []; | ||
stream.on("data", (chunk) => { | ||
chunks.push(chunk); | ||
length += chunk.length; | ||
}); | ||
stream.getBufferedValue = () => { | ||
return (isBuffer ? Buffer.concat(chunks, length) : chunks.join("")); | ||
}; | ||
stream.getBufferedLength = () => length; | ||
return stream; | ||
} | ||
async function getStream(inputStream, options) { | ||
const stream = bufferStream(options); | ||
await new Promise((resolve, reject) => { | ||
const rejectPromise = (error) => { | ||
// Don't retrieve an oversized buffer. | ||
if (error && stream.getBufferedLength() <= node_buffer_1.constants.MAX_LENGTH) { | ||
error.bufferedData = stream.getBufferedValue(); | ||
} | ||
reject(error); | ||
}; | ||
(async () => { | ||
try { | ||
await streamPipelinePromisified(inputStream, stream); | ||
resolve(); | ||
} | ||
catch (error) { | ||
rejectPromise(error); | ||
} | ||
})(); | ||
stream.on("data", () => { | ||
// 80mb | ||
if (stream.getBufferedLength() > 1000 * 1000 * 80) { | ||
rejectPromise(new MaxBufferError()); | ||
} | ||
}); | ||
}); | ||
return stream.getBufferedValue(); | ||
} | ||
// On failure, `result.stdout|stderr` should contain the currently buffered stream | ||
async function getBufferedData(stream, streamPromise) { | ||
stream.destroy(); | ||
try { | ||
return await streamPromise; | ||
} | ||
catch (error) { | ||
return error.bufferedData; | ||
} | ||
} | ||
async function getSpawnedResult({ stdout, stderr }, { encoding }, processDone) { | ||
const stdoutPromise = getStream(stdout, { encoding }); | ||
const stderrPromise = getStream(stderr, { encoding }); | ||
try { | ||
return await Promise.all([processDone, stdoutPromise, stderrPromise]); | ||
} | ||
catch (error) { | ||
return Promise.all([ | ||
{ | ||
error: error, | ||
exitCode: null, | ||
signal: error.signal, | ||
timedOut: error.timedOut || false, | ||
}, | ||
getBufferedData(stdout, stdoutPromise), | ||
getBufferedData(stderr, stderrPromise), | ||
]); | ||
} | ||
} | ||
function stripFinalNewline(input) { | ||
@@ -268,9 +142,7 @@ const LF = typeof input === "string" ? "\n" : "\n".charCodeAt(0); | ||
const spawned = node_child_process_1.default.spawn(file, args, options); | ||
const spawnedPromise = getSpawnedPromise(spawned); | ||
const timedPromise = setupTimeout(spawned, options, spawnedPromise); | ||
const processDone = setExitHandler(spawned, timedPromise); | ||
const spawnedPromise = (0, exec_utils_1.getSpawnedPromise)(spawned, options); | ||
if (input) { | ||
spawned.stdin.end(input); | ||
} | ||
const [{ error, exitCode, signal, timedOut }, stdoutResult, stderrResult] = await getSpawnedResult(spawned, options, processDone); | ||
const [{ error, exitCode, signal, timedOut }, stdoutResult, stderrResult] = await (0, exec_utils_1.getSpawnedResult)(spawned, options, spawnedPromise); | ||
const stdout = handleOutput(options, stdoutResult); | ||
@@ -277,0 +149,0 @@ const stderr = handleOutput(options, stderrResult); |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { MutableRefObject } from "react"; | ||
@@ -3,0 +2,0 @@ import { FunctionReturningPromise, PromiseReturnType, UsePromiseReturnType } from "./types"; |
@@ -9,19 +9,9 @@ "use strict"; | ||
const api_1 = require("@raycast/api"); | ||
const promises_1 = require("fs/promises"); | ||
const fs_1 = require("fs"); | ||
const react_1 = require("react"); | ||
const node_os_1 = __importDefault(require("node:os")); | ||
const sql_js_1 = __importDefault(require("sql.js")); | ||
const node_child_process_1 = __importDefault(require("node:child_process")); | ||
const usePromise_1 = require("./usePromise"); | ||
const useLatest_1 = require("./useLatest"); | ||
// @ts-expect-error importing a wasm is tricky :) | ||
// eslint-disable-next-line import/no-unresolved | ||
const sql_wasm_wasm_1 = __importDefault(require("sql.js/dist/sql-wasm.wasm")); | ||
let SQL; | ||
async function loadDatabase(path) { | ||
if (!SQL) { | ||
SQL = await (0, sql_js_1.default)({ wasmBinary: Buffer.from(sql_wasm_wasm_1.default) }); | ||
} | ||
const fileContents = await (0, promises_1.readFile)(path); | ||
return new SQL.Database(fileContents); | ||
} | ||
const exec_utils_1 = require("./exec-utils"); | ||
/** | ||
@@ -63,8 +53,8 @@ * Executes a query on a local SQL database and returns the {@link AsyncState} corresponding to the query of the command. The last value will be kept between command runs. | ||
const { permissionPriming, ...usePromiseOptions } = options || {}; | ||
const databaseRef = (0, react_1.useRef)(); | ||
const [permissionView, setPermissionView] = (0, react_1.useState)(); | ||
const latestOptions = (0, useLatest_1.useLatest)(options || {}); | ||
const abortable = (0, react_1.useRef)(); | ||
const handleError = (0, react_1.useCallback)((_error) => { | ||
console.error(_error); | ||
const error = _error instanceof Error && _error.message.includes("operation not permitted") | ||
const error = _error instanceof Error && _error.message.includes("authorization denied") | ||
? new PermissionError("You do not have permission to access the database.") | ||
@@ -95,20 +85,18 @@ : _error; | ||
}, [latestOptions]); | ||
const fn = (0, react_1.useCallback)(async (query) => { | ||
if (!databaseRef.current) { | ||
databaseRef.current = await loadDatabase(databasePath); | ||
const fn = (0, react_1.useMemo)(() => { | ||
if (!(0, fs_1.existsSync)(databasePath)) { | ||
throw new Error("The database does not exist"); | ||
} | ||
const newResults = []; | ||
const statement = databaseRef.current.prepare(query); | ||
while (statement.step()) { | ||
newResults.push(statement.getAsObject()); | ||
} | ||
statement.free(); | ||
return newResults; | ||
return async (query) => { | ||
const spawned = node_child_process_1.default.spawn("sqlite3", ["--json", databasePath, query], { | ||
signal: abortable.current?.signal, | ||
}); | ||
const spawnedPromise = (0, exec_utils_1.getSpawnedPromise)(spawned); | ||
const [{ error, exitCode, signal }, stdoutResult, stderrResult] = await (0, exec_utils_1.getSpawnedResult)(spawned, { encoding: "utf-8" }, spawnedPromise); | ||
if (error || exitCode !== 0 || signal !== null) { | ||
throw new Error(stderrResult); | ||
} | ||
return JSON.parse(stdoutResult.trim()); | ||
}; | ||
}, [databasePath]); | ||
(0, react_1.useEffect)(() => { | ||
return () => { | ||
databaseRef.current?.close(); | ||
databaseRef.current = undefined; | ||
}; | ||
}, []); | ||
return { | ||
@@ -115,0 +103,0 @@ ...(0, usePromise_1.usePromise)(fn, [query], { ...usePromiseOptions, onError: handleError }), |
{ | ||
"name": "@raycast/utils", | ||
"version": "1.4.1", | ||
"version": "1.4.2", | ||
"description": "Set of utilities to streamline building Raycast extensions", | ||
@@ -44,3 +44,2 @@ "author": "Raycast Technologies Ltd.", | ||
"@types/signal-exit": "^3.0.1", | ||
"@types/sql.js": "^1.4.3", | ||
"@typescript-eslint/eslint-plugin": "5.26.0", | ||
@@ -56,6 +55,3 @@ "@typescript-eslint/parser": "5.26.0", | ||
"typescript": "4.7.2" | ||
}, | ||
"optionalDependencies": { | ||
"sql.js": "^1.7.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
120641
7
15
71
2627