/** * Copyright 2021 Insta Industries, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. */ import { Completer } from './async-utils'; /** * The phase interface allows different implementations of the public API. * A bot has two phases: the configuration, and execution phase. * * The configuration phase allows to scan dependencies, and configure the * execution graph. * The execution phase executes the code as expected. * * This interface is also used to provide a different implementation for * the execution of actions. Since the client executes the code in a Web browser, * a different phase implementation allows to send exec calls to the bot. * * This a private API. */ export interface Phase { getContext: () => ContextType; setContext: (ctx: ContextType) => any; /** called whenever the current phase is set via `setCurrentPhase`. */ init: () => void; task: (name: TaskName, fn: (...inputs: I[]) => Promise, options?: TaskOptions) => Task; group: (name: string, fn: () => any) => any; action: (name: string, filename: string, data?: any) => any; exec: (command: string, flags: string[], options?: ExecOptions) => Promise; sleep: (timeMs: number) => Promise; attachFile: (path: string, name?: string) => any; attachFileContent: (content: string, name: string) => any; stream: (name: string, options?: StreamOptions) => StreamInterface; logger: { info: (message: any) => any; error: (message: any) => any; }; } /** Sets the phase. */ export declare function setCurrentPhase(phase: Phase): void; /** Gets the current phase. */ export declare function getCurrentPhase(): Phase; /** * A stream that doesn't write anything. * This is used to provide a stream interface during the configuration phase. */ export declare const noopStream: StreamInterface; /** Alias for the task name. */ export declare type TaskName = string; /** The task callback is main function body. */ export declare type TaskCallback = (...input: I[]) => Promise; /** * A task holds all state of a task. * * It's an internal representation, which is used to access * the current state. */ export declare type Task = { /** The canonical task id. */ id: any; /** The task execution id if any. */ executionId?: any; /** If set, replaces `id` for this value. */ impersonationId?: any; /** If set, replaces the impersonation task id for this one. */ impersonationExecutionId?: any; /** The name of the task. */ name: any; /** The dependencies of the task. */ dependsOn: Task[]; /** The input dependencies of the task. */ dependsOnInput: Task[]; /** The function that is executed when the task is running. */ callback: TaskCallback; /** The output after running callback. */ callbackOutput?: O; /** The parent task if any. */ parent?: Task; /** * The child tasks. * Children is `null` when this is a leaf task. */ children?: Map>; /** A completer that completes when the task completes. */ completer: Completer; /** Where the task logs are written to. */ logs?: StreamInterface; }; /** * The file descriptor options. */ export declare type FileDescriptorOptions = { /** * The file's mediatype as specified in RFC 2046. * Use `text/plain for text logs, or `application/octet-stream` for arbitraty binary data. */ mediatype: string; /** * If available, the SHA1 of the file. */ sha1?: string; /** * If available, the size of the file in bytes. */ size?: number; }; /** * Exec options. */ export declare type ExecOptions = { /** Where the stdout and stderr are written to. */ stream?: StreamInterface; /** The directory in which the program is executed. */ cwd?: string; }; /** * Executes a program, and streams the stdout, and stderr. * The enclosing task fails if the program exited with a code. * * To change the directory in which the program is executed, * set `options.cwd: ''`. * * By default, the stdout and stderr streams of the command are written * to the enclosing task log stream, but a custom stream can be set via * `options.stream`. * * @param command The command to execute. * @param flags The flags to pass to the command. * @param options Optional. Options such as current directory, or write stream. */ export declare function exec(command: string, flags: string[], options?: ExecOptions): Promise; /** Task options. */ export declare type TaskOptions = { /** * The list of tasks that the current task depends on. * * This means that the current task must run after these tasks have completed. * If one task dependency isn't completed successfully, the current task is * skipped. * * The order in the array doesn't mean that the tasks runs in that order. * To enforce the order of execution, you use `dependsOn` on those tasks. * * Circular dependencies aren't allowed. */ dependsOn?: Task[]; /** * The list of tasks that provide the input to the current task. * * For example, the current task may depend on a file that is * generated by another task. * * ```tsx * const producer = task('Produce foo.txt', async () => { * await exec('touch', ['foo.txt']); * return attachFile('foo.txt'); * }); * * task('Consumes file', async (file?: AttachedFile) => { * if (file) { * const content = await file.read(); * logger.info(`Got file ${file.name} and the content is ${content}`); * } * }, { * dependsOnInput: [ producer ], * }); * ``` * * Similar to `dependsOn`, if one task dependency isn't completed successfully, * the current task is skipped. * * The order in the array doesn't mean that the tasks runs in that order. * To enforce the order of execution, you use `dependsOn` or `dependsOnInput` * on those tasks. * * Circular dependencies aren't allowed. */ dependsOnInput?: Task[]; }; /** * A task is the unit of execution of the task runner, and allows to * execute commands, upload artifacts, or actions. * * Tasks cannot be programatically configured. They must be defined statically. * That is, they aren't enclosed in control flow blocks that depends on values * provided at runtime. * * For example, consider a task that verifies the format of the code. * * Such task executes a command that verifies that the code format is correct. * It also registers a sub-task that allows the user to automatically fix the format. * * ## Basic Example * * For example, here's a task that takes 1s to run: * * ```ts * task('Sample task', async () => { * await sleep(1000); * }); * ``` * * ## Task Dependencies * * Tasks run in parallel unless otherwise specified by setting `options.dependsOn`, * for example: * * ```ts * const task1 = task('Task 1', async () => { * await sleep(1000); * }); * * task('Task 2', async () => { * await sleep(1000); * }, dependsOn: [ task1 ]); * ``` * In the above scenario, "Task 2" runs once "Task 1" has completed. * * ## Input and Output * * In many cases, it's useful to have a task depend on the dynamic output * of another task. For example, one task may produce a file, that another * task takes as an input to produce a report. * * As a result, a task can use `dependsOnInput` to make the output of another * task its input. * * You use type generics to specify the desired input and output of task. * For example: * * ```tsx * const producer = task('Produce foo.txt', async () => { * await exec('touch', ['foo.txt']); * return attachFile('foo.txt'); * }); * * task('Consumes file', async (file?: AttachedFile) => { * if (file) { * const content = await file.read(); * logger.info(`Got file ${file.name} and the content is ${content}`); * } * }, { * dependsOnInput: [ producer ], * }); * ``` * * The above example sends `Got value foo` to the logger. * * @param name The name of the task. * @param fn A function that defines what to run. * @param options Task options such as `dependsOn`. */ export declare function task(name: TaskName, fn: (...inputs: I[]) => Promise, options?: TaskOptions): Task; /** * Creates a group of tasks. * A group can also contain other groups. * * For example: * * ```ts * group('Build', () => { * task('Sleep for 1s', async () => { * await sleep(1000); * }); * }); * * @param name The name of the group. * @param fn The function that defines tasks or other groups. */ export declare function group(name: string, fn: () => any): void; /** * An action is an interrupt handler that allows to a custom UI to access * the current state of the task, and resume the task until completion. * * It's similar to executing a CLI command, and then yielding * to stdin. The difference is that actions can take more complex user input, * from custom UI components. * * Actions can be very powerful to visualize output of previous commands, * trigger new commands, and check in generated files to an existing branch * or pull request. * * For example: * * *tasks.ts** (Bot-side code) * * ```ts * import { action, task } from 'iret/bot'; * * task('Task with interrupt and return handler', async () => { * try { * throw new Error('Something bad happened'); * } catch (error) { * action('Show hostname', 'show-hostname.tsx'); * throw error; * } * }); * ``` * * **show-hostname.tsx** (Client-side code) * * ```tsx * import React, { useState } from 'react'; * import { exec, logger, resumeTask } from 'iret/client'; * * export default function() { * const [currHostname, setCurrHostname] = useState('unknown'); * return ( *
The hostname is: {currHostname}.
* * ); * } * ``` * @param name The name of the action. * @param filename Either a ts or tsx filename that provides the interface * through the default export. * @param data If present, the data to pass to the default function. */ export declare function action(name: string, filename: string, data?: any): any; /** * Sleeps for the given time in milliseconds. * * @param timeMs The time in milliseconds. */ export declare function sleep(timeMs: number): Promise; /** * Sends messages to the logger. */ export declare const logger: { /** * An info message. * * @param message The message to log. */ info(message: any): void; /** * An error message. * * @param message The message to log. */ error(message: any): void; }; /** * Attaches a file to the task. * * @param path The path to the file. * @param name Optional. If set, this becomes the name of the file, * otherwise the filename is used. */ export declare function attachFile(path: string, name?: string): any; /** * Defines the current step in the task. * * This is handy to understand the execution of the task and the context around it. * For example, when a file is attached, it's associated to the last step if defined. * * For example, here's a task that runs itself for ever after installing the dependencies, * and waiting for 1 second: * * ```ts * import { task, step, sleep } from 'iret/bot'; * * task('Build app', async () => { * step('Install dependencies'); * await exec('npm', ['install']); * * step('Wait for 1s'); * await sleep(1000); * * step('Run tasks'); * await exec('npm', ['run', 'tasks']); * }); * ``` * * @param name The name of the step. */ export declare function step(name: string): void; /** * Stream options. */ export declare type StreamOptions = { /** * The media type of the stream. * * Supported media types are: * `text/plain` and `application/octet-stream`. */ mediatype?: string; }; /** * The stream interface allows to manipulate the stream. */ export interface StreamInterface { /** * Provides a function that generates the content of the stream. * * For example: * ```ts * await customStream.generate(async function* () { * yield 'message 1'; * yield 'message 2'; * }); * ``` * @param generator The function that generates the chunks of the stream. */ generate(generator: () => AsyncGenerator): Promise; /** * Writes the bytes to the stream. * @param content The bytes to write. */ writeBytes(content: Uint8Array): Promise; /** * Writes the content to the stream. * @param content The string content. */ writeString(content: string): Promise; /** * Closes the stream. */ close(): Promise; } /** * Creates a stream. * * Streams are sequences of text by default. This allows Insta to index * the streams for quick access in the future. * * Streams can also be used for other media types such as binary blobs. * You would just need to specify the `mediatype` in the `options`. * * For example, the stdout or stderr of a program's execution can be * written to a custom stream: * * ```ts * import { task, stream, exec } from 'iret/bot'; * * task('Show Hacker News headers', async () => { * await exec('curl', [ '-I', 'https://news.ycombinator.com'], * { * stream: stream('headers'), * }); * }); * ``` * * Streams can use Async iterators: * * ```ts * import { task, stream } from 'iret/bot'; * * task('Task with custom stream', async () => { * await stream('Custom stream'). * generate(async function* () { * yield "Message 1"; * yield "Message 2"; * yield "Message 3"; * }, * ); * }); * ``` * * @param name The name of the stream. */ export declare function stream(name: string, options?: StreamOptions): StreamInterface;