Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
"type": "string",
"default": "https://stream.launchdarkly.com",
"markdownDescription": "LaunchDarkly stream URI. Change only if you are using the Relay Proxy. [More information](https://docs.launchdarkly.com/home/advanced/relay-proxy/using)"
},
"launchdarkly.devServerUri": {
"type": "string",
"default": "http://localhost:8765",
"markdownDescription": "URI for the LaunchDarkly CLI dev-server. Used for local development and testing. [More information](https://launchdarkly.com/docs/guides/flags/ldcli-dev-server-reference)"
}
}
},
Expand Down Expand Up @@ -256,6 +261,24 @@
{
"command": "launchdarkly.signOut",
"title": "LaunchDarkly: Sign Out"
},
{
"command": "launchdarkly.connectDevServer",
"title": "LaunchDarkly: Connect to Dev Server"
},
{
"command": "launchdarkly.disconnectDevServer",
"title": "LaunchDarkly: Disconnect from Dev Server"
},
{
"command": "launchdarkly.setDevServerOverride",
"title": "LaunchDarkly: Set Dev Server Override",
"icon": "$(edit)"
},
{
"command": "launchdarkly.removeDevServerOverride",
"title": "LaunchDarkly: Remove Dev Server Override",
"icon": "$(close)"
}
],
"menus": {
Expand Down Expand Up @@ -406,6 +429,16 @@
"command": "launchdarkly.openBrowser",
"when": "view == launchdarklyFeatureFlags && viewItem == flagViewBrowser",
"group": "inline"
},
{
"command": "launchdarkly.setDevServerOverride",
"when": "view == launchdarklyFeatureFlags && (viewItem == flagParentItem || viewItem == flagParentItemOverridden)",
"group": "devserver@1"
},
{
"command": "launchdarkly.removeDevServerOverride",
"when": "view == launchdarklyFeatureFlags && viewItem == flagParentItemOverridden",
"group": "devserver@2"
}
]
},
Expand Down Expand Up @@ -532,21 +565,21 @@
"@types/lodash": "4.14.116",
"@types/opn": "5.1.0",
"ajv": "^6.12.6",
"axios": "^1.6.0",
"axios": "1.13.5",
"axios-retry": "4.0.0",
"axios-retry-after": "2.0.0",
"csv-parser": "^2.3.3",
"form-data": "^4.0.0",
"gunzip-maybe": "^1.4.2",
"launchdarkly-api-typescript": "6.0.0",
"lodash": "^4.17.21",
"lodash": "^4.17.23",
"lodash.debounce": "4.0.8",
"lodash.kebabcase": "4.1.1",
"node-fetch": "^3.3.2",
"opn": "5.3.0",
"request": "^2.34",
"rimraf": "^3.0.2",
"tar-fs": "^2.1.1",
"tar-fs": "2.1.4",
"uuid": "^9.0.1"
},
"resolutions": {
Expand Down
138 changes: 138 additions & 0 deletions src/commands/connectDevServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { commands, Disposable, ProgressLocation, window } from 'vscode';
import { LDExtensionConfiguration } from '../ldExtensionConfiguration';
import { CMD_LD_CONNECT_DEV_SERVER, CMD_LD_DISCONNECT_DEV_SERVER, CMD_LD_REFRESH_ENTRY } from '../utils/commands';
import { registerCommand } from '../utils/registerCommand';
import { updateDevServerStatusBar } from '../devServerStatusBar';

const DEFAULT_DEV_SERVER_URI = 'http://localhost:8765';

export function connectDevServerCommand(config: LDExtensionConfiguration): Disposable {
return registerCommand(CMD_LD_CONNECT_DEV_SERVER, async () => {
try {
const devServerUri = config.getConfig().getDevServerUri();

// Confirm connection with the user
const confirm = await window.showInformationMessage(
`Connect to LaunchDarkly dev-server at ${devServerUri}?`,
{ modal: false },
'Connect',
'Change URI',
);

if (!confirm) {
return;
}

let finalUri = devServerUri;

if (confirm === 'Change URI') {
const inputUri = await window.showInputBox({
prompt: 'Enter the dev-server URI',
value: devServerUri,
placeHolder: DEFAULT_DEV_SERVER_URI,
validateInput: (value) => {
try {
new URL(value);
return null;
} catch {
return 'Please enter a valid URL';
}
},
});

if (!inputUri) {
return;
}
finalUri = inputUri;
}

// Update the dev-server URI in the configuration, but don't enable yet until we verify the connection.
await config.getConfig().update('devServerUri', finalUri, false);

// Test connection before enabling
const devServerProvider = config.getDevServerProvider();
const isAvailable = await devServerProvider?.getApi().isAvailable();

if (!isAvailable) {
const retry = await window.showErrorMessage(
`Could not connect to dev-server at ${finalUri}. Is the dev-server running?`,
'Retry',
'Cancel',
);
if (retry === 'Retry') {
commands.executeCommand(CMD_LD_CONNECT_DEV_SERVER);
}
return;
}

await config.getConfig().setDevServerEnabled(true);

// Reload the flag store to reconnect with dev-server
if (config.getFlagStore()) {
await window.withProgress(
{
location: ProgressLocation.Notification,
title: '[LaunchDarkly] Connecting to dev-server...',
cancellable: false,
},
async () => {
await config.getFlagStore().reload();
},
);
}

// Refresh dev-server data
await devServerProvider?.refresh();

// Update status bar to show dev-server connection
updateDevServerStatusBar(config);

// Refresh the flags view to load dev-server values
await commands.executeCommand(CMD_LD_REFRESH_ENTRY);

window.showInformationMessage(`Connected to LaunchDarkly dev-server at ${finalUri}`);
} catch (err) {
console.error(`Failed to connect to dev-server: ${err}`);
window.showErrorMessage(`Failed to connect to dev-server: ${err.message}`);
}
});
}

export function disconnectDevServerCommand(config: LDExtensionConfiguration): Disposable {
return registerCommand(CMD_LD_DISCONNECT_DEV_SERVER, async () => {
try {
if (!config.getConfig().isDevServerEnabled()) {
window.showInformationMessage('Not currently connected to a dev-server');
return;
}

await config.getConfig().setDevServerEnabled(false);
config.getDevServerProvider()?.clearCache();

// Reload the flag store to reconnect to LaunchDarkly
if (config.getFlagStore()) {
await window.withProgress(
{
location: ProgressLocation.Notification,
title: '[LaunchDarkly] Disconnecting from dev-server...',
cancellable: false,
},
async () => {
await config.getFlagStore().reload();
},
);
}

// Update status bar to hide dev-server indicator
updateDevServerStatusBar(config);

// Refresh the flags view to show LaunchDarkly values
await commands.executeCommand(CMD_LD_REFRESH_ENTRY);

window.showInformationMessage('Disconnected from dev-server. Flag values now come from LaunchDarkly.');
} catch (err) {
console.error(`Failed to disconnect from dev-server: ${err}`);
window.showErrorMessage(`Failed to disconnect from dev-server: ${err.message}`);
}
});
}
78 changes: 78 additions & 0 deletions src/commands/devServerOverrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ILDExtensionConfiguration } from '../models';
import { commands, window } from 'vscode';
import { showSmartOverrideInput } from '../utils/smartOverrideInput';

export async function setDevServerOverride(config: ILDExtensionConfiguration, flagKey: string): Promise<void> {
const devServerProvider = config.getDevServerProvider();
if (!devServerProvider || !config.getConfig().isDevServerEnabled()) {
window.showErrorMessage('Not connected to dev-server');
return;
}

// Get flag info with variations
const flagInfo = devServerProvider.getFlag(flagKey);
if (!flagInfo) {
window.showErrorMessage('Flag not found in dev-server');
return;
}

// Always show current value (override if it exists, otherwise base value)
const currentValue = (flagInfo.override?.value ?? flagInfo.flag.value) as
| string
| number
| boolean
| object
| undefined;
const isEditing = flagInfo.isOverridden;

// Show smart input based on flag type
const value = await showSmartOverrideInput(flagInfo.flag, currentValue, isEditing);

if (value === undefined) {
return;
}

try {
const success = await devServerProvider.setOverride(flagKey, value);

if (success) {
window.showInformationMessage(`${isEditing ? 'Updated' : 'Set'} dev-server override for flag "${flagKey}"`);
await commands.executeCommand('launchdarkly.refreshEntry');
} else {
window.showErrorMessage(`Failed to ${isEditing ? 'update' : 'set'} override`);
}
} catch (err) {
window.showErrorMessage(`Failed to ${isEditing ? 'update' : 'set'} override: ${err.message}`);
}
}

export async function removeDevServerOverride(config: ILDExtensionConfiguration, flagKey: string): Promise<void> {
const devServerProvider = config.getDevServerProvider();
if (!devServerProvider || !config.getConfig().isDevServerEnabled()) {
window.showErrorMessage('Not connected to dev-server');
return;
}

const confirm = await window.showWarningMessage(
`Remove dev-server override for flag "${flagKey}"?`,
{ modal: true },
'Remove',
);

if (confirm !== 'Remove') {
return;
}

try {
const success = await devServerProvider.removeOverride(flagKey);

if (success) {
window.showInformationMessage(`Removed dev-server override for flag "${flagKey}"`);
await commands.executeCommand('launchdarkly.refreshEntry');
} else {
window.showErrorMessage('Failed to remove override');
}
} catch (err) {
window.showErrorMessage(`Failed to remove override: ${err.message}`);
}
}
25 changes: 25 additions & 0 deletions src/commands/flagActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CMD_LD_FLAG_ACTION, CMD_LD_OPEN_BROWSER } from '../utils/commands';
import { flagCodeSearch } from '../utils/flagCodeSearch';
import { registerCommand } from '../utils/registerCommand';
import { ILDExtensionConfiguration } from '../models';
import { removeDevServerOverride, setDevServerOverride } from './devServerOverrides';

const cache = new ToggleCache();

Expand Down Expand Up @@ -56,6 +57,12 @@ export default function flagCmd(config: ILDExtensionConfiguration): Disposable {
return;
}
cache.set(flagWindow.value);

// Build command list based on dev-server connection
const isDevServerConnected = config.getConfig().isDevServerEnabled();
const devServerProvider = config.getDevServerProvider();
const isOverridden = isDevServerConnected && devServerProvider?.isOverridden(flagWindow.value);

const userCommands = [
{ label: 'Quick Targeting', detail: 'Quickly add individual targeting or rule to the selected flag.' },
{ label: 'Toggle Flag', detail: 'Toggle selected flag on or off.' },
Expand All @@ -65,6 +72,18 @@ export default function flagCmd(config: ILDExtensionConfiguration): Disposable {
{ label: 'Update fallthrough variation', detail: 'Change fallthrough variation for selected flag' },
{ label: 'Update off variation', detail: 'Change off variation for selected flag' },
];

// Add dev-server commands if connected
if (isDevServerConnected) {
userCommands.push({
label: 'Set Dev Server Override',
detail: 'Set or update an override value for this flag in the dev-server',
});
if (isOverridden) {
userCommands.push({ label: 'Remove Dev Server Override', detail: 'Remove the override for this flag' });
}
}

const selectedCommand = await window.showQuickPick(userCommands, {
title: 'Select Command for flag',
placeHolder: 'Type command to execute',
Expand Down Expand Up @@ -97,6 +116,12 @@ export default function flagCmd(config: ILDExtensionConfiguration): Disposable {
case 'Update off variation':
flagOffFallthroughPatch(config, 'updateOffVariation', flagWindow.value);
break;
case 'Set Dev Server Override':
await setDevServerOverride(config, flagWindow.value);
break;
case 'Remove Dev Server Override':
await removeDevServerOverride(config, flagWindow.value);
break;
}

return;
Expand Down
Loading