mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-22 00:03:35 +00:00
feat(cli): auto-infer args for wmill app push (#9091)
Run `wmill app push` from inside an app folder (e.g. `f/foo/my_app.app/`) with no args. The local path defaults to CWD, and the remote path is derived from CWD relative to `wmill.yaml`, with `.app`/`.raw_app`/ `__app`/`__raw_app` suffixes stripped. Either, both, or neither positional argument can be passed. Also resolves `file_path` against the user's original CWD before `resolveWorkspace` may chdir to the wmill.yaml root, so a relative `file_path` argument is interpreted from where the user invoked the command (previously it could resolve against the wrong directory). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2c5d661f9c
commit
ba3d05c2fc
+113
-18
@@ -4,7 +4,7 @@ import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { sep as SEP, isAbsolute, resolve as pathResolve, relative as pathRelative, basename } from "node:path";
|
||||
import { stat } from "node:fs/promises";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
@@ -12,6 +12,7 @@ import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { ListableApp, Policy } from "../../../gen/types.gen.ts";
|
||||
|
||||
import { GlobalOptions, isSuperset } from "../../types.ts";
|
||||
import { getWmillYamlPath } from "../../core/conf.ts";
|
||||
import { readInlinePathSync } from "../../utils/utils.ts";
|
||||
import devCommand from "./dev.ts";
|
||||
import lintCommand from "./lint.ts";
|
||||
@@ -261,31 +262,122 @@ async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
if (!validatePath(remotePath)) {
|
||||
return;
|
||||
}
|
||||
const APP_FOLDER_SUFFIXES = ["__raw_app", ".raw_app", "__app", ".app"] as const;
|
||||
|
||||
async function push(
|
||||
opts: GlobalOptions,
|
||||
filePath?: string,
|
||||
remotePath?: string
|
||||
) {
|
||||
// Capture original CWD before resolveWorkspace, which may chdir to the
|
||||
// wmill.yaml root. We need it to resolve relative inputs and to derive
|
||||
// the remote path from the user's location when auto-inferring.
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
// Detect raw apps by checking for raw_app.yaml or __raw_app/.raw_app suffix
|
||||
const normalizedPath = filePath.endsWith(SEP) ? filePath.slice(0, -1) : filePath;
|
||||
const isRawApp = normalizedPath.endsWith("__raw_app") || normalizedPath.endsWith(".raw_app");
|
||||
// Auto-infer file path from CWD when omitted
|
||||
if (!filePath) {
|
||||
filePath = originalCwd;
|
||||
}
|
||||
const absoluteFilePath = isAbsolute(filePath)
|
||||
? filePath
|
||||
: pathResolve(originalCwd, filePath);
|
||||
|
||||
// Detect app folder type (regular vs raw)
|
||||
const normalizedPath = absoluteFilePath.endsWith(SEP)
|
||||
? absoluteFilePath.slice(0, -1)
|
||||
: absoluteFilePath;
|
||||
const dirName = basename(normalizedPath);
|
||||
const isRawAppByName =
|
||||
dirName.endsWith("__raw_app") || dirName.endsWith(".raw_app");
|
||||
const isAppByName = dirName.endsWith("__app") || dirName.endsWith(".app");
|
||||
|
||||
let hasRawAppYaml = false;
|
||||
if (!isRawApp) {
|
||||
let hasAppYaml = false;
|
||||
try {
|
||||
await stat(normalizedPath + SEP + "raw_app.yaml");
|
||||
hasRawAppYaml = true;
|
||||
} catch { /* not a raw app */ }
|
||||
if (!hasRawAppYaml) {
|
||||
try {
|
||||
const rawAppPath = (filePath.endsWith(SEP) ? filePath : filePath + SEP) + "raw_app.yaml";
|
||||
await stat(rawAppPath);
|
||||
hasRawAppYaml = true;
|
||||
} catch { /* not a raw app */ }
|
||||
await stat(normalizedPath + SEP + "app.yaml");
|
||||
hasAppYaml = true;
|
||||
} catch { /* not an app */ }
|
||||
}
|
||||
|
||||
if (isRawApp || hasRawAppYaml) {
|
||||
if (!isRawAppByName && !isAppByName && !hasRawAppYaml && !hasAppYaml) {
|
||||
log.error(
|
||||
colors.red(
|
||||
`'${filePath}' is not an app folder (no app.yaml or raw_app.yaml, and not a *.app/*.raw_app folder).`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-infer remote path from the folder location relative to wmill.yaml root
|
||||
if (!remotePath) {
|
||||
const wmillYamlPath = getWmillYamlPath();
|
||||
if (!wmillYamlPath) {
|
||||
log.error(
|
||||
colors.red(
|
||||
"Could not infer remote path: no wmill.yaml found. Run 'wmill init' or pass <remote_path> explicitly."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
// After resolveWorkspace, process.cwd() is the wmill.yaml dir
|
||||
const wmillRoot = process.cwd();
|
||||
let inferred = pathRelative(wmillRoot, normalizedPath).replaceAll(SEP, "/");
|
||||
if (inferred.startsWith("..") || isAbsolute(inferred)) {
|
||||
log.error(
|
||||
colors.red(
|
||||
`Could not infer remote path: '${filePath}' is outside the wmill.yaml root (${wmillRoot}). Move the folder under the root or pass <remote_path> explicitly.`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const suffix of APP_FOLDER_SUFFIXES) {
|
||||
if (inferred.endsWith(suffix)) {
|
||||
inferred = inferred.slice(0, -suffix.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inferred) {
|
||||
log.error(
|
||||
colors.red(
|
||||
"Could not infer remote path: app folder is at the wmill.yaml root. Pass <remote_path> explicitly."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!inferred.startsWith("u/") &&
|
||||
!inferred.startsWith("g/") &&
|
||||
!inferred.startsWith("f/")
|
||||
) {
|
||||
log.error(
|
||||
colors.red(
|
||||
`Could not infer remote path: '${inferred}' is not under u/, g/, or f/. Move the app under one of these prefixes or pass <remote_path> explicitly.`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
remotePath = inferred;
|
||||
log.info(colors.gray(`Inferred remote path: ${remotePath}`));
|
||||
}
|
||||
|
||||
if (!validatePath(remotePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRawAppByName || hasRawAppYaml) {
|
||||
const { pushRawApp } = await import("./raw_apps.ts");
|
||||
await pushRawApp(workspace.workspaceId, remotePath, filePath);
|
||||
await pushRawApp(workspace.workspaceId, remotePath, absoluteFilePath);
|
||||
log.info(colors.bold.underline.green("Raw app pushed"));
|
||||
} else {
|
||||
await pushApp(workspace.workspaceId, remotePath, filePath);
|
||||
await pushApp(workspace.workspaceId, remotePath, absoluteFilePath);
|
||||
log.info(colors.bold.underline.green("App pushed"));
|
||||
}
|
||||
}
|
||||
@@ -301,8 +393,11 @@ const command = new Command()
|
||||
.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>")
|
||||
.command(
|
||||
"push",
|
||||
"push a local app. With no args, infers the app from the current directory and the remote path from its location relative to wmill.yaml."
|
||||
)
|
||||
.arguments("[file_path:string] [remote_path:string]")
|
||||
.action(push as any)
|
||||
.command("dev", devCommand)
|
||||
.command("lint", lintCommand)
|
||||
|
||||
@@ -6588,7 +6588,7 @@ app related commands
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`app get <path:string>\` - get an app's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`app push <file_path:string> <remote_path:string>\` - push a local app
|
||||
- \`app push [file_path:string] [remote_path:string]\` - push a local app. With no args, infers the app from the current directory and the remote path from its location relative to wmill.yaml.
|
||||
- \`app dev [app_folder:string]\` - Start a development server for building apps with live reload and hot module replacement
|
||||
- \`--port <port:number>\` - Port to run the dev server on (will find next available port if occupied)
|
||||
- \`--host <host:string>\` - Host to bind the dev server to
|
||||
|
||||
@@ -26,7 +26,7 @@ app related commands
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `app get <path:string>` - get an app's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `app push <file_path:string> <remote_path:string>` - push a local app
|
||||
- `app push [file_path:string] [remote_path:string]` - push a local app. With no args, infers the app from the current directory and the remote path from its location relative to wmill.yaml.
|
||||
- `app dev [app_folder:string]` - Start a development server for building apps with live reload and hot module replacement
|
||||
- `--port <port:number>` - Port to run the dev server on (will find next available port if occupied)
|
||||
- `--host <host:string>` - Host to bind the dev server to
|
||||
|
||||
@@ -2174,7 +2174,7 @@ app related commands
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`app get <path:string>\` - get an app's details
|
||||
- \`--json\` - Output as JSON (for piping to jq)
|
||||
- \`app push <file_path:string> <remote_path:string>\` - push a local app
|
||||
- \`app push [file_path:string] [remote_path:string]\` - push a local app. With no args, infers the app from the current directory and the remote path from its location relative to wmill.yaml.
|
||||
- \`app dev [app_folder:string]\` - Start a development server for building apps with live reload and hot module replacement
|
||||
- \`--port <port:number>\` - Port to run the dev server on (will find next available port if occupied)
|
||||
- \`--host <host:string>\` - Host to bind the dev server to
|
||||
|
||||
@@ -31,7 +31,7 @@ app related commands
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `app get <path:string>` - get an app's details
|
||||
- `--json` - Output as JSON (for piping to jq)
|
||||
- `app push <file_path:string> <remote_path:string>` - push a local app
|
||||
- `app push [file_path:string] [remote_path:string]` - push a local app. With no args, infers the app from the current directory and the remote path from its location relative to wmill.yaml.
|
||||
- `app dev [app_folder:string]` - Start a development server for building apps with live reload and hot module replacement
|
||||
- `--port <port:number>` - Port to run the dev server on (will find next available port if occupied)
|
||||
- `--host <host:string>` - Host to bind the dev server to
|
||||
|
||||
Reference in New Issue
Block a user