Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
01c0685
chore: update CI to not try to publish unless a version changed
cspath1 Jan 30, 2026
aae0f64
chore: add SECURITY.md
cspath1 Feb 2, 2026
751a0da
feat: add simple dev server connect/disconnect commands
cspath1 Feb 9, 2026
6cd9477
feat: show dev-server values when connected
cspath1 Feb 9, 2026
dbe03cf
feat: surface overrides
cspath1 Feb 10, 2026
849432a
feat: add persistent connection between sessions
cspath1 Feb 10, 2026
84c1f5f
feat: set, edit, remove overrides for flags
cspath1 Feb 10, 2026
97f12f6
test: add tests
cspath1 Feb 10, 2026
c686df9
feat: expand functionality
cspath1 Feb 11, 2026
d2709b4
refactor: remove edit command
cspath1 Feb 11, 2026
54b98e7
fix: no custom value for boolean flags
cspath1 Feb 11, 2026
c5b2a79
feat: improve surface area
cspath1 Feb 11, 2026
f01d1c6
refactor: refresh dev server on streaming change
cspath1 Feb 11, 2026
f9e3ee2
chore: clean up
cspath1 Feb 11, 2026
37fc7ef
fix: clean up the UX by fixing some small inconsistencies
cspath1 Feb 11, 2026
39ff37d
fix: improve UX + error handling/safe-guarding
cspath1 Feb 12, 2026
48b4bb8
fix: clean up subscription
cspath1 Feb 12, 2026
02a7dda
chore: run npm run perttier:write
cspath1 Feb 12, 2026
23fa8fc
fix: rev versions to fix security issues
cspath1 Feb 12, 2026
cfdeb91
feat: add internal analytics client (#147)
cspath1 Feb 26, 2026
71048ff
Merge branch 'main' into cspath/dev-server-integration
cspath1 Feb 26, 2026
bd0745b
fix: only show dev server commands when connected
cspath1 Feb 26, 2026
ca4fbad
test: add some tests
cspath1 Feb 27, 2026
7444585
docs: update docs
cspath1 Feb 27, 2026
e3ef4f4
docs: update docs
cspath1 Feb 27, 2026
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
7 changes: 6 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
run: yarn build:setup

- name: Setup xvfb
run: xvfb-run -a yarn run vscode:prepublish
run: xvfb-run -a yarn run vscode:prepublish
env:
LD_ANALYTICS_CLIENT_ID: ${{ secrets.LD_ANALYTICS_CLIENT_ID }}
LD_ANALYTICS_BASE_URL: ${{ secrets.LD_ANALYTICS_BASE_URL }}
LD_ANALYTICS_STREAM_URL: ${{ secrets.LD_ANALYTICS_STREAM_URL }}
LD_ANALYTICS_EVENTS_URL: ${{ secrets.LD_ANALYTICS_EVENTS_URL }}

- name: Run xvfb test
run: xvfb-run -a yarn test
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
LD_ANALYTICS_CLIENT_ID: ${{ secrets.LD_ANALYTICS_CLIENT_ID }}
LD_ANALYTICS_BASE_URL: ${{ secrets.LD_ANALYTICS_BASE_URL }}
LD_ANALYTICS_STREAM_URL: ${{ secrets.LD_ANALYTICS_STREAM_URL }}
LD_ANALYTICS_EVENTS_URL: ${{ secrets.LD_ANALYTICS_EVENTS_URL }}

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ coderefs/
.vscode/settings.json
.idea
.yarn/
package-lock.json
package-lock.json
.env*
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The LaunchDarkly VS Code extension requires authentication using a **Personal Ac
- [Quick Links](#quick-links) to LaunchDarkly
- [Flags in File](#flags-in-file)
- [Flag Lens](#flag-lens)
- [Dev Server Integration](#dev-server-integration): connect to a local dev-server for local development and testing with local flag values and overrides

Read our official documentation about this extension at <https://docs.launchdarkly.com/integrations/vscode>

Expand Down Expand Up @@ -109,6 +110,48 @@ __Variation Information__: Which variation or value of the flag is currently bei

This is OFF by default. It can be enabled through Settings > LaunchDarkly Extension > Enable Flag Lens.

### Dev Server Integration

The extension allows for integrating with the [LaunchDarkly CLI dev-server](https://launchdarkly.com/docs/guides/flags/ldcli-dev-server-reference) for local development and testing. When connected, the extension operates in a hybrid mode: it continues using your LaunchDarkly account for API calls and flag metadata while fetching flag values and streaming updates from the local dev-server.

#### Prerequisites

Install the [LaunchDarkly CLI](https://launchdarkly.com/docs/home/getting-started/setting-up/quickstart#install-the-launchdarkly-cli) and start the dev-server:

```bash
ldcli dev-server
```

By default, the dev-server runs at `http://localhost:8765`. You can configure a custom URI in Settings > LaunchDarkly Extension > Dev Server URI.

#### Connecting

1. Open the Command Palette (`Cmd/Ctrl + Shift + P`)
2. Run `LaunchDarkly: Connect to Dev Server`
3. Confirm the URI or choose "Change URI" to enter a custom one

The status bar will display a dev-server indicator when connected. If the dev-server becomes unavailable, the extension will prompt you to retry, disconnect, or dismiss.

#### Features when connected

- **Local flag values**: The Feature Flag Explorer shows flag values from the dev-server instead of LaunchDarkly cloud, updated in real-time via streaming.
- **Override management**: Set or remove flag value overrides directly from the extension. Right-click any flag in the explorer and select "Set Dev Server Override" to choose from available variations or enter a custom value. Overridden flags are marked with a visual indicator.
- **Hover and Code Lens**: Hover tooltips and Flag Lens annotations reflect dev-server values and indicate when a flag is overridden.
- **Automatic reconnection**: If the extension was previously connected to the dev-server, it will attempt to reconnect on startup.

#### Commands

| Command | Description |
|---------|-------------|
| `LaunchDarkly: Connect to Dev Server` | Connect to a running dev-server instance |
| `LaunchDarkly: Disconnect from Dev Server` | Disconnect and return to LaunchDarkly cloud values |
| `LaunchDarkly: Set Dev Server Override` | Set a local override value for a flag (available when connected) |
| `LaunchDarkly: Remove Dev Server Override` | Remove an existing override for a flag (available when connected) |

#### Disconnecting

Run `LaunchDarkly: Disconnect from Dev Server` from the Command Palette. Flag values will revert to those served by LaunchDarkly.

## Contributing

LaunchDarkly for Visual Studio Code is an [open source project](https://github.com/launchdarkly/ld-vscode). If you experience any issues, please [log an issue on our issue tracker](https://github.com/launchdarkly/ld-vscode/issues). If you'd like to contribute, we're happily taking pull requests.
Expand Down
55 changes: 49 additions & 6 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 @@ -312,11 +335,19 @@
"command": "launchdarkly.enableCodeLens",
"when": "false"
},
{
"command": "launchdarkly.setMaintainer",
"when": "false"
}
],
{
"command": "launchdarkly.setMaintainer",
"when": "false"
},
{
"command": "launchdarkly.setDevServerOverride",
"when": "launchdarkly:devServerConnected"
},
{
"command": "launchdarkly.removeDevServerOverride",
"when": "launchdarkly:devServerConnected"
}
],
"editor/context": [
{
"command": "launchdarkly.openInLaunchDarkly",
Expand Down Expand Up @@ -406,6 +437,16 @@
"command": "launchdarkly.openBrowser",
"when": "view == launchdarklyFeatureFlags && viewItem == flagViewBrowser",
"group": "inline"
},
{
"command": "launchdarkly.setDevServerOverride",
"when": "view == launchdarklyFeatureFlags && launchdarkly:devServerConnected && (viewItem == flagParentItem || viewItem == flagParentItemOverridden)",
"group": "devserver@1"
},
{
"command": "launchdarkly.removeDevServerOverride",
"when": "view == launchdarklyFeatureFlags && launchdarkly:devServerConnected && viewItem == flagParentItemOverridden",
"group": "devserver@2"
}
]
},
Expand Down Expand Up @@ -507,6 +548,7 @@
"@vscode/test-electron": "^2.3.8",
"chai": "^4.3.10",
"clean-webpack-plugin": "4.0.0",
"dotenv": "^17.3.1",
"eslint": "8.55.0",
"eslint-config-prettier": "9.1.0",
"expect": "^26.1.0",
Expand All @@ -532,13 +574,14 @@
"@types/lodash": "4.14.116",
"@types/opn": "5.1.0",
"ajv": "^6.12.6",
"axios": "^1.12.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.4",
"gunzip-maybe": "^1.4.2",
"launchdarkly-api-typescript": "6.0.0",
"launchdarkly-node-client-sdk": "^3.3.1",
"lodash": "^4.17.23",
"lodash.debounce": "4.0.8",
"lodash.kebabcase": "4.1.1",
Expand Down
104 changes: 104 additions & 0 deletions src/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as LDClient from 'launchdarkly-node-client-sdk';
import { Disposable, env } from 'vscode';

export interface AnalyticsConfig {
clientSideId: string;
baseUrl: string;
streamUrl: string;
eventsUrl: string;
}

function getConfigFromEnv(): AnalyticsConfig | null {
const clientSideId = process.env.LD_ANALYTICS_CLIENT_ID;
const baseUrl = process.env.LD_ANALYTICS_BASE_URL;
const streamUrl = process.env.LD_ANALYTICS_STREAM_URL;
const eventsUrl = process.env.LD_ANALYTICS_EVENTS_URL;

if (!clientSideId || !baseUrl || !streamUrl || !eventsUrl) {
return null;
}
return { clientSideId, baseUrl, streamUrl, eventsUrl };
}

/**
* Internal analytics client using the LaunchDarkly Node Client SDK.
* Uses a client-side ID (safe to embed — not a secret) to send
* usage telemetry events. Respects VS Code telemetry settings.
*/
export class AnalyticsClient implements Disposable {
private client: LDClient.LDClient | null = null;
private enabled = false;
private initPromise: Promise<void> | null = null;
private config: AnalyticsConfig | null;

constructor(config?: AnalyticsConfig | null) {
this.config = config ?? getConfigFromEnv();
}

async initialize(extensionVersion: string): Promise<void> {
if (!this.config || !env.isTelemetryEnabled) {
return;
}

if (this.initPromise) {
return this.initPromise;
}

this.initPromise = this.doInitialize(extensionVersion);
return this.initPromise;
}

private async doInitialize(extensionVersion: string): Promise<void> {
try {
const context: LDClient.LDContext = {
kind: 'ld-vscode-user',
key: env.machineId,
vscodeVersion: env.appName,
extensionVersion,
};

this.client = LDClient.initialize(this.config.clientSideId, context, {
baseUrl: this.config.baseUrl,
streamUrl: this.config.streamUrl,
eventsUrl: this.config.eventsUrl,
});
await this.client.waitForInitialization();
this.enabled = true;

env.onDidChangeTelemetryEnabled((telemetryEnabled) => {
this.enabled = telemetryEnabled;
});
} catch (err) {
console.error('[LaunchDarkly Analytics] Failed to initialize:', err);
this.client = null;
this.enabled = false;
}
}

track(event: string, data?: LDClient.LDFlagValue): void {
if (!this.enabled || !this.client) {
return;
}
try {
this.client.track(event, data);
} catch (err) {
console.error(`[LaunchDarkly Analytics] Failed to track event "${event}":`, err);
}
}

async dispose(): Promise<void> {
if (this.client) {
try {
await this.client.flush();
} catch {
// Best-effort flush on shutdown
}
this.client.close();
this.client = null;
}
this.enabled = false;
this.initPromise = null;
}
}

export const analytics = new AnalyticsClient();
2 changes: 2 additions & 0 deletions src/commands/configureLaunchDarkly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FlagStore } from '../flagStore';
import { LDExtensionConfiguration } from '../ldExtensionConfiguration';
import { CMD_LD_CONFIG } from '../utils/commands';
import { registerCommand } from '../utils/registerCommand';
import { analytics } from '../analytics';

export default function configureLaunchDarkly(config: LDExtensionConfiguration) {
const configureExtension: Disposable = registerCommand(CMD_LD_CONFIG, async () => {
Expand All @@ -16,6 +17,7 @@ export default function configureLaunchDarkly(config: LDExtensionConfiguration)
await config.getFlagStore().reload();
}
await config.getCtx().globalState.update('LDConfigured', true);
analytics.track('environment-configured');
window.withProgress(
{
location: ProgressLocation.Notification,
Expand Down
Loading
Loading