VSCode Cuenv Extension: Integrate Tasks And Env Variables

by Rajiv Sharma 58 views

Hey guys! πŸ‘‹ We're diving deep into the exciting project of creating an official VSCode extension for cuenv. This extension aims to bring the power of cuenv directly into your VSCode workflow, offering feature parity with the Direnv VSCode extension and adding some killer developer experience enhancements. Think Env panels, Tasks panels, and CodeLens – cool, right? 😎

Scope: What We're Building πŸ’ͺ

Our goal is to make managing environment variables and tasks defined in your env.cue files a breeze. Here’s what the VSCode extension will include:

  • Auto-detection: Automatically detect env.cue files in your workspace folders (yes, multi-root support!).
  • Environment Loading: Load and apply environment variables using cuenv.
  • Status Display: Show the status of your environment (loaded, pending reload, error, not found).
  • Env and Tasks Panels: Dedicated side bar views for environment variables and tasks.
  • CodeLens Integration: "Run" links above each task definition in your env.cue files.
  • Task Execution: Run tasks in the integrated terminal with the environment applied.
  • Configuration: Basic settings for binary path, auto-load toggle, and masking patterns.
  • Secret Handling: Optional masking of sensitive environment variables.
  • Graceful Degradation: Handle cases where the cuenv binary is unavailable.

Out of Scope: What We're NOT Building (Yet!) 🚫

To keep things focused, we're excluding these features for now:

  • Language server features like syntax highlighting, IntelliSense, and LSP (the CUE team owns these).
  • Debug adapter integration (maybe in the future!).
  • Telemetry (future and optional).
  • Problem matcher enrichment (future).
  • Persisted environment caching (future).

Key Definitions πŸ€“

Let's get some terms straight:

  • env.cue: This is the root file where you define your environment variables and tasks.
  • Task: A named executable unit defined within your env.cue file (more on this in "Assumptions").
  • Environment Load: The process of executing cuenv to produce a set of key/value pairs that are applied to new terminals.

Assumptions: Let's Validate! πŸ€”

Before we go too far, we need to make sure these assumptions hold true:

  1. cuenv can output environment variables as machine-readable JSON (e.g., cuenv export --json).
  2. cuenv can list tasks and their metadata in JSON (e.g., cuenv tasks --json).
  3. Tasks are defined under a top-level field tasks: { <name>: { command: string, (optional) args: [...], (optional) description: string } } (we'll adjust if the schema is different).

If any of these aren't supported by the CLI, we'll create follow-up issues to add:

  • export --json
  • tasks --json
  • run <taskName> (already present?) or tasks run <taskName>

We'll also ensure consistent exit codes (0 for success, non-zero for errors) and proper error messages on stderr.

High-Level Architecture πŸ—οΈ

We're structuring the extension with these key components:

Per Workspace Folder Controllers:

  • EnvironmentManager
    • load(), reload(), detectChanges(): These methods manage the loading and reloading of environment variables, as well as detecting changes in the env.cue file.
    • Watches env.cue (debounced): This ensures that changes to the env.cue file are monitored, but not too frequently, to prevent performance issues.
    • Emits events: loaded, error, pendingReload: These events signal the state of the environment loading process.
  • TaskManager
    • fetchTasks(): Retrieves the list of tasks defined in the env.cue file.
    • runTask(name): Executes a specific task.
    • Emits: taskStarted, taskFinished, taskError: These events provide feedback on the execution of tasks.

Shared Services:

  • CLIAdapter: Executes cuenv with timeouts and parses JSON output.
  • ConfigurationService: Reads VSCode settings (e.g., cuenv.*).
  • StatusBarService: Updates the status item in the VSCode status bar per active folder.
  • View Providers: EnvTreeDataProvider, TasksTreeDataProvider: These provide the data for the Env and Tasks panels.
  • CodeLensProvider: Adds "Run Task" CodeLenses in env.cue files.
  • Logger: Wraps the VSCode OutputChannel "cuenv" for logging.

User-Facing Features (Detailed) ✨

Let's dive into the features you'll actually use:

1. Auto Environment Load πŸš€

  • On extension activation OR when a folder is added:
    • Search for env.cue at the root. (Future: Allow configurable patterns.)
    • If found and auto-load is enabled, run cuenv export --json.
    • Store the last loaded hash (hash of file contents) to detect changes.
  • Acceptance Criteria:
    • Opening a workspace with env.cue triggers a load (visible status bar change).
    • Errors (missing binary, parse errors) show in the output channel and status bar.

2. Status Bar Item 🚦

  • States (icon + text):
    • Loaded (βœ“)
    • Pending Reload (β†Ί)
    • Error (⚠)
    • Binary Not Found (β›”)
    • Disabled (⏹ if auto-load disabled)
  • Clicking the item opens a quick pick:
    • Reload Environment
    • Open cuenv Output
    • Toggle Auto Load
    • Reveal env.cue
  • Acceptance: Each state should be reachable under simulated conditions.

3. Commands (Command Palette) ⌨️

We'll add these commands to the Command Palette:

  • cuenv.reload: This command allows users to manually reload the environment variables.
  • cuenv.viewOutput: Opens the cuenv Output channel for viewing logs and debugging information.
  • cuenv.toggleAutoLoad: Toggles the auto-load feature on or off.
  • cuenv.runTask (internal, invoked with argument): An internal command to run a specific task.
  • cuenv.refreshEnvPanel: Refreshes the Env panel to display the latest environment variables.
  • cuenv.refreshTasksPanel: Refreshes the Tasks panel to display the latest tasks.

4. Env Panel (Tree View) 🌳

  • Displays VARIABLE = value (value masked if it matches mask patterns).
  • Top-level toolbar:
    • Refresh: Updates the environment variables displayed in the panel.
    • Toggle Masking (if masking feature is implemented in Milestone 3): Toggles the masking of sensitive values.
    • Filter input (use when pattern typed to narrow): Filters the displayed variables based on a typed pattern.
  • Right-click variable:
    • Copy Name: Copies the name of the selected variable.
    • Copy Value (unmasked copy): Copies the unmasked value of the selected variable.
  • Acceptance:
    • Refresh after reload updates values: Ensures that the panel updates correctly after a reload.
    • Mask applies based on name regex patterns (e.g., .*SECRET.*, .*TOKEN.* by default configurable): Verifies that masking is applied based on configured regex patterns.

5. Tasks Panel πŸ“

  • Lists tasks with:
    • Label: task name
    • Description (if provided)
    • Run action (β–Ά)
    • Context menu: Run, Run in New Terminal, Reveal Definition
  • On Run:
    • Opens (or reuses) terminal named cuenv: <folder> (default) OR new terminal if configured.
    • Executes: cuenv run <taskName> (adjust if CLI differs).
  • Acceptance:
    • Running task shows the command in the terminal.
    • Errors propagate to the Output channel.
    • Reveal Definition opens env.cue and selects the line range where the task is defined.

6. CodeLens in env.cue πŸ’‘

  • Appears above each task block definition.
  • Text: Run Task <name>
  • Executes the same run command as the panel.
  • Acceptance: Adding/removing a task updates CodeLens after file save.

7. Configuration (Settings) βš™οΈ

  • cuenv.executablePath (string, default: cuenv): Specifies the path to the cuenv executable.
  • cuenv.autoLoad.enabled (boolean, default: true): Enables or disables auto-loading of the environment.
  • cuenv.env.maskPatterns (string[], default: ["(?i)(secret|token|password|key)"]): Defines regex patterns for masking sensitive environment variables.
  • cuenv.tasks.terminal.strategy (enum: "shared" | "new", default: shared): Determines whether tasks run in a shared or new terminal.
  • cuenv.watch.debounceMs (number, default: 300): Sets the debounce time for file watcher events.
  • Acceptance: Changing settings triggers the appropriate behavior without requiring a reload (where practical).

8. Reload / Change Detection πŸ”„

  • A file watcher on env.cue sets the state to Pending Reload if contents have changed since the last load.
  • Provides a prompt (notification) with actions: Reload Now, Dismiss.
  • Acceptance: Editing the file triggers the pending state within the debounce interval.

9. Secrets Masking (Milestone 3) 🀫

  • Mask value display with β€’β€’β€’β€’β€’ (length preserved or constant) unless the user toggles β€œReveal” per variable (temporary until view refresh).
  • Acceptance: Copy Value copies the real value even if masked.

Error Handling Guidelines ⚠️

  • Missing Binary: Show an actionable message: cuenv binary not found. Set cuenv.executablePath or install from <link>.
  • JSON Parse Error: Show truncated stderr and suggest running the CLI manually.
  • Task Run Failure: Show the exit code; keep the terminal open.

Security Considerations πŸ›‘οΈ

  • Never log full masked values.
  • Provide a setting to disable masking entirely.
  • Avoid evaluating arbitrary codeβ€”only call the cuenv CLI.

Performance Considerations ⚑

  • Debounce reload events.
  • Cache the last successful environment JSON in memory; no disk persistence in MVP.
  • Limit tree refresh to diff-based update (optional optimization; full rebuild acceptable initially).

Milestones & Step-by-Step Tasks πŸͺœ

Let's break this down into manageable milestones:

Milestone 1: Foundation (Direnv Parity Core) πŸ—οΈ

  1. Scaffolding

    • Create package.json with activationEvents: onStartupFinished, onCommand:cuenv.reload, onFileSystem for env.cue.
    • Add a basic extension.ts entry point.
  2. Output Channel + Logger

  3. Configuration Definitions (contributes.configuration)

  4. CLIAdapter (execFile / spawn wrapper)

  5. EnvironmentManager (single-root first)

  6. StatusBarService with minimal states (Loaded, Error, Not Found)

  7. Commands: reload, viewOutput, toggleAutoLoad

  8. Auto-load on startup if env.cue is present

    Acceptance Tests:

    • Rename cuenv binary path to invalid -> status shows Not Found.
    • Modify env.cue and manually reload -> updated variables logged.

Milestone 2: Env & Tasks UI + Task Execution 🎨

  1. EnvTreeDataProvider using cached environment JSON

  2. TasksTreeDataProvider parsing tasks via CLI

  3. TaskManager + runTask

  4. Terminal management (shared terminal)

  5. CodeLensProvider for tasks

  6. Reveal Definition logic (TextDocument + regex or parse help)

  7. Add context menu + actions

    Acceptance:

    • Tasks appear; running them produces output.
    • CodeLens appears/disappears when adding/removing a task after save.

Milestone 3: Multi-root, Change Detection, Masking πŸ“‚

  1. Support multiple EnvironmentManager / TaskManager keyed by folder.

  2. Update status bar to reflect the active editor’s folder context.

  3. Watch env.cue -> pending reload state & notification.

  4. Masking logic & toggle.

    Acceptance:

    • Two workspace folders each load independently.
    • Editing env.cue in one folder updates only that folder’s state.

Milestone 4: Polish & Optional Enhancements ✨

  1. Problem matcher integration (optional; parse common error lines).

  2. Settings refinement (debounceMs functional).

  3. Edge cases (rapid edits, failing reload then recovering).

  4. Documentation (README + screenshots).

    Acceptance:

    • Simulated errors appear in the Problems panel (if implemented).
    • README instructions verified end-to-end.

Nice-to-Have (Defer Unless Time) 🎁

  • command: cuenv.dumpToEnvFile (writes .env preview)
  • Telemetry opt-in
  • Setting: cuenv.env.showDiff (display changed vars since last load)

Required Follow-Up Issues (If CLI Lacks Features) πŸ›

  • Add cuenv export --json command
  • Add cuenv tasks --json command
  • Ensure cuenv run <taskName> returns structured stderr on failure

Testing / QA Plan πŸ§ͺ

Manual Scenarios:

  • Fresh install with valid env.cue -> auto-load success.
  • Remove cuenv from PATH -> Not Found state.
  • Change env.cue variable -> pending reload state appears.
  • Task fails (exit 1) -> error visible in terminal + status stays Loaded (environment unchanged).
  • Multi-root: one folder with env.cue, one without -> only one shows active status.
  • Masking: variable named API_SECRET masked; NON_SECRET not masked.

Automated Tests (Recommended):

  • Use VSCode Extension Test Runner (Mocha).
  • Mock CLIAdapter to return fixture JSON.
  • Test EnvironmentManager state transitions.
  • Test TaskManager runTask invocation.

Data Structures (Example) πŸ—‚οΈ

Environment JSON (expected from cuenv export --json):

{
  "variables": {
    "API_URL": "https://service",
    "API_SECRET": "s3cr3t"
  }
}

Tasks JSON (expected from cuenv tasks --json):

{
  "tasks": [
    { "name": "build", "command": "go build ./...", "description": "Compile project" },
    { "name": "test", "command": "go test ./...", "description": "Run tests" }
  ]
}

File Outline (Proposed) πŸ“‚

src/
  extension.ts
  services/
    cliAdapter.ts
    environmentManager.ts
    taskManager.ts
    statusBarService.ts
    configurationService.ts
    logger.ts
  views/
    envTree.ts
    tasksTree.ts
  lenses/
    taskCodeLens.ts
  types/
    environment.ts
    task.ts
  util/
    hashing.ts
    masking.ts
test/
  environmentManager.test.ts
  taskManager.test.ts

Pseudo-Code Snippets πŸ“

Environment Load:

const result = await cli.exportEnv(folder);
envCache[folder] = result.variables;
events.emit('loaded', folder);

Tasks Fetch:

const tasks = await cli.listTasks(folder);
tasksCache[folder] = tasks;
tasksTree.refresh();

CodeLens Provider:

provideCodeLenses(doc) {
  if (!isEnvCue(doc)) return [];
  const tasks = detectTaskBlocks(doc); // regex fallback
  return tasks.map(t => new CodeLens(t.range, { command: 'cuenv.runTask', title: `Run Task ${t.name}`, arguments: [t.name, doc.uri] }));
}

Terminal Execution:

runTask(name) {
  const term = getOrCreateTerminal(folder);
  term.show();
  term.sendText(`${cliPath} run ${name}`);
}

Acceptance Criteria Summary (All Milestones) βœ…

  • Opening workspace β†’ environment loaded automatically (if enabled).
  • The status bar accurately reflects each lifecycle state.
  • The Env panel shows all variables; masking works per patterns.
  • The Tasks panel + CodeLens both run tasks identically.
  • Multi-root independent management confirmed.
  • Errors are discoverable via the Output channel without crashing the extension.

Documentation To Produce (Before Closing Issue) πŸ“„

  • README section: Installation, Features, Settings, Task Execution, Troubleshooting.
  • GIF or screenshots: Env panel, Tasks panel, CodeLens run.
  • CONTRIBUTING notes on adding new states or commands.

Sign-Off Checklist βœ…

  • [ ] CLI JSON commands available or follow-up issues created
  • [ ] MVP (Milestone 1) merged
  • [ ] UI panels functional
  • [ ] CodeLens functional
  • [ ] Multi-root tested
  • [ ] Masking verified
  • [ ] README updated
  • [ ] Manual QA scenarios executed
  • [ ] Issue acceptance criteria fully met

Next Action πŸš€

Intern: Start with Milestone 1 tasks exactly in order; report blockers if CLI JSON output not available.

Let me know if you’d like this split into multiple GitHub issues or need a trimmed-down MVP version!