fix(git-sync): initialize repo with gitBranches set

This commit is contained in:
Ruben Fiszel
2025-11-30 17:42:16 +00:00
parent da1cf3bd6a
commit 9f2ca01e5e
6 changed files with 158 additions and 106 deletions
+7 -6
View File
@@ -116,12 +116,13 @@ export async function createFrameworkPlugins(appDir: string): Promise<any[]> {
if (frameworks.vue) {
log.info(colors.blue("🔧 Vue detected, adding vue plugin..."));
try {
const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1");
plugins.push(esbuildPluginVue.default());
} catch (error: any) {
log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`));
}
throw new Error("Vue plugin not supported yet");
// try {
// const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1");
// plugins.push(esbuildPluginVue.default());
// } catch (error: any) {
// log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`));
// }
}
return plugins;
+17 -33
View File
@@ -1,14 +1,9 @@
import {
colors,
Command,
log,
yamlStringify,
Confirm,
} from "../../../deps.ts";
import { colors, Command, log, yamlStringify, Confirm } 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";
import { getActiveWorkspaceOrFallback } from "../workspace/workspace.ts";
export interface InitOptions {
useDefault?: boolean;
@@ -61,12 +56,10 @@ async function initAction(opts: InitOptions) {
// Offer to bind workspace profile to current branch
if (isGitRepository()) {
const { getActiveWorkspace } = await import("../workspace/workspace.ts");
const activeWorkspace = await getActiveWorkspace(
const activeWorkspace = await getActiveWorkspaceOrFallback(
opts as GlobalOptions
);
const currentBranch = getCurrentGitBranch();
if (activeWorkspace && currentBranch) {
// Determine binding behavior based on flags
const shouldBind = opts.bindProfile === true;
@@ -74,10 +67,10 @@ async function initAction(opts: InitOptions) {
opts.bindProfile === undefined &&
Deno.stdin.isTerminal() &&
!opts.useDefault;
const shouldSkip =
opts.bindProfile === false ||
opts.useDefault ||
(!Deno.stdin.isTerminal() && opts.bindProfile === undefined);
opts.bindProfile != true &&
(opts.useDefault || !Deno.stdin.isTerminal());
if (shouldSkip) {
return;
@@ -86,15 +79,11 @@ async function initAction(opts: InitOptions) {
// Show workspace info if we're binding or prompting
if (shouldBind || shouldPrompt) {
log.info(
colors.yellow(
`\nCurrent Git branch: ${colors.bold(currentBranch)}`
)
colors.yellow(`\nCurrent Git branch: ${colors.bold(currentBranch)}`)
);
log.info(
colors.yellow(
`Active workspace profile: ${colors.bold(
activeWorkspace.name
)}`
`Active workspace profile: ${colors.bold(activeWorkspace.name)}`
)
);
log.info(
@@ -123,15 +112,15 @@ async function initAction(opts: InitOptions) {
currentConfig.gitBranches[currentBranch] = { overrides: {} };
}
log.info(
`binding branch ${currentBranch} to workspace ${activeWorkspace.name} on ${activeWorkspace.remote}`
);
currentConfig.gitBranches[currentBranch].baseUrl =
activeWorkspace.remote;
currentConfig.gitBranches[currentBranch].workspaceId =
activeWorkspace.workspaceId;
await Deno.writeTextFile(
"wmill.yaml",
yamlStringify(currentConfig)
);
await Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig));
log.info(
colors.green(
@@ -149,10 +138,10 @@ async function initAction(opts: InitOptions) {
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
const { getActiveWorkspace } = await import(
"../workspace/workspace.ts"
);
const activeWorkspace = await getActiveWorkspace(opts as GlobalOptions);
if (!activeWorkspace) {
log.info("No workspace configured. Using default settings.");
@@ -233,9 +222,7 @@ async function initAction(opts: InitOptions) {
replace: true, // Auto-replace when using backend settings during init
});
log.info(
colors.green("Git-sync settings applied from backend")
);
log.info(colors.green("Git-sync settings applied from backend"));
}
}
} catch (error) {
@@ -266,10 +253,7 @@ async function initAction(opts: InitOptions) {
}
if (!(await Deno.stat(".cursor/rules/flow.mdc").catch(() => null))) {
await Deno.writeTextFile(
".cursor/rules/flow.mdc",
flowGuidanceContent
);
await Deno.writeTextFile(".cursor/rules/flow.mdc", flowGuidanceContent);
log.info(colors.green("Created .cursor/rules/flow.mdc"));
}
+13 -1
View File
@@ -397,6 +397,18 @@ async function whoami(_opts: GlobalOptions) {
log.info("Active: " + colors.green.bold(activeName || "none"));
}
export async function getActiveWorkspaceOrFallback(opts: GlobalOptions) {
let activeWorkspace = await getActiveWorkspace(opts);
if (!activeWorkspace && opts.baseUrl && opts.workspace) {
activeWorkspace = {
name: opts.workspace,
remote: opts.baseUrl,
workspaceId: opts.workspace,
token: "",
};
}
return activeWorkspace;
}
async function bind(
opts: GlobalOptions & { branch?: string },
bindWorkspace?: boolean
@@ -419,7 +431,7 @@ async function bind(
const { readConfigFile } = await import("../../core/conf.ts");
const config = await readConfigFile();
const activeWorkspace = await getActiveWorkspace(opts);
const activeWorkspace = await getActiveWorkspaceOrFallback(opts);
if (!activeWorkspace && bindWorkspace) {
log.error(
colors.red(
+114 -45
View File
@@ -12,12 +12,14 @@ import {
allWorkspaces,
addWorkspace,
} from "../commands/workspace/workspace.ts";
import {
getLastUsedProfile,
setLastUsedProfile
} from "./branch-profiles.ts";
import { getLastUsedProfile, setLastUsedProfile } from "./branch-profiles.ts";
import { readConfigFile } from "./conf.ts";
import { getCurrentGitBranch, getOriginalBranchForWorkspaceForks, getWorkspaceIdForWorkspaceForkFromBranchName, isGitRepository } from "../utils/git.ts";
import {
getCurrentGitBranch,
getOriginalBranchForWorkspaceForks,
getWorkspaceIdForWorkspaceForkFromBranchName,
isGitRepository,
} from "../utils/git.ts";
import { WM_FORK_PREFIX } from "../main.ts";
// Helper function to select from multiple matching profiles
@@ -33,11 +35,22 @@ async function selectFromMultipleProfiles(
}
// Check for last used profile
const lastUsedProfileName = await getLastUsedProfile("", baseUrl, workspaceId, configDir);
const lastUsedProfileName = await getLastUsedProfile(
"",
baseUrl,
workspaceId,
configDir
);
if (lastUsedProfileName) {
const lastUsedProfile = profiles.find(p => p.name === lastUsedProfileName);
const lastUsedProfile = profiles.find(
(p) => p.name === lastUsedProfileName
);
if (lastUsedProfile) {
log.info(colors.green(`Using last used profile '${lastUsedProfile.name}' for ${context}`));
log.info(
colors.green(
`Using last used profile '${lastUsedProfile.name}' for ${context}`
)
);
return lastUsedProfile;
}
}
@@ -45,7 +58,11 @@ async function selectFromMultipleProfiles(
// No last used or it no longer exists - prompt for selection
if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) {
const selectedProfile = profiles[0];
log.info(colors.yellow(`Multiple profiles found for ${context}. Using first available profile: '${selectedProfile.name}'`));
log.info(
colors.yellow(
`Multiple profiles found for ${context}. Using first available profile: '${selectedProfile.name}'`
)
);
// Save selection for next time
await setLastUsedProfile(
@@ -59,17 +76,19 @@ async function selectFromMultipleProfiles(
return selectedProfile;
}
log.info(colors.yellow(`\nMultiple workspace profiles found for ${context}:`));
log.info(
colors.yellow(`\nMultiple workspace profiles found for ${context}:`)
);
const selectedName = await Select.prompt({
message: "Select profile",
options: profiles.map(p => ({
options: profiles.map((p) => ({
name: `${p.name} (${p.workspaceId} on ${p.remote})`,
value: p.name,
})),
});
const selectedProfile = profiles.find(p => p.name === selectedName)!;
const selectedProfile = profiles.find((p) => p.name === selectedName)!;
// Save selection for next time
await setLastUsedProfile(
@@ -95,19 +114,25 @@ async function createWorkspaceProfileInteractively(
): Promise<Workspace | undefined> {
// Log appropriate message based on context
if (!context.isForked) {
log.info(colors.yellow(
`\nNo workspace profile found for branch '${context.rawBranch}'\n` +
`(${normalizedBaseUrl}, ${workspaceId})`
));
log.info(
colors.yellow(
`\nNo workspace profile found for branch '${context.rawBranch}'\n` +
`(${normalizedBaseUrl}, ${workspaceId})`
)
);
} else {
log.info(colors.yellow(
`\nNo workspace profile was found for this forked workspace\n` +
`(${normalizedBaseUrl}, ${workspaceId})`
));
log.info(
colors.yellow(
`\nNo workspace profile was found for this forked workspace\n` +
`(${normalizedBaseUrl}, ${workspaceId})`
)
);
}
if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) {
log.info("Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first.");
log.info(
"Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first."
);
return undefined;
}
@@ -143,9 +168,19 @@ async function createWorkspaceProfileInteractively(
await addWorkspace(newWorkspace, opts);
// Set as last used for this branch
await setLastUsedProfile(currentBranch, normalizedBaseUrl, workspaceId, profileName, opts.configDir);
await setLastUsedProfile(
currentBranch,
normalizedBaseUrl,
workspaceId,
profileName,
opts.configDir
);
log.info(colors.green(`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`));
log.info(
colors.green(
`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`
)
);
log.info(colors.green(`✓ Profile '${profileName}' is now active`));
return newWorkspace;
@@ -200,9 +235,12 @@ export async function tryResolveBranchWorkspace(
let currentBranch: string;
const originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawBranch);
const workspaceIdIfForked = getWorkspaceIdForWorkspaceForkFromBranchName(rawBranch);
const workspaceIdIfForked =
getWorkspaceIdForWorkspaceForkFromBranchName(rawBranch);
if (originalBranchIfForked) {
log.info(`Using original branch \`${originalBranchIfForked}\` for finding workspace profile from gitBranches section in wmill.yaml`);
log.info(
`Using original branch \`${originalBranchIfForked}\` for finding workspace profile from gitBranches section in wmill.yaml`
);
currentBranch = originalBranchIfForked;
} else {
currentBranch = rawBranch;
@@ -217,20 +255,26 @@ export async function tryResolveBranchWorkspace(
return undefined;
}
let { baseUrl, workspaceId } = branchConfig;
log.info(
`Using branch configuration for branch \`${currentBranch}\` set in gitBranches`
);
const { baseUrl, workspaceId } = branchConfig;
let normalizedBaseUrl: string;
try {
normalizedBaseUrl = new URL(baseUrl).toString();
} catch (error) {
log.error(colors.red(`Invalid baseUrl in branch configuration: ${baseUrl}`));
log.error(
colors.red(`Invalid baseUrl in branch configuration: ${baseUrl}`)
);
return undefined;
}
// Find all profiles matching this baseUrl and workspaceId
const allProfiles = await allWorkspaces(opts.configDir);
const matchingProfiles = allProfiles.filter(
w => w.remote === normalizedBaseUrl && w.workspaceId === workspaceId
(w) => w.remote === normalizedBaseUrl && w.workspaceId === workspaceId
);
if (matchingProfiles.length === 0) {
@@ -244,14 +288,16 @@ export async function tryResolveBranchWorkspace(
);
}
// Handle multiple profiles - use special branch-aware logic
let selectedProfile: Workspace;
if (matchingProfiles.length === 1) {
selectedProfile = matchingProfiles[0];
log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch} with workspace id \`${workspaceId}\``));
log.info(
colors.green(
`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}' with workspace id \`${workspaceId}\``
)
);
} else {
// For multiple profiles, check branch-specific last used first
const lastUsedName = await getLastUsedProfile(
@@ -262,9 +308,15 @@ export async function tryResolveBranchWorkspace(
);
if (lastUsedName) {
const lastUsedProfile = matchingProfiles.find(p => p.name === lastUsedName);
const lastUsedProfile = matchingProfiles.find(
(p) => p.name === lastUsedName
);
if (lastUsedProfile) {
log.info(colors.green(`Using workspace profile '${lastUsedProfile.name}' for branch '${currentBranch}' (last used)`));
log.info(
colors.green(
`Using workspace profile '${lastUsedProfile.name}' for branch '${currentBranch}' (last used)`
)
);
return lastUsedProfile;
}
}
@@ -287,15 +339,19 @@ export async function tryResolveBranchWorkspace(
opts.configDir
);
log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`));
log.info(
colors.green(
`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`
)
);
}
if (workspaceIdIfForked) {
selectedProfile.name = `${selectedProfile.name}/${workspaceIdIfForked}`;
selectedProfile.workspaceId = workspaceIdIfForked;
log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `);
log.info(
`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `
);
}
return selectedProfile;
@@ -320,14 +376,20 @@ export async function resolveWorkspace(
// 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);
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("../commands/workspace/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
(w) =>
w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl
);
if (matchingWorkspaces.length >= 1) {
@@ -385,7 +447,9 @@ export async function resolveWorkspace(
if (!branch || !branch.startsWith(WM_FORK_PREFIX)) {
return res.value;
} else {
log.info(`Found an active workspace \`${res.value.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\``);
log.info(
`Found an active workspace \`${res.value.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\``
);
}
}
@@ -395,9 +459,13 @@ export async function resolveWorkspace(
(opts as any).__secret_workspace = branchWorkspace;
return branchWorkspace;
} else {
const originalBranch = getOriginalBranchForWorkspaceForks(branch)
const originalBranch = getOriginalBranchForWorkspaceForks(branch);
if (originalBranch) {
log.error(colors.red.bold(`Failed to resolve workspace profile for workspace fork. This most likely means that the original branch \`${originalBranch}\` where \`${branch}\` is originally forked from, is not setup in the wmill.yaml. You need to update the \`gitBranches\` section for \`${originalBranch}\` to include workspaceId and baseUrl.`))
log.error(
colors.red.bold(
`Failed to resolve workspace profile for workspace fork. This most likely means that the original branch \`${originalBranch}\` where \`${branch}\` is originally forked from, is not setup in the wmill.yaml. You need to update the \`gitBranches\` section for \`${originalBranch}\` to include workspaceId and baseUrl.`
)
);
return Deno.exit(-1);
}
}
@@ -414,7 +482,6 @@ export async function resolveWorkspace(
return Deno.exit(-1);
}
export async function fetchVersion(baseUrl: string): Promise<string> {
const requestHeaders = new Headers();
@@ -433,7 +500,9 @@ export async function fetchVersion(baseUrl: string): Promise<string> {
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}`);
throw new Error(
`Failed to fetch version: ${response.status} ${response.statusText}`
);
}
return await response.text();
@@ -108,7 +108,7 @@
)
// Determine display description based on variant and mode
const targetOrDefaultBranch = $derived(targetBranch ? `'${targetBranch}'` : 'repo\'s default' )
const targetOrDefaultBranch = $derived(targetBranch ? `'${targetBranch}'` : "repo's default")
const displayDescription = $derived(
variant === 'primary-sync' || variant === 'primary-promotion'
? mode === 'sync'
@@ -189,7 +189,7 @@
{#snippet headerActions()}
{#if !isLegacy}
{#if validation?.hasChanges && validation?.isValid && !repo.isUnsavedConnection}
<Button size="xs" onclick={handleSave} startIcon={{ icon: Save }}>
<Button size="xs" variant="accent" onclick={handleSave} startIcon={{ icon: Save }}>
{repo.legacyImported ? 'Migrate and save' : 'Save changes'}
</Button>
{#if idx !== null && gitSyncContext.initialRepositories[idx] && !repo.legacyImported}
@@ -361,10 +361,7 @@
<!-- Configuration -->
{#if repo.isUnsavedConnection && !emptyString(repo.git_repo_resource_path) && idx !== null}
<DetectionFlow
{idx}
mode={repoMode}
/>
<DetectionFlow {idx} mode={repoMode} />
{:else}
<GitSyncFilterSettings
bind:git_repo_resource_path={repo.git_repo_resource_path}
@@ -389,11 +386,7 @@
<div class="flex justify-between items-start">
<!-- Display mode settings as prominent text -->
<div class="flex-1 mr-4">
<GitSyncModeDisplay
mode={repoMode}
{targetBranch}
repository={repo}
/>
<GitSyncModeDisplay mode={repoMode} {targetBranch} repository={repo} />
</div>
<!-- Manual sync section for existing repos -->
+3 -10
View File
@@ -17,17 +17,10 @@
"gitSync_15": "hub/19816/sync-script-to-git-repo-windmill",
"gitSync_16": "hub/19818/sync-script-to-git-repo-windmill",
"gitSync_17": "hub/28073/sync-script-to-git-repo-windmill",
"gitSync": "hub/28078/sync-script-to-git-repo-windmill",
"gitSyncTest_0": "hub/9073/git-repo-test-read-write-windmill",
"gitSyncTest_1": "hub/11499/git-repo-test-read-write-windmill",
"gitSyncTest_2": "hub/11667/git-repo-test-read-write-windmill",
"gitSyncTest_3": "hub/11669/git-repo-test-read-write-windmill",
"gitSync_18": "hub/28078/sync-script-to-git-repo-windmill",
"gitSync": "hub/28081/sync-script-to-git-repo-windmill",
"gitSyncTest": "hub/19799/git-repo-test-read-write-windmill",
"gitInitRepo_0": "hub/19787/git-sync%3A-init-repository-windmill",
"gitInitRepo_1": "hub/19797/git-sync%3A-init-repository-windmill",
"gitInitRepo_2": "hub/19817/git-sync%3A-init-repository-windmill",
"gitInitRepo_3": "hub/28072/git-sync%3A-init-repository-windmill",
"gitInitRepo": "hub/28077/git-sync%3A-init-repository-windmill",
"gitInitRepo": "hub/28090/git-sync%3A-init-repository-windmill",
"slackErrorHandler": "hub/19741/workspace-or-schedule-error-handler-slack",
"slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack",
"slackErrorHandler_1": "hub/9206/workspace-or-schedule-error-handler-slack",