#!/usr/bin/env node const fs = require("fs"); const os = require("os"); const path = require("path"); const assert = require("assert"); const extension = require("../vscode-extension/extension"); const packageJson = require("../vscode-extension/package.json"); const extensionSource = fs.readFileSync( path.join(__dirname, "../vscode-extension/extension.js"), "utf8" ); const repo = path.resolve(__dirname, ".."); assert.strictEqual(packageJson.main, "./extension.js"); assert(fs.existsSync(path.join(__dirname, "../vscode-extension", packageJson.main))); assert.deepStrictEqual(packageJson.dependencies || {}, {}); const root = fs.mkdtempSync(path.join(os.tmpdir(), "clusterflux-vscode-")); fs.mkdirSync(path.join(root, "envs/linux"), { recursive: true }); fs.writeFileSync(path.join(root, "envs/linux/Containerfile"), "FROM alpine\n"); fs.mkdirSync(path.join(root, ".clusterflux"), { recursive: true }); fs.writeFileSync( extension.clusterfluxViewStatePath(root), JSON.stringify({ nodes: [{ id: "node-linux", status: "online", capabilities: "Command RootlessPodman" }], processes: [{ id: "vp-build", status: "running", entry: "build" }], logs: [{ task: "compile-linux", message: "stdout=12 stderr=0", bytes: 12 }], artifacts: [{ path: "/vfs/artifacts/app.tar.zst", status: "retained", size: 12 }], inspector: [{ label: "debug", value: "attached" }] }) ); const envs = extension.discoverEnvironmentNames(root); assert.deepStrictEqual(envs, ["linux"]); const diagnostics = extension.diagnoseEnvReferences( 'let _ = env!("linux"); let _ = env!("windows");', envs ); assert.strictEqual(diagnostics.length, 1); assert.strictEqual(diagnostics[0].name, "windows"); assert.match(diagnostics[0].message, /envs\/windows\/Containerfile/); const inspectCommand = extension.bundleInspectCommand(root, "/repo"); assert.strictEqual(inspectCommand.command, "cargo"); assert.deepStrictEqual(inspectCommand.args.slice(0, 8), [ "run", "-q", "-p", "clusterflux-cli", "--bin", "clusterflux", "--", "bundle" ]); assert(inspectCommand.args.includes("inspect")); assert(inspectCommand.args.includes("--project")); assert(inspectCommand.args.includes(root)); assert(inspectCommand.args.includes("--json")); const refreshed = extension.refreshBundleBeforeLaunch(root, "/repo", (command, args, options) => { assert.strictEqual(command, "cargo"); assert(args.includes("bundle")); assert.strictEqual(options.cwd, "/repo"); return { status: 0, stdout: JSON.stringify({ metadata: { identity: "sha256:abc" } }), stderr: "" }; }); assert.strictEqual(refreshed.metadata.identity, "sha256:abc"); const launch = extension.resolveClusterfluxDebugConfiguration( { uri: { fsPath: root } }, {} ); assert.deepStrictEqual(launch, { name: "Clusterflux: Launch Virtual Process", type: "clusterflux", request: "launch", entry: "build", project: root, runtimeBackend: "local-services" }); assert.strictEqual( extension.clusterfluxProcessId("/workspace/app", "build"), "vp-e4bd6ef50539" ); assert.strictEqual( extension.existingProcessRelationship( { process: "vp-e4bd6ef50539", state: "running" }, "vp-e4bd6ef50539" ), "same_launch_target" ); assert.strictEqual( extension.existingProcessRelationship( { process: "vp-other", state: "running" }, "vp-e4bd6ef50539" ), "different_launch_target" ); const liveProcesses = extension.loadLiveProcesses(root, "/repo", (_command, args) => { assert.deepStrictEqual(args.slice(-3), ["process", "list", "--json"]); return { status: 0, stdout: JSON.stringify({ coordinator: "https://clusterflux.michelpaulissen.com", tenant: "tenant-live", project: "project-live", user: "user-live", processes: [{ process: "vp-live", state: "cancelling" }] }), stderr: "" }; }); assert.deepStrictEqual(liveProcesses.processes, [ { process: "vp-live", state: "cancelling" } ]); assert.strictEqual(liveProcesses.project, "project-live"); const adapter = extension.debugAdapterExecutableSpec(root, repo); assert.strictEqual(adapter.command, "cargo"); assert.deepStrictEqual(adapter.args, [ "run", "-q", "-p", "clusterflux-dap", "--bin", "clusterflux-debug-dap" ]); assert.deepStrictEqual(adapter.options, { cwd: repo }); const releasedAdapterPath = path.join(root, process.platform === "win32" ? "clusterflux-debug-dap.exe" : "clusterflux-debug-dap"); fs.writeFileSync(releasedAdapterPath, ""); const releasedAdapter = extension.debugAdapterExecutableSpec(root, repo); assert.strictEqual(releasedAdapter.command, releasedAdapterPath); assert.deepStrictEqual(releasedAdapter.args, []); assert.deepStrictEqual(releasedAdapter.options, { cwd: root }); assert.throws( () => extension.refreshBundleBeforeLaunch(root, "/repo", () => ({ status: 1, stdout: "", stderr: "missing environment linux" })), /missing environment linux/ ); assert( packageJson.contributes.viewsContainers.activitybar.some( (container) => container.id === "clusterflux" && container.title === "Clusterflux" ), "package.json must contribute a Clusterflux activity-bar container" ); assert( fs.existsSync(path.join(__dirname, "../vscode-extension/resources/clusterflux.svg")), "Clusterflux activity-bar icon must exist" ); const packageViewIds = packageJson.contributes.views.clusterflux.map((view) => view.id).sort(); const descriptorViewIds = extension.clusterfluxViewDescriptors().map((view) => view.id).sort(); assert.deepStrictEqual(descriptorViewIds, [ "clusterflux.artifacts", "clusterflux.inspector", "clusterflux.logs", "clusterflux.nodes", "clusterflux.processes" ]); assert.deepStrictEqual(packageViewIds, descriptorViewIds); for (const viewId of descriptorViewIds) { const items = extension.clusterfluxViewItems( extension.loadClusterfluxViewState(root), viewId ); assert( items.length > 0 && !items[0].label.startsWith("No "), `${viewId} should render state-backed items` ); } assert.deepStrictEqual( extension.clusterfluxViewItems(extension.loadClusterfluxViewState(root), "clusterflux.nodes")[0], { label: "node-linux", description: "online Command RootlessPodman" } ); assert( packageJson.contributes.debuggers.some((debuggerContribution) => debuggerContribution.type === "clusterflux"), "package.json must contribute the clusterflux debugger type" ); for (const viewId of descriptorViewIds) { assert( packageJson.activationEvents.includes(`onView:${viewId}`), `${viewId} should activate the extension when opened` ); } const launchProperties = packageJson.contributes.debuggers[0].configurationAttributes.launch.properties; assert.strictEqual(launchProperties.runtimeBackend.default, "local-services"); assert(launchProperties.runtimeBackend.enum.includes("live-services")); assert.strictEqual(launchProperties.coordinatorEndpoint.default, undefined); assert.strictEqual(launchProperties.tenant, undefined); assert.strictEqual(launchProperties.projectId, undefined); assert.strictEqual(launchProperties.actorUser, undefined); assert( packageJson.contributes.debuggers[0].configurationAttributes.attach, "package.json must contribute an attach configuration" ); for (const command of [ "clusterflux.refreshProcesses", "clusterflux.process.attach", "clusterflux.process.cancel", "clusterflux.process.abort" ]) { assert( packageJson.contributes.commands.some((entry) => entry.command === command), `${command} must be contributed` ); } assert.match(extensionSource, /registerDebugAdapterDescriptorFactory\("clusterflux"/); assert.match(extensionSource, /clusterflux-debug-dap/); assert.match(extensionSource, /\.clusterflux\/views\.json/); fs.rmSync(root, { recursive: true, force: true }); console.log("VS Code extension smoke passed");