From d5468853ca67d1eca331096307b2339ea62a44f2 Mon Sep 17 00:00:00 2001 From: David Dooling Date: Fri, 17 Aug 2018 14:25:35 -0500 Subject: [PATCH] Move scripts to bin directory Move scripts to a bin directory. This is standard for Node.js packages. Unlike most Node.js packages, these scripts are in src/bin rather than bin, but with the rearrangement on packaging, they will end up in bin and, should this project ever get rearranged to be more standard, the location of the bin will not change. Existing scripts remain in place with a deprecation warning added. --- src/bin/command.ts | 112 ++++++++++++++++++++++++++++++++++++++++ src/bin/git-info.ts | 54 +++++++++++++++++++ src/bin/gql-gen.ts | 117 ++++++++++++++++++++++++++++++++++++++++++ src/bin/start.ts | 22 ++++++++ src/start.client.ts | 2 + src/start.command.ts | 2 + src/start.git-info.ts | 2 + src/start.gql-gen.ts | 2 + 8 files changed, 313 insertions(+) create mode 100644 src/bin/command.ts create mode 100644 src/bin/git-info.ts create mode 100644 src/bin/gql-gen.ts create mode 100644 src/bin/start.ts diff --git a/src/bin/command.ts b/src/bin/command.ts new file mode 100644 index 000000000..605d02565 --- /dev/null +++ b/src/bin/command.ts @@ -0,0 +1,112 @@ +#!/usr/bin/env node +/* + * Copyright © 2018 Atomist, Inc. + * + * See LICENSE file. + */ + +process.env.SUPPRESS_NO_CONFIG_WARNING = "true"; + +import * as stringify from "json-stringify-safe"; + +import { automationClient } from "../automationClient"; +import { + Configuration, + loadConfiguration, +} from "../configuration"; +import { HandlerContext } from "../HandlerContext"; +import { CommandInvocation } from "../internal/invoker/Payload"; +import { consoleMessageClient } from "../internal/message/ConsoleMessageClient"; +import { LoggingConfig } from "../internal/util/logger"; +import { guid } from "../internal/util/string"; +import { enableDefaultScanning } from "../scan"; +import { AutomationServer } from "../server/AutomationServer"; + +LoggingConfig.format = "cli"; + +main(); + +/** + * Parse command line CommandInvocation argument, set up, and call the + * command handler. This method will not return. + */ +async function main() { + if (!process.argv[2]) { + console.error(`[ERROR] Missing command, you must supply the CommandInvocation on the command line`); + process.exit(3); + } + if (process.argv.length > 3) { + console.warn(`[WARN] Extra command line arguments will be ignored: ${process.argv.slice(3).join(" ")}`); + } + const ciString = process.argv[2]; + try { + const ci: CommandInvocation = JSON.parse(ciString); + const configuration = await loadConfiguration(); + enableDefaultScanning(configuration); + const node = automationClient(configuration); + await invokeOnConsole(node.automationServer, ci, createHandlerContext(configuration)); + } catch (e) { + console.error(`[ERROR] Unhandled exception: ${e.message}`); + process.exit(101); + } + console.error(`[ERROR] Illegal state: unhandled execution path`); + process.exit(99); +} + +/** + * Create a simple handler context for running command handlers from + * the command line. + */ +function createHandlerContext(config: Configuration): HandlerContext { + return { + workspaceId: config.workspaceIds[0], + correlationId: guid(), + messageClient: consoleMessageClient, + }; +} + +/** + * Run a command handler on the command line. This function will not + * return. + * + * @param automationServer automation server with the command + * @param ci command and its parameters + * @param ctx suitable execution context + */ +async function invokeOnConsole(automationServer: AutomationServer, ci: CommandInvocation, ctx: HandlerContext) { + + // Set up the parameter, mappend parameters and secrets + const handler = automationServer.automations.commands.find(c => c.name === ci.name); + if (!handler) { + const commands = automationServer.automations.commands.map(c => c.name).join(" "); + console.error(`[ERROR] Unable to find command ${ci.name}, available commands: ${commands}`); + process.exit(4); + } + const invocation: CommandInvocation = { + name: ci.name, + args: ci.args ? ci.args.filter(a => + handler.parameters.some(p => p.name === a.name)) : undefined, + mappedParameters: ci.args ? ci.args.filter(a => + handler.mapped_parameters.some(p => p.name === a.name)) : undefined, + secrets: ci.args ? ci.args.filter(a => handler.secrets.some(p => p.name === a.name)) + .map(a => { + const s = handler.secrets.find(p => p.name === a.name); + return { uri: s.uri, value: a.value }; + }) : undefined, + }; + + try { + automationServer.validateCommandInvocation(invocation); + } catch (e) { + console.error(`[ERROR] Invalid parameters: ${e.message}`); + process.exit(2); + } + try { + const result = await automationServer.invokeCommand(invocation, ctx); + console.log(`Command succeeded: ${stringify(result, null, 2)}`); + } catch (e) { + console.error(`[ERROR] Command failed: ${stringify(e, null, 2)}`); + process.exit(1); + } + process.exit(0); +} diff --git a/src/bin/git-info.ts b/src/bin/git-info.ts new file mode 100644 index 000000000..1ca3569d4 --- /dev/null +++ b/src/bin/git-info.ts @@ -0,0 +1,54 @@ +#! /usr/bin/env node +/* + * Copyright © 2018 Atomist, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from "fs-extra"; +import * as path from "path"; + +import { + obtainGitInfo, +} from "../internal/env/gitInfo"; +import { + logger, + LoggingConfig, +} from "../internal/util/logger"; + +LoggingConfig.format = "cli"; + +/** + * Generate git-info.json for automation client. + */ +async function main(): Promise { + try { + const cwd = process.cwd(); + const gitInfoName = "git-info.json"; + const gitInfoPath = path.join(cwd, gitInfoName); + const gitInfo = await obtainGitInfo(cwd); + await fs.writeJson(gitInfoPath, gitInfo, { spaces: 2, encoding: "utf8" }); + logger.info(`Successfully wrote git information to '${gitInfoPath}'`); + process.exit(0); + } catch (e) { + logger.error(`Failed to generate Git information: ${e.message}`); + process.exit(1); + } + throw new Error("Should never get here, process.exit() called above"); +} + +main() + .catch((err: Error) => { + logger.error(`Unhandled exception: ${err.message}`); + process.exit(101); + }); diff --git a/src/bin/gql-gen.ts b/src/bin/gql-gen.ts new file mode 100644 index 000000000..a134221c2 --- /dev/null +++ b/src/bin/gql-gen.ts @@ -0,0 +1,117 @@ +#! /usr/bin/env node +/* + * Copyright © 2018 Atomist, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as child_process from "child_process"; +import * as spawn from "cross-spawn"; +import * as fs from "fs-extra"; +import * as glob from "glob"; +import * as path from "path"; +import * as util from "util"; + +import { + logger, + LoggingConfig, +} from "../internal/util/logger"; + +LoggingConfig.format = "cli"; + +/** + * Figure out whether the lib directory is named lib or src. lib is + * preferred, meaning if it exists, it is returned and if neither it + * nor src exists, it is returned. + * + * @param cwd directory to use as base for location of lib dir + * @return Resolved, full path to lib directory + */ +function libDir(cwd: string): string { + const lib = path.resolve(cwd, "lib"); + const src = path.resolve(cwd, "src"); + if (fs.existsSync(lib)) { + return lib; + } else if (fs.existsSync(src)) { + return src; + } else { + return lib; + } +} + +/** + * Generate TypeScript typings for GraphQL schema entities. + */ +async function main(): Promise { + try { + const cwd = process.cwd(); + const lib = libDir(cwd); + + // check if the project has a custom schema + const customSchemaLocation = path.join(lib, "graphql", "schema.json"); + const defaultSchemaLocation = path.join(cwd, "node_modules", "@atomist", "automation-client", + "graph", "schema.cortex.json"); + const schema = fs.existsSync(customSchemaLocation) ? customSchemaLocation : defaultSchemaLocation; + + const gqlGenCmd = path.join(cwd, "node_modules", ".bin", "gql-gen") + + ((process.platform === "win32") ? ".cmd" : ""); + const gqlGenOutput = path.join(lib, "typings", "types.ts"); + const gqlGenArgs = [ + "--file", schema, + "--template", "typescript", + "--no-schema", + "--out", gqlGenOutput, + ]; + + const opts: child_process.SpawnOptions = { + cwd, + env: process.env, + stdio: "inherit", + }; + + const graphQlGlob = `${lib}/**/*.graphql`; + + const graphqlFiles = await util.promisify(glob)(graphQlGlob); + if (graphqlFiles && graphqlFiles.length > 0) { + gqlGenArgs.push(graphQlGlob); + } else { + logger.info("No GraphQL files found in project, generating default types"); + } + + const cp = spawn(gqlGenCmd, gqlGenArgs, opts); + cp.on("exit", (code, signal) => { + if (code === 0) { + process.exit(code); + } else if (code) { + logger.error(`Generating GraphQL failed with non-zero status: ${code}`); + process.exit(code); + } else { + logger.error(`Generating GraphQL exited due to signal: ${signal}`); + process.exit(128 + 2); + } + }); + cp.on("error", err => { + logger.error(`Generating GraphQL types errored: ${err.message}`); + process.exit(2); + }); + } catch (e) { + logger.error(`Generating GraphQL types failed: ${e.message}`); + process.exit(1); + } +} + +main() + .catch((err: Error) => { + logger.error(`Unhandled exception: ${err.message}`); + process.exit(101); + }); diff --git a/src/bin/start.ts b/src/bin/start.ts new file mode 100644 index 000000000..581f8893c --- /dev/null +++ b/src/bin/start.ts @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +import "source-map-support/register"; +import { automationClient } from "../automationClient"; +import { loadConfiguration } from "../configuration"; +import { enableDefaultScanning } from "../scan"; + +try { + loadConfiguration() + .then(configuration => { + enableDefaultScanning(configuration); + return configuration; + }) + .then(configuration => automationClient(configuration).run()) + .catch(e => { + console.error(e); + process.exit(1); + }); +} catch (e) { + console.error(`Uncaught exception: ${e.message}`); + process.exit(10); +} diff --git a/src/start.client.ts b/src/start.client.ts index c2eecea6e..9dd767a7d 100644 --- a/src/start.client.ts +++ b/src/start.client.ts @@ -5,6 +5,8 @@ import { automationClient } from "./automationClient"; import { loadConfiguration } from "./configuration"; import { enableDefaultScanning } from "./scan"; +console.warn("[WARN] This script is deprecated, use 'bin/start.js'"); + try { loadConfiguration() .then(configuration => { diff --git a/src/start.command.ts b/src/start.command.ts index e9604efd3..ae0d3cdac 100644 --- a/src/start.command.ts +++ b/src/start.command.ts @@ -24,6 +24,8 @@ import { AutomationServer } from "./server/AutomationServer"; LoggingConfig.format = "cli"; +console.warn("[WARN] This script is deprecated, use 'bin/command.js'"); + main(); /** diff --git a/src/start.git-info.ts b/src/start.git-info.ts index f3e6b14a6..0d211b7ff 100644 --- a/src/start.git-info.ts +++ b/src/start.git-info.ts @@ -28,6 +28,8 @@ import { LoggingConfig.format = "cli"; +console.warn("[WARN] This script is deprecated, use 'bin/git-info.js'"); + /** * Generate git-info.json for automation client. */ diff --git a/src/start.gql-gen.ts b/src/start.gql-gen.ts index d14fee49f..69a520201 100644 --- a/src/start.gql-gen.ts +++ b/src/start.gql-gen.ts @@ -29,6 +29,8 @@ import { LoggingConfig.format = "cli"; +console.warn("[WARN] This script is deprecated, use 'bin/gql-gen.js'"); + /** * Figure out whether the lib directory is named lib or src. lib is * preferred, meaning if it exists, it is returned and if neither it