From 59f39d860aad620e63ead88fafce4582625b50c6 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:59:07 +0200 Subject: [PATCH] chore(cli): better folder structure + add config utils (#6319) * organize in folders * add config command * fix * cleaning * move utility functions * merge * only show token with option * only show token with option * fix * remove config command * add config utils * change paths * nit * clean path assigner --------- Co-authored-by: Alexander Petric --- .github/change-versions-mac.sh | 2 +- .github/change-versions.sh | 2 +- cli/add-ts-ext.sh | 64 +++ cli/build-mac.sh | 30 +- cli/build.sh | 27 +- cli/dnt.ts | 8 +- cli/main.ts | 461 ------------------ cli/{ => src/commands/app}/apps.ts | 14 +- cli/{ => src/commands/dev}/dev.ts | 24 +- cli/{ => src/commands/flow}/flow.ts | 26 +- cli/{ => src/commands/folder}/folder.ts | 12 +- .../gitsync-settings}/gitsync-settings.ts | 16 +- cli/{ => src/commands/hub}/hub.ts | 14 +- cli/src/commands/init/init.ts | 228 +++++++++ cli/{ => src/commands/instance}/instance.ts | 34 +- cli/{ => src/commands/queues}/queues.ts | 8 +- .../commands/resource-type}/resource-type.ts | 12 +- cli/{ => src/commands/resource}/resource.ts | 14 +- cli/{ => src/commands/schedule}/schedule.ts | 12 +- cli/{ => src/commands/script}/script.ts | 32 +- cli/{ => src/commands/sync}/pull.ts | 8 +- cli/{ => src/commands/sync}/push.ts | 4 +- cli/{ => src/commands/sync}/sync.ts | 96 +--- cli/{ => src/commands/trigger}/trigger.ts | 12 +- cli/{ => src/commands/user}/user.ts | 12 +- cli/{ => src/commands/variable}/variable.ts | 12 +- .../commands/worker-groups}/worker_groups.ts | 10 +- cli/{ => src/commands/workers}/workers.ts | 8 +- cli/{ => src/commands/workspace}/workspace.ts | 28 +- cli/{ => src/core}/auth.ts | 10 +- cli/{ => src/core}/conf.ts | 7 +- cli/{ => src/core}/context.ts | 16 +- cli/{ => src/core}/login.ts | 4 +- cli/{ => src/core}/settings.ts | 17 +- cli/src/core/store.ts | 22 + cli/{ => src/guidance}/flow_guidance.ts | 0 cli/{ => src/guidance}/script_guidance.ts | 0 cli/src/main.ts | 241 +++++++++ cli/{ => src}/types.ts | 30 +- cli/{ => src/utils}/codebase.ts | 4 +- cli/{ => src/utils}/local_encryption.ts | 0 cli/{ => src/utils}/metadata.ts | 69 ++- cli/{ => src/utils}/script_common.ts | 0 cli/{ => src/utils}/upgrade.ts | 2 +- cli/{ => src/utils}/utils.ts | 13 +- cli/store.ts | 82 ---- cli/test.nu | 8 +- cli/test/containerized_backend.ts | 4 +- cli/test/test_config_helpers.ts | 4 +- .../src/config/config.ts | 199 ++++++++ .../src/config/index.ts | 1 + cli/windmill-utils-internal/src/index.ts | 1 + .../src/inline-scripts/extractor.ts | 17 +- .../src/path-utils/path-assigner.ts | 52 +- 54 files changed, 1084 insertions(+), 949 deletions(-) create mode 100755 cli/add-ts-ext.sh delete mode 100644 cli/main.ts rename cli/{ => src/commands/app}/apps.ts (90%) rename cli/{ => src/commands/dev}/dev.ts (89%) rename cli/{ => src/commands/flow}/flow.ts (90%) rename cli/{ => src/commands/folder}/folder.ts (88%) rename cli/{ => src/commands/gitsync-settings}/gitsync-settings.ts (98%) rename cli/{ => src/commands/hub}/hub.ts (85%) create mode 100644 cli/src/commands/init/init.ts rename cli/{ => src/commands/instance}/instance.ts (95%) rename cli/{ => src/commands/queues}/queues.ts (94%) rename cli/{ => src/commands/resource-type}/resource-type.ts (89%) rename cli/{ => src/commands/resource}/resource.ts (88%) rename cli/{ => src/commands/schedule}/schedule.ts (91%) rename cli/{ => src/commands/script}/script.ts (97%) rename cli/{ => src/commands/sync}/pull.ts (91%) rename cli/{ => src/commands/sync}/push.ts (79%) rename cli/{ => src/commands/sync}/sync.ts (95%) rename cli/{ => src/commands/trigger}/trigger.ts (95%) rename cli/{ => src/commands/user}/user.ts (98%) rename cli/{ => src/commands/variable}/variable.ts (92%) rename cli/{ => src/commands/worker-groups}/worker_groups.ts (90%) rename cli/{ => src/commands/workers}/workers.ts (94%) rename cli/{ => src/commands/workspace}/workspace.ts (93%) rename cli/{ => src/core}/auth.ts (85%) rename cli/{ => src/core}/conf.ts (96%) rename cli/{ => src/core}/context.ts (91%) rename cli/{ => src/core}/login.ts (96%) rename cli/{ => src/core}/settings.ts (97%) create mode 100644 cli/src/core/store.ts rename cli/{ => src/guidance}/flow_guidance.ts (100%) rename cli/{ => src/guidance}/script_guidance.ts (100%) create mode 100644 cli/src/main.ts rename cli/{ => src}/types.ts (90%) rename cli/{ => src/utils}/codebase.ts (91%) rename cli/{ => src/utils}/local_encryption.ts (100%) rename cli/{ => src/utils}/metadata.ts (92%) rename cli/{ => src/utils}/script_common.ts (100%) rename cli/{ => src/utils}/upgrade.ts (97%) rename cli/{ => src/utils}/utils.ts (94%) delete mode 100644 cli/store.ts create mode 100644 cli/windmill-utils-internal/src/config/config.ts create mode 100644 cli/windmill-utils-internal/src/config/index.ts diff --git a/.github/change-versions-mac.sh b/.github/change-versions-mac.sh index 50ec3b17ed..b12d83b530 100755 --- a/.github/change-versions-mac.sh +++ b/.github/change-versions-mac.sh @@ -7,7 +7,7 @@ VERSION=$1 echo "Updating versions to: $VERSION" sed -i '' -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/backend/Cargo.toml -sed -i '' -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/cli/main.ts +sed -i '' -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/cli/src/main.ts sed -i '' -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/benchmarks/lib.ts sed -i '' -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/backend/windmill-api/openapi.yaml sed -i '' -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/openflow.openapi.yaml diff --git a/.github/change-versions.sh b/.github/change-versions.sh index 454f373038..7db47d014c 100755 --- a/.github/change-versions.sh +++ b/.github/change-versions.sh @@ -7,7 +7,7 @@ VERSION=$1 echo "Updating versions to: $VERSION" sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" ${root_dirpath}/backend/Cargo.toml -sed -i -e "/^export const VERSION =/s/= .*/= \"$VERSION\";/" ${root_dirpath}/cli/main.ts +sed -i -e "/^export const VERSION =/s/= .*/= \"$VERSION\";/" ${root_dirpath}/cli/src/main.ts sed -i -e "/^export const VERSION =/s/= .*/= \"v$VERSION\";/" ${root_dirpath}/benchmarks/lib.ts sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/backend/windmill-api/openapi.yaml sed -i -e "/version: /s/: .*/: $VERSION/" ${root_dirpath}/openflow.openapi.yaml diff --git a/cli/add-ts-ext.sh b/cli/add-ts-ext.sh new file mode 100755 index 0000000000..07af4db948 --- /dev/null +++ b/cli/add-ts-ext.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Set script to exit on any error +set -e + +# Default to regular sed +REVERT_MODE=false + +# Function to show usage +show_usage() { + echo "Usage: $0 [-r] [-h]" + echo " -r Revert changes (restore from .orig files)" + echo " -h Show this help message" + exit 1 +} + +# Parse command line arguments +while getopts "rh" opt; do + case ${opt} in + r ) + REVERT_MODE=true + ;; + h ) + show_usage + ;; + \? ) + echo "Invalid option: -$OPTARG" >&2 + show_usage + ;; + esac +done + +# Function to add .ts extensions to relative imports +add_ts_extensions() { + echo "Adding .ts extensions to imports..." + find windmill-utils-internal/src -name "*.ts" -type f | while read -r file; do + # Create backup of original + cp "$file" "$file.orig" + + # Add .ts to relative imports that don't already have extensions + sed -E \ + -e 's/(from[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ + -e 's/(import[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ + "$file.orig" > "$file" + done + echo "✓ Added .ts extensions" +} + +# Function to revert to original files +revert_extensions() { + echo "Removing .ts extensions..." + find windmill-utils-internal/src -name "*.orig" -type f | while read -r backup_file; do + original_file="${backup_file%.orig}" + mv "$backup_file" "$original_file" + done + echo "✓ All files reverted" +} + +# Main execution +if [ "$REVERT_MODE" = true ]; then + revert_extensions +else + add_ts_extensions +fi diff --git a/cli/build-mac.sh b/cli/build-mac.sh index 7b8406032d..408ad23252 100755 --- a/cli/build-mac.sh +++ b/cli/build-mac.sh @@ -1,39 +1,19 @@ #!/usr/bin/env bash +# Set script to exit on any error +set -e + # Generate client files ./gen_wm_client-mac.sh # Generate utils client files ./windmill-utils-internal/gen_wm_client-mac.sh -# Function to add .ts extensions to relative imports -add_ts_extensions() { - find windmill-utils-internal/src -name "*.ts" -type f | while read -r file; do - # Create backup of original - cp "$file" "$file.orig" - - # Add .ts to relative imports that don't already have extensions - gsed -E \ - -e 's/(from[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - -e 's/(import[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - "$file.orig" > "$file" - done -} - -# Function to revert to original files -revert_extensions() { - find windmill-utils-internal/src -name "*.orig" -type f | while read -r backup_file; do - original_file="${backup_file%.orig}" - mv "$backup_file" "$original_file" - done -} - # Set up trap to ensure cleanup happens on exit, error, or interruption -trap 'echo "Cleaning up..."; revert_extensions' EXIT ERR INT TERM +trap 'echo "Cleaning up..."; ./add-ts-ext.sh -r' EXIT ERR INT TERM # Add .ts extensions for Deno -echo "Adding .ts extensions for Deno build..." -add_ts_extensions +./add-ts-ext.sh # Run dnt echo "Running dnt..." diff --git a/cli/build.sh b/cli/build.sh index e6244d95f6..bffdc4208e 100755 --- a/cli/build.sh +++ b/cli/build.sh @@ -9,34 +9,11 @@ set -e # Generate utils client files ./windmill-utils-internal/gen_wm_client.sh -# Function to add .ts extensions to relative imports -add_ts_extensions() { - find windmill-utils-internal/src -name "*.ts" -type f | while read -r file; do - # Create backup of original - cp "$file" "$file.orig" - - # Add .ts to relative imports that don't already have extensions - sed -E \ - -e 's/(from[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - -e 's/(import[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - "$file.orig" > "$file" - done -} - -# Function to revert to original files -revert_extensions() { - find windmill-utils-internal/src -name "*.orig" -type f | while read -r backup_file; do - original_file="${backup_file%.orig}" - mv "$backup_file" "$original_file" - done -} - # Set up trap to ensure cleanup happens on exit, error, or interruption -trap 'echo "Cleaning up..."; revert_extensions' EXIT ERR INT TERM +trap 'echo "Cleaning up..."; ./add-ts-ext.sh -r' EXIT ERR INT TERM # Add .ts extensions for Deno -echo "Adding .ts extensions for Deno build..." -add_ts_extensions +./add-ts-ext.sh # Run dnt echo "Running dnt..." diff --git a/cli/dnt.ts b/cli/dnt.ts index 2e1666eab1..ac99fc1c4a 100644 --- a/cli/dnt.ts +++ b/cli/dnt.ts @@ -1,15 +1,15 @@ // ex. scripts/build_npm.ts import { build, emptyDir } from "jsr:@deno/dnt@0.41.3"; -import { VERSION } from "./main.ts"; +import { VERSION } from "./src/main.ts"; await emptyDir("./npm"); await build({ entryPoints: [ - "main.ts", + "src/main.ts", { kind: "bin", name: "wmill", // command name - path: "./main.ts", + path: "./src/main.ts", }, ], outDir: "./npm", @@ -33,7 +33,7 @@ await build({ diagnostic.file?.fileName.includes("node_modules/") || diagnostic.file?.fileName.includes("src/deps/") || diagnostic.file?.fileName.includes("src/deps.ts") || - diagnostic.file?.fileName.includes("src/utils.ts") + diagnostic.file?.fileName.includes("src/utils/utils.ts") ) { return false; // ignore all diagnostics in this file } diff --git a/cli/main.ts b/cli/main.ts deleted file mode 100644 index 8e00f07599..0000000000 --- a/cli/main.ts +++ /dev/null @@ -1,461 +0,0 @@ -import { - Command, - CompletionsCommand, - UpgradeCommand, - colors, - esMain, - log, - yamlStringify, -} from "./deps.ts"; -import flow from "./flow.ts"; -import app from "./apps.ts"; -import script from "./script.ts"; -import workspace, { getActiveWorkspace } from "./workspace.ts"; -import resource from "./resource.ts"; -import resourceType from "./resource-type.ts"; -import user from "./user.ts"; -import variable from "./variable.ts"; -import hub from "./hub.ts"; -import folder from "./folder.ts"; -import schedule from "./schedule.ts"; -import trigger from "./trigger.ts"; -import sync from "./sync.ts"; -import gitsyncSettings from "./gitsync-settings.ts"; -import instance from "./instance.ts"; -import workerGroups from "./worker_groups.ts"; -import { SCRIPT_GUIDANCE } from "./script_guidance.ts"; - -import dev from "./dev.ts"; -import { fetchVersion } from "./context.ts"; -import { GlobalOptions } from "./types.ts"; -import { OpenAPI } from "./gen/index.ts"; -import { getHeaders } from "./utils.ts"; -import { NpmProvider } from "./upgrade.ts"; -import { pull as hubPull } from "./hub.ts"; -import { pull, push } from "./sync.ts"; -import { add as workspaceAdd } from "./workspace.ts"; -import workers from "./workers.ts"; -import queues from "./queues.ts"; -import { readLockfile } from "./metadata.ts"; -import { FLOW_GUIDANCE } from "./flow_guidance.ts"; - -export { - flow, - app, - script, - workspace, - resource, - resourceType, - user, - variable, - hub, - folder, - schedule, - trigger, - sync, - gitsyncSettings, - instance, - dev, - hubPull, - pull, - push, - workspaceAdd, -}; - -// addEventListener("error", (event) => { -// if (event.error) { -// console.error("Error details of: " + event.error.message); -// console.error(JSON.stringify(event.error, null, 4)); -// } -// }); - -export const VERSION = "1.518.2"; - -const command = new Command() - .name("wmill") - .action(() => - log.info(`Welcome to Windmill CLI ${VERSION}. Use -h for help.`) - ) - .description("Windmill CLI") - - .globalOption( - "--workspace ", - "Specify the target workspace. This overrides the default workspace." - ) - .globalOption("--debug --verbose", "Show debug/verbose logs") - .globalOption( - "--show-diffs", - "Show diff informations when syncing (may show sensitive informations)" - ) - .globalOption( - "--token ", - "Specify an API token. This will override any stored token." - ) - .globalOption( - "--base-url ", - "Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace already set will be used." - ) - .globalOption( - "--config-dir ", - "Specify a custom config directory. Overrides WMILL_CONFIG_DIR environment variable and default ~/.config location." - ) - .env( - "HEADERS ", - "Specify headers to use for all requests. e.g: \"HEADERS='h1: v1, h2: v2'\"" - ) - .version(VERSION) - .versionOption(false) - .command("init", "Bootstrap a windmill project with a wmill.yaml file") - .option("--use-default", "Use default settings without checking backend") - .option("--use-backend", "Use backend git-sync settings if available") - .option( - "--repository ", - "Specify repository path (e.g., u/user/repo) when using backend settings" - ) - .action( - async (opts: { - useDefault?: boolean; - useBackend?: boolean; - repository?: string; - workspace?: string; - debug?: unknown; - showDiffs?: boolean; - token?: string; - baseUrl?: string; - configDir?: string; - }) => { - if (await Deno.stat("wmill.yaml").catch(() => null)) { - log.error(colors.red("wmill.yaml already exists")); - } else { - // Import DEFAULT_SYNC_OPTIONS from conf.ts - const { DEFAULT_SYNC_OPTIONS } = await import("./conf.ts"); - - // Create initial config with defaults - const initialConfig = { - defaultTs: DEFAULT_SYNC_OPTIONS.defaultTs, - includes: DEFAULT_SYNC_OPTIONS.includes, - excludes: DEFAULT_SYNC_OPTIONS.excludes, - codebases: DEFAULT_SYNC_OPTIONS.codebases, - skipVariables: DEFAULT_SYNC_OPTIONS.skipVariables, - skipResources: DEFAULT_SYNC_OPTIONS.skipResources, - skipSecrets: DEFAULT_SYNC_OPTIONS.skipSecrets, - skipScripts: DEFAULT_SYNC_OPTIONS.skipScripts, - skipFlows: DEFAULT_SYNC_OPTIONS.skipFlows, - skipApps: DEFAULT_SYNC_OPTIONS.skipApps, - skipFolders: DEFAULT_SYNC_OPTIONS.skipFolders, - includeSchedules: DEFAULT_SYNC_OPTIONS.includeSchedules, - includeTriggers: DEFAULT_SYNC_OPTIONS.includeTriggers, - overrides: {}, - }; - - await Deno.writeTextFile("wmill.yaml", yamlStringify(initialConfig)); - log.info(colors.green("wmill.yaml created with default settings")); - - // Create lock file - await readLockfile(); - - // Check for backend git-sync settings unless --use-default is specified - if (!opts.useDefault) { - try { - const { requireLogin } = await import("./auth.ts"); - const { resolveWorkspace } = await import("./context.ts"); - - // Check if user has workspace configured - const { getActiveWorkspace } = await import("./workspace.ts"); - const activeWorkspace = await getActiveWorkspace( - opts as GlobalOptions - ); - - if (!activeWorkspace) { - log.info("No workspace configured. Using default settings."); - log.info( - "You can configure a workspace later with 'wmill workspace add'" - ); - return; - } - - await requireLogin(opts as GlobalOptions); - const workspace = await resolveWorkspace(opts as GlobalOptions); - - const wmill = await import("./gen/services.gen.ts"); - const settings = await wmill.getSettings({ - workspace: workspace.workspaceId, - }); - - if ( - settings.git_sync?.repositories && - settings.git_sync.repositories.length > 0 - ) { - let useBackendSettings = opts.useBackend; - - // If repository is specified, implicitly use backend settings - if (opts.repository && !opts.useDefault) { - useBackendSettings = true; - } - - if (useBackendSettings === undefined) { - // Interactive prompt - const { Select } = await import("./deps.ts"); - const choice = await Select.prompt({ - message: - "Git-sync settings found on backend. What would you like to do?", - options: [ - { - name: "Use backend git-sync settings", - value: "backend", - }, - { - name: "Use default settings", - value: "default", - }, - { - name: "Cancel", - value: "cancel", - }, - ], - }); - - if (choice === "cancel") { - // Clean up the created files - try { - await Deno.remove("wmill.yaml"); - await Deno.remove("wmill-lock.yaml"); - } catch (e) { - // Ignore cleanup errors - } - log.info("Init cancelled"); - Deno.exit(0); - } - - useBackendSettings = choice === "backend"; - } - - if (useBackendSettings) { - log.info("Applying git-sync settings from backend..."); - - // Import and run the pull git-sync settings logic - const { pullGitSyncSettings } = await import( - "./gitsync-settings.ts" - ); - await pullGitSyncSettings({ - ...(opts as GlobalOptions), - repository: opts.repository, - jsonOutput: false, - diff: false, - replace: true, // Auto-replace when using backend settings during init - }); - - log.info( - colors.green("Git-sync settings applied from backend") - ); - } - } - } catch (error) { - // If there's an error checking backend settings, just continue with defaults - const errorMessage = - error instanceof Error ? error.message : String(error); - log.warn( - `Could not check backend for git-sync settings: ${errorMessage}` - ); - log.info("Continuing with default settings"); - } - } - } - - // Create .cursor/rules directory and files with SCRIPT_GUIDANCE content - try { - const scriptGuidanceContent = SCRIPT_GUIDANCE; - const flowGuidanceContent = FLOW_GUIDANCE; - - // Create .cursor/rules directory - await Deno.mkdir(".cursor/rules", { recursive: true }); - - // Create windmill.mdc file - if (!(await Deno.stat(".cursor/rules/script.mdc").catch(() => null))) { - await Deno.writeTextFile( - ".cursor/rules/script.mdc", - scriptGuidanceContent - ); - log.info(colors.green("Created .cursor/rules/script.mdc")); - } - - if (!(await Deno.stat(".cursor/rules/flow.mdc").catch(() => null))) { - await Deno.writeTextFile( - ".cursor/rules/flow.mdc", - flowGuidanceContent - ); - log.info(colors.green("Created .cursor/rules/flow.mdc")); - } - - // Create CLAUDE.md file - if (!(await Deno.stat("CLAUDE.md").catch(() => null))) { - await Deno.writeTextFile( - "CLAUDE.md", - ` - # Claude - - You are a helpful assistant that can help with Windmill scripts and flows creation. - - ## Script Guidance - ${scriptGuidanceContent} - - ## Flow Guidance - ${flowGuidanceContent} - ` - ); - log.info(colors.green("Created CLAUDE.md")); - } - } catch (error) { - if (error instanceof Error) { - log.warn(`Could not create guidance files: ${error.message}`); - } else { - log.warn(`Could not create guidance files: ${error}`); - } - } - } - ) - .command("app", app) - .command("flow", flow) - .command("script", script) - .command("workspace", workspace) - .command("resource", resource) - .command("resource-type", resourceType) - .command("user", user) - .command("variable", variable) - .command("hub", hub) - .command("folder", folder) - .command("schedule", schedule) - .command("trigger", trigger) - .command("dev", dev) - .command("sync", sync) - .command("gitsync-settings", gitsyncSettings) - .command("instance", instance) - .command("worker-groups", workerGroups) - .command("workers", workers) - .command("queues", queues) - .command("version --version", "Show version information") - .action(async (opts) => { - console.log("CLI version: " + VERSION); - try { - const provider = new NpmProvider({ package: "windmill-cli" }); - const versions = await provider.getVersions("windmill-cli"); - if (versions.latest !== VERSION) { - console.log( - `CLI is outdated. Latest version ${versions.latest} is available. Run \`wmill upgrade\` to update.` - ); - } else { - console.log("CLI is up to date"); - } - } catch (e) { - console.warn( - `Cannot fetch latest CLI version on npmjs to check if up-to-date: ${e}` - ); - } - const workspace = await getActiveWorkspace(opts as GlobalOptions); - if (workspace) { - try { - const backendVersion = await fetchVersion(workspace.remote); - console.log("Backend Version: " + backendVersion); - } catch (e) { - console.warn("Cannot fetch backend version: " + e); - } - } else { - console.warn( - "Cannot fetch backend version: no active workspace selected, choose one to pick a remote to fetch version of" - ); - } - }) - .command( - "upgrade", - new UpgradeCommand({ - provider: new NpmProvider({ package: "windmill-cli" }), - }).error((e: any) => { - log.error(e); - log.info( - "Try running with sudo and otherwise check the result of the command: npm uninstall windmill-cli && npm install -g windmill-cli" - ); - }) - ) - .command("completions", new CompletionsCommand()); - -export let showDiffs = false; - -let isWin: boolean | undefined = undefined; - -export async function getIsWin() { - if (isWin === undefined) { - const os = await import("node:os"); - isWin = os.platform() === "win32"; - } - return isWin; -} - -async function main() { - try { - if (Deno.args.length === 0) { - command.showHelp(); - } - const LOG_LEVEL = - Deno.args.includes("--verbose") || Deno.args.includes("--debug") - ? "DEBUG" - : "INFO"; - // const NO_COLORS = Deno.args.includes("--no-colors"); - showDiffs = Deno.args.includes("--show-diffs"); - - log.setup({ - handlers: { - console: new log.ConsoleHandler(LOG_LEVEL, { - formatter: ({ msg }) => `${msg}`, - useColors: isWin ? false : true, - }), - }, - loggers: { - default: { - level: LOG_LEVEL, - handlers: ["console"], - }, - }, - }); - log.debug("Debug logging enabled. CLI build against " + VERSION); - - const extraHeaders = getHeaders(); - if (extraHeaders) { - OpenAPI.HEADERS = extraHeaders; - } - await command.parse(Deno.args); - } catch (e) { - if (e.name === "ApiError") { - console.log("Server failed. " + e.statusText + ": " + e.body); - } - throw e; - } -} - -function isMain() { - // dnt-shim-ignore - const { Deno } = globalThis as any; - - const isDeno = Deno != undefined; - - if (isDeno) { - const isMain = import.meta.main; - if (isMain) { - if (!Deno.args.includes("completions")) { - if (Deno.env.get("SKIP_DENO_DEPRECATION_WARNING") !== "true") { - log.warn( - "Using the deno runtime for the Windmill CLI is deprecated, you can now use node: deno uninstall wmill && npm install -g windmill-cli. To skip this warning set SKIP_DENO_DEPRECATION_WARNING=true" - ); - } - } - } - return isMain; - } else { - //@ts-ignore - return esMain.default(import.meta); - } -} -if (isMain()) { - main(); -} - -export default command; diff --git a/cli/apps.ts b/cli/src/commands/app/apps.ts similarity index 90% rename from cli/apps.ts rename to cli/src/commands/app/apps.ts index 324275d3ed..4f1b41281d 100644 --- a/cli/apps.ts +++ b/cli/src/commands/app/apps.ts @@ -1,12 +1,12 @@ // deno-lint-ignore-file no-explicit-any -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; -import { colors, Command, log, SEP, Table, yamlParseFile } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { ListableApp, Policy } from "./gen/types.gen.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import { colors, Command, log, SEP, Table, yamlParseFile } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { ListableApp, Policy } from "../../../gen/types.gen.ts"; -import { GlobalOptions, isSuperset } from "./types.ts"; -import { readInlinePathSync } from "./utils.ts"; +import { GlobalOptions, isSuperset } from "../../types.ts"; +import { readInlinePathSync } from "../../utils/utils.ts"; export interface AppFile { value: any; diff --git a/cli/dev.ts b/cli/src/commands/dev/dev.ts similarity index 89% rename from cli/dev.ts rename to cli/src/commands/dev/dev.ts index 3644b8a5fa..58d8d03b33 100644 --- a/cli/dev.ts +++ b/cli/src/commands/dev/dev.ts @@ -9,22 +9,22 @@ import { open, WebSocket, yamlParseFile, -} from "./deps.ts"; -import { getTypeStrFromPath, GlobalOptions } from "./types.ts"; -import { ignoreF } from "./sync.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace } from "./context.ts"; +} from "../../../deps.ts"; +import { getTypeStrFromPath, GlobalOptions } from "../../types.ts"; +import { ignoreF } from "../sync/sync.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; import { SyncOptions, mergeConfigWithConfigFile, readConfigFile, -} from "./conf.ts"; -import { exts, findGlobalDeps, removeExtensionToPath } from "./script.ts"; -import { inferContentTypeFromFilePath } from "./script_common.ts"; -import { OpenFlow } from "./gen/types.gen.ts"; -import { FlowFile } from "./flow.ts"; -import { replaceInlineScripts } from "./windmill-utils-internal/src/inline-scripts/replacer.ts"; -import { parseMetadataFile } from "./metadata.ts"; +} from "../../core/conf.ts"; +import { exts, findGlobalDeps, removeExtensionToPath } from "../script/script.ts"; +import { inferContentTypeFromFilePath } from "../../utils/script_common.ts"; +import { OpenFlow } from "../../../gen/types.gen.ts"; +import { FlowFile } from "../flow/flow.ts"; +import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { parseMetadataFile } from "../../utils/metadata.ts"; const PORT = 3001; async function dev(opts: GlobalOptions & SyncOptions) { diff --git a/cli/flow.ts b/cli/src/commands/flow/flow.ts similarity index 90% rename from cli/flow.ts rename to cli/src/commands/flow/flow.ts index 2cbeb06174..93938e6254 100644 --- a/cli/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -1,18 +1,18 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions, isSuperset } from "./types.ts"; -import { Confirm, SEP, log, yamlStringify } from "./deps.ts"; -import { colors, Command, Table, yamlParseFile } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +import { GlobalOptions, isSuperset } from "../../types.ts"; +import { Confirm, SEP, log, yamlStringify } from "../../../deps.ts"; +import { colors, Command, Table, yamlParseFile } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; -import { resolve, track_job } from "./script.ts"; -import { defaultFlowDefinition } from "./bootstrap/flow_bootstrap.ts"; -import { generateFlowLockInternal } from "./metadata.ts"; -import { SyncOptions, mergeConfigWithConfigFile } from "./conf.ts"; -import { FSFSElement, elementsToMap, ignoreF } from "./sync.ts"; -import { Flow } from "./gen/types.gen.ts"; -import { replaceInlineScripts } from "./windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import { resolve, track_job } from "../script/script.ts"; +import { defaultFlowDefinition } from "../../../bootstrap/flow_bootstrap.ts"; +import { generateFlowLockInternal } from "../../utils/metadata.ts"; +import { SyncOptions, mergeConfigWithConfigFile } from "../../core/conf.ts"; +import { FSFSElement, elementsToMap, ignoreF } from "../sync/sync.ts"; +import { Flow } from "../../../gen/types.gen.ts"; +import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts"; export interface FlowFile { diff --git a/cli/folder.ts b/cli/src/commands/folder/folder.ts similarity index 88% rename from cli/folder.ts rename to cli/src/commands/folder/folder.ts index e60dc08903..421d142326 100644 --- a/cli/folder.ts +++ b/cli/src/commands/folder/folder.ts @@ -1,11 +1,11 @@ // deno-lint-ignore-file no-explicit-any -import { colors, Command, log, SEP, Table } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +import { colors, Command, log, SEP, Table } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; -import { GlobalOptions, isSuperset, parseFromFile } from "./types.ts"; -import { Folder } from "./gen/types.gen.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import { GlobalOptions, isSuperset, parseFromFile } from "../../types.ts"; +import { Folder } from "../../../gen/types.gen.ts"; export interface FolderFile { owners: Array | undefined; diff --git a/cli/gitsync-settings.ts b/cli/src/commands/gitsync-settings/gitsync-settings.ts similarity index 98% rename from cli/gitsync-settings.ts rename to cli/src/commands/gitsync-settings/gitsync-settings.ts index 27de3b7056..858bcc3498 100644 --- a/cli/gitsync-settings.ts +++ b/cli/src/commands/gitsync-settings/gitsync-settings.ts @@ -1,15 +1,15 @@ -import { colors, Command, Confirm, log, yamlStringify } from "./deps.ts"; -import { GlobalOptions } from "./types.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace } from "./context.ts"; -import * as wmill from "./gen/services.gen.ts"; +import { colors, Command, Confirm, log, yamlStringify } from "../../../deps.ts"; +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 { DEFAULT_SYNC_OPTIONS, getEffectiveSettings, readConfigFile, SyncOptions, -} from "./conf.ts"; -import { deepEqual, Repository, selectRepository } from "./utils.ts"; +} from "../../core/conf.ts"; +import { deepEqual, Repository, selectRepository } from "../../utils/utils.ts"; // Constants for git-sync fields to avoid duplication const GIT_SYNC_FIELDS = [ @@ -746,7 +746,7 @@ async function pullGitSyncSettings( } // Interactive mode - ask user - const { Select } = await import("./deps.ts"); + const { Select } = await import("../../../deps.ts"); const choice = await Select.prompt({ message: "Settings conflict detected. How would you like to proceed?", options: [ diff --git a/cli/hub.ts b/cli/src/commands/hub/hub.ts similarity index 85% rename from cli/hub.ts rename to cli/src/commands/hub/hub.ts index 2f0eb0c567..79bf02ee47 100644 --- a/cli/hub.ts +++ b/cli/src/commands/hub/hub.ts @@ -1,12 +1,12 @@ // deno-lint-ignore-file no-explicit-any -import { Command, log } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +import { Command, log } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace } from "./context.ts"; -import { pushResourceType } from "./resource-type.ts"; -import { GlobalOptions } from "./types.ts"; -import { deepEqual } from "./utils.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import { pushResourceType } from "../resource-type/resource-type.ts"; +import { GlobalOptions } from "../../types.ts"; +import { deepEqual } from "../../utils/utils.ts"; const DEFAULT_HUB_BASE_URL = "https://hub.windmill.dev"; diff --git a/cli/src/commands/init/init.ts b/cli/src/commands/init/init.ts new file mode 100644 index 0000000000..fc612e7922 --- /dev/null +++ b/cli/src/commands/init/init.ts @@ -0,0 +1,228 @@ +import { + colors, + Command, + log, + yamlStringify, +} from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { readLockfile } from "../../utils/metadata.ts"; +import { SCRIPT_GUIDANCE } from "../../guidance/script_guidance.ts"; +import { FLOW_GUIDANCE } from "../../guidance/flow_guidance.ts"; + +export interface InitOptions { + useDefault?: boolean; + useBackend?: boolean; + repository?: string; + workspace?: string; + debug?: unknown; + showDiffs?: boolean; + token?: string; + baseUrl?: string; + configDir?: string; +} + +/** + * Bootstrap a windmill project with a wmill.yaml file + */ +async function initAction(opts: InitOptions) { + if (await Deno.stat("wmill.yaml").catch(() => null)) { + log.error(colors.red("wmill.yaml already exists")); + } else { + // Import DEFAULT_SYNC_OPTIONS from conf.ts + const { DEFAULT_SYNC_OPTIONS } = await import("../../core/conf.ts"); + + // Create initial config with defaults + const initialConfig = { + defaultTs: DEFAULT_SYNC_OPTIONS.defaultTs, + includes: DEFAULT_SYNC_OPTIONS.includes, + excludes: DEFAULT_SYNC_OPTIONS.excludes, + codebases: DEFAULT_SYNC_OPTIONS.codebases, + skipVariables: DEFAULT_SYNC_OPTIONS.skipVariables, + skipResources: DEFAULT_SYNC_OPTIONS.skipResources, + skipSecrets: DEFAULT_SYNC_OPTIONS.skipSecrets, + skipScripts: DEFAULT_SYNC_OPTIONS.skipScripts, + skipFlows: DEFAULT_SYNC_OPTIONS.skipFlows, + skipApps: DEFAULT_SYNC_OPTIONS.skipApps, + skipFolders: DEFAULT_SYNC_OPTIONS.skipFolders, + includeSchedules: DEFAULT_SYNC_OPTIONS.includeSchedules, + includeTriggers: DEFAULT_SYNC_OPTIONS.includeTriggers, + overrides: {}, + }; + + await Deno.writeTextFile("wmill.yaml", yamlStringify(initialConfig)); + log.info(colors.green("wmill.yaml created with default settings")); + + // Create lock file + await readLockfile(); + + // Check for backend git-sync settings unless --use-default is specified + if (!opts.useDefault) { + try { + const { requireLogin } = await import("../../core/auth.ts"); + const { resolveWorkspace } = await import("../../core/context.ts"); + + // Check if user has workspace configured + const { getActiveWorkspace } = await import("../workspace/workspace.ts"); + const activeWorkspace = await getActiveWorkspace( + opts as GlobalOptions + ); + + if (!activeWorkspace) { + log.info("No workspace configured. Using default settings."); + log.info( + "You can configure a workspace later with 'wmill workspace add'" + ); + return; + } + + await requireLogin(opts as GlobalOptions); + const workspace = await resolveWorkspace(opts as GlobalOptions); + + const wmill = await import("../../../gen/services.gen.ts"); + const settings = await wmill.getSettings({ + workspace: workspace.workspaceId, + }); + + if ( + settings.git_sync?.repositories && + settings.git_sync.repositories.length > 0 + ) { + let useBackendSettings = opts.useBackend; + + // If repository is specified, implicitly use backend settings + if (opts.repository && !opts.useDefault) { + useBackendSettings = true; + } + + if (useBackendSettings === undefined) { + // Interactive prompt + const { Select } = await import("../../../deps.ts"); + const choice = await Select.prompt({ + message: + "Git-sync settings found on backend. What would you like to do?", + options: [ + { + name: "Use backend git-sync settings", + value: "backend", + }, + { + name: "Use default settings", + value: "default", + }, + { + name: "Cancel", + value: "cancel", + }, + ], + }); + + if (choice === "cancel") { + // Clean up the created files + try { + await Deno.remove("wmill.yaml"); + await Deno.remove("wmill-lock.yaml"); + } catch (e) { + // Ignore cleanup errors + } + log.info("Init cancelled"); + Deno.exit(0); + } + + useBackendSettings = choice === "backend"; + } + + if (useBackendSettings) { + log.info("Applying git-sync settings from backend..."); + + // Import and run the pull git-sync settings logic + const { pullGitSyncSettings } = await import( + "../gitsync-settings/gitsync-settings.ts" + ); + await pullGitSyncSettings({ + ...(opts as GlobalOptions), + repository: opts.repository, + jsonOutput: false, + diff: false, + replace: true, // Auto-replace when using backend settings during init + }); + + log.info( + colors.green("Git-sync settings applied from backend") + ); + } + } + } catch (error) { + // If there's an error checking backend settings, just continue with defaults + const errorMessage = + error instanceof Error ? error.message : String(error); + log.warn( + `Could not check backend for git-sync settings: ${errorMessage}` + ); + log.info("Continuing with default settings"); + } + } + } + + // Create .cursor/rules directory and files with SCRIPT_GUIDANCE content + try { + const scriptGuidanceContent = SCRIPT_GUIDANCE; + const flowGuidanceContent = FLOW_GUIDANCE; + + // Create .cursor/rules directory + await Deno.mkdir(".cursor/rules", { recursive: true }); + + // Create windmill.mdc file + if (!(await Deno.stat(".cursor/rules/script.mdc").catch(() => null))) { + await Deno.writeTextFile( + ".cursor/rules/script.mdc", + scriptGuidanceContent + ); + log.info(colors.green("Created .cursor/rules/script.mdc")); + } + + if (!(await Deno.stat(".cursor/rules/flow.mdc").catch(() => null))) { + await Deno.writeTextFile( + ".cursor/rules/flow.mdc", + flowGuidanceContent + ); + log.info(colors.green("Created .cursor/rules/flow.mdc")); + } + + // Create CLAUDE.md file + if (!(await Deno.stat("CLAUDE.md").catch(() => null))) { + await Deno.writeTextFile( + "CLAUDE.md", + ` + # Claude + + You are a helpful assistant that can help with Windmill scripts and flows creation. + + ## Script Guidance + ${scriptGuidanceContent} + + ## Flow Guidance + ${flowGuidanceContent} + ` + ); + log.info(colors.green("Created CLAUDE.md")); + } + } catch (error) { + if (error instanceof Error) { + log.warn(`Could not create guidance files: ${error.message}`); + } else { + log.warn(`Could not create guidance files: ${error}`); + } + } +} + +const command = new Command() + .description("Bootstrap a windmill project with a wmill.yaml file") + .option("--use-default", "Use default settings without checking backend") + .option("--use-backend", "Use backend git-sync settings if available") + .option( + "--repository ", + "Specify repository path (e.g., u/user/repo) when using backend settings" + ) + .action(initAction as any); + +export default command; \ No newline at end of file diff --git a/cli/instance.ts b/cli/src/commands/instance/instance.ts similarity index 95% rename from cli/instance.ts rename to cli/src/commands/instance/instance.ts index 99c0066b33..7daa4c0f18 100644 --- a/cli/instance.ts +++ b/cli/src/commands/instance/instance.ts @@ -7,36 +7,36 @@ import { Command, setClient, Table, -} from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +} from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; -import { Input, colors, log } from "./deps.ts"; -import { loginInteractive } from "./login.ts"; -import { getRootStore } from "./store.ts"; -import { push, pull } from "./sync.ts"; -import { showDiff } from "./types.ts"; +import { Input, colors, log } from "../../../deps.ts"; +import { loginInteractive } from "../../core/login.ts"; +import { getActiveInstanceFilePath, getInstancesConfigFilePath } from "../../../windmill-utils-internal/src/config/config.ts"; +import { push, pull } from "../sync/sync.ts"; +import { showDiff } from "../../types.ts"; import { pushInstanceUsers, pullInstanceUsers, pullInstanceGroups, pushInstanceGroups, -} from "./user.ts"; +} from "../user/user.ts"; import { add as workspaceSetup, addWorkspace, removeWorkspace, setActiveWorkspace, -} from "./workspace.ts"; +} from "../workspace/workspace.ts"; import { pushInstanceSettings, pullInstanceSettings, pullInstanceConfigs, pushInstanceConfigs, type SimplifiedSettings, -} from "./settings.ts"; -import { deepEqual } from "./utils.ts"; -import { getActiveWorkspace } from "./workspace.ts"; +} from "../../core/settings.ts"; +import { deepEqual } from "../../utils/utils.ts"; +import { getActiveWorkspace } from "../workspace/workspace.ts"; export interface Instance { remote: string; @@ -47,7 +47,7 @@ export interface Instance { export async function allInstances(): Promise { try { - const file = (await getRootStore()) + "instances.ndjson"; + const file = await getInstancesConfigFilePath(); const txt = await Deno.readTextFile(file); return txt .split("\n") @@ -115,7 +115,7 @@ export async function addInstance( async function appendInstance(instance: Instance) { instance.remote = new URL(instance.remote).toString(); // add trailing slash in all cases! await removeInstance(instance.name); - const file = await Deno.open((await getRootStore()) + "instances.ndjson", { + const file = await Deno.open(await getInstancesConfigFilePath(), { append: true, write: true, read: true, @@ -130,7 +130,7 @@ async function removeInstance(name: string) { const orgWorkspaces = await allInstances(); await Deno.writeTextFile( - (await getRootStore()) + "instances.ndjson", + await getInstancesConfigFilePath(), orgWorkspaces .filter((x) => x.name !== name) .map((x) => JSON.stringify(x)) @@ -625,7 +625,7 @@ async function switchI(opts: {}, instanceName: string) { } await Deno.writeTextFile( - (await getRootStore()) + "/activeInstance", + await getActiveInstanceFilePath(), instanceName ); @@ -639,7 +639,7 @@ export async function getActiveInstance(opts: { return opts.instance; } try { - return await Deno.readTextFile((await getRootStore()) + "/activeInstance"); + return await Deno.readTextFile(await getActiveInstanceFilePath()); } catch { return undefined; } diff --git a/cli/queues.ts b/cli/src/commands/queues/queues.ts similarity index 94% rename from cli/queues.ts rename to cli/src/commands/queues/queues.ts index 12586f1abb..1afcaea269 100644 --- a/cli/queues.ts +++ b/cli/src/commands/queues/queues.ts @@ -1,7 +1,7 @@ -import { Command, Table } from "./deps.ts"; -import { log } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { pickInstance } from "./instance.ts"; +import { Command, Table } from "../../../deps.ts"; +import { log } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { pickInstance } from "../instance/instance.ts"; type Data = { count: number; diff --git a/cli/resource-type.ts b/cli/src/commands/resource-type/resource-type.ts similarity index 89% rename from cli/resource-type.ts rename to cli/src/commands/resource-type/resource-type.ts index c6b5585bac..60dbd28f65 100644 --- a/cli/resource-type.ts +++ b/cli/src/commands/resource-type/resource-type.ts @@ -4,12 +4,12 @@ import { isSuperset, parseFromFile, removeType, -} from "./types.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace } from "./context.ts"; -import { colors, Command, log, Table } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { ResourceType } from "./gen/types.gen.ts"; +} from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import { colors, Command, log, Table } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { ResourceType } from "../../../gen/types.gen.ts"; export interface ResourceTypeFile { schema?: any; diff --git a/cli/resource.ts b/cli/src/commands/resource/resource.ts similarity index 88% rename from cli/resource.ts rename to cli/src/commands/resource/resource.ts index 0d620f68e3..3c62665842 100644 --- a/cli/resource.ts +++ b/cli/src/commands/resource/resource.ts @@ -4,13 +4,13 @@ import { isSuperset, parseFromFile, removeType, -} from "./types.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; -import { colors, Command, log, SEP, Table } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { Resource } from "./gen/types.gen.ts"; -import { readInlinePathSync } from "./utils.ts"; +} from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import { colors, Command, log, SEP, Table } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { Resource } from "../../../gen/types.gen.ts"; +import { readInlinePathSync } from "../../utils/utils.ts"; export interface ResourceFile { value: any; diff --git a/cli/schedule.ts b/cli/src/commands/schedule/schedule.ts similarity index 91% rename from cli/schedule.ts rename to cli/src/commands/schedule/schedule.ts index 34ec378a18..6eea0de934 100644 --- a/cli/schedule.ts +++ b/cli/src/commands/schedule/schedule.ts @@ -1,16 +1,16 @@ // deno-lint-ignore-file no-explicit-any -import { colors, Command, log, SEP, Table } from "./deps.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; -import * as wmill from "./gen/services.gen.ts"; +import { colors, Command, log, SEP, Table } from "../../../deps.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import * as wmill from "../../../gen/services.gen.ts"; import { GlobalOptions, isSuperset, parseFromFile, removeType, -} from "./types.ts"; -import { Schedule } from "./gen/types.gen.ts"; +} from "../../types.ts"; +import { Schedule } from "../../../gen/types.gen.ts"; export interface ScheduleFile { schedule: string; diff --git a/cli/script.ts b/cli/src/commands/script/script.ts similarity index 97% rename from cli/script.ts rename to cli/src/commands/script/script.ts index 8ae5e3f6e6..2becb662e4 100644 --- a/cli/script.ts +++ b/cli/src/commands/script/script.ts @@ -1,7 +1,7 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions } from "./types.ts"; -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; +import { GlobalOptions } from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; import { colors, Command, @@ -12,46 +12,46 @@ import { Table, writeAllSync, yamlStringify, -} from "./deps.ts"; -import { deepEqual } from "./utils.ts"; -import * as wmill from "./gen/services.gen.ts"; +} from "../../../deps.ts"; +import { deepEqual } from "../../utils/utils.ts"; +import * as wmill from "../../../gen/services.gen.ts"; import { defaultScriptMetadata, scriptBootstrapCode, -} from "./bootstrap/script_bootstrap.ts"; +} from "../../../bootstrap/script_bootstrap.ts"; -import { Workspace } from "./workspace.ts"; +import { Workspace } from "../workspace/workspace.ts"; import { generateScriptMetadataInternal, parseMetadataFile, -} from "./metadata.ts"; +} from "../../utils/metadata.ts"; import { LanguageWithRawReqsSupport, ScriptLanguage, inferContentTypeFromFilePath, languagesWithRawReqsSupport, -} from "./script_common.ts"; +} from "../../utils/script_common.ts"; import { elementsToMap, findCodebase, readDirRecursiveWithIgnore, Skips, yamlOptions, -} from "./sync.ts"; -import { ignoreF } from "./sync.ts"; -import { FSFSElement } from "./sync.ts"; +} from "../sync/sync.ts"; +import { ignoreF } from "../sync/sync.ts"; +import { FSFSElement } from "../sync/sync.ts"; import { SyncOptions, mergeConfigWithConfigFile, readConfigFile, -} from "./conf.ts"; -import { SyncCodebase, listSyncCodebases } from "./codebase.ts"; +} from "../../core/conf.ts"; +import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts"; import fs from "node:fs"; import { type Tarball } from "npm:@ayonli/jsext/archive"; import { execSync } from "node:child_process"; -import { NewScript, Script } from "./gen/types.gen.ts"; +import { NewScript, Script } from "../../../gen/types.gen.ts"; export interface ScriptFile { parent_hash?: string; diff --git a/cli/pull.ts b/cli/src/commands/sync/pull.ts similarity index 91% rename from cli/pull.ts rename to cli/src/commands/sync/pull.ts index f3286d5b17..18e0e350fb 100644 --- a/cli/pull.ts +++ b/cli/src/commands/sync/pull.ts @@ -1,8 +1,8 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions } from "./types.ts"; -import { colors, Command, JSZip, log } from "./deps.ts"; -import { Workspace } from "./workspace.ts"; -import { getHeaders } from "./utils.ts"; +import { GlobalOptions } from "../../types.ts"; +import { colors, Command, JSZip, log } from "../../../deps.ts"; +import { Workspace } from "../workspace/workspace.ts"; +import { getHeaders } from "../../utils/utils.ts"; export async function downloadZip( workspace: Workspace, diff --git a/cli/push.ts b/cli/src/commands/sync/push.ts similarity index 79% rename from cli/push.ts rename to cli/src/commands/sync/push.ts index 9df95da998..01f6bea2da 100644 --- a/cli/push.ts +++ b/cli/src/commands/sync/push.ts @@ -1,6 +1,6 @@ // deno-lint-ignore-file no-explicit-any -import { colors, Command, log } from "./deps.ts"; -import { GlobalOptions } from "./types.ts"; +import { colors, Command, log } from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; function stub(_opts: GlobalOptions, _dir?: string) { log.info( diff --git a/cli/sync.ts b/cli/src/commands/sync/sync.ts similarity index 95% rename from cli/sync.ts rename to cli/src/commands/sync/sync.ts index 1fcdc78b3b..32d387cf76 100644 --- a/cli/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -1,5 +1,5 @@ -import { requireLogin } from "./auth.ts"; -import { fetchVersion, resolveWorkspace } from "./context.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { fetchVersion, resolveWorkspace } from "../../core/context.ts"; import { colors, Command, @@ -13,8 +13,8 @@ import { yamlStringify, yamlParseContent, SEP, -} from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +} from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; import { getTypeStrFromPath, @@ -23,7 +23,7 @@ import { pushObj, showConflict, showDiff, -} from "./types.ts"; +} from "../../types.ts"; import { downloadZip } from "./pull.ts"; import { @@ -33,23 +33,23 @@ import { findResourceFile, handleScriptMetadata, removeExtensionToPath, -} from "./script.ts"; +} from "../script/script.ts"; -import { handleFile } from "./script.ts"; -import { deepEqual, isFileResource } from "./utils.ts"; -import { SyncOptions, readConfigFile, getEffectiveSettings } from "./conf.ts"; -import { Workspace } from "./workspace.ts"; -import { removePathPrefix } from "./types.ts"; -import { SyncCodebase, listSyncCodebases } from "./codebase.ts"; +import { handleFile } from "../script/script.ts"; +import { deepEqual, isFileResource } from "../../utils/utils.ts"; +import { SyncOptions, readConfigFile, getEffectiveSettings } from "../../core/conf.ts"; +import { Workspace } from "../workspace/workspace.ts"; +import { removePathPrefix } from "../../types.ts"; +import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts"; import { generateFlowLockInternal, generateScriptMetadataInternal, readLockfile, -} from "./metadata.ts"; -import { OpenFlow } from "./gen/types.gen.ts"; -import { pushResource } from "./resource.ts"; -import { assignPath } from "./windmill-utils-internal/src/path-utils/path-assigner.ts"; -import { extractInlineScripts as extractInlineScriptsForFlows } from "./windmill-utils-internal/src/inline-scripts/extractor.ts"; +} from "../../utils/metadata.ts"; +import { OpenFlow } from "../../../gen/types.gen.ts"; +import { pushResource } from "../resource/resource.ts"; +import { newPathAssigner, PathAssigner } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; +import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; // Merge CLI options with effective settings, preserving CLI flags as overrides function mergeCliWithEffectiveOptions< @@ -404,7 +404,6 @@ function ZipFSElement( {}, SEP, defaultTs, - newPathAssigner(defaultTs) ); for (const s of inlineScripts) { yield { @@ -2126,65 +2125,4 @@ const command = new Command() // deno-lint-ignore no-explicit-any .action(push as any); - interface PathAssigner { - assignPath(summary: string | undefined, language: string): [string, string]; - } - const INLINE_SCRIPT = "inline_script"; - - export function newPathAssigner(defaultTs: "bun" | "deno"): PathAssigner { - let counter = 0; - const seen_names = new Set(); - function assignPath( - summary: string | undefined, - language: string - ): [string, string] { - let name; - - name = summary?.toLowerCase()?.replaceAll(" ", "_") ?? ""; - - let original_name = name; - - if (name == "") { - original_name = INLINE_SCRIPT; - name = `${INLINE_SCRIPT}_0`; - } - - while (seen_names.has(name)) { - counter++; - name = `${original_name}_${counter}`; - } - seen_names.add(name); - - let ext; - if (language == "python3") ext = "py"; - else if (language == defaultTs || language == "bunnative") ext = "ts"; - else if (language == "bun") ext = "bun.ts"; - else if (language == "deno") ext = "deno.ts"; - else if (language == "go") ext = "go"; - else if (language == "bash") ext = "sh"; - else if (language == "powershell") ext = "ps1"; - else if (language == "postgresql") ext = "pg.sql"; - else if (language == "mysql") ext = "my.sql"; - else if (language == "bigquery") ext = "bq.sql"; - else if (language == "oracledb") ext = "odb.sql"; - else if (language == "snowflake") ext = "sf.sql"; - else if (language == "mssql") ext = "ms.sql"; - else if (language == "graphql") ext = "gql"; - else if (language == "nativets") ext = "native.ts"; - else if (language == "frontend") ext = "frontend.js"; - else if (language == "php") ext = "php"; - else if (language == "rust") ext = "rs"; - else if (language == "csharp") ext = "cs"; - else if (language == "nu") ext = "nu"; - else if (language == "ansible") ext = "playbook.yml"; - else if (language == "java") ext = "java"; - else if (language == "duckdb") ext = "duckdb.sql"; - // for related places search: ADD_NEW_LANG - else ext = "no_ext"; - - return [`${name}.inline_script.`, ext]; - } - return { assignPath }; - } - export default command; diff --git a/cli/trigger.ts b/cli/src/commands/trigger/trigger.ts similarity index 95% rename from cli/trigger.ts rename to cli/src/commands/trigger/trigger.ts index df21755794..c9cd0e013d 100644 --- a/cli/trigger.ts +++ b/cli/src/commands/trigger/trigger.ts @@ -1,4 +1,4 @@ -import * as wmill from "./gen/services.gen.ts"; +import * as wmill from "../../../gen/services.gen.ts"; import { GcpTrigger, HttpTrigger, @@ -8,16 +8,16 @@ import { PostgresTrigger, SqsTrigger, WebsocketTrigger, -} from "./gen/types.gen.ts"; -import { colors, Command, log, SEP, Table } from "./deps.ts"; +} from "../../../gen/types.gen.ts"; +import { colors, Command, log, SEP, Table } from "../../../deps.ts"; import { GlobalOptions, isSuperset, parseFromFile, removeType, -} from "./types.ts"; -import { requireLogin } from "./auth.ts"; -import { validatePath, resolveWorkspace } from "./context.ts"; +} from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { validatePath, resolveWorkspace } from "../../core/context.ts"; type Trigger = { http: HttpTrigger; diff --git a/cli/user.ts b/cli/src/commands/user/user.ts similarity index 98% rename from cli/user.ts rename to cli/src/commands/user/user.ts index 668f294d67..93ab9236c5 100644 --- a/cli/user.ts +++ b/cli/src/commands/user/user.ts @@ -1,12 +1,12 @@ // deno-lint-ignore-file no-explicit-any -import { requireLogin } from "./auth.ts"; +import { requireLogin } from "../../core/auth.ts"; import { GlobalOptions, isSuperset, removeType, removePathPrefix, -} from "./types.ts"; -import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts"; +} from "../../types.ts"; +import { compareInstanceObjects, InstanceSyncOptions } from "../instance/instance.ts"; import { colors, Command, @@ -14,14 +14,14 @@ import { Table, yamlStringify, yamlParseFile, -} from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; +} from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; import { ExportedInstanceGroup, ExportedUser, GlobalUserInfo, InstanceGroup, -} from "./gen/types.gen.ts"; +} from "../../../gen/types.gen.ts"; const INSTANCE_USERS_PATH = "instance_users.yaml"; let instanceUsersPath = INSTANCE_USERS_PATH; diff --git a/cli/variable.ts b/cli/src/commands/variable/variable.ts similarity index 92% rename from cli/variable.ts rename to cli/src/commands/variable/variable.ts index 8524959c32..60a4f7320d 100644 --- a/cli/variable.ts +++ b/cli/src/commands/variable/variable.ts @@ -1,16 +1,16 @@ // deno-lint-ignore-file no-explicit-any -import { requireLogin } from "./auth.ts"; -import { resolveWorkspace, validatePath } from "./context.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; import { GlobalOptions, isSuperset, parseFromFile, removeType, -} from "./types.ts"; -import { colors, Command, Confirm, log, SEP, Table } from "./deps.ts"; +} from "../../types.ts"; +import { colors, Command, Confirm, log, SEP, Table } from "../../../deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { ListableVariable } from "./gen/types.gen.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { ListableVariable } from "../../../gen/types.gen.ts"; async function list(opts: GlobalOptions) { const workspace = await resolveWorkspace(opts); diff --git a/cli/worker_groups.ts b/cli/src/commands/worker-groups/worker_groups.ts similarity index 90% rename from cli/worker_groups.ts rename to cli/src/commands/worker-groups/worker_groups.ts index 17ff13ec40..a683021bc0 100644 --- a/cli/worker_groups.ts +++ b/cli/src/commands/worker-groups/worker_groups.ts @@ -1,9 +1,9 @@ -import { Command, Confirm, setClient, Table } from "./deps.ts"; +import { Command, Confirm, setClient, Table } from "../../../deps.ts"; -import { log } from "./deps.ts"; -import { allInstances, getActiveInstance, Instance, InstanceSyncOptions, pickInstance } from "./instance.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { pullInstanceConfigs, pushInstanceConfigs } from "./settings.ts"; +import { log } from "../../../deps.ts"; +import { allInstances, getActiveInstance, InstanceSyncOptions, pickInstance } from "../instance/instance.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { pullInstanceConfigs, pushInstanceConfigs } from "../../core/settings.ts"; type GlobalOptions = { instance?: string; diff --git a/cli/workers.ts b/cli/src/commands/workers/workers.ts similarity index 94% rename from cli/workers.ts rename to cli/src/commands/workers/workers.ts index ff28a1c504..f3d00ff63f 100644 --- a/cli/workers.ts +++ b/cli/src/commands/workers/workers.ts @@ -1,7 +1,7 @@ -import { Command, Table } from "./deps.ts"; -import { log } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { pickInstance } from "./instance.ts"; +import { Command, Table } from "../../../deps.ts"; +import { log } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { pickInstance } from "../instance/instance.ts"; type GlobalOptions = { instance?: string; diff --git a/cli/workspace.ts b/cli/src/commands/workspace/workspace.ts similarity index 93% rename from cli/workspace.ts rename to cli/src/commands/workspace/workspace.ts index 9c0b865a00..b4bab0866c 100644 --- a/cli/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -1,11 +1,11 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions } from "./types.ts"; -import { getRootStore } from "./store.ts"; -import { loginInteractive, tryGetLoginInfo } from "./login.ts"; -import { colors, Command, Confirm, Input, log, setClient, Table } from "./deps.ts"; -import { requireLogin } from "./auth.ts"; +import { GlobalOptions } from "../../types.ts"; +import { getActiveWorkspaceConfigFilePath, getWorkspaceConfigFilePath } from "../../../windmill-utils-internal/src/config/config.ts"; +import { loginInteractive, tryGetLoginInfo } from "../../core/login.ts"; +import { colors, Command, Confirm, Input, log, setClient, Table } from "../../../deps.ts"; +import { requireLogin } from "../../core/auth.ts"; -import * as wmill from "./gen/services.gen.ts"; +import * as wmill from "../../../gen/services.gen.ts"; export interface Workspace { remote: string; @@ -16,7 +16,7 @@ export interface Workspace { export async function allWorkspaces(configDirOverride?: string): Promise { try { - const file = (await getRootStore(configDirOverride)) + "remotes.ndjson"; + const file = await getWorkspaceConfigFilePath(configDirOverride); const txt = await Deno.readTextFile(file); return txt .split("\n") @@ -40,14 +40,15 @@ async function getActiveWorkspaceName( return opts?.workspace; } try { - return await Deno.readTextFile((await getRootStore(opts?.configDir)) + "/activeWorkspace"); + const file = await getActiveWorkspaceConfigFilePath(opts?.configDir); + return await Deno.readTextFile(file); } catch { return undefined; } } export async function getActiveWorkspace( - opts: GlobalOptions | undefined + opts: GlobalOptions | undefined = undefined ): Promise { const name = await getActiveWorkspaceName(opts); if (!name) { @@ -121,8 +122,9 @@ async function switchC(opts: GlobalOptions, workspaceName: string) { } export async function setActiveWorkspace(workspaceName: string, configDirOverride?: string) { + const file = await getActiveWorkspaceConfigFilePath(configDirOverride); await Deno.writeTextFile( - (await getRootStore(configDirOverride)) + "/activeWorkspace", + file, workspaceName ); } @@ -336,7 +338,8 @@ export async function addWorkspace(workspace: Workspace, opts: any) { await removeWorkspace(workspace.name, true, opts); // Add the new workspace - const file = await Deno.open((await getRootStore(opts.configDir)) + "remotes.ndjson", { + const filePath = await getWorkspaceConfigFilePath(opts.configDir); + const file = await Deno.open(filePath, { append: true, write: true, read: true, @@ -367,8 +370,9 @@ export async function removeWorkspace( log.info(colors.yellow(`Removing existing workspace ${name}`)); } + const filePath = await getWorkspaceConfigFilePath(opts.configDir); await Deno.writeTextFile( - (await getRootStore(opts.configDir)) + "remotes.ndjson", + filePath, orgWorkspaces .filter((x) => x.name !== name) .map((x) => JSON.stringify(x)) diff --git a/cli/auth.ts b/cli/src/core/auth.ts similarity index 85% rename from cli/auth.ts rename to cli/src/core/auth.ts index 8e85de879b..2831761141 100644 --- a/cli/auth.ts +++ b/cli/src/core/auth.ts @@ -1,10 +1,10 @@ // deno-lint-ignore-file no-explicit-any -import { colors, log, setClient } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { GlobalUserInfo } from "./gen/types.gen.ts"; +import { colors, log, setClient } from "../../deps.ts"; +import * as wmill from "../../gen/services.gen.ts"; +import { GlobalUserInfo } from "../../gen/types.gen.ts"; import { loginInteractive, tryGetLoginInfo } from "./login.ts"; -import { GlobalOptions } from "./types.ts"; +import { GlobalOptions } from "../types.ts"; /** * Main authentication function - moved from context.ts to break circular dependencies @@ -43,7 +43,7 @@ export async function requireLogin( } // Update workspace token - const { removeWorkspace, addWorkspace } = await import("./workspace.ts"); + const { removeWorkspace, addWorkspace } = await import("../commands/workspace/workspace.ts"); removeWorkspace(workspace.name, false, opts); workspace.token = newToken; addWorkspace(workspace, opts); diff --git a/cli/conf.ts b/cli/src/core/conf.ts similarity index 96% rename from cli/conf.ts rename to cli/src/core/conf.ts index 6c4e37e3c8..efbbb56f17 100644 --- a/cli/conf.ts +++ b/cli/src/core/conf.ts @@ -1,4 +1,9 @@ -import { log, yamlParseFile } from "./deps.ts"; +import { log, yamlParseFile } from "../../deps.ts"; + +export let showDiffs = false; +export function setShowDiffs(value: boolean) { + showDiffs = value; +} export interface SyncOptions { stateful?: boolean; diff --git a/cli/context.ts b/cli/src/core/context.ts similarity index 91% rename from cli/context.ts rename to cli/src/core/context.ts index 359854c880..a305f597ff 100644 --- a/cli/context.ts +++ b/cli/src/core/context.ts @@ -1,18 +1,13 @@ // deno-lint-ignore-file no-explicit-any -import { colors, log, setClient } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { GlobalUserInfo } from "./gen/types.gen.ts"; +import { colors, log } from "../../deps.ts"; -import { loginInteractive, tryGetLoginInfo } from "./login.ts"; -import { GlobalOptions } from "./types.ts"; -import { getHeaders } from "./utils.ts"; +import { GlobalOptions } from "../types.ts"; +import { getHeaders } from "../utils/utils.ts"; import { - addWorkspace, getActiveWorkspace, getWorkspaceByName, - removeWorkspace, Workspace, -} from "./workspace.ts"; +} from "../commands/workspace/workspace.ts"; export type Context = { workspace: string; @@ -66,7 +61,7 @@ export async function resolveWorkspace( // If not found by name, try to find by workspaceId + remote match if (!existingWorkspace) { - const { allWorkspaces } = await import("./workspace.ts"); + const { allWorkspaces } = await import("../commands/workspace/workspace.ts"); const workspaces = await allWorkspaces(opts.configDir); const matchingWorkspaces = workspaces.filter( w => w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl @@ -128,7 +123,6 @@ export async function fetchVersion(baseUrl: string): Promise { const extraHeaders = getHeaders(); if (extraHeaders) { for (const [key, value] of Object.entries(extraHeaders)) { - // @ts-ignore requestHeaders.set(key, value); } } diff --git a/cli/login.ts b/cli/src/core/login.ts similarity index 96% rename from cli/login.ts rename to cli/src/core/login.ts index 3476007301..6da90b8042 100644 --- a/cli/login.ts +++ b/cli/src/core/login.ts @@ -1,5 +1,5 @@ -import { GlobalOptions } from "./types.ts"; -import { colors, getPort, log, open, Secret, Select } from "./deps.ts"; +import { GlobalOptions } from "../types.ts"; +import { colors, getPort, log, open, Secret, Select } from "../../deps.ts"; import * as http from "node:http"; export async function loginInteractive(remote: string) { diff --git a/cli/settings.ts b/cli/src/core/settings.ts similarity index 97% rename from cli/settings.ts rename to cli/src/core/settings.ts index b31af79d15..00b69128fd 100644 --- a/cli/settings.ts +++ b/cli/src/core/settings.ts @@ -1,11 +1,12 @@ import process from "node:process"; -import { colors, Confirm, log, yamlParseFile, yamlStringify } from "./deps.ts"; -import * as wmill from "./gen/services.gen.ts"; -import { AIConfig, Config, GlobalSetting } from "./gen/types.gen.ts"; -import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts"; -import { isSuperset } from "./types.ts"; -import { deepEqual } from "./utils.ts"; -import { removeWorkerPrefix } from "./worker_groups.ts"; +import { colors, Confirm, log, yamlParseFile, yamlStringify } from "../../deps.ts"; +import * as wmill from "../../gen/services.gen.ts"; +import { AIConfig, Config, GlobalSetting } from "../../gen/types.gen.ts"; +import { compareInstanceObjects, InstanceSyncOptions } from "../commands/instance/instance.ts"; +import { isSuperset } from "../types.ts"; +import { deepEqual } from "../utils/utils.ts"; +import { removeWorkerPrefix } from "../commands/worker-groups/worker_groups.ts"; +import { decrypt, encrypt } from "../utils/local_encryption.ts"; export interface SimplifiedSettings { // slack_team_id?: string; @@ -313,8 +314,6 @@ export async function readInstanceSettings(opts: InstanceSyncOptions) { return localSettings; } -import { decrypt, encrypt } from "./local_encryption.ts"; - const SENSITIVE_FIELD: string[] = ["license_key", "jwt_secret"]; async function processInstanceSettings( diff --git a/cli/src/core/store.ts b/cli/src/core/store.ts new file mode 100644 index 0000000000..5843b6ad02 --- /dev/null +++ b/cli/src/core/store.ts @@ -0,0 +1,22 @@ +import { ensureDir } from "../../deps.ts"; +import { getConfigDirPath } from "../../windmill-utils-internal/src/config/config.ts"; + +function hash_string(str: string): number { + let hash = 0, + i, + chr; + if (str.length === 0) return hash; + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +} + +export async function getStore(baseUrl: string, configDirOverride?: string): Promise { + const baseHash = Math.abs(hash_string(baseUrl)).toString(16); + const baseStore = (await getConfigDirPath(configDirOverride)) + baseHash + "/"; + await ensureDir(baseStore); + return baseStore; +} \ No newline at end of file diff --git a/cli/flow_guidance.ts b/cli/src/guidance/flow_guidance.ts similarity index 100% rename from cli/flow_guidance.ts rename to cli/src/guidance/flow_guidance.ts diff --git a/cli/script_guidance.ts b/cli/src/guidance/script_guidance.ts similarity index 100% rename from cli/script_guidance.ts rename to cli/src/guidance/script_guidance.ts diff --git a/cli/src/main.ts b/cli/src/main.ts new file mode 100644 index 0000000000..894fc32d25 --- /dev/null +++ b/cli/src/main.ts @@ -0,0 +1,241 @@ +import { + Command, + CompletionsCommand, + UpgradeCommand, + esMain, + log, +} from "../deps.ts"; +import flow from "./commands/flow/flow.ts"; +import app from "./commands/app/apps.ts"; +import script from "./commands/script/script.ts"; +import workspace, { getActiveWorkspace } from "./commands/workspace/workspace.ts"; +import resource from "./commands/resource/resource.ts"; +import resourceType from "./commands/resource-type/resource-type.ts"; +import user from "./commands/user/user.ts"; +import variable from "./commands/variable/variable.ts"; +import hub from "./commands/hub/hub.ts"; +import folder from "./commands/folder/folder.ts"; +import schedule from "./commands/schedule/schedule.ts"; +import trigger from "./commands/trigger/trigger.ts"; +import sync from "./commands/sync/sync.ts"; +import gitsyncSettings from "./commands/gitsync-settings/gitsync-settings.ts"; +import instance from "./commands/instance/instance.ts"; +import workerGroups from "./commands/worker-groups/worker_groups.ts"; + +import dev from "./commands/dev/dev.ts"; +import { fetchVersion } from "./core/context.ts"; +import { GlobalOptions } from "./types.ts"; +import { OpenAPI } from "../gen/index.ts"; +import { getHeaders, getIsWin } from "./utils/utils.ts"; +import { setShowDiffs } from "./core/conf.ts"; +import { NpmProvider } from "./utils/upgrade.ts"; +import { pull as hubPull } from "./commands/hub/hub.ts"; +import { pull, push } from "./commands/sync/sync.ts"; +import { add as workspaceAdd } from "./commands/workspace/workspace.ts"; +import workers from "./commands/workers/workers.ts"; +import queues from "./commands/queues/queues.ts"; +import init from "./commands/init/init.ts"; + +export { + flow, + app, + script, + workspace, + resource, + resourceType, + user, + variable, + hub, + folder, + schedule, + trigger, + sync, + gitsyncSettings, + instance, + dev, + hubPull, + pull, + push, + workspaceAdd, +}; + +// addEventListener("error", (event) => { +// if (event.error) { +// console.error("Error details of: " + event.error.message); +// console.error(JSON.stringify(event.error, null, 4)); +// } +// }); + +export const VERSION = "1.518.2"; + +const command = new Command() + .name("wmill") + .action(() => + log.info(`Welcome to Windmill CLI ${VERSION}. Use -h for help.`) + ) + .description("Windmill CLI") + + .globalOption( + "--workspace ", + "Specify the target workspace. This overrides the default workspace." + ) + .globalOption("--debug --verbose", "Show debug/verbose logs") + .globalOption( + "--show-diffs", + "Show diff informations when syncing (may show sensitive informations)" + ) + .globalOption( + "--token ", + "Specify an API token. This will override any stored token." + ) + .globalOption( + "--base-url ", + "Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace already set will be used." + ) + .globalOption( + "--config-dir ", + "Specify a custom config directory. Overrides WMILL_CONFIG_DIR environment variable and default ~/.config location." + ) + .env( + "HEADERS ", + "Specify headers to use for all requests. e.g: \"HEADERS='h1: v1, h2: v2'\"" + ) + .version(VERSION) + .versionOption(false) + .command("init", init) + .command("app", app) + .command("flow", flow) + .command("script", script) + .command("workspace", workspace) + .command("resource", resource) + .command("resource-type", resourceType) + .command("user", user) + .command("variable", variable) + .command("hub", hub) + .command("folder", folder) + .command("schedule", schedule) + .command("trigger", trigger) + .command("dev", dev) + .command("sync", sync) + .command("gitsync-settings", gitsyncSettings) + .command("instance", instance) + .command("worker-groups", workerGroups) + .command("workers", workers) + .command("queues", queues) + .command("version --version", "Show version information") + .action(async (opts: any) => { + console.log("CLI version: " + VERSION); + try { + const provider = new NpmProvider({ package: "windmill-cli" }); + const versions = await provider.getVersions("windmill-cli"); + if (versions.latest !== VERSION) { + console.log( + `CLI is outdated. Latest version ${versions.latest} is available. Run \`wmill upgrade\` to update.` + ); + } else { + console.log("CLI is up to date"); + } + } catch (e) { + console.warn( + `Cannot fetch latest CLI version on npmjs to check if up-to-date: ${e}` + ); + } + const workspace = await getActiveWorkspace(opts as GlobalOptions); + if (workspace) { + try { + const backendVersion = await fetchVersion(workspace.remote); + console.log("Backend Version: " + backendVersion); + } catch (e) { + console.warn("Cannot fetch backend version: " + e); + } + } else { + console.warn( + "Cannot fetch backend version: no active workspace selected, choose one to pick a remote to fetch version of" + ); + } + }) + .command( + "upgrade", + new UpgradeCommand({ + provider: new NpmProvider({ package: "windmill-cli" }), + }).error((e: any) => { + log.error(e); + log.info( + "Try running with sudo and otherwise check the result of the command: npm uninstall windmill-cli && npm install -g windmill-cli" + ); + }) + ) + .command("completions", new CompletionsCommand()); + + + +async function main() { + try { + if (Deno.args.length === 0) { + command.showHelp(); + } + const LOG_LEVEL = + Deno.args.includes("--verbose") || Deno.args.includes("--debug") + ? "DEBUG" + : "INFO"; + // const NO_COLORS = Deno.args.includes("--no-colors"); + setShowDiffs(Deno.args.includes("--show-diffs")); + + const isWin = await getIsWin(); + log.setup({ + handlers: { + console: new log.ConsoleHandler(LOG_LEVEL, { + formatter: ({ msg }) => `${msg}`, + useColors: isWin ? false : true, + }), + }, + loggers: { + default: { + level: LOG_LEVEL, + handlers: ["console"], + }, + }, + }); + log.debug("Debug logging enabled. CLI build against " + VERSION); + + const extraHeaders = getHeaders(); + if (extraHeaders) { + OpenAPI.HEADERS = extraHeaders; + } + await command.parse(Deno.args); + } catch (e) { + if (e && typeof e === "object" && "name" in e && e.name === "ApiError") { + console.log("Server failed. " + (e as any).statusText + ": " + (e as any).body); + } + throw e; + } +} + +function isMain() { + // dnt-shim-ignore + const { Deno } = globalThis as any; + + const isDeno = Deno != undefined; + + if (isDeno) { + const isMain = import.meta.main; + if (isMain) { + if (!Deno.args.includes("completions")) { + if (Deno.env.get("SKIP_DENO_DEPRECATION_WARNING") !== "true") { + log.warn( + "Using the deno runtime for the Windmill CLI is deprecated, you can now use node: deno uninstall wmill && npm install -g windmill-cli. To skip this warning set SKIP_DENO_DEPRECATION_WARNING=true" + ); + } + } + } + return isMain; + } else { + //@ts-ignore + return esMain.default(import.meta); + } +} +if (isMain()) { + main(); +} + +export default command; diff --git a/cli/types.ts b/cli/src/types.ts similarity index 90% rename from cli/types.ts rename to cli/src/types.ts index c74598e999..e74d302a42 100644 --- a/cli/types.ts +++ b/cli/src/types.ts @@ -8,21 +8,21 @@ import { path, yamlParseContent, yamlStringify, -} from "./deps.ts"; -import { pushApp } from "./apps.ts"; -import { pushFolder } from "./folder.ts"; -import { pushFlow } from "./flow.ts"; -import { pushResource } from "./resource.ts"; -import { pushResourceType } from "./resource-type.ts"; -import { pushVariable } from "./variable.ts"; -import { yamlOptions } from "./sync.ts"; -import { showDiffs } from "./main.ts"; -import { deepEqual, isFileResource } from "./utils.ts"; -import { pushSchedule } from "./schedule.ts"; -import { pushWorkspaceUser } from "./user.ts"; -import { pushGroup } from "./user.ts"; -import { pushWorkspaceSettings, pushWorkspaceKey } from "./settings.ts"; -import { pushTrigger } from "./trigger.ts"; +} from "../deps.ts"; +import { pushApp } from "./commands/app/apps.ts"; +import { pushFolder } from "./commands/folder/folder.ts"; +import { pushFlow } from "./commands/flow/flow.ts"; +import { pushResource } from "./commands/resource/resource.ts"; +import { pushResourceType } from "./commands/resource-type/resource-type.ts"; +import { pushVariable } from "./commands/variable/variable.ts"; +import { yamlOptions } from "./commands/sync/sync.ts"; +import { showDiffs } from "./core/conf.ts"; +import { deepEqual, isFileResource } from "./utils/utils.ts"; +import { pushSchedule } from "./commands/schedule/schedule.ts"; +import { pushWorkspaceUser } from "./commands/user/user.ts"; +import { pushGroup } from "./commands/user/user.ts"; +import { pushWorkspaceSettings, pushWorkspaceKey } from "./core/settings.ts"; +import { pushTrigger } from "./commands/trigger/trigger.ts"; export interface DifferenceCreate { type: "CREATE"; diff --git a/cli/codebase.ts b/cli/src/utils/codebase.ts similarity index 91% rename from cli/codebase.ts rename to cli/src/utils/codebase.ts index 8af46ff284..36dfbb16ed 100644 --- a/cli/codebase.ts +++ b/cli/src/utils/codebase.ts @@ -1,5 +1,5 @@ -import { Codebase, SyncOptions } from "./conf.ts"; -import { log } from "./deps.ts"; +import { Codebase, SyncOptions } from "../core/conf.ts"; +import { log } from "../../deps.ts"; import { digestDir } from "./utils.ts"; export type SyncCodebase = Codebase & { getDigest: () => Promise }; diff --git a/cli/local_encryption.ts b/cli/src/utils/local_encryption.ts similarity index 100% rename from cli/local_encryption.ts rename to cli/src/utils/local_encryption.ts diff --git a/cli/metadata.ts b/cli/src/utils/metadata.ts similarity index 92% rename from cli/metadata.ts rename to cli/src/utils/metadata.ts index 338bc14557..cb5de7391a 100644 --- a/cli/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -1,5 +1,5 @@ // deno-lint-ignore-file no-explicit-any -import { GlobalOptions } from "./types.ts"; +import { GlobalOptions } from "../types.ts"; import { SEP, colors, @@ -7,28 +7,28 @@ import { path, yamlParseFile, yamlStringify, -} from "./deps.ts"; +} from "../../deps.ts"; import { ScriptMetadata, defaultScriptMetadata, -} from "./bootstrap/script_bootstrap.ts"; -import { Workspace } from "./workspace.ts"; +} from "../../bootstrap/script_bootstrap.ts"; +import { Workspace } from "../commands/workspace/workspace.ts"; import { languagesWithRawReqsSupport, LanguageWithRawReqsSupport, ScriptLanguage, } from "./script_common.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; -import { GlobalDeps, exts, findGlobalDeps } from "./script.ts"; -import { FSFSElement, findCodebase, newPathAssigner, yamlOptions } from "./sync.ts"; +import { GlobalDeps, exts, findGlobalDeps } from "../commands/script/script.ts"; +import { FSFSElement, findCodebase, yamlOptions } from "../commands/sync/sync.ts"; import { generateHash, readInlinePathSync } from "./utils.ts"; import { SyncCodebase } from "./codebase.ts"; -import { FlowFile } from "./flow.ts"; -import { replaceInlineScripts } from "./windmill-utils-internal/src/inline-scripts/replacer.ts"; -import { extractInlineScripts as extractInlineScriptsForFlows } from "./windmill-utils-internal/src/inline-scripts/extractor.ts"; -import { argSigToJsonSchemaType } from "./windmill-utils-internal/src/parse/parse-schema.ts"; -import { getIsWin } from "./main.ts"; -import { FlowValue } from "./gen/types.gen.ts"; +import { FlowFile } from "../commands/flow/flow.ts"; +import { replaceInlineScripts } from "../../windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { extractInlineScripts as extractInlineScriptsForFlows } from "../../windmill-utils-internal/src/inline-scripts/extractor.ts"; +import { argSigToJsonSchemaType } from "../../windmill-utils-internal/src/parse/parse-schema.ts"; +import { getIsWin } from "./utils.ts"; +import { FlowValue } from "../../gen/types.gen.ts"; export class LockfileGenerationError extends Error { constructor(message: string) { @@ -192,7 +192,6 @@ export async function generateFlowLockInternal( {}, SEP, opts.defaultTs, - newPathAssigner(opts.defaultTs ?? "bun") ); inlineScripts .filter((s) => s.path.endsWith(".lock")) @@ -533,23 +532,23 @@ export async function inferSchema( }> { let inferedSchema: any; if (language === "python3") { - const { parse_python } = await import("./wasm/py/windmill_parser_wasm.js"); + const { parse_python } = await import("../../wasm/py/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_python(content)); } else if (language === "nativets") { - const { parse_deno } = await import("./wasm/ts/windmill_parser_wasm.js"); + const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_deno(content)); } else if (language === "bun") { - const { parse_deno } = await import("./wasm/ts/windmill_parser_wasm.js"); + const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_deno(content)); } else if (language === "deno") { - const { parse_deno } = await import("./wasm/ts/windmill_parser_wasm.js"); + const { parse_deno } = await import("../../wasm/ts/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_deno(content)); } else if (language === "go") { - const { parse_go } = await import("./wasm/go/windmill_parser_wasm.js"); + const { parse_go } = await import("../../wasm/go/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_go(content)); } else if (language === "mysql") { const { parse_mysql } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_mysql(content)); @@ -559,7 +558,7 @@ export async function inferSchema( ]; } else if (language === "bigquery") { const { parse_bigquery } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_bigquery(content)); inferedSchema.args = [ @@ -568,7 +567,7 @@ export async function inferSchema( ]; } else if (language === "oracledb") { const { parse_oracledb } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_oracledb(content)); inferedSchema.args = [ @@ -577,7 +576,7 @@ export async function inferSchema( ]; } else if (language === "snowflake") { const { parse_snowflake } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_snowflake(content)); inferedSchema.args = [ @@ -586,7 +585,7 @@ export async function inferSchema( ]; } else if (language === "mssql") { const { parse_mssql } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_mssql(content)); inferedSchema.args = [ @@ -594,7 +593,7 @@ export async function inferSchema( ...inferedSchema.args, ]; } else if (language === "postgresql") { - const { parse_sql } = await import("./wasm/regex/windmill_parser_wasm.js"); + const { parse_sql } = await import("../../wasm/regex/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_sql(content)); inferedSchema.args = [ { name: "database", typ: { resource: "postgresql" } }, @@ -602,12 +601,12 @@ export async function inferSchema( ]; } else if (language === "duckdb") { const { parse_duckdb } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_duckdb(content)); } else if (language === "graphql") { const { parse_graphql } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_graphql(content)); inferedSchema.args = [ @@ -615,34 +614,34 @@ export async function inferSchema( ...inferedSchema.args, ]; } else if (language === "bash") { - const { parse_bash } = await import("./wasm/regex/windmill_parser_wasm.js"); + const { parse_bash } = await import("../../wasm/regex/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_bash(content)); } else if (language === "powershell") { const { parse_powershell } = await import( - "./wasm/regex/windmill_parser_wasm.js" + "../../wasm/regex/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_powershell(content)); } else if (language === "php") { - const { parse_php } = await import("./wasm/php/windmill_parser_wasm.js"); + const { parse_php } = await import("../../wasm/php/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_php(content)); } else if (language === "rust") { - const { parse_rust } = await import("./wasm/rust/windmill_parser_wasm.js"); + const { parse_rust } = await import("../../wasm/rust/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_rust(content)); } else if (language === "csharp") { const { parse_csharp } = await import( - "./wasm/csharp/windmill_parser_wasm.js" + "../../wasm/csharp/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_csharp(content)); } else if (language === "nu") { - const { parse_nu } = await import("./wasm/nu/windmill_parser_wasm.js"); + const { parse_nu } = await import("../../wasm/nu/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_nu(content)); } else if (language === "ansible") { const { parse_ansible } = await import( - "./wasm/yaml/windmill_parser_wasm.js" + "../../wasm/yaml/windmill_parser_wasm.js" ); inferedSchema = JSON.parse(parse_ansible(content)); } else if (language === "java") { - const { parse_java } = await import("./wasm/java/windmill_parser_wasm.js"); + const { parse_java } = await import("../../wasm/java/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_java(content)); // for related places search: ADD_NEW_LANG } else { @@ -910,4 +909,4 @@ export async function updateMetadataGlobalLock( WMILL_LOCKFILE, yamlStringify(conf as Record, yamlOptions) ); -} +} \ No newline at end of file diff --git a/cli/script_common.ts b/cli/src/utils/script_common.ts similarity index 100% rename from cli/script_common.ts rename to cli/src/utils/script_common.ts diff --git a/cli/upgrade.ts b/cli/src/utils/upgrade.ts similarity index 97% rename from cli/upgrade.ts rename to cli/src/utils/upgrade.ts index 0c7c1778de..83bbe6b4ab 100644 --- a/cli/upgrade.ts +++ b/cli/src/utils/upgrade.ts @@ -1,4 +1,4 @@ -import { Provider } from "./deps.ts"; +import { Provider } from "../../deps.ts"; export type NpmProviderOptions = { main?: string; logger?: any } & ( | { diff --git a/cli/utils.ts b/cli/src/utils/utils.ts similarity index 94% rename from cli/utils.ts rename to cli/src/utils/utils.ts index af5548330c..96bdfcebb0 100644 --- a/cli/utils.ts +++ b/cli/src/utils/utils.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck This file is copied from a JS project, so it's not type-safe. -import { colors, log, encodeHex, SEP } from "./deps.ts"; +import { colors, log, encodeHex, SEP } from "../../deps.ts"; import crypto from "node:crypto"; export function deepEqual(a: T, b: T): boolean { @@ -180,7 +180,7 @@ export async function selectRepository( } // Import Select dynamically to avoid dependency issues - const { Select } = await import("./deps.ts"); + const { Select } = await import("../../deps.ts"); console.log(`\nMultiple repositories found. Please select which repository to ${operation || 'use'}:\n`); @@ -197,3 +197,12 @@ export async function selectRepository( return repositories.find((r) => r.git_repo_resource_path === selectedRepo)!; } + +let isWin: boolean | undefined = undefined; +export async function getIsWin(): Promise { + if (isWin === undefined) { + const os = await import("node:os"); + isWin = os.platform() === "win32"; + } + return isWin; +} diff --git a/cli/store.ts b/cli/store.ts deleted file mode 100644 index 09d854606c..0000000000 --- a/cli/store.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ensureDir } from "./deps.ts"; - -function hash_string(str: string): number { - let hash = 0, - i, - chr; - if (str.length === 0) return hash; - for (i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = (hash << 5) - hash + chr; - hash |= 0; // Convert to 32bit integer - } - return hash; -} - -export async function getRootStore(configDirOverride?: string): Promise { - const baseDir = configDirOverride ?? - Deno.env.get("WMILL_CONFIG_DIR") ?? - config_dir() ?? - tmp_dir() ?? - "/tmp/"; - const store = baseDir + "/windmill/"; - await ensureDir(store); - return store; -} - -export async function getStore(baseUrl: string, configDirOverride?: string): Promise { - const baseHash = Math.abs(hash_string(baseUrl)).toString(16); - const baseStore = (await getRootStore(configDirOverride)) + baseHash + "/"; - await ensureDir(baseStore); - return baseStore; -} - -//inlined import dir from "https://deno.land/x/dir/mod.ts"; -function tmp_dir(): string | null { - switch (Deno.build.os) { - case "linux": { - const xdg = Deno.env.get("XDG_RUNTIME_DIR"); - if (xdg) return `${xdg}-/tmp`; - - const tmpDir = Deno.env.get("TMPDIR"); - if (tmpDir) return tmpDir; - - const tempDir = Deno.env.get("TEMP"); - if (tempDir) return tempDir; - - const tmp = Deno.env.get("TMP"); - if (tmp) return tmp; - - return "/var/tmp"; - } - case "darwin": - return Deno.env.get("TMPDIR") ?? null; - case "windows": - return Deno.env.get("TMP") ?? Deno.env.get("TEMP") ?? null; - } - return null; -} - -function config_dir(): string | null { - switch (Deno.build.os) { - case "linux": { - const xdg = Deno.env.get("XDG_CONFIG_HOME"); - if (xdg) return xdg; - - const home = Deno.env.get("HOME"); - if (home) return `${home}/.config`; - break; - } - - case "darwin": { - const home = Deno.env.get("HOME"); - if (home) return `${home}/Library/Preferences`; - break; - } - - case "windows": - return Deno.env.get("APPDATA") ?? null; - } - - return null; -} diff --git a/cli/test.nu b/cli/test.nu index 21b4bdd77c..af0a66882d 100755 --- a/cli/test.nu +++ b/cli/test.nu @@ -40,9 +40,9 @@ def 'main test deno' [] { print $"Testing Deno" $languages | each { |l| print $"+ ($l)" - deno run -A ../cli/main.ts script bootstrap $"f/tests/(random uuid)" $l + deno run -A ../cli/src/main.ts script bootstrap $"f/tests/(random uuid)" $l } - deno run -A ../cli/main.ts script generate-metadata + deno run -A ../cli/src/main.ts script generate-metadata print $"\nDeno has passed!\n" } @@ -73,7 +73,7 @@ def 'main build' [] { # main clean; # $languages | each { |l| # print $"+ ($l)" -# # deno run -A ../cli/main.ts script bootstrap $"f/tests/(random uuid)" $l +# # deno run -A ../cli/src/main.ts script bootstrap $"f/tests/(random uuid)" $l # # # let cmd = r#' # import { inferSchema } from '../cli/metadata.ts'; @@ -86,7 +86,7 @@ def 'main build' [] { # deno eval $cmd # } -# # deno run -A ../cli/main.ts script generate-metadata +# # deno run -A ../cli/src/main.ts script generate-metadata # } # def 'main clean' [] { diff --git a/cli/test/containerized_backend.ts b/cli/test/containerized_backend.ts index 05695b430d..b6facf945d 100644 --- a/cli/test/containerized_backend.ts +++ b/cli/test/containerized_backend.ts @@ -1023,10 +1023,10 @@ export async function main( ...args ]; - console.log('🔧 CLI Command:', ['/home/alex/.deno/bin/deno', 'run', '-A', '/home/alex/windmill/windmill/cli/main.ts', ...fullArgs].join(' ')); + console.log('🔧 CLI Command:', ['/home/alex/.deno/bin/deno', 'run', '-A', '/home/alex/windmill/windmill/cli/src/main.ts', ...fullArgs].join(' ')); return new Deno.Command('/home/alex/.deno/bin/deno', { - args: ['run', '-A', '/home/alex/windmill/windmill/cli/main.ts', ...fullArgs], + args: ['run', '-A', '/home/alex/windmill/windmill/cli/src/main.ts', ...fullArgs], cwd: workingDir, stdout: 'piped', stderr: 'piped', diff --git a/cli/test/test_config_helpers.ts b/cli/test/test_config_helpers.ts index 8d36fde844..c4b3816663 100644 --- a/cli/test/test_config_helpers.ts +++ b/cli/test/test_config_helpers.ts @@ -1,4 +1,4 @@ -import { getRootStore } from "../store.ts"; +import { getWorkspaceConfigFilePath } from "../windmill-utils-internal/src/config/config.ts"; /** * Create a temporary config directory for testing that doesn't interfere with user's config @@ -23,7 +23,7 @@ export async function withTestConfig(callback: (testConfigDir: string) => Pro * Clear the remotes file in test config directory */ export async function clearTestRemotes(testConfigDir: string): Promise { - const remoteFile = (await getRootStore(testConfigDir)) + "remotes.ndjson"; + const remoteFile = await getWorkspaceConfigFilePath(testConfigDir); await Deno.writeTextFile(remoteFile, ""); } diff --git a/cli/windmill-utils-internal/src/config/config.ts b/cli/windmill-utils-internal/src/config/config.ts new file mode 100644 index 0000000000..d2431e3861 --- /dev/null +++ b/cli/windmill-utils-internal/src/config/config.ts @@ -0,0 +1,199 @@ +// Runtime detection +// @ts-ignore - Cross-platform runtime detection +const isDeno = typeof Deno !== "undefined"; +// @ts-ignore - Cross-platform runtime detection +const isNode = typeof process !== "undefined" && process.versions?.node; + +export const WINDMILL_CONFIG_DIR = "windmill"; +export const WINDMILL_ACTIVE_WORKSPACE_FILE = "activeWorkspace"; +export const WINDMILL_WORKSPACE_CONFIG_FILE = "remotes.ndjson"; +export const INSTANCES_CONFIG_FILE = "instances.ndjson"; +export const WINDMILL_ACTIVE_INSTANCE_FILE = "activeInstance"; + +// Cross-platform environment variable access +function getEnv(key: string): string | undefined { + if (isDeno) { + // @ts-ignore - Deno API + return Deno.env.get(key); + } else { + // @ts-ignore - Node API + return process.env[key]; + } +} + +// Cross-platform OS detection with normalization +function getOS(): "linux" | "darwin" | "windows" | null { + if (isDeno) { + // @ts-ignore - Deno API + return Deno.build.os as "linux" | "darwin" | "windows"; + } else if (isNode) { + // @ts-ignore - Node API + const platform = process.platform; + switch (platform) { + case "linux": return "linux"; + case "darwin": return "darwin"; + case "win32": return "windows"; // Normalize win32 to windows + default: return null; + } + } + return null; +} + +// Cross-platform file system operations +async function stat(path: string | URL): Promise { + if (isDeno) { + // @ts-ignore - Deno API + return await Deno.stat(path); + } else { + // @ts-ignore - Node API + const fs = await import('fs/promises'); + return await fs.stat(path); + } +} + +async function mkdir(path: string | URL, options?: { recursive?: boolean }): Promise { + if (isDeno) { + // @ts-ignore - Deno API + await Deno.mkdir(path, options); + } else { + // @ts-ignore - Node API + const fs = await import('fs/promises'); + await fs.mkdir(path, options); + } +} + +function throwIfNotDirectory(fileInfo: any): void { + if (!fileInfo.isDirectory) { + throw new Error("Path is not a directory"); + } +} + +function config_dir(): string | null { + const os = getOS(); + switch (os) { + case "linux": { + const xdg = getEnv("XDG_CONFIG_HOME"); + if (xdg) return xdg; + + const home = getEnv("HOME"); + if (home) return `${home}/.config`; + break; + } + + case "darwin": { + const home = getEnv("HOME"); + if (home) return `${home}/Library/Preferences`; + break; + } + + case "windows": + return getEnv("APPDATA") ?? null; + } + + return null; +} + +function tmp_dir(): string | null { + const os = getOS(); + switch (os) { + case "linux": { + const xdg = getEnv("XDG_RUNTIME_DIR"); + if (xdg) return `${xdg}-/tmp`; + + const tmpDir = getEnv("TMPDIR"); + if (tmpDir) return tmpDir; + + const tempDir = getEnv("TEMP"); + if (tempDir) return tempDir; + + const tmp = getEnv("TMP"); + if (tmp) return tmp; + + return "/var/tmp"; + } + case "darwin": + return getEnv("TMPDIR") ?? null; + case "windows": + return getEnv("TMP") ?? getEnv("TEMP") ?? null; + } + return null; +} + +async function ensureDir(dir: string | URL) { + try { + const fileInfo = await stat(dir); + throwIfNotDirectory(fileInfo); + return; + } catch (err: any) { + // Check for file not found error in cross-platform way + if (isDeno) { + // @ts-ignore - Deno API + if (!(err instanceof Deno.errors.NotFound)) { + throw err; + } + } else { + // Node.js error codes + if (err.code !== 'ENOENT') { + throw err; + } + } + } + + // The dir doesn't exist. Create it. + // This can be racy. So we catch AlreadyExists and check stat again. + try { + await mkdir(dir, { recursive: true }); + } catch (err: any) { + // Check for already exists error in cross-platform way + if (isDeno) { + // @ts-ignore - Deno API + if (!(err instanceof Deno.errors.AlreadyExists)) { + throw err; + } + } else { + // Node.js error codes + if (err.code !== 'EEXIST') { + throw err; + } + } + + const fileInfo = await stat(dir); + throwIfNotDirectory(fileInfo); + } +} + +export async function getBaseConfigDir(configDirOverride?: string): Promise { + const baseDir = configDirOverride ?? + getEnv("WMILL_CONFIG_DIR") ?? + config_dir() ?? + tmp_dir() ?? + "/tmp/"; + return baseDir; +} + +export async function getConfigDirPath(configDirOverride?: string): Promise { + const baseDir = await getBaseConfigDir(configDirOverride); + const store = baseDir + `/${WINDMILL_CONFIG_DIR}/`; + await ensureDir(store); + return store; +} + +export async function getWorkspaceConfigFilePath(configDirOverride?: string): Promise { + const configDir = await getConfigDirPath(configDirOverride); + return `${configDir}/${WINDMILL_WORKSPACE_CONFIG_FILE}`; +} + +export async function getActiveWorkspaceConfigFilePath(configDirOverride?: string): Promise { + const configDir = await getConfigDirPath(configDirOverride); + return `${configDir}/${WINDMILL_ACTIVE_WORKSPACE_FILE}`; +} + +export async function getInstancesConfigFilePath(configDirOverride?: string): Promise { + const configDir = await getConfigDirPath(configDirOverride); + return `${configDir}/${INSTANCES_CONFIG_FILE}`; +} + +export async function getActiveInstanceFilePath(configDirOverride?: string): Promise { + const configDir = await getConfigDirPath(configDirOverride); + return `${configDir}/${WINDMILL_ACTIVE_INSTANCE_FILE}`; +} \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/config/index.ts b/cli/windmill-utils-internal/src/config/index.ts new file mode 100644 index 0000000000..e23ba6ca86 --- /dev/null +++ b/cli/windmill-utils-internal/src/config/index.ts @@ -0,0 +1 @@ +export * from "./config"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/index.ts b/cli/windmill-utils-internal/src/index.ts index e87d22d473..da314a7c5a 100644 --- a/cli/windmill-utils-internal/src/index.ts +++ b/cli/windmill-utils-internal/src/index.ts @@ -11,4 +11,5 @@ export * from "./inline-scripts"; export * from "./path-utils"; export * from "./parse"; +export * from "./config"; export { SEP, DELIMITER } from "./constants"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/inline-scripts/extractor.ts b/cli/windmill-utils-internal/src/inline-scripts/extractor.ts index 1154b2deb0..b64e7ca789 100644 --- a/cli/windmill-utils-internal/src/inline-scripts/extractor.ts +++ b/cli/windmill-utils-internal/src/inline-scripts/extractor.ts @@ -1,4 +1,4 @@ -import { assignPath } from "../path-utils/path-assigner"; +import { newPathAssigner } from "../path-utils/path-assigner"; import { FlowModule } from "../gen/types.gen"; /** @@ -20,26 +20,17 @@ interface InlineScript { * @param defaultTs - Default TypeScript runtime to use ("bun" or "deno") * @returns Array of inline scripts with their paths and content */ - -interface PathAssigner { - assignPath(summary: string | undefined, language: string): [string, string]; -} -const INLINE_SCRIPT = "inline_script"; export function extractInlineScripts( modules: FlowModule[], mapping: Record = {}, separator: string = "/", - defaultTs?: "bun" | "deno", - pathAssigner?: PathAssigner + defaultTs?: "bun" | "deno" ): InlineScript[] { + const pathAssigner = newPathAssigner(defaultTs ?? "bun"); return modules.flatMap((m) => { if (m.value.type == "rawscript") { let basePath, ext; - if (pathAssigner) { - [basePath, ext] = pathAssigner.assignPath(m.summary, m.value.language); - } else { - [basePath, ext] = assignPath(m.id, m.value.language, defaultTs); - } + [basePath, ext] = pathAssigner.assignPath(m.summary, m.value.language); const path = mapping[m.id] ?? basePath + ext; const content = m.value.content; const r = [{ path: path, content: content }]; diff --git a/cli/windmill-utils-internal/src/path-utils/path-assigner.ts b/cli/windmill-utils-internal/src/path-utils/path-assigner.ts index f6153c9f21..9020974214 100644 --- a/cli/windmill-utils-internal/src/path-utils/path-assigner.ts +++ b/cli/windmill-utils-internal/src/path-utils/path-assigner.ts @@ -1,5 +1,7 @@ import { RawScript } from "../gen/types.gen"; +const INLINE_SCRIPT_PREFIX = "inline_script"; + /** * Union type of all supported programming languages in Windmill */ @@ -31,7 +33,8 @@ export const LANGUAGE_EXTENSIONS: Record = { ansible: "playbook.yml", java: "java", duckdb: "duckdb.sql", - bunnative: "ts" + bunnative: "ts", + // for related places search: ADD_NEW_LANG }; /** @@ -52,20 +55,43 @@ export function getLanguageExtension( return LANGUAGE_EXTENSIONS[language] || "no_ext"; } +export interface PathAssigner { + assignPath(summary: string | undefined, language: SupportedLanguage): [string, string]; +} + /** - * Assigns a file path for an inline script based on its ID and language. - * Returns both the base path and extension as separate components. + * Creates a new path assigner for inline scripts. * - * @param id - Unique identifier for the script - * @param language - Programming language of the script * @param defaultTs - Default TypeScript runtime ("bun" or "deno") - * @returns Tuple containing [basePath, extension] + * @returns Path assigner function */ -export function assignPath( - id: string, - language: SupportedLanguage, - defaultTs: "bun" | "deno" = "bun" -): [string, string] { - const ext = getLanguageExtension(language, defaultTs); - return [`${id}.inline_script.`, ext]; +export function newPathAssigner(defaultTs: "bun" | "deno"): PathAssigner { + let counter = 0; + const seen_names = new Set(); + function assignPath( + summary: string | undefined, + language: SupportedLanguage + ): [string, string] { + let name; + + name = summary?.toLowerCase()?.replaceAll(" ", "_") ?? ""; + + let original_name = name; + + if (name == "") { + original_name = INLINE_SCRIPT_PREFIX; + name = `${INLINE_SCRIPT_PREFIX}_0`; + } + + while (seen_names.has(name)) { + counter++; + name = `${original_name}_${counter}`; + } + seen_names.add(name); + + const ext = getLanguageExtension(language, defaultTs); + + return [`${name}.inline_script.`, ext]; + } + return { assignPath }; } \ No newline at end of file