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:
Ruben Fiszel
2026-03-28 14:33:49 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 0ea9b945e0
commit f40cdaf434
14 changed files with 107 additions and 22 deletions
@@ -15,7 +15,7 @@
]
},
"nullable": [
null
true
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
@@ -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"
}
@@ -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"
}
+5 -3
View File
@@ -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 {
+2
View File
@@ -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)]
+5 -1
View File
@@ -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,
});
+32 -2
View File
@@ -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>")
+1 -1
View File
@@ -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(
+24 -3
View File
@@ -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>")
+2
View 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
+2
View File
@@ -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