fix: fix circular dependancy breaking bundling of cli (#6219)
* fix: fix circular dependancy breaking bundling of cli * remove hubpaths
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
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";
|
||||
|
||||
61
cli/auth.ts
Normal file
61
cli/auth.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 { loginInteractive, tryGetLoginInfo } from "./login.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import {
|
||||
addWorkspace,
|
||||
removeWorkspace,
|
||||
Workspace
|
||||
} from "./workspace.ts";
|
||||
|
||||
/**
|
||||
* Main authentication function - moved from context.ts to break circular dependencies
|
||||
* This function maintains the original API signature from context.ts
|
||||
*/
|
||||
export async function requireLogin(
|
||||
opts: GlobalOptions
|
||||
): Promise<GlobalUserInfo> {
|
||||
// Import resolveWorkspace to avoid circular dependency at module level
|
||||
const { resolveWorkspace } = await import("./context.ts");
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
|
||||
if (!token) {
|
||||
token = workspace.token;
|
||||
}
|
||||
|
||||
setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
|
||||
|
||||
try {
|
||||
return await wmill.globalWhoami();
|
||||
} catch (error) {
|
||||
// Check for network errors and provide clearer messages
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
if (errorMsg.includes('fetch') || errorMsg.includes('connection') || errorMsg.includes('ECONNREFUSED') || errorMsg.includes('refused')) {
|
||||
throw new Error(`Network error: Could not connect to Windmill server at ${workspace.remote}`);
|
||||
}
|
||||
|
||||
log.info(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth..."
|
||||
);
|
||||
const newToken = await loginInteractive(workspace.remote);
|
||||
if (!newToken) {
|
||||
throw new Error("Unauthorized: Could not authenticate with the provided credentials");
|
||||
}
|
||||
|
||||
// Update workspace token
|
||||
removeWorkspace(workspace.name, false, opts);
|
||||
workspace.token = newToken;
|
||||
addWorkspace(workspace, opts);
|
||||
|
||||
setClient(
|
||||
newToken,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1)
|
||||
);
|
||||
return await wmill.globalWhoami();
|
||||
}
|
||||
}
|
||||
@@ -58,12 +58,12 @@ export async function resolveWorkspace(
|
||||
if (opts.baseUrl) {
|
||||
if (opts.workspace && opts.token) {
|
||||
const normalizedBaseUrl = new URL(opts.baseUrl).toString(); // add trailing slash if not present
|
||||
|
||||
|
||||
// Try to find existing workspace profile by name, then by workspaceId + remote
|
||||
if (opts.workspace) {
|
||||
// Try by workspace name first
|
||||
let existingWorkspace = await getWorkspaceByName(opts.workspace, opts.configDir);
|
||||
|
||||
|
||||
// If not found by name, try to find by workspaceId + remote match
|
||||
if (!existingWorkspace) {
|
||||
const { allWorkspaces } = await import("./workspace.ts");
|
||||
@@ -71,13 +71,13 @@ export async function resolveWorkspace(
|
||||
const matchingWorkspaces = workspaces.filter(
|
||||
w => w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl
|
||||
);
|
||||
|
||||
|
||||
// Due to uniqueness constraint, there can only be 0 or 1 match
|
||||
if (matchingWorkspaces.length === 1) {
|
||||
existingWorkspace = matchingWorkspaces[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (existingWorkspace) {
|
||||
// Validate that the base URL matches the profile's remote
|
||||
if (existingWorkspace.remote !== normalizedBaseUrl) {
|
||||
@@ -95,7 +95,7 @@ export async function resolveWorkspace(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// No existing profile found, create temporary workspace
|
||||
return {
|
||||
remote: normalizedBaseUrl,
|
||||
@@ -121,45 +121,6 @@ export async function resolveWorkspace(
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireLogin(
|
||||
opts: GlobalOptions
|
||||
): Promise<GlobalUserInfo> {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
|
||||
if (!token) {
|
||||
token = workspace.token;
|
||||
}
|
||||
|
||||
setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
|
||||
|
||||
try {
|
||||
return await wmill.globalWhoami();
|
||||
} catch (error) {
|
||||
// Check for network errors and provide clearer messages
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
if (errorMsg.includes('fetch') || errorMsg.includes('connection') || errorMsg.includes('ECONNREFUSED') || errorMsg.includes('refused')) {
|
||||
throw new Error(`Network error: Could not connect to Windmill server at ${workspace.remote}`);
|
||||
}
|
||||
|
||||
log.info(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth..."
|
||||
);
|
||||
const newToken = await loginInteractive(workspace.remote);
|
||||
if (!newToken) {
|
||||
throw new Error("Unauthorized: Could not authenticate with the provided credentials");
|
||||
}
|
||||
removeWorkspace(workspace.name, false, opts);
|
||||
workspace.token = newToken;
|
||||
addWorkspace(workspace, opts);
|
||||
|
||||
setClient(
|
||||
newToken,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1)
|
||||
);
|
||||
return await wmill.globalWhoami();
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchVersion(baseUrl: string): Promise<string> {
|
||||
const requestHeaders = new Headers();
|
||||
@@ -176,13 +137,13 @@ export async function fetchVersion(baseUrl: string): Promise<string> {
|
||||
new URL(new URL(baseUrl).origin + "/api/version"),
|
||||
{ headers: requestHeaders, method: "GET" }
|
||||
);
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
// Consume response body even on error to avoid resource leak
|
||||
await response.text();
|
||||
throw new Error(`Failed to fetch version: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
|
||||
return await response.text();
|
||||
}
|
||||
export async function tryResolveVersion(
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
} from "./deps.ts";
|
||||
import { getTypeStrFromPath, GlobalOptions } from "./types.ts";
|
||||
import { ignoreF } from "./sync.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { resolveWorkspace } from "./context.ts";
|
||||
import {
|
||||
SyncOptions,
|
||||
mergeConfigWithConfigFile,
|
||||
|
||||
@@ -4,7 +4,8 @@ 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, resolveWorkspace, validatePath } from "./context.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 { blueColor, generateFlowLockInternal } from "./metadata.ts";
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { colors, Command, log, SEP, Table } from "./deps.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.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";
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { colors, Command, log, yamlStringify } from "./deps.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { resolveWorkspace } from "./context.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
import {
|
||||
DEFAULT_SYNC_OPTIONS,
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
import { Command, log } from "./deps.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
|
||||
import { requireLogin, resolveWorkspace } from "./context.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";
|
||||
|
||||
@@ -159,9 +159,8 @@ const command = new Command()
|
||||
// Check for backend git-sync settings unless --use-default is specified
|
||||
if (!opts.useDefault) {
|
||||
try {
|
||||
const { requireLogin, resolveWorkspace } = await import(
|
||||
"./context.ts"
|
||||
);
|
||||
const { requireLogin } = await import("./auth.ts");
|
||||
const { resolveWorkspace } = await import("./context.ts");
|
||||
|
||||
// Check if user has workspace configured
|
||||
const { getActiveWorkspace } = await import(
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
parseFromFile,
|
||||
removeType,
|
||||
} from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.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";
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
parseFromFile,
|
||||
removeType,
|
||||
} from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.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";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, Command, log, SEP, Table } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "./context.ts";
|
||||
import * as wmill from "./gen/services.gen.ts";
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fetchVersion, requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { fetchVersion, resolveWorkspace } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
|
||||
@@ -16,9 +16,8 @@ import {
|
||||
parseFromFile,
|
||||
removeType,
|
||||
} from "./types.ts";
|
||||
import { requireLogin } from "./context.ts";
|
||||
import { validatePath } from "./context.ts";
|
||||
import { resolveWorkspace } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { validatePath, resolveWorkspace } from "./context.ts";
|
||||
|
||||
type Trigger = {
|
||||
http: HttpTrigger;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import {
|
||||
GlobalOptions,
|
||||
isSuperset,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { requireLogin } from "./auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
GlobalOptions,
|
||||
isSuperset,
|
||||
|
||||
@@ -3,9 +3,9 @@ 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 * as wmill from "./gen/services.gen.ts";
|
||||
import { requireLogin } from "./context.ts";
|
||||
|
||||
export interface Workspace {
|
||||
remote: string;
|
||||
@@ -258,11 +258,11 @@ export async function add(
|
||||
|
||||
export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
|
||||
|
||||
|
||||
// Check for conflicts before adding
|
||||
const existingWorkspaces = await allWorkspaces(opts.configDir);
|
||||
const isInteractive = Deno.stdin.isTerminal() && Deno.stdout.isTerminal() && !opts.force;
|
||||
|
||||
|
||||
// Check 1: Workspace name already exists
|
||||
const nameConflict = existingWorkspaces.find(w => w.name === workspace.name);
|
||||
if (nameConflict) {
|
||||
@@ -274,7 +274,7 @@ export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
log.info(colors.red.bold(`❌ Workspace name "${workspace.name}" already exists!`));
|
||||
log.info(` Existing: ${nameConflict.workspaceId} on ${nameConflict.remote}`);
|
||||
log.info(` New: ${workspace.workspaceId} on ${workspace.remote}`);
|
||||
|
||||
|
||||
if (!isInteractive) {
|
||||
// In non-interactive mode (tests, scripts), auto-overwrite with force flag
|
||||
if (opts.force) {
|
||||
@@ -287,7 +287,7 @@ export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
message: "Do you want to overwrite the existing workspace?",
|
||||
default: false,
|
||||
});
|
||||
|
||||
|
||||
if (!overwrite) {
|
||||
log.info(colors.yellow("Operation cancelled."));
|
||||
return;
|
||||
@@ -295,20 +295,20 @@ export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check 2: Same (remote, workspaceId) tuple already exists under different name
|
||||
const tupleConflict = existingWorkspaces.find(w =>
|
||||
w.remote === workspace.remote &&
|
||||
w.workspaceId === workspace.workspaceId &&
|
||||
const tupleConflict = existingWorkspaces.find(w =>
|
||||
w.remote === workspace.remote &&
|
||||
w.workspaceId === workspace.workspaceId &&
|
||||
w.name !== workspace.name
|
||||
);
|
||||
|
||||
|
||||
if (tupleConflict) {
|
||||
log.info(colors.red.bold(`❌ Workspace ${workspace.workspaceId} on ${workspace.remote} already exists!`));
|
||||
log.info(` Existing name: "${tupleConflict.name}"`);
|
||||
log.info(` New name: "${workspace.name}"`);
|
||||
log.info(colors.yellow(`\nNote: Backend constraint prevents duplicate (remote, workspaceId) combinations.`));
|
||||
|
||||
|
||||
if (!isInteractive) {
|
||||
// In non-interactive mode (tests, scripts), auto-overwrite with force flag
|
||||
if (opts.force) {
|
||||
@@ -321,20 +321,20 @@ export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
message: `Do you want to overwrite the existing workspace "${tupleConflict.name}"?`,
|
||||
default: false,
|
||||
});
|
||||
|
||||
|
||||
if (!overwrite) {
|
||||
log.info(colors.yellow("Operation cancelled."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove the conflicting workspace
|
||||
await removeWorkspace(tupleConflict.name, true, opts);
|
||||
}
|
||||
|
||||
|
||||
// Remove existing workspace with same name (if updating)
|
||||
await removeWorkspace(workspace.name, true, opts);
|
||||
|
||||
|
||||
// Add the new workspace
|
||||
const file = await Deno.open((await getRootStore(opts.configDir)) + "remotes.ndjson", {
|
||||
append: true,
|
||||
|
||||
Reference in New Issue
Block a user