From 42f6d2e0ee6294f8a1d97f5f62f2adb6edfd2fed Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 21 Mar 2023 02:01:28 +0100 Subject: [PATCH] fix(cli): add support for non metadataed scripts --- cli/apps.ts | 114 +++++++++++++++++++++++++++++++++++++++--------- cli/context.ts | 18 +------- cli/deps.ts | 4 +- cli/flow.ts | 60 ++++++++++--------------- cli/folder.ts | 2 +- cli/main.ts | 2 + cli/resource.ts | 41 ++++++++--------- cli/script.ts | 70 ++++++++++++++--------------- cli/variable.ts | 46 ++++++++++--------- 9 files changed, 204 insertions(+), 153 deletions(-) diff --git a/cli/apps.ts b/cli/apps.ts index 1bbd1bafd5..6d37d535a4 100644 --- a/cli/apps.ts +++ b/cli/apps.ts @@ -1,12 +1,22 @@ -import { Any, model, property } from "./decoverto.ts"; +import { requireLogin, resolveWorkspace, validatePath } from "./context.ts"; +import { Any, decoverto, model, property } from "./decoverto.ts"; import { AppService, AppWithLastVersion, colors, + Command, + ListableApp, microdiff, Policy, + Table, } from "./deps.ts"; -import { Difference, PushDiffs, Resource, setValueByPath } from "./types.ts"; +import { + Difference, + GlobalOptions, + PushDiffs, + Resource, + setValueByPath, +} from "./types.ts"; @model() export class AppFile implements Resource, PushDiffs { @@ -17,7 +27,6 @@ export class AppFile implements Resource, PushDiffs { @property(Any) policy: Policy; - constructor(value: string, summary: string, policy: Policy) { this.value = value; this.summary = summary; @@ -26,7 +35,7 @@ export class AppFile implements Resource, PushDiffs { async pushDiffs( workspace: string, remotePath: string, - diffs: Difference[], + diffs: Difference[] ): Promise { let app: AppWithLastVersion | undefined = undefined; try { @@ -36,8 +45,8 @@ export class AppFile implements Resource, PushDiffs { if (app) { console.log( colors.bold.yellow( - `Applying ${diffs.length} diffs to existing app... ${remotePath}`, - ), + `Applying ${diffs.length} diffs to existing app... ${remotePath}` + ) ); const changeset: { summary?: string | undefined; @@ -47,14 +56,10 @@ export class AppFile implements Resource, PushDiffs { for (const diff of diffs) { if ( diff.type !== "REMOVE" && - ( - diff.path[0] !== "value" && diff.path[0] !== "policy" && ( - diff.path.length !== 1 || - !["summary"].includes( - diff.path[0] as string, - ) - ) - ) + diff.path[0] !== "value" && + diff.path[0] !== "policy" && + (diff.path.length !== 1 || + !["summary"].includes(diff.path[0] as string)) ) { throw new Error("Invalid app diff with path " + diff.path); } @@ -65,15 +70,21 @@ export class AppFile implements Resource, PushDiffs { } } - if ((!changeset?.policy || JSON.stringify(changeset?.policy) == JSON.stringify(app.policy)) - && (!changeset?.value || JSON.stringify(changeset?.value) == JSON.stringify(app.value)) - && (!changeset?.summary || changeset.summary == app.summary)) { - console.log(colors.yellow(`No changes to push for app ${remotePath}, skipping`)) + if ( + (!changeset?.policy || + JSON.stringify(changeset?.policy) == JSON.stringify(app.policy)) && + (!changeset?.value || + JSON.stringify(changeset?.value) == JSON.stringify(app.value)) && + (!changeset?.summary || changeset.summary == app.summary) + ) { + console.log( + colors.yellow(`No changes to push for app ${remotePath}, skipping`) + ); return; } - const hasChanges = Object.values(changeset).some((v) => - v !== null && typeof v !== "undefined" + const hasChanges = Object.values(changeset).some( + (v) => v !== null && typeof v !== "undefined" ); if (!hasChanges) { return; @@ -86,6 +97,7 @@ export class AppFile implements Resource, PushDiffs { }); } else { console.log(colors.yellow.bold("Creating new app...")); + await AppService.createApp({ workspace, requestBody: { @@ -101,7 +113,67 @@ export class AppFile implements Resource, PushDiffs { await this.pushDiffs( workspace, remotePath, - microdiff({}, this, { cyclesFix: false }), + microdiff({}, this, { cyclesFix: false }) ); } } + +async function list(opts: GlobalOptions) { + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + let page = 0; + const perPage = 10; + const total: ListableApp[] = []; + while (true) { + const res = await AppService.listApps({ + workspace: workspace.workspaceId, + page, + perPage, + }); + page += 1; + total.push(...res); + if (res.length < perPage) { + break; + } + } + + new Table() + .header(["path", "summary"]) + .padding(2) + .border(true) + .body(total.map((x) => [x.path, x.summary])) + .render(); +} + +async function push(opts: GlobalOptions, filePath: string) { + const remotePath = filePath.split(".")[0]; + if (!validatePath(remotePath)) { + return; + } + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + await pushApp(filePath, workspace.workspaceId, remotePath); + console.log(colors.bold.underline.green("App pushed")); +} + +export async function pushApp( + filePath: string, + workspace: string, + remotePath: string +) { + const data = decoverto + .type(AppFile) + .rawToInstance(await Deno.readTextFile(filePath)); + await data.push(workspace, remotePath); +} + +const command = new Command() + .description("app related commands") + .action(list as any) + .command("push", "push a local app ") + .arguments("") + .action(push as any); + +export default command; diff --git a/cli/context.ts b/cli/context.ts index 5caab6931e..e459084428 100644 --- a/cli/context.ts +++ b/cli/context.ts @@ -117,22 +117,8 @@ export async function tryResolveVersion( } } -export async function validatePath( - opts: GlobalOptions, - path: string -): Promise { - const backendVersion = await tryResolveVersion(opts); - if (path.startsWith("f")) { - if (!backendVersion || backendVersion >= 1550) { - return true; - } - console.log( - `Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.` - ); - return false; - } - - if (!(path.startsWith("g") || path.startsWith("u"))) { +export function validatePath(path: string): boolean { + if (!(path.startsWith("g") || path.startsWith("u") || path.startsWith("f"))) { console.log( colors.red( "Given remote path looks invalid. Remote paths are typically of the form //..." diff --git a/cli/deps.ts b/cli/deps.ts index 7d01da7b18..d84f02b092 100644 --- a/cli/deps.ts +++ b/cli/deps.ts @@ -33,9 +33,7 @@ export { passwordGenerator } from "https://deno.land/x/password_generator@latest export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/mod.ts"; export * as cbor from "https://deno.land/x/cbor@v1.4.1/index.js"; export { default as Murmurhash3 } from "https://deno.land/x/murmurhash@v1.0.0/mod.ts"; -export { - default as microdiff, -} from "https://deno.land/x/microdiff@v1.3.1/index.ts"; +export { default as microdiff } from "https://deno.land/x/microdiff@v1.3.1/index.ts"; export { default as objectHash } from "https://deno.land/x/object_hash@2.0.3.1/mod.ts"; export { default as gitignore_parser } from "npm:gitignore-parser"; export { default as JSZip } from "npm:jszip@3.7.1"; diff --git a/cli/flow.ts b/cli/flow.ts index eb455c0dda..10d2c70f0c 100644 --- a/cli/flow.ts +++ b/cli/flow.ts @@ -20,7 +20,6 @@ import { requireLogin, resolveWorkspace, validatePath } from "./context.ts"; import { resolve, track_job } from "./script.ts"; import { Any, decoverto, model, property } from "./decoverto.ts"; - // this is effectively "OpenFlow" but a copy as it is accepted by the CLI @model() export class FlowFile implements Resource, PushDiffs { @@ -40,7 +39,7 @@ export class FlowFile implements Resource, PushDiffs { async pushDiffs( workspace: string, remotePath: string, - diffs: Difference[], + diffs: Difference[] ): Promise { if ( await FlowService.existsFlowByPath({ @@ -50,8 +49,8 @@ export class FlowFile implements Resource, PushDiffs { ) { console.log( colors.bold.yellow( - `Applying ${diffs.length} diffs to existing flow... ${remotePath}`, - ), + `Applying ${diffs.length} diffs to existing flow... ${remotePath}` + ) ); // TODO: Make these optional in backend (not path ofc) @@ -66,14 +65,11 @@ export class FlowFile implements Resource, PushDiffs { for (const diff of diffs) { if ( diff.type !== "REMOVE" && - ( - diff.path[0] !== "value" && ( - diff.path.length !== 1 || - !["summary", "description", "schema"].includes( - diff.path[0] as string, - ) - ) - ) + diff.path[0] !== "value" && + (diff.path.length !== 1 || + !["summary", "description", "schema"].includes( + diff.path[0] as string + )) ) { throw new Error("Invalid flow diff with path " + diff.path); } @@ -83,8 +79,8 @@ export class FlowFile implements Resource, PushDiffs { setValueByPath(changeset, diff.path, null); } } - const hasChanges = Object.values(changeset).some((v) => - v !== null && typeof v !== "undefined" + const hasChanges = Object.values(changeset).some( + (v) => v !== null && typeof v !== "undefined" ); if (!hasChanges) { return; @@ -93,7 +89,7 @@ export class FlowFile implements Resource, PushDiffs { const update = { ...changeset, ...base_changeset, - } + }; await FlowService.updateFlow({ workspace: workspace, @@ -115,11 +111,10 @@ export class FlowFile implements Resource, PushDiffs { } } async push(workspace: string, remotePath: string): Promise { - await this.pushDiffs( workspace, remotePath, - microdiff({}, this, { cyclesFix: false }), + microdiff({}, this, { cyclesFix: false }) ); } } @@ -127,24 +122,24 @@ export class FlowFile implements Resource, PushDiffs { type Options = GlobalOptions; async function push(opts: Options, filePath: string, remotePath: string) { - if (!await validatePath(opts, remotePath)) { + if (!validatePath(remotePath)) { return; } const workspace = await resolveWorkspace(opts); await requireLogin(opts); - await pushFlow(filePath, workspace.remote, remotePath); + await pushFlow(filePath, workspace.workspaceId, remotePath); console.log(colors.bold.underline.green("Flow pushed")); } export async function pushFlow( filePath: string, workspace: string, - remotePath: string, + remotePath: string ) { - const data = decoverto.type(FlowFile).rawToInstance( - await Deno.readTextFile(filePath), - ); + const data = decoverto + .type(FlowFile) + .rawToInstance(await Deno.readTextFile(filePath)); await data.push(workspace, remotePath); } @@ -173,13 +168,7 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) { .header(["path", "summary", "edited by"]) .padding(2) .border(true) - .body( - total.map((x) => [ - x.path, - x.summary, - x.edited_by, - ]), - ) + .body(total.map((x) => [x.path, x.summary, x.edited_by])) .render(); } async function run( @@ -187,14 +176,13 @@ async function run( data?: string; silent: boolean; }, - path: string, + path: string ) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); const input = opts.data ? await resolve(opts.data) : {}; - const id = await JobService.runFlowByPath({ workspace: workspace.workspaceId, path, @@ -229,7 +217,7 @@ async function run( if (!opts.silent) { console.log(colors.green.underline.bold("Flow ran to completion")); - console.log() + console.log(); } const jobInfo = await JobService.getCompletedJob({ workspace: workspace.workspaceId, @@ -244,7 +232,7 @@ const command = new Command() .action(list as any) .command( "push", - "push a local flow spec. This overrides any remote versions.", + "push a local flow spec. This overrides any remote versions." ) .arguments(" ") .action(push as any) @@ -252,11 +240,11 @@ const command = new Command() .arguments("") .option( "-d --data ", - "Inputs specified as a JSON string or a file using @ or stdin using @-.", + "Inputs specified as a JSON string or a file using @ or stdin using @-." ) .option( "-s --silent", - "Do not ouput anything other then the final output. Useful for scripting.", + "Do not ouput anything other then the final output. Useful for scripting." ) .action(run as any); diff --git a/cli/folder.ts b/cli/folder.ts index cfd49957d9..4849b2820e 100644 --- a/cli/folder.ts +++ b/cli/folder.ts @@ -137,7 +137,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); - if (!(await validatePath(opts, remotePath))) { + if (!validatePath(remotePath)) { return; } diff --git a/cli/main.ts b/cli/main.ts index f91b5df06a..e7bbd9c9f3 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -5,6 +5,7 @@ import { UpgradeCommand, } from "./deps.ts"; import flow from "./flow.ts"; +import app from "./apps.ts"; import script from "./script.ts"; import workspace from "./workspace.ts"; import resource from "./resource.ts"; @@ -32,6 +33,7 @@ let command: any = new Command() "Specify an API token. This will override any stored token." ) .version(VERSION) + .command("app", app) .command("flow", flow) .command("script", script) .command("workspace", workspace) diff --git a/cli/resource.ts b/cli/resource.ts index c1165b034e..2c65c7cdbf 100644 --- a/cli/resource.ts +++ b/cli/resource.ts @@ -34,7 +34,7 @@ export class ResourceFile implements Resource2, PushDiffs { async pushDiffs( workspace: string, remotePath: string, - diffs: Difference[], + diffs: Difference[] ): Promise { if ( await ResourceService.existsResource({ @@ -43,7 +43,9 @@ export class ResourceFile implements Resource2, PushDiffs { }) ) { console.log( - colors.yellow.bold(`Applying ${diffs.length} diffs to existing resource... ${remotePath}`), + colors.yellow.bold( + `Applying ${diffs.length} diffs to existing resource... ${remotePath}` + ) ); const changeset: EditResource = { @@ -56,14 +58,13 @@ export class ResourceFile implements Resource2, PushDiffs { } if ( diff.type !== "REMOVE" && - ( - diff.path[0] !== "value" && ( - diff.path.length !== 1 || - diff.path[0] !== "description" - ) && diff.path[0] !== "resource_type" - ) + diff.path[0] !== "value" && + (diff.path.length !== 1 || diff.path[0] !== "description") && + diff.path[0] !== "resource_type" ) { - console.log(colors.red("Invalid variable diff with path " + diff.path)); + console.log( + colors.red("Invalid variable diff with path " + diff.path) + ); throw new Error("Invalid folder diff with path " + diff.path); } if (diff.type === "CREATE" || diff.type === "CHANGE") { @@ -73,8 +74,8 @@ export class ResourceFile implements Resource2, PushDiffs { } } - const hasChanges = Object.values(changeset).some((v) => - v !== null && typeof v !== "undefined" + const hasChanges = Object.values(changeset).some( + (v) => v !== null && typeof v !== "undefined" ); if (!hasChanges) { return; @@ -89,8 +90,8 @@ export class ResourceFile implements Resource2, PushDiffs { if (typeof this.is_oauth !== "undefined") { console.log( colors.yellow( - "! is_oauth has been removed in newer versions. Ignoring.", - ), + "! is_oauth has been removed in newer versions. Ignoring." + ) ); } @@ -110,7 +111,7 @@ export class ResourceFile implements Resource2, PushDiffs { await this.pushDiffs( workspace, remotePath, - microdiff({}, this, { cyclesFix: false }), + microdiff({}, this, { cyclesFix: false }) ); } } @@ -118,11 +119,11 @@ export class ResourceFile implements Resource2, PushDiffs { export async function pushResource( workspace: string, filePath: string, - remotePath: string, + remotePath: string ) { - const data = decoverto.type(ResourceFile).rawToInstance( - await Deno.readTextFile(filePath), - ); + const data = decoverto + .type(ResourceFile) + .rawToInstance(await Deno.readTextFile(filePath)); await data.push(workspace, remotePath); } @@ -131,7 +132,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); - if (!await validatePath(opts, remotePath)) { + if (!validatePath(remotePath)) { return; } @@ -178,7 +179,7 @@ const command = new Command() .action(list as any) .command( "push", - "push a local resource spec. This overrides any remote versions.", + "push a local resource spec. This overrides any remote versions." ) .arguments(" ") .action(push as any); diff --git a/cli/script.ts b/cli/script.ts index cd6113970e..128bc40f1d 100644 --- a/cli/script.ts +++ b/cli/script.ts @@ -55,14 +55,11 @@ export class ScriptFile { } type PushOptions = GlobalOptions; -async function push( - opts: PushOptions, - filePath: string, - remotePath: string, - contentPath?: string -) { +async function push(opts: PushOptions, filePath: string) { const workspace = await resolveWorkspace(opts); - if (!(await validatePath(opts, remotePath))) { + const remotePath = filePath.split(".")[0]; + + if (!validatePath(remotePath)) { return; } @@ -70,17 +67,18 @@ async function push( if (!fstat.isFile) { throw new Error("file path must refer to a file."); } - if (!contentPath) { + let contentPath: string; + let metaPath: string | undefined; + if (filePath.endsWith(".script.json")) { + metaPath = filePath; contentPath = await findContentFile(filePath); } else { - const fstat = await Deno.stat(filePath); - if (!fstat.isFile) { - throw new Error("content path must refer to a file."); - } + contentPath = filePath; + metaPath = undefined; } await requireLogin(opts); - await pushScript(filePath, contentPath, workspace.workspaceId, remotePath); + await pushScript(metaPath, contentPath, workspace.workspaceId, remotePath); console.log(colors.bold.underline.green(`Script ${remotePath} pushed`)); } @@ -181,15 +179,15 @@ export async function handleFile( workspace: workspace, requestBody: { content, - description: typed.description, + description: typed?.description ?? "", language, path: remotePath, - summary: typed.summary, - is_template: typed.is_template, - kind: typed.kind, - lock: typed.lock, + summary: typed?.summary ?? "", + is_template: typed?.is_template, + kind: typed?.kind, + lock: typed?.lock, parent_hash: undefined, - schema: typed.schema, + schema: typed?.schema, }, }); console.log( @@ -254,18 +252,20 @@ export function inferContentTypeFromFilePath( } export async function pushScript( - filePath: string, + filePath: string | undefined, contentPath: string, workspace: string, remotePath: string ) { - const data = decoverto - .type(ScriptFile) - .rawToInstance(await Deno.readTextFile(filePath)); + const data = filePath + ? decoverto + .type(ScriptFile) + .rawToInstance(await Deno.readTextFile(filePath)) + : undefined; const content = await Deno.readTextFile(contentPath); const language = inferContentTypeFromFilePath(contentPath); - let parent_hash = data.parent_hash; + let parent_hash = data?.parent_hash; if (!parent_hash) { try { parent_hash = ( @@ -284,15 +284,15 @@ export async function pushScript( workspace: workspace, requestBody: { path: remotePath, - summary: data.summary, + summary: data?.summary ?? "", content: content, - description: data.description, + description: data?.description ?? "", language: language, - is_template: data.is_template, - kind: data.kind, - lock: data.lock, + is_template: data?.is_template, + kind: data?.kind, + lock: data?.lock, parent_hash: parent_hash, - schema: data.schema, + schema: data?.schema, }, }); } @@ -484,17 +484,17 @@ const command = new Command() .action(list as any) .command( "push", - "push a local script spec. This overrides any remote versions." + "push a local script spec. This overrides any remote versions. Can use a script file (.ts, .js, .py, .sh) or a script spec file (.json). " ) - .arguments(" [content_path:string]") + .arguments("") .action(push as any) .command("show", "show a scripts content") - .arguments("") + .arguments("") .action(show as any) .command("run", "run a script by path") - .arguments("") + .arguments("") .option( - "-d --data ", + "-d --data ", "Inputs specified as a JSON string or a file using @ or stdin using @-." ) .option( diff --git a/cli/variable.ts b/cli/variable.ts index 3c1b4527ce..75a7323e17 100644 --- a/cli/variable.ts +++ b/cli/variable.ts @@ -35,7 +35,7 @@ async function list(opts: GlobalOptions) { x.is_secret ? "true" : "false", x.account ?? "-", x.value ?? "-", - ]), + ]) ) .render(); } @@ -61,26 +61,31 @@ export class VariableFile implements Resource, PushDiffs { async pushDiffs( workspace: string, remotePath: string, - diffs: Difference[], + diffs: Difference[] ): Promise { if (await VariableService.existsVariable({ workspace, path: remotePath })) { console.log( colors.bold.yellow( - `Applying ${diffs.length} diffs to existing variable... ${remotePath}`, - ), + `Applying ${diffs.length} diffs to existing variable... ${remotePath}` + ) ); const changeset: EditVariable = {}; for (const diff of diffs) { if ( diff.type !== "REMOVE" && - ( - diff.path.length !== 1 || - !["path", "value", "is_secret", "description", "account", "is_oauth"].includes( - diff.path[0] as string, - ) - ) + (diff.path.length !== 1 || + ![ + "path", + "value", + "is_secret", + "description", + "account", + "is_oauth", + ].includes(diff.path[0] as string)) ) { - console.log(colors.red("Invalid variable diff with path " + diff.path)); + console.log( + colors.red("Invalid variable diff with path " + diff.path) + ); throw new Error("Invalid variable diff with path " + diff.path); } if (diff.type === "CREATE" || diff.type === "CHANGE") { @@ -90,8 +95,8 @@ export class VariableFile implements Resource, PushDiffs { } } - const hasChanges = Object.values(changeset).some((v) => - v !== null && typeof v !== "undefined" + const hasChanges = Object.values(changeset).some( + (v) => v !== null && typeof v !== "undefined" ); if (!hasChanges) { return; @@ -103,7 +108,6 @@ export class VariableFile implements Resource, PushDiffs { alreadyEncrypted: true, requestBody: changeset, }); - } else { console.log(colors.yellow.bold("Creating new variable...")); await VariableService.createVariable({ @@ -124,7 +128,7 @@ export class VariableFile implements Resource, PushDiffs { await this.pushDiffs( workspace, remotePath, - microdiff({}, this, { cyclesFix: false }), + microdiff({}, this, { cyclesFix: false }) ); } } @@ -133,7 +137,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { const workspace = await resolveWorkspace(opts); await requireLogin(opts); - if (!await validatePath(opts, remotePath)) { + if (!validatePath(remotePath)) { return; } @@ -151,11 +155,11 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { export async function pushVariable( workspace: string, filePath: string, - remotePath: string, + remotePath: string ) { - const data = decoverto.type(VariableFile).rawToInstance( - await Deno.readTextFile(filePath), - ); + const data = decoverto + .type(VariableFile) + .rawToInstance(await Deno.readTextFile(filePath)); await data.push(workspace, remotePath); } @@ -164,7 +168,7 @@ const command = new Command() .action(list as any) .command( "push", - "Push a local variable spec. This overrides any remote versions.", + "Push a local variable spec. This overrides any remote versions." ) .arguments(" ") .action(push as any);