mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 00:06:06 +00:00
feat(cli): add consistent get/list/new subcommands for all item types (#8047)
* feat(cli): add consistent get/list/new subcommands for all item types Make the CLI consistent so every item type (script, flow, app, resource, resource-type, variable, schedule, folder, trigger) supports get/list/new subcommands, enabling the CLI to be used as a full API client in bash scripts with jq piping. - Add --json flag to all list commands for machine-readable output - Register explicit "list" subcommand alongside default action - Add "get <path> [--json]" subcommand to fetch single items from API - Rename "bootstrap" to "new" for script/flow, keep "bootstrap" as alias - Add "new" subcommand for resource, resource-type, variable, schedule, folder, and trigger to create local template YAML files - Update cli-commands skill documentation for wmill init - Add integration tests for all new commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * feat: install wmill CLI in Docker images and use it for bash variable/resource access - Install windmill-cli via bun in all Dockerfiles that include bun - DockerfileCli: switch from node:slim to oven/bun:slim - CLI: auto-configure from WM_WORKSPACE/WM_TOKEN/BASE_INTERNAL_URL env vars as last-resort fallback when no workspace is configured - Frontend: replace curl-based bash snippets with wmill variable/resource get - Add backend integration tests for wmill CLI in bash scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): install windmill-cli in backend test workflow Ensures wmill is available on PATH for bash integration tests that use `wmill variable get` and `wmill resource get`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(cli): replace @std/* Deno dependencies with Node.js equivalents Replace @std/log with a lightweight custom logger (core/log.ts), @std/path with node:path, and @std/yaml with the yaml npm package. Also fix process hang on exit, add --node option to install_dev.sh, and add missing hasRequiredPermissions to NpmProvider. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * all * all * refactor(cli): replace @ayonli/jsext and @std/encoding with lightweight alternatives Replace @ayonli/jsext (8.4MB) with tar-stream (32kB) for tar creation, replace @std/encoding with Node.js Buffer.toString("hex"), and fix @windmill-labs/shared-utils to use direct npm instead of JSR mirror. Also resolve merge conflicts in sync.ts and fix pre-existing type errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(cli): use singleQuote YAML output and pass yamlOptions in gitsync pull The yaml library defaults to double quotes, but the codebase (and tests) expect single-quoted strings. Add singleQuote: true to yamlOptions and pass yamlOptions to gitsync-settings pull writeFile calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * all * fix(cli): address code review feedback - Install CLI from source in backend tests instead of npm - Fix script bootstrap catch block to re-throw "File already exists" - Add type-safe local variable after trigger kind validation - Use created_by instead of policy.on_behalf_of for app get output - Note --kind is recommended for faster trigger lookup in help text - Document node symlink purpose in Dockerfiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): use /usr/bin for wmill wrapper to ensure it's in PATH Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): install wmill to ~/.local/bin to avoid permission issues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci(backend): switch to Blacksmith runner and add cargo caching - Switch from ubicloud-standard-16 to blacksmith-16vcpu-ubuntu-2404 for faster NVMe-backed builds - Add stickydisk for cargo target directory (persistent NVMe cache across runs) - Add cache for cargo registry and git dependencies - Upgrade DuckDB FFI cache from actions/cache@v3 to useblacksmith/cache@v1 - Enable CARGO_INCREMENTAL=1 to benefit from persistent target cache Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix ci --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a91c532eca
commit
4fedfdfd11
@@ -3,8 +3,8 @@ import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
@@ -185,7 +185,7 @@ export async function generatingPolicy(
|
||||
}
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions & { includeDraftOnly?: boolean }) {
|
||||
async function list(opts: GlobalOptions & { includeDraftOnly?: boolean; json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -206,12 +206,32 @@ async function list(opts: GlobalOptions & { includeDraftOnly?: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["path", "summary"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const a = await wmill.getAppByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(a));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + a.path);
|
||||
console.log(colors.bold("Summary:") + " " + (a.summary ?? ""));
|
||||
console.log(colors.bold("Created by:") + " " + (a.created_by ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
@@ -227,7 +247,15 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("app related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all apps")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get an app's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("push", "push a local app ")
|
||||
.arguments("<file_path:string> <remote_path:string>")
|
||||
.action(push as any)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import path from "node:path";
|
||||
import { readFile, mkdir } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import {
|
||||
checkifMetadataUptodate,
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import process from "node:process";
|
||||
import { spawn } from "node:child_process";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
export interface BundleOptions {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as getPort from "get-port";
|
||||
|
||||
@@ -5,7 +5,7 @@ import process from "node:process";
|
||||
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
||||
import process from "node:process";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { createBundle } from "./bundle.ts";
|
||||
|
||||
@@ -4,8 +4,8 @@ import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
import * as log from "@std/log";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { generateAgentsDocumentation, generateDatatablesDocumentation, yamlOptions } from "../sync/sync.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { Policy } from "../../../gen/types.gen.ts";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { resolveWorkspace } from "../../core/context.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import fs from "node:fs";
|
||||
import { workspaceDependenciesPathToLanguageAndFilename } from "../../utils/metadata.ts";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { readFile } from "node:fs/promises";
|
||||
@@ -113,7 +113,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
}
|
||||
|
||||
async function list(
|
||||
opts: GlobalOptions & { showArchived?: boolean; includeDraftOnly?: boolean }
|
||||
opts: GlobalOptions & { showArchived?: boolean; includeDraftOnly?: boolean; json?: boolean }
|
||||
) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
@@ -136,13 +136,35 @@ async function list(
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary", "edited by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.edited_by]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["path", "summary", "edited by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.edited_by]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const f = await wmill.getFlowByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(f));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + f.path);
|
||||
console.log(colors.bold("Summary:") + " " + (f.summary ?? ""));
|
||||
console.log(colors.bold("Description:") + " " + (f.description ?? ""));
|
||||
console.log(colors.bold("Edited by:") + " " + (f.edited_by ?? ""));
|
||||
console.log(colors.bold("Edited at:") + " " + (f.edited_at ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
async function run(
|
||||
opts: GlobalOptions & {
|
||||
data?: string;
|
||||
@@ -375,8 +397,17 @@ export function bootstrap(
|
||||
|
||||
const command = new Command()
|
||||
.description("flow related commands")
|
||||
.option("--show-archived", "Enable archived scripts in output")
|
||||
.option("--show-archived", "Enable archived flows in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all flows")
|
||||
.option("--show-archived", "Enable archived flows in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a flow's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local flow spec. This overrides any remote versions."
|
||||
@@ -423,10 +454,15 @@ const command = new Command()
|
||||
"Comma separated patterns to specify which file to NOT take into account."
|
||||
)
|
||||
.action(generateLocks as any)
|
||||
.command("bootstrap", "create a new empty flow")
|
||||
.command("new", "create a new empty flow")
|
||||
.arguments("<flow_path:string>")
|
||||
.option("--summary <summary:string>", "script summary")
|
||||
.option("--description <description:string>", "script description")
|
||||
.option("--summary <summary:string>", "flow summary")
|
||||
.option("--description <description:string>", "flow description")
|
||||
.action(bootstrap as any)
|
||||
.command("bootstrap", "create a new empty flow (alias for new)")
|
||||
.arguments("<flow_path:string>")
|
||||
.option("--summary <summary:string>", "flow summary")
|
||||
.option("--description <description:string>", "flow description")
|
||||
.action(bootstrap as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as path from "@std/path";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile, mkdir } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
@@ -18,7 +19,7 @@ export interface FolderFile {
|
||||
display_name: string | undefined;
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -26,18 +27,60 @@ async function list(opts: GlobalOptions) {
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
|
||||
new Table()
|
||||
.header(["Name", "Owners", "Extra Perms"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
folders.map((x) => [
|
||||
x.name,
|
||||
x.owners?.join(",") ?? "-",
|
||||
JSON.stringify(x.extra_perms ?? {}),
|
||||
])
|
||||
)
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(folders));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Name", "Owners", "Extra Perms"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
folders.map((x) => [
|
||||
x.name,
|
||||
x.owners?.join(",") ?? "-",
|
||||
JSON.stringify(x.extra_perms ?? {}),
|
||||
])
|
||||
)
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function newFolder(opts: GlobalOptions, name: string) {
|
||||
const dirPath = `f${SEP}${name}`;
|
||||
const filePath = `${dirPath}${SEP}folder.meta.yaml`;
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template: Omit<FolderFile, "display_name"> = {
|
||||
owners: [],
|
||||
extra_perms: {},
|
||||
};
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, name: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const f = await wmill.getFolder({
|
||||
workspace: workspace.workspaceId,
|
||||
name,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(f));
|
||||
} else {
|
||||
console.log(colors.bold("Name:") + " " + f.name);
|
||||
console.log(colors.bold("Summary:") + " " + (f.summary ?? ""));
|
||||
console.log(colors.bold("Owners:") + " " + (f.owners?.join(", ") ?? "-"));
|
||||
console.log(colors.bold("Extra Perms:") + " " + JSON.stringify(f.extra_perms ?? {}));
|
||||
}
|
||||
}
|
||||
|
||||
export async function pushFolder(
|
||||
@@ -126,7 +169,18 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("folder related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all folders")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a folder's details")
|
||||
.arguments("<name:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new folder locally")
|
||||
.arguments("<name:string>")
|
||||
.action(newFolder as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local folder spec. This overrides any remote versions."
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { SyncOptions, readConfigFile, getEffectiveSettings, DEFAULT_SYNC_OPTIONS, getWmillYamlPath } from "../../core/conf.ts";
|
||||
import { yamlOptions } from "../sync/sync.ts";
|
||||
import { deepEqual } from "../../utils/utils.ts";
|
||||
import { getCurrentGitBranch, isGitRepository } from "../../utils/git.ts";
|
||||
|
||||
@@ -176,7 +177,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write the new configuration
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig), "utf-8");
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
@@ -372,7 +373,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write updated configuration
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig), "utf-8");
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
@@ -449,7 +450,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write updated configuration
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig), "utf-8");
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import process from "node:process";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { deepEqual, selectRepository } from "../../utils/utils.ts";
|
||||
import { SyncOptions, getEffectiveSettings, DEFAULT_SYNC_OPTIONS } from "../../core/conf.ts";
|
||||
import { GitSyncRepository, GIT_SYNC_FIELDS } from "./types.ts";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
|
||||
@@ -2,8 +2,8 @@ import { stat, writeFile, rm, mkdir } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { readLockfile } from "../../utils/metadata.ts";
|
||||
import { getActiveWorkspaceOrFallback } from "../workspace/workspace.ts";
|
||||
|
||||
@@ -6,9 +6,9 @@ import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import * as path from "@std/path";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { setClient } from "../../core/client.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { resolveWorkspace } from "../../core/context.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { mergeConfigWithConfigFile } from "../../core/conf.ts";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
@@ -3,9 +3,9 @@ import process from "node:process";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import * as path from "@std/path";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { mergeConfigWithConfigFile } from "../../core/conf.ts";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { pickInstance } from "../instance/instance.ts";
|
||||
|
||||
@@ -124,7 +124,7 @@ async function displayQueues(opts: GlobalOptions, workspace?: string) {
|
||||
table.body(body).render();
|
||||
|
||||
} catch (error) {
|
||||
log.error("Failed to fetch queue metrics:", error);
|
||||
log.error(`Failed to fetch queue metrics: ${error}`);
|
||||
}
|
||||
} else {
|
||||
log.info("No active instance found");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import {
|
||||
GlobalOptions,
|
||||
@@ -14,7 +15,7 @@ import { resolveWorkspace } from "../../core/context.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { ResourceType } from "../../../gen/types.gen.ts";
|
||||
import { compileResourceTypeToTsType } from "../../utils/resource_types.ts";
|
||||
@@ -85,14 +86,16 @@ async function push(opts: PushOptions, filePath: string, name: string) {
|
||||
log.info(colors.bold.underline.green("Resource pushed"));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions & { schema?: boolean }) {
|
||||
async function list(opts: GlobalOptions & { schema?: boolean; json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const res = await wmill.listResourceType({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
|
||||
if (opts.schema) {
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(res));
|
||||
} else if (opts.schema) {
|
||||
new Table()
|
||||
.header(["Workspace", "Name", "Schema"])
|
||||
.padding(2)
|
||||
@@ -115,6 +118,44 @@ async function list(opts: GlobalOptions & { schema?: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function newResourceType(opts: GlobalOptions, name: string) {
|
||||
const filePath = name + ".resource-type.yaml";
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template: ResourceTypeFile = {
|
||||
schema: {},
|
||||
description: "",
|
||||
};
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const rt = await wmill.getResourceType({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(rt));
|
||||
} else {
|
||||
console.log(colors.bold("Name:") + " " + rt.name);
|
||||
console.log(colors.bold("Description:") + " " + (rt.description ?? ""));
|
||||
console.log(colors.bold("Workspace:") + " " + (rt.workspace_id ?? "Global"));
|
||||
if (rt.schema) {
|
||||
console.log(colors.bold("Schema:") + " " + JSON.stringify(rt.schema, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateRTNamespace(opts: GlobalOptions) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
@@ -146,10 +187,19 @@ export async function generateRTNamespace(opts: GlobalOptions) {
|
||||
|
||||
const command = new Command()
|
||||
.description("resource type related commands")
|
||||
.action(() => log.info("2 actions available, list and push."))
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all resource types")
|
||||
.option("--schema", "Show schema in the output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a resource type's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new resource type locally")
|
||||
.arguments("<name:string>")
|
||||
.action(newResourceType as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local resource spec. This overrides any remote versions."
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import {
|
||||
GlobalOptions,
|
||||
@@ -11,8 +12,8 @@ import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { Resource } from "../../../gen/types.gen.ts";
|
||||
import { readInlinePathSync } from "../../utils/utils.ts";
|
||||
@@ -131,7 +132,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) {
|
||||
log.info(colors.bold.underline.green(`Resource ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
let page = 0;
|
||||
@@ -150,17 +151,73 @@ async function list(opts: GlobalOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["Path", "Resource Type"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.resource_type]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Path", "Resource Type"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.resource_type]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function newResource(opts: GlobalOptions, path: string) {
|
||||
if (!validatePath(path)) {
|
||||
return;
|
||||
}
|
||||
const filePath = path + ".resource.yaml";
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
// file doesn't exist, proceed
|
||||
}
|
||||
const template: ResourceFile = {
|
||||
value: {},
|
||||
resource_type: "",
|
||||
description: "",
|
||||
};
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const r = await wmill.getResource({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(r));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + r.path);
|
||||
console.log(colors.bold("Resource Type:") + " " + (r.resource_type ?? ""));
|
||||
console.log(colors.bold("Description:") + " " + (r.description ?? ""));
|
||||
console.log(colors.bold("Value:") + " " + JSON.stringify(r.value, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
.description("resource related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all resources")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a resource's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new resource locally")
|
||||
.arguments("<path:string>")
|
||||
.action(newResource as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local resource spec. This overrides any remote versions."
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
@@ -27,7 +28,7 @@ export interface ScheduleFile {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -35,12 +36,62 @@ async function list(opts: GlobalOptions) {
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
|
||||
new Table()
|
||||
.header(["Path", "Schedule"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(schedules.map((x) => [x.path, x.schedule]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(schedules));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Path", "Schedule"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(schedules.map((x) => [x.path, x.schedule]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function newSchedule(opts: GlobalOptions, path: string) {
|
||||
if (!validatePath(path)) {
|
||||
return;
|
||||
}
|
||||
const filePath = path + ".schedule.yaml";
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template: ScheduleFile = {
|
||||
schedule: "0 */6 * * *",
|
||||
on_failure: "",
|
||||
script_path: "",
|
||||
args: {},
|
||||
timezone: "Etc/UTC",
|
||||
is_flow: false,
|
||||
enabled: false,
|
||||
};
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const s = await wmill.getSchedule({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(s));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + s.path);
|
||||
console.log(colors.bold("Schedule:") + " " + s.schedule);
|
||||
console.log(colors.bold("Timezone:") + " " + (s.timezone ?? ""));
|
||||
console.log(colors.bold("Script Path:") + " " + (s.script_path ?? ""));
|
||||
console.log(colors.bold("Is Flow:") + " " + (s.is_flow ? "true" : "false"));
|
||||
console.log(colors.bold("Enabled:") + " " + (s.enabled ? "true" : "false"));
|
||||
}
|
||||
}
|
||||
|
||||
export async function pushSchedule(
|
||||
@@ -137,7 +188,18 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("schedule related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all schedules")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a schedule's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new schedule locally")
|
||||
.arguments("<path:string>")
|
||||
.action(newSchedule as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local schedule spec. This overrides any remote versions."
|
||||
|
||||
@@ -7,9 +7,9 @@ import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { deepEqual } from "../../utils/utils.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import * as specificItems from "../../core/specific_items.ts";
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
} from "../../core/conf.ts";
|
||||
import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts";
|
||||
import fs from "node:fs";
|
||||
import { type Tarball } from "@ayonli/jsext/archive";
|
||||
import { createTarBlob, type TarEntry } from "../../utils/tar.ts";
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { NewScript, Script } from "../../../gen/types.gen.ts";
|
||||
@@ -246,7 +246,7 @@ export async function handleFile(
|
||||
const codebase =
|
||||
language == "bun" ? findCodebase(path, codebases) : undefined;
|
||||
|
||||
let bundleContent: string | Tarball | undefined = undefined;
|
||||
let bundleContent: string | Blob | undefined = undefined;
|
||||
|
||||
let forceTar = false;
|
||||
if (codebase) {
|
||||
@@ -292,7 +292,6 @@ export async function handleFile(
|
||||
);
|
||||
}
|
||||
if (outputFiles.length > 1) {
|
||||
const archiveNpm = await import("@ayonli/jsext/archive");
|
||||
log.info(
|
||||
`Found multiple output files for ${path}, creating a tarball... ${outputFiles
|
||||
.map((file) => file.path)
|
||||
@@ -300,54 +299,49 @@ export async function handleFile(
|
||||
);
|
||||
forceTar = true;
|
||||
const startTime = performance.now();
|
||||
const tarball = new archiveNpm.Tarball();
|
||||
const mainPath = path.split(SEP).pop()?.split(".")[0] + ".js";
|
||||
const content =
|
||||
const mainContent =
|
||||
outputFiles.find((file) => file.path == "/" + mainPath)?.text ?? "";
|
||||
log.info(`Main content: ${content.length}chars`);
|
||||
tarball.append(new File([content], "main.js", { type: "text/plain" }));
|
||||
log.info(`Main content: ${mainContent.length}chars`);
|
||||
const entries: TarEntry[] = [
|
||||
{ name: "main.js", content: mainContent },
|
||||
];
|
||||
for (const file of outputFiles) {
|
||||
if (file.path == "/" + mainPath) {
|
||||
continue;
|
||||
}
|
||||
log.info(`Adding file: ${file.path.substring(1)}`);
|
||||
|
||||
const fil = new File([file.contents as any], file.path.substring(1));
|
||||
tarball.append(fil);
|
||||
entries.push({ name: file.path.substring(1), content: file.contents });
|
||||
}
|
||||
bundleContent = await createTarBlob(entries);
|
||||
const endTime = performance.now();
|
||||
log.info(
|
||||
`Finished creating tarball for ${path}: ${(
|
||||
tarball.size / 1024
|
||||
bundleContent.size / 1024
|
||||
).toFixed(0)}kB (${(endTime - startTime).toFixed(0)}ms)`
|
||||
);
|
||||
bundleContent = tarball;
|
||||
} else {
|
||||
if (Array.isArray(codebase.assets) && codebase.assets.length > 0) {
|
||||
const archiveNpm = await import("@ayonli/jsext/archive");
|
||||
log.info(
|
||||
`Using the following asset configuration for ${path}: ${JSON.stringify(
|
||||
codebase.assets
|
||||
)}`
|
||||
);
|
||||
const startTime = performance.now();
|
||||
const tarball = new archiveNpm.Tarball();
|
||||
tarball.append(
|
||||
new File([bundleContent], "main.js", { type: "text/plain" })
|
||||
);
|
||||
const entries: TarEntry[] = [
|
||||
{ name: "main.js", content: bundleContent },
|
||||
];
|
||||
for (const asset of codebase.assets) {
|
||||
const data = fs.readFileSync(asset.from);
|
||||
const blob = new Blob([data], { type: "text/plain" });
|
||||
const file = new File([blob], asset.to);
|
||||
tarball.append(file);
|
||||
entries.push({ name: asset.to, content: data });
|
||||
}
|
||||
bundleContent = await createTarBlob(entries);
|
||||
const endTime = performance.now();
|
||||
log.info(
|
||||
`Finished creating tarball for ${path}: ${(
|
||||
tarball.size / 1024
|
||||
bundleContent.size / 1024
|
||||
).toFixed(0)}kB (${(endTime - startTime).toFixed(0)}ms)`
|
||||
);
|
||||
bundleContent = tarball;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,31 +506,8 @@ export async function handleFile(
|
||||
return false;
|
||||
}
|
||||
|
||||
async function streamToBlob(stream: ReadableStream<Uint8Array>): Promise<Blob> {
|
||||
// Create a reader from the stream
|
||||
const reader = stream.getReader();
|
||||
const chunks = [];
|
||||
|
||||
// Read the data from the stream
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
// If stream is finished, break the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Push the chunk to the array
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
|
||||
const blob = new Blob(chunks as any);
|
||||
return blob;
|
||||
}
|
||||
|
||||
async function createScript(
|
||||
bundleContent: string | Tarball | undefined,
|
||||
bundleContent: string | Blob | undefined,
|
||||
workspaceId: string,
|
||||
body: NewScript,
|
||||
workspace: Workspace
|
||||
@@ -563,7 +534,7 @@ async function createScript(
|
||||
"file",
|
||||
typeof bundleContent == "string"
|
||||
? bundleContent
|
||||
: await streamToBlob(bundleContent.stream())
|
||||
: bundleContent
|
||||
);
|
||||
|
||||
const url =
|
||||
@@ -726,6 +697,7 @@ async function list(
|
||||
showArchived?: boolean;
|
||||
includeWithoutMain?: boolean;
|
||||
includeDraftOnly?: boolean;
|
||||
json?: boolean;
|
||||
}
|
||||
) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
@@ -750,12 +722,16 @@ async function list(
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary", "language", "created by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.language, x.created_by]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["path", "summary", "language", "created by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.language, x.created_by]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolve(input: string): Promise<Record<string, any>> {
|
||||
@@ -916,6 +892,26 @@ async function show(opts: GlobalOptions, path: string) {
|
||||
log.info(s.content);
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const s = await wmill.getScriptByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(s));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + s.path);
|
||||
console.log(colors.bold("Summary:") + " " + (s.summary ?? ""));
|
||||
console.log(colors.bold("Description:") + " " + (s.description ?? ""));
|
||||
console.log(colors.bold("Language:") + " " + s.language);
|
||||
console.log(colors.bold("Kind:") + " " + (s.kind ?? "script"));
|
||||
console.log(colors.bold("Created by:") + " " + (s.created_by ?? ""));
|
||||
console.log(colors.bold("Created at:") + " " + (s.created_at ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
async function bootstrap(
|
||||
opts: GlobalOptions & { summary: string; description: string },
|
||||
scriptPath: string,
|
||||
@@ -941,10 +937,15 @@ async function bootstrap(
|
||||
|
||||
try {
|
||||
await stat(scriptCodeFileFullPath);
|
||||
throw new Error("File already exists: " + scriptCodeFileFullPath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
try {
|
||||
await stat(scriptMetadataFileFullPath);
|
||||
throw new Error("File already exists in repository");
|
||||
} catch {
|
||||
// file does not exist, we can continue
|
||||
throw new Error("File already exists: " + scriptMetadataFileFullPath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
|
||||
const scriptMetadata = defaultScriptMetadata();
|
||||
@@ -1155,38 +1156,34 @@ async function preview(
|
||||
|
||||
// Handle multiple output files (create tarball)
|
||||
if (out.outputFiles.length > 1) {
|
||||
const archiveNpm = await import("@ayonli/jsext/archive");
|
||||
if (!opts.silent) {
|
||||
log.info(`Creating tarball for multiple output files...`);
|
||||
}
|
||||
const tarball = new archiveNpm.Tarball();
|
||||
const mainPath = filePath.split(SEP).pop()?.split(".")[0] + ".js";
|
||||
const mainContent =
|
||||
out.outputFiles.find((file: OutputFile) => file.path == "/" + mainPath)?.text ?? "";
|
||||
tarball.append(new File([mainContent], "main.js", { type: "text/plain" }));
|
||||
const entries: TarEntry[] = [
|
||||
{ name: "main.js", content: mainContent },
|
||||
];
|
||||
for (const file of out.outputFiles) {
|
||||
if (file.path == "/" + mainPath) continue;
|
||||
|
||||
const fil = new File([file.contents as any], file.path.substring(1));
|
||||
tarball.append(fil);
|
||||
entries.push({ name: file.path.substring(1), content: file.contents });
|
||||
}
|
||||
bundledContent = await streamToBlob(tarball.stream());
|
||||
bundledContent = await createTarBlob(entries);
|
||||
isTar = true;
|
||||
} else if (Array.isArray(codebase.assets) && codebase.assets.length > 0) {
|
||||
// Handle assets
|
||||
const archiveNpm = await import("@ayonli/jsext/archive");
|
||||
if (!opts.silent) {
|
||||
log.info(`Adding assets to tarball...`);
|
||||
}
|
||||
const tarball = new archiveNpm.Tarball();
|
||||
tarball.append(new File([bundledContent], "main.js", { type: "text/plain" }));
|
||||
const entries: TarEntry[] = [
|
||||
{ name: "main.js", content: bundledContent },
|
||||
];
|
||||
for (const asset of codebase.assets) {
|
||||
const data = fs.readFileSync(asset.from);
|
||||
const blob = new Blob([data], { type: "text/plain" });
|
||||
const file = new File([blob], asset.to);
|
||||
tarball.append(file);
|
||||
entries.push({ name: asset.to, content: data });
|
||||
}
|
||||
bundledContent = await streamToBlob(tarball.stream());
|
||||
bundledContent = await createTarBlob(entries);
|
||||
isTar = true;
|
||||
}
|
||||
|
||||
@@ -1290,6 +1287,11 @@ async function preview(
|
||||
const command = new Command()
|
||||
.description("script related commands")
|
||||
.option("--show-archived", "Enable archived scripts in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all scripts")
|
||||
.option("--show-archived", "Enable archived scripts in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command(
|
||||
"push",
|
||||
@@ -1297,7 +1299,11 @@ const command = new Command()
|
||||
)
|
||||
.arguments("<path:file>")
|
||||
.action(push as any)
|
||||
.command("show", "show a scripts content")
|
||||
.command("get", "get a script's details")
|
||||
.arguments("<path:file>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("show", "show a script's content (alias for get)")
|
||||
.arguments("<path:file>")
|
||||
.action(show as any)
|
||||
.command("run", "run a script by path")
|
||||
@@ -1325,7 +1331,12 @@ const command = new Command()
|
||||
"Do not output anything other than the final output. Useful for scripting."
|
||||
)
|
||||
.action(preview as any)
|
||||
.command("bootstrap", "create a new script")
|
||||
.command("new", "create a new script")
|
||||
.arguments("<path:file> <language:string>")
|
||||
.option("--summary <summary:string>", "script summary")
|
||||
.option("--description <description:string>", "script description")
|
||||
.action(bootstrap as any)
|
||||
.command("bootstrap", "create a new script (alias for new)")
|
||||
.arguments("<path:file> <language:string>")
|
||||
.option("--summary <summary:string>", "script summary")
|
||||
.option("--description <description:string>", "script description")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
|
||||
let GLOBAL_VERSIONS: {
|
||||
remoteMajor: number | undefined;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import JSZip from "jszip";
|
||||
import { Workspace } from "../workspace/workspace.ts";
|
||||
import { getHeaders } from "../../utils/utils.ts";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
|
||||
function stub(_opts: GlobalOptions, _dir?: string) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import { readFile, writeFile, readdir, stat, rm, copyFile, mkdir } from "node:fs
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import * as path from "@std/path";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify, type DocumentOptions, type SchemaOptions, type CreateNodeOptions, type ToStringOptions } from "yaml";
|
||||
import JSZip from "jszip";
|
||||
import { minimatch } from "minimatch";
|
||||
import { yamlParseContent } from "../../utils/yaml.ts";
|
||||
@@ -276,13 +276,12 @@ function prioritizeName(name: string): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
export const yamlOptions = {
|
||||
sortKeys: (a: any, b: any) => {
|
||||
return prioritizeName(a).localeCompare(prioritizeName(b));
|
||||
export const yamlOptions: DocumentOptions & SchemaOptions & CreateNodeOptions & ToStringOptions = {
|
||||
sortMapEntries: (a, b) => {
|
||||
return prioritizeName(String(a.key)).localeCompare(prioritizeName(String(b.key)));
|
||||
},
|
||||
noCompatMode: true,
|
||||
noRefs: true,
|
||||
skipInvalid: true,
|
||||
aliasDuplicateObjects: false,
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
export interface InlineScript {
|
||||
@@ -1338,17 +1337,19 @@ async function compareDynFSElement(
|
||||
continue;
|
||||
}
|
||||
if (!ignoreCodebaseChanges) {
|
||||
const beforeCodebase = before?.codebase;
|
||||
const afterCodebase = after?.codebase;
|
||||
if (before?.codebase != undefined) {
|
||||
delete before.codebase;
|
||||
m2[k] = yamlStringify(before, yamlOptions);
|
||||
}
|
||||
if (after?.codebase != undefined) {
|
||||
if (before.codebase != after.codebase) {
|
||||
codebaseChanges[k] = after.codebase;
|
||||
}
|
||||
delete after.codebase;
|
||||
v = yamlStringify(after, yamlOptions);
|
||||
}
|
||||
if (beforeCodebase != afterCodebase) {
|
||||
codebaseChanges[k] = afterCodebase ?? beforeCodebase ?? "";
|
||||
}
|
||||
}
|
||||
if (skipMetadata) {
|
||||
continue;
|
||||
@@ -2214,7 +2215,7 @@ export async function push(
|
||||
`\nPush aborted: ${lockIssues.length} script(s) missing locks.`,
|
||||
),
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
log.info(colors.green("All scripts have valid locks."));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import {
|
||||
@@ -18,8 +19,8 @@ import {
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import {
|
||||
GlobalOptions,
|
||||
isSuperset,
|
||||
@@ -295,37 +296,192 @@ export async function pushNativeTrigger(
|
||||
}
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
const triggerTemplates: Record<TriggerType, Record<string, any>> = {
|
||||
http: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
route_path: "",
|
||||
http_method: "get",
|
||||
is_async: false,
|
||||
requires_auth: true,
|
||||
},
|
||||
websocket: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
url: "",
|
||||
enabled: false,
|
||||
},
|
||||
kafka: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
kafka_resource_path: "",
|
||||
group_id: "",
|
||||
topics: [],
|
||||
enabled: false,
|
||||
},
|
||||
nats: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
nats_resource_path: "",
|
||||
subjects: [],
|
||||
enabled: false,
|
||||
},
|
||||
postgres: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
postgres_resource_path: "",
|
||||
publication_name: "",
|
||||
replication_slot_name: "",
|
||||
enabled: false,
|
||||
},
|
||||
mqtt: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
mqtt_resource_path: "",
|
||||
topics: [],
|
||||
subscribe_qos: 0,
|
||||
enabled: false,
|
||||
},
|
||||
sqs: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
sqs_resource_path: "",
|
||||
queue_url: "",
|
||||
enabled: false,
|
||||
},
|
||||
gcp: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
gcp_resource_path: "",
|
||||
subscription_id: "",
|
||||
topic_id: "",
|
||||
enabled: false,
|
||||
},
|
||||
email: {
|
||||
script_path: "",
|
||||
is_flow: false,
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
async function newTrigger(opts: GlobalOptions & { kind: string }, path: string) {
|
||||
if (!validatePath(path)) {
|
||||
return;
|
||||
}
|
||||
if (!opts.kind) {
|
||||
throw new Error("--kind is required. Valid kinds: " + TRIGGER_TYPES.join(", "));
|
||||
}
|
||||
if (!checkIfValidTrigger(opts.kind)) {
|
||||
throw new Error("Invalid trigger kind: " + opts.kind + ". Valid kinds: " + TRIGGER_TYPES.join(", "));
|
||||
}
|
||||
const kind: TriggerType = opts.kind;
|
||||
const filePath = `${path}.${kind}_trigger.yaml`;
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template = triggerTemplates[kind];
|
||||
await writeFile(filePath, yamlStringify(template), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
const httpTriggers = await wmill.listHttpTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const websocketTriggers = await wmill.listWebsocketTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const kafkaTriggers = await wmill.listKafkaTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const natsTriggers = await wmill.listNatsTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const postgresTriggers = await wmill.listPostgresTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const mqttTriggers = await wmill.listMqttTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const sqsTriggers = await wmill.listSqsTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const gcpTriggers = await wmill.listGcpTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
const emailTriggers = await wmill.listEmailTriggers({
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
if (opts.kind) {
|
||||
if (!checkIfValidTrigger(opts.kind)) {
|
||||
throw new Error("Invalid trigger kind: " + opts.kind + ". Valid kinds: " + TRIGGER_TYPES.join(", "));
|
||||
}
|
||||
const trigger = await getTrigger(opts.kind, workspace.workspaceId, path);
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(trigger));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + (trigger as any).path);
|
||||
console.log(colors.bold("Kind:") + " " + opts.kind);
|
||||
console.log(colors.bold("Enabled:") + " " + ((trigger as any).enabled ?? "-"));
|
||||
console.log(colors.bold("Script Path:") + " " + ((trigger as any).script_path ?? ""));
|
||||
console.log(colors.bold("Is Flow:") + " " + ((trigger as any).is_flow ? "true" : "false"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Try all trigger types and collect matches
|
||||
const matches: { kind: string; trigger: any }[] = [];
|
||||
for (const kind of TRIGGER_TYPES) {
|
||||
try {
|
||||
const trigger = await getTrigger(kind, workspace.workspaceId, path);
|
||||
matches.push({ kind, trigger });
|
||||
} catch {
|
||||
// not found for this kind
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.length === 0) {
|
||||
throw new Error("No trigger found at path: " + path);
|
||||
}
|
||||
|
||||
if (matches.length === 1) {
|
||||
const { kind, trigger } = matches[0];
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(trigger));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + trigger.path);
|
||||
console.log(colors.bold("Kind:") + " " + kind);
|
||||
console.log(colors.bold("Enabled:") + " " + (trigger.enabled ?? "-"));
|
||||
console.log(colors.bold("Script Path:") + " " + (trigger.script_path ?? ""));
|
||||
console.log(colors.bold("Is Flow:") + " " + (trigger.is_flow ? "true" : "false"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple matches — ask user to specify --kind
|
||||
console.log("Multiple triggers found at path " + path + ":");
|
||||
for (const m of matches) {
|
||||
console.log(" - " + m.kind);
|
||||
}
|
||||
console.log("Please specify --kind <type> to select one.");
|
||||
}
|
||||
|
||||
async function listOrEmpty<T>(fn: () => Promise<T[]>): Promise<T[]> {
|
||||
try {
|
||||
return await fn();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
const ws = workspace.workspaceId;
|
||||
const [
|
||||
httpTriggers,
|
||||
websocketTriggers,
|
||||
kafkaTriggers,
|
||||
natsTriggers,
|
||||
postgresTriggers,
|
||||
mqttTriggers,
|
||||
sqsTriggers,
|
||||
gcpTriggers,
|
||||
emailTriggers,
|
||||
] = await Promise.all([
|
||||
listOrEmpty(() => wmill.listHttpTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listWebsocketTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listKafkaTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listNatsTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listPostgresTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listMqttTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listSqsTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listGcpTriggers({ workspace: ws })),
|
||||
listOrEmpty(() => wmill.listEmailTriggers({ workspace: ws })),
|
||||
]);
|
||||
const triggers = [
|
||||
...httpTriggers.map((x) => ({ path: x.path, kind: "http" })),
|
||||
...websocketTriggers.map((x) => ({ path: x.path, kind: "websocket" })),
|
||||
@@ -338,12 +494,16 @@ async function list(opts: GlobalOptions) {
|
||||
...emailTriggers.map((x) => ({ path: x.path, kind: "email" })),
|
||||
];
|
||||
|
||||
new Table()
|
||||
.header(["Path", "Kind"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(triggers.map((x) => [x.path, x.kind]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(triggers));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Path", "Kind"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(triggers.map((x) => [x.path, x.kind]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfValidTrigger(kind: string | undefined): kind is TriggerType {
|
||||
@@ -401,7 +561,20 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("trigger related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all triggers")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a trigger's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.option("--kind <kind:string>", "Trigger kind (http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email). Recommended for faster lookup")
|
||||
.action(get as any)
|
||||
.command("new", "create a new trigger locally")
|
||||
.arguments("<path:string>")
|
||||
.option("--kind <kind:string>", "Trigger kind (required: http, websocket, kafka, nats, postgres, mqtt, sqs, gcp, email)")
|
||||
.action(newTrigger as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local trigger spec. This overrides any remote versions."
|
||||
|
||||
@@ -11,8 +11,8 @@ import { compareInstanceObjects, InstanceSyncOptions } from "../instance/instanc
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import { stringify as yamlStringify } from "@std/yaml";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { stat } from "node:fs/promises";
|
||||
import { stat, writeFile } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
@@ -12,13 +13,13 @@ import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import { SEPARATOR as SEP } from "@std/path";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { ListableVariable } from "../../../gen/types.gen.ts";
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -26,19 +27,64 @@ async function list(opts: GlobalOptions) {
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
|
||||
new Table()
|
||||
.header(["Path", "Is Secret", "Account", "Value"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
variables.map((x) => [
|
||||
x.path,
|
||||
x.is_secret ? "true" : "false",
|
||||
x.account ?? "-",
|
||||
x.value ?? "-",
|
||||
])
|
||||
)
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(variables));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Path", "Is Secret", "Account", "Value"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
variables.map((x) => [
|
||||
x.path,
|
||||
x.is_secret ? "true" : "false",
|
||||
x.account ?? "-",
|
||||
x.value ?? "-",
|
||||
])
|
||||
)
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function newVariable(opts: GlobalOptions, path: string) {
|
||||
if (!validatePath(path)) {
|
||||
return;
|
||||
}
|
||||
const filePath = path + ".variable.yaml";
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template: VariableFile = {
|
||||
value: "",
|
||||
is_secret: false,
|
||||
description: "",
|
||||
};
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const v = await wmill.getVariable({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(v));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + v.path);
|
||||
console.log(colors.bold("Value:") + " " + (v.value ?? "-"));
|
||||
console.log(colors.bold("Is Secret:") + " " + (v.is_secret ? "true" : "false"));
|
||||
console.log(colors.bold("Description:") + " " + (v.description ?? ""));
|
||||
console.log(colors.bold("Account:") + " " + (v.account ?? "-"));
|
||||
}
|
||||
}
|
||||
|
||||
export interface VariableFile {
|
||||
@@ -178,7 +224,18 @@ async function add(
|
||||
|
||||
const command = new Command()
|
||||
.description("variable related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all variables")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a variable's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new variable locally")
|
||||
.arguments("<path:string>")
|
||||
.action(newVariable as any)
|
||||
.command(
|
||||
"push",
|
||||
"Push a local variable spec. This overrides any remote versions."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { setClient } from "../../core/client.ts";
|
||||
import { allInstances, getActiveInstance, InstanceSyncOptions, pickInstance } from "../instance/instance.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { pickInstance } from "../instance/instance.ts";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { setClient } from "../../core/client.ts";
|
||||
import { allWorkspaces, list, removeWorkspace } from "./workspace.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "@std/log";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { setClient } from "../../core/client.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { createWorkspaceFork, deleteWorkspaceFork } from "./fork.ts";
|
||||
@@ -518,7 +518,7 @@ async function bind(
|
||||
}
|
||||
|
||||
// Write back the updated config
|
||||
const { stringify: yamlStringify } = await import("@std/yaml");
|
||||
const { stringify: yamlStringify } = await import("yaml");
|
||||
try {
|
||||
await writeFile("wmill.yaml", yamlStringify(config), "utf-8");
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user