/** * @license * Copyright 2026 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ import type { IExecuteContext, TaskConfig, TaskEntitlements } from "@workglow/task-graph"; import { CreateWorkflow, Task } from "@workglow/task-graph"; import type { DataPortSchema, FromSchema } from "@workglow/util/schema"; declare const inputSchema: { readonly type: "object"; readonly properties: { readonly url: { readonly type: "string"; readonly title: "URL"; readonly description: "URL to transform (http://, https://). The Node/Electron build additionally accepts a `file://` URL or an absolute filesystem path, subject to the `filesystem:read` entitlement and any configured `roots`."; readonly format: "uri"; }; readonly pattern: { readonly type: "string"; readonly title: "Pattern"; readonly description: "Regular expression, or a literal string when fixedString is set"; }; readonly replacement: { readonly type: "string"; readonly title: "Replacement"; readonly description: "Text substituted for each match. Supports $1/$& when the pattern is a regular expression; literal when fixedString is set"; }; readonly ignoreCase: { readonly type: "boolean"; readonly title: "Ignore Case"; readonly description: "Case-insensitive matching (s///i)"; }; readonly fixedString: { readonly type: "boolean"; readonly title: "Fixed String"; readonly description: "Treat pattern and replacement as literal strings"; }; readonly global: { readonly type: "boolean"; readonly title: "Global"; readonly description: "Replace every occurrence on a line rather than the first (s///g)"; }; readonly maxReplacements: { readonly type: "integer"; readonly title: "Max Replacements"; readonly description: "Stop substituting after this many replacements; later lines pass through"; readonly minimum: 1; }; readonly onlyChangedLines: { readonly type: "boolean"; readonly title: "Only Changed Lines"; readonly description: "Emit only the lines that changed (sed -n 's///p')"; }; readonly maxOutputLines: { readonly type: "integer"; readonly title: "Max Output Lines"; readonly description: "Maximum number of output lines. Defaults to 10000; set this to override."; readonly minimum: 0; }; readonly maxOutputChars: { readonly type: "integer"; readonly title: "Max Output Characters"; readonly description: "Maximum number of output characters, including newlines. Defaults to 1000000; set this to override."; readonly minimum: 0; }; }; readonly required: readonly ["url", "pattern", "replacement"]; readonly additionalProperties: false; }; declare const outputSchema: { readonly type: "object"; readonly properties: { readonly text: { readonly type: "string"; readonly title: "Text"; readonly description: "Transformed document"; }; readonly replacementCount: { readonly type: "integer"; readonly title: "Replacement Count"; readonly description: "Number of substitutions performed"; }; readonly linesChanged: { readonly type: "integer"; readonly title: "Lines Changed"; readonly description: "Number of lines with at least one substitution"; }; readonly truncated: { readonly type: "boolean"; readonly title: "Truncated"; readonly description: "Output stopped before EOF because an output cap or the search deadline was reached, or an input line was capped"; }; }; readonly required: readonly ["text", "replacementCount", "linesChanged", "truncated"]; readonly additionalProperties: false; }; export type FileSedTaskInput = FromSchema; export type FileSedTaskOutput = FromSchema; export type SedOptions = Omit; /** One batch of substituted lines, positionally aligned with the input. */ export interface SedBatchResult { readonly texts: readonly string[]; readonly counts: readonly number[]; } /** * Substitutes over a batch of lines at once. Batching is what makes an * interruptible substituter affordable — see `createBoundedRegexReplacer` on * the server build, whose per-call cost only amortizes over a batch. * * `budget` is how many replacements the whole run may still make, so a global * substitution can be cut mid-line and the rest of the document copied verbatim. */ export interface SedLineSubstituter { readonly substituteBatch: (texts: readonly string[], budget: number) => SedBatchResult; } /** Compiles the substitution pattern, screening it for ReDoS shapes first. */ export declare function createSedRegex(pattern: string, options: SedOptions): RegExp; /** * The expansion `String.replace` would do itself if it were handed a string. * A literal replacement must not be re-read for `$1` / `$&` expansion. */ export declare function createSedExpander(replacement: string, options: SedOptions): (args: unknown[]) => string; /** Unbounded substituter; the server build overrides it with a bounded one. */ export declare function createSubstituter(pattern: string, replacement: string, options: SedOptions): SedLineSubstituter; /** * `String.replace` only expands `$n` when handed a string, and a callback is * what bounds the substitution count — so the expansion is done here instead. */ export declare function expandReplacement(replacement: string, args: unknown[]): string; /** * Substitutes over `lines` in batches of {@link SECURITY_LIMITS.regexMatchBatchLines}. * * Abort and the overall search deadline are checked once per BATCH, not per * line: a single `String.replace` is uninterruptible, so a per-line check * bought nothing a hostile pattern could not ignore. The granularity that * matters is therefore the batch, and the substituter itself bounds how long * one batch may run. * * The deadline is what stops a run under `onlyChangedLines`, where the output * caps are skipped for every unchanged line: a pattern that matches nothing * emits nothing, so without it EOF would be the only stopping condition and a * huge source would be read to the end. */ export declare function sedLines(lines: AsyncIterable, pattern: string, replacement: string, options?: SedOptions, signal?: AbortSignal, substituter?: SedLineSubstituter | undefined): Promise; /** * Task for substituting text in documents fetched from URLs. * Works in all environments (browser, Node.js, Bun) by using fetch API. * `file://` filesystem access is implemented in the server build only; see * FileSedTask.server. * * The source is never modified: the transformed document is returned as output. */ export declare class FileSedTask extends Task { static type: string; static category: string; static title: string; static description: string; static cacheable: boolean; static hasDynamicEntitlements: boolean; /** * The owned `FetchUrlTask` does the network access, but an owned child is * created inside `execute()` and so is absent from the graph-start snapshot * `computeGraphEntitlements` takes over `graph.getTasks()`. Declaring the * fetch's entitlements here is what puts them in front of the enforcer. */ static entitlements(): TaskEntitlements; entitlements(): TaskEntitlements; static inputSchema(): DataPortSchema; static outputSchema(): DataPortSchema; /** * Seam for the substituter the scan runs. The server build overrides it to * bound matching with an interruptible time budget; there is no `vm` in a * browser, so a hostile pattern there still blocks that tab (a tab — not the * process hosting a local API). */ protected createSubstituter(pattern: string, replacement: string, options: SedOptions): SedLineSubstituter; execute(input: FileSedTaskInput, context: IExecuteContext): Promise; } /** * Config for both builds. `roots` is declared here rather than beside the * server subclass because a `declare module` augmentation must state one type * across every file that contributes it, and both files augment `Workflow` * with `fileSed`. */ export interface FileSedTaskConfig extends TaskConfig { /** * Directories a local path must resolve inside, checked after symlinks are * resolved. Omitted means `[process.cwd()]`, NOT unrestricted — the * `filesystem:read` entitlement is only consulted when the embedder runs * with `enforceEntitlements` and a registered enforcer, so it cannot stand * in as the default fence. State {@link allowAnyRoot} to opt out. * * Honored only by the server build — the cross-platform class reaches * http(s) through `FetchUrlTask` and touches no filesystem. */ readonly roots?: readonly string[] | undefined; /** * Read any path the process can open, skipping containment entirely. Only * the literal `true` does so, and it is never implied by leaving `roots` * unset. */ readonly allowAnyRoot?: boolean | undefined; } export declare const fileSed: (input: FileSedTaskInput, config?: FileSedTaskConfig) => Promise<{ linesChanged: number; replacementCount: number; text: string; truncated: boolean; }>; declare module "@workglow/task-graph" { interface Workflow { fileSed: CreateWorkflow; } } export {};