UNPKG

aws-local-stepfunctions

Version:
302 lines (293 loc) 12.5 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/cli/CLI.ts var CLI_exports = {}; __export(CLI_exports, { makeProgram: () => makeProgram }); module.exports = __toCommonJS(CLI_exports); var import_readline = __toESM(require("readline"), 1); var import_commander = require("commander"); // package.json var version = "2.0.1"; // src/cli/ArgumentParsers.ts var import_fs = require("fs"); var import_child_process = require("child_process"); // src/util/index.ts var isBrowserEnvironment = typeof window !== "undefined" && typeof window.document !== "undefined"; function tryJSONParse(jsonStr) { try { return JSON.parse(jsonStr); } catch (error) { return error; } } // src/cli/ArgumentParsers.ts function parseDefinitionOption(command, definition) { const jsonOrError = tryJSONParse(definition); if (jsonOrError instanceof Error) { command.error( `error: parsing of state machine definition passed in option '-d, --definition <definition>' failed: ${jsonOrError.message}`, { exitCode: 1 /* PreExecutionFailure */ } ); } return jsonOrError; } function parseDefinitionFileOption(command, definitionFile) { let file; try { file = (0, import_fs.readFileSync)(definitionFile).toString(); } catch (error) { command.error(`error: ${error.message}`, { exitCode: 1 /* PreExecutionFailure */ }); } const jsonOrError = tryJSONParse(file); if (jsonOrError instanceof Error) { command.error( `error: parsing of state machine definition in file '${definitionFile}' failed: ${jsonOrError.message}`, { exitCode: 1 /* PreExecutionFailure */ } ); } return jsonOrError; } function parseOverrideTaskOption(value, previous = {}) { const [taskStateName, scriptPath] = value.split(":"); previous[taskStateName] = (input) => { const spawnResult = (0, import_child_process.spawnSync)(scriptPath, [JSON.stringify(input)]); if (spawnResult.error) { throw new Error( `Attempt to run task override '${scriptPath}' for state '${taskStateName}' failed: ${spawnResult.error.message}` ); } if (spawnResult.status !== 0) { throw new Error(`${scriptPath} ('${taskStateName}'): ${spawnResult.stderr.toString()}`); } const overrideResult = spawnResult.stdout.toString(); const jsonOrError = tryJSONParse(overrideResult); if (jsonOrError instanceof Error) { throw new Error( `Parsing of output '${overrideResult}' from task override '${scriptPath}' for state '${taskStateName}' failed: ${jsonOrError.message}` ); } return Promise.resolve(jsonOrError); }; return previous; } function parseOverrideWaitOption(value, previous = {}) { const [waitStateName, duration] = value.split(":"); previous[waitStateName] = Number(duration); return previous; } function parseOverrideRetryOption(value, previous = {}) { const [stateName, duration] = value.split(":"); if (!isNaN(duration)) { previous[stateName] = Number(duration); } else { previous[stateName] = duration.split(",").map(Number); } return previous; } function parseContextOption(command, context) { const jsonOrError = tryJSONParse(context); if (jsonOrError instanceof Error) { command.error( `error: parsing of context object passed in option '--context <json>' failed: ${jsonOrError.message}`, { exitCode: 1 /* PreExecutionFailure */ } ); } return jsonOrError; } function parseContextFileOption(command, contextFile) { let file; try { file = (0, import_fs.readFileSync)(contextFile).toString(); } catch (error) { command.error(`error: ${error.message}`, { exitCode: 1 /* PreExecutionFailure */ }); } const jsonOrError = tryJSONParse(file); if (jsonOrError instanceof Error) { command.error(`error: parsing of context object in file '${contextFile}' failed: ${jsonOrError.message}`, { exitCode: 1 /* PreExecutionFailure */ }); } return jsonOrError; } function parseInputArguments(command, value, previous = []) { const jsonOrError = tryJSONParse(value); if (jsonOrError instanceof Error) { command.error(`error: parsing of input value '${value}' failed: ${jsonOrError.message}`, { exitCode: 1 /* PreExecutionFailure */ }); } return previous.concat(jsonOrError); } // src/cli/CommandHandler.ts var import_main = require("../build/main.node.cjs"); async function commandAction(inputs, options, command) { let stateMachine; try { stateMachine = new import_main.StateMachine(options.definition ?? options.definitionFile, { validationOptions: { checkPaths: options.jsonpathValidation, checkArn: options.arnValidation, noValidate: !options.validation } }); } catch (error) { command.error(`error: ${error.message}`); } const resultsPromises = inputs.map((input) => { const { result } = stateMachine.run(input, { overrides: { taskResourceLocalHandlers: options.overrideTask, waitTimeOverrides: options.overrideWait, retryIntervalOverrides: options.overrideRetry }, context: options.context ?? options.contextFile }); return result; }); const results = await Promise.allSettled(resultsPromises); let exitCode = 0 /* Success */; for (const result of results) { if (result.status === "fulfilled") { console.log(result.value); } else { exitCode = 2 /* StateMachineExecutionFailure */; let msg = result.reason.message; if (result.reason instanceof import_main.ExecutionTimeoutError) { msg = "Execution timed out"; } console.log(msg.trim()); } } process.exitCode = exitCode; } function preActionHook(thisCommand) { const options = thisCommand.opts(); if (thisCommand.args.length === 0) { thisCommand.help(); } if (!options["definition"] && !options["definitionFile"]) { thisCommand.error( "error: missing either option '-d, --definition <definition>' or option '-f, --definition-file <path>'", { exitCode: 1 /* PreExecutionFailure */ } ); } } // src/cli/CLI.ts function makeProgram() { const command = new import_commander.Command(); command.name("local-sfn").description( `Execute an Amazon States Language state machine with the given inputs. The result of each execution will be printed in a new line and in the same order as its corresponding input.` ).helpOption("-h, --help", "Print help for command and exit.").configureHelp({ helpWidth: 80 }).addHelpText( "after", ` Exit codes: 0 All executions ran successfully. 1 An error occurred before the state machine could be executed. 2 At least one execution had an error. Example calls: $ local-sfn -f state-machine.json '{ "num1": 2, "num2": 2 }' $ local-sfn -f state-machine.json -t SendRequest:./override.sh -w WaitResponse:2000 '{ "num1": 2, "num2": 2 }' $ cat inputs.txt | local-sfn -f state-machine.json` ).version(version, "-V, --version", "Print the version number and exit.").addOption( new import_commander.Option("-d, --definition <definition>", "A JSON definition of a state machine.").conflicts("definition-file").argParser((value) => parseDefinitionOption(command, value)) ).addOption( new import_commander.Option("-f, --definition-file <path>", "Path to a file containing a JSON state machine definition.").conflicts("definition").argParser((value) => parseDefinitionFileOption(command, value)) ).addOption( new import_commander.Option( "-t, --override-task <mapping>", "Override a Task state to run an executable file or script, instead of calling the service specified in the 'Resource' field of the state definition. The mapping value has to be provided in the format [TaskStateToOverride]:[path/to/override/script]. The override script will be passed the input of the Task state as first argument, which can then be used to compute the task result. The script must print the task result as a JSON value to the standard output." ).argParser(parseOverrideTaskOption) ).addOption( new import_commander.Option( "-w, --override-wait <mapping>", "Override a Wait state to pause for the specified amount of milliseconds, instead of pausing for the duration specified in the state definition. The mapping value has to be provided in the format [WaitStateToOverride]:[number]." ).argParser(parseOverrideWaitOption) ).addOption( new import_commander.Option( "-r, --override-retry <mapping>", "Override a 'Retry' field to pause for the specified amount of milliseconds, instead of pausing for the duration specified by the retry policy. The mapping value has to be provided in the format [NameOfStateWithRetryField]:[number]." ).argParser(parseOverrideRetryOption) ).addOption( new import_commander.Option( "--context <json>", "A JSON object that will be passed to each execution as the context object." ).argParser((value) => parseContextOption(command, value)) ).addOption( new import_commander.Option( "--context-file <path>", "Path to a file containing a JSON object that will be passed to each execution as the context object." ).argParser((value) => parseContextFileOption(command, value)) ).addOption( new import_commander.Option("--no-jsonpath-validation", "Disable validation of JSONPath strings in the state machine definition.") ).addOption(new import_commander.Option("--no-arn-validation", "Disable validation of ARNs in the state machine definition.")).addOption( new import_commander.Option( "--no-validation", "Disable validation of the state machine definition entirely. Use this option at your own risk, there are no guarantees when passing an invalid or non-standard definition to the state machine. Running it might result in undefined/unsupported behavior." ) ).argument( "[inputs...]", "Input data for the state machine, can be any valid JSON value. Each input represents a state machine execution.\n\nWhen reading from the standard input, if the first line can be parsed as a single JSON value, then each line will be considered as an input. Otherwise, the entire standard input will be considered as a single JSON input.", (value, previous) => parseInputArguments(command, value, previous) ).hook("preAction", preActionHook).action(commandAction); return command; } if (require.main === module) { (async function() { const program = makeProgram(); if (process.stdin.isTTY) { await program.parseAsync(); } else { const rl = import_readline.default.createInterface({ input: process.stdin }); const stdin = []; for await (const line of rl) { stdin.push(line); } const firstLineJSON = tryJSONParse(stdin[0]); if (firstLineJSON instanceof Error) { await program.parseAsync([...process.argv, stdin.join("").trim()]); return; } await program.parseAsync([...process.argv, ...stdin.filter((line) => line)]); } })(); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { makeProgram });