mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 00:06:14 +00:00
fix(cli): app push crash, lint path, push --message, run validation, history timestamps (#8585)
* fix(cli): app push crash, lint entry point, push --message, run arg validation, history timestamps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): update sqlx cache and fix second history query missing created_at Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(cli): regenerate system prompts after new CLI options Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0ea9b945e0
commit
f40cdaf434
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg, s.created_at as created_at\n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -12,6 +12,11 @@
|
||||
"ordinal": 1,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -22,8 +27,9 @@
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "726e956cfcd3ac7c07abeecdf92cf0996efe7fa7b671ac2b3b000ead0ea307de"
|
||||
"hash": "9a1483a81f5b086e0765d3d69483e29b09f66090e1f9d394564c16d921d2e66c"
|
||||
}
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg \n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC LIMIT 1",
|
||||
"query": "SELECT s.hash as hash, dm.deployment_msg as deployment_msg, s.created_at as created_at\n FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash\n WHERE s.workspace_id = $1 AND s.path = $2\n ORDER by s.created_at DESC LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -12,6 +12,11 @@
|
||||
"ordinal": 1,
|
||||
"name": "deployment_msg",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -22,8 +27,9 @@
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "cf2a6ad6471a40b6298775cda9300aeecdd75503bed59d80cd62091d1642d1ec"
|
||||
"hash": "c73e98e5a937f44724a96ee1b74d31fa71a7be3b8ba3dec9f59f54a6c4030462"
|
||||
}
|
||||
@@ -1447,7 +1447,7 @@ async fn get_script_history(
|
||||
check_scopes(&authed, || format!("scripts:read:{}", path))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let query_result = sqlx::query!(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg, s.created_at as created_at
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by s.created_at DESC",
|
||||
@@ -1463,6 +1463,7 @@ async fn get_script_history(
|
||||
.map(|row| ScriptHistory {
|
||||
script_hash: ScriptHash(row.hash),
|
||||
deployment_msg: row.deployment_msg,
|
||||
created_at: Some(row.created_at),
|
||||
})
|
||||
.collect();
|
||||
return Ok(Json(result));
|
||||
@@ -1477,7 +1478,7 @@ async fn get_latest_version(
|
||||
check_scopes(&authed, || format!("scripts:read:{}", path))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let row_o = sqlx::query!(
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg
|
||||
"SELECT s.hash as hash, dm.deployment_msg as deployment_msg, s.created_at as created_at
|
||||
FROM script s LEFT JOIN deployment_metadata dm ON s.hash = dm.script_hash
|
||||
WHERE s.workspace_id = $1 AND s.path = $2
|
||||
ORDER by s.created_at DESC LIMIT 1",
|
||||
@@ -1491,7 +1492,8 @@ async fn get_latest_version(
|
||||
if let Some(row) = row_o {
|
||||
let result = ScriptHistory {
|
||||
script_hash: ScriptHash(row.hash),
|
||||
deployment_msg: row.deployment_msg, //
|
||||
deployment_msg: row.deployment_msg,
|
||||
created_at: Some(row.created_at),
|
||||
};
|
||||
return Ok(Json(Some(result)));
|
||||
} else {
|
||||
|
||||
@@ -450,6 +450,8 @@ pub struct ScriptHistory {
|
||||
pub script_hash: ScriptHash,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployment_msg: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -6,7 +6,7 @@ import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { createBundle } from "./bundle.ts";
|
||||
import { createBundle, detectFrameworks } from "./bundle.ts";
|
||||
import { APP_BACKEND_FOLDER } from "./app_metadata.ts";
|
||||
import { loadRunnablesFromBackend } from "./raw_apps.ts";
|
||||
import {
|
||||
@@ -113,7 +113,11 @@ async function validateBuild(
|
||||
log.info(colors.blue("🔨 Testing build..."));
|
||||
|
||||
// Try to create a bundle - this will validate that all dependencies are in place
|
||||
const frameworks = detectFrameworks(appDir);
|
||||
const entryFile = frameworks.svelte || frameworks.vue ? "index.ts" : "index.tsx";
|
||||
const entryPoint = path.join(appDir, entryFile);
|
||||
await createBundle({
|
||||
entryPoint,
|
||||
production: true,
|
||||
minify: false,
|
||||
});
|
||||
|
||||
@@ -204,14 +204,14 @@ export async function pushFlow(
|
||||
|
||||
type Options = GlobalOptions;
|
||||
|
||||
async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
async function push(opts: Options & { message?: string }, filePath: string, remotePath: string) {
|
||||
if (!validatePath(remotePath)) {
|
||||
return;
|
||||
}
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
await pushFlow(workspace.workspaceId, remotePath, filePath);
|
||||
await pushFlow(workspace.workspaceId, remotePath, filePath, opts.message);
|
||||
log.info(colors.bold.underline.green("Flow pushed"));
|
||||
}
|
||||
|
||||
@@ -295,6 +295,24 @@ async function run(
|
||||
|
||||
const input = opts.data ? await resolve(opts.data) : {};
|
||||
|
||||
// Validate required args against schema when no data provided
|
||||
if (!opts.data) {
|
||||
try {
|
||||
const flow = await wmill.getFlowByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
const required = (flow.schema as any)?.required ?? [];
|
||||
if (required.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required arguments: ${required.join(", ")}.\nUse -d '{"${required[0]}": ...}' to provide input data.`
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("Missing required")) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const id = await wmill.runFlowByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
@@ -467,6 +485,17 @@ async function preview(
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e.body) {
|
||||
// If a failure_module ran, the body contains its result — not an error
|
||||
if (e.body.result !== undefined) {
|
||||
if (opts.silent) {
|
||||
console.log(JSON.stringify(e.body.result));
|
||||
} else {
|
||||
log.info(colors.yellow.bold("Flow failed, error handler result:"));
|
||||
log.info(JSON.stringify(e.body.result, null, 2));
|
||||
}
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
log.error(`Flow preview failed: ${JSON.stringify(e.body)}`);
|
||||
}
|
||||
throw e;
|
||||
@@ -675,6 +704,7 @@ const command = new Command()
|
||||
"push a local flow spec. This overrides any remote versions."
|
||||
)
|
||||
.arguments("<file_path:string> <remote_path:string>")
|
||||
.option("--message <message:string>", "Deployment message")
|
||||
.action(push as any)
|
||||
.command("run", "run a flow by path.")
|
||||
.arguments("<path:string>")
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface InitOptions {
|
||||
*/
|
||||
async function initAction(opts: InitOptions) {
|
||||
if (await stat("wmill.yaml").catch(() => null)) {
|
||||
log.error(colors.red("wmill.yaml already exists"));
|
||||
log.info("wmill.yaml already exists, skipping config generation");
|
||||
} else {
|
||||
// Detect current git branch for template
|
||||
const { isGitRepository, getCurrentGitBranch } = await import(
|
||||
|
||||
@@ -104,7 +104,7 @@ export function isFlowInlineScriptPath(filePath: string): boolean {
|
||||
return isFlowInlineScriptPathInternal(filePath);
|
||||
}
|
||||
|
||||
type PushOptions = GlobalOptions;
|
||||
type PushOptions = GlobalOptions & { message?: string };
|
||||
async function push(opts: PushOptions, filePath: string) {
|
||||
opts = await mergeConfigWithConfigFile(opts);
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
@@ -148,7 +148,7 @@ async function push(opts: PushOptions, filePath: string) {
|
||||
filePath,
|
||||
workspace,
|
||||
[],
|
||||
undefined,
|
||||
opts.message,
|
||||
opts,
|
||||
await getRawWorkspaceDependencies(true),
|
||||
codebases
|
||||
@@ -948,6 +948,25 @@ async function run(
|
||||
await requireLogin(opts);
|
||||
|
||||
const input = opts.data ? await resolve(opts.data) : {};
|
||||
|
||||
// Validate required args against schema when no data provided
|
||||
if (!opts.data) {
|
||||
try {
|
||||
const script = await wmill.getScriptByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
const required = (script.schema as any)?.required ?? [];
|
||||
if (required.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required arguments: ${required.join(", ")}.\nUse -d '{"${required[0]}": ...}' to provide input data.`
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("Missing required")) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
let id: string;
|
||||
try {
|
||||
id = await wmill.runScriptByPath({
|
||||
@@ -1552,13 +1571,14 @@ async function history(
|
||||
return;
|
||||
}
|
||||
new Table()
|
||||
.header(["#", "Hash", "Deployment Message"])
|
||||
.header(["#", "Hash", "Created At", "Deployment Message"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
versions.map((v, i) => [
|
||||
String(versions.length - i),
|
||||
v.script_hash,
|
||||
v.created_at ? new Date(v.created_at).toLocaleString() : "-",
|
||||
v.deployment_msg ?? "-",
|
||||
])
|
||||
)
|
||||
@@ -1580,6 +1600,7 @@ const command = new Command()
|
||||
"push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh)"
|
||||
)
|
||||
.arguments("<path:file>")
|
||||
.option("--message <message:string>", "Deployment message")
|
||||
.action(push as any)
|
||||
.command("get", "get a script's details")
|
||||
.arguments("<path:file>")
|
||||
|
||||
@@ -5058,6 +5058,7 @@ flow related commands
|
||||
- \`flow get <path:string>\` - get a flow's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`flow push <file_path:string> <remote_path:string>\` - push a local flow spec. This overrides any remote versions.
|
||||
- \`--message <message:string>\` - Deployment message
|
||||
- \`flow run <path:string>\` - run a flow by path.
|
||||
- \`-d --data <data:string>\` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
|
||||
- \`-s --silent\` - Do not ouput anything other then the final output. Useful for scripting.
|
||||
@@ -5336,6 +5337,7 @@ script related commands
|
||||
- \`--show-archived\` - Enable archived scripts in output
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`script push <path:file>\` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
|
||||
- \`--message <message:string>\` - Deployment message
|
||||
- \`script get <path:file>\` - get a script's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`script show <path:file>\` - show a script's content (alias for get
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import type { Policy, ScriptLang } from '$lib/gen'
|
||||
import { collectStaticFields, hash, type TriggerableV2 } from '../apps/editor/commonAppUtils'
|
||||
import { isRunnableByName, isRunnableByPath, type InlineScript, type RunnableWithFields } from '../apps/inputType'
|
||||
import {
|
||||
isRunnableByName,
|
||||
isRunnableByPath,
|
||||
type InlineScript,
|
||||
type RunnableWithFields
|
||||
} from '../apps/inputType'
|
||||
|
||||
export async function updateRawAppPolicy(
|
||||
runnables: Record<string, Runnable>,
|
||||
currentPolicy: Policy | undefined
|
||||
): Promise<Policy> {
|
||||
const triggerables_v2 = Object.fromEntries(
|
||||
(await Promise.all(
|
||||
const entries = (
|
||||
await Promise.all(
|
||||
Object.entries(runnables).map(async ([id, runnable]) => {
|
||||
return await processRunnable(id, runnable, runnable?.fields ?? {})
|
||||
})
|
||||
)) as [string, TriggerableV2][]
|
||||
)
|
||||
)
|
||||
).filter((entry): entry is [string, TriggerableV2] => entry != null)
|
||||
const triggerables_v2 = Object.fromEntries(entries)
|
||||
return {
|
||||
...currentPolicy,
|
||||
triggerables_v2
|
||||
|
||||
@@ -100,6 +100,7 @@ flow related commands
|
||||
- `flow get <path:string>` - get a flow's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `flow push <file_path:string> <remote_path:string>` - push a local flow spec. This overrides any remote versions.
|
||||
- `--message <message:string>` - Deployment message
|
||||
- `flow run <path:string>` - run a flow by path.
|
||||
- `-d --data <data:string>` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
|
||||
- `-s --silent` - Do not ouput anything other then the final output. Useful for scripting.
|
||||
@@ -378,6 +379,7 @@ script related commands
|
||||
- `--show-archived` - Enable archived scripts in output
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `script push <path:file>` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
|
||||
- `--message <message:string>` - Deployment message
|
||||
- `script get <path:file>` - get a script's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `script show <path:file>` - show a script's content (alias for get
|
||||
|
||||
@@ -1627,6 +1627,7 @@ flow related commands
|
||||
- \`flow get <path:string>\` - get a flow's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`flow push <file_path:string> <remote_path:string>\` - push a local flow spec. This overrides any remote versions.
|
||||
- \`--message <message:string>\` - Deployment message
|
||||
- \`flow run <path:string>\` - run a flow by path.
|
||||
- \`-d --data <data:string>\` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
|
||||
- \`-s --silent\` - Do not ouput anything other then the final output. Useful for scripting.
|
||||
@@ -1905,6 +1906,7 @@ script related commands
|
||||
- \`--show-archived\` - Enable archived scripts in output
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`script push <path:file>\` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
|
||||
- \`--message <message:string>\` - Deployment message
|
||||
- \`script get <path:file>\` - get a script's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`script show <path:file>\` - show a script's content (alias for get
|
||||
|
||||
@@ -105,6 +105,7 @@ flow related commands
|
||||
- `flow get <path:string>` - get a flow's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `flow push <file_path:string> <remote_path:string>` - push a local flow spec. This overrides any remote versions.
|
||||
- `--message <message:string>` - Deployment message
|
||||
- `flow run <path:string>` - run a flow by path.
|
||||
- `-d --data <data:string>` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
|
||||
- `-s --silent` - Do not ouput anything other then the final output. Useful for scripting.
|
||||
@@ -383,6 +384,7 @@ script related commands
|
||||
- `--show-archived` - Enable archived scripts in output
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `script push <path:file>` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
|
||||
- `--message <message:string>` - Deployment message
|
||||
- `script get <path:file>` - get a script's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `script show <path:file>` - show a script's content (alias for get
|
||||
|
||||
Reference in New Issue
Block a user