Editor extension: tested in Cursor and VS Code including Publish to Diffinery; docs screenshots; marketplace notes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ3j7radTeNPDXuJ936W3K
8 files changed+99−19
Files (8)
- apps/diffinery/docs/images/editor-clone-picker.webp+0−0
- apps/diffinery/docs/images/editor-consent.webp+0−0
- apps/diffinery/docs/images/editor-repositories.webp+0−0
- apps/diffinery/docs/repositories/clone-in-your-editor.md+5−5
- apps/diffinery/scripts/docs-screenshots.mjs+10−4
- apps/diffinery/test/e2e/editor-smoke.mjs+45−9
- apps/vscode/PUBLISHING.md+38−0
- apps/vscode/src/git.ts+1−1
addedapps/diffinery/docs/images/editor-clone-picker.webp+0−0View file
Binary file not shown.
addedapps/diffinery/docs/images/editor-consent.webp+0−0View file
Binary file not shown.
addedapps/diffinery/docs/images/editor-repositories.webp+0−0View file
Binary file not shown.
modifiedapps/diffinery/docs/repositories/clone-in-your-editor.md+5−5View file
| @@ -36,14 +36,14 @@ | ||
| 36 | 36 | |
| 37 | 37 | ## 2. Clone a repository |
| 38 | 38 | |
| 39 | -1. Open the Command Palette (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>, or <kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> on a Mac) and run **Git: Clone**, or choose **Clone Repository** on the editor's welcome page. | |
| 39 | +1. Open the Command Palette (`Ctrl+Shift+P`, or `Cmd+Shift+P` on a Mac) and run **Git: Clone**, or choose **Clone Repository** on the editor's welcome page. | |
| 40 | 40 | 2. Choose **Clone from Diffinery**. |
| 41 | 41 | 3. The first time, the editor asks to sign in with Diffinery. Choose **Allow**: your browser opens Diffinery's consent page. |
| 42 | -4. On the consent page, check that it says **Diffinery extension for VS Code and Cursor**, published by diffinery.com, keep **Act as** set to you and **Access** set to **Write**, and choose **Connect**. Your browser asks to open the editor; allow it. | |
| 42 | +4. On the consent page, check that it says **Diffinery extension for VS Code and Cursor**, published by diffinery.com, keep **Act as** set to you and **Access** set to **Write**, and choose **Connect**. The browser shows **You're signed in to Diffinery**; close the tab and go back to the editor. | |
| 43 | 43 | 5. Back in the editor, pick a repository. The list shows your repositories and your organizations', most recently pushed first. Type to search every repository you can read, or paste `owner/name` or a diffinery.com link. |
| 44 | 44 | 6. Choose a folder. The editor clones the repository and opens it. |
| 45 | 45 | |
| 46 | - | |
| 46 | + | |
| 47 | 47 | |
| 48 | 48 |  |
| 49 | 49 | |
| @@ -106,11 +106,11 @@ | ||
| 106 | 106 | | What you see | What to do | |
| 107 | 107 | | --- | --- | |
| 108 | 108 | | No **Clone from Diffinery** in the picker | Check the extension is installed and enabled in the Extensions view, then reload the window (**Developer: Reload Window**). It needs the editor's built-in Git support (`git.enabled`). | |
| 109 | -| The browser opens but nothing happens after **Connect** | The browser has to hand the link back to the editor. Allow it when the browser asks to open the editor, and make sure the editor is still running. Your editor's link is shown under **Sends you back to** on the consent page. | | |
| 109 | +| The browser opens but the editor doesn't continue after **Connect** | The browser hands the answer to the editor on a one-time address on your computer (`127.0.0.1`). Make sure the editor is still running and the sign-in notification is still showing, then try again. A firewall or proxy that blocks local addresses stops it; use **Diffinery: Sign In with an Access Token** instead. | | |
| 110 | 110 | | "This app isn't registered with Diffinery" on the consent page | `diffinery.serverUrl` points at a server that doesn't have the editor sign-in yet. Use **Diffinery: Sign In with an Access Token**, or update the server. | |
| 111 | 111 | | Git still asks for a password | Check `diffinery.gitAuthentication` is on, and that the remote URL is on diffinery.com (`git remote -v`). An old saved password for diffinery.com in your keychain can win; remove it, and the extension's renewals keep the new one current. | |
| 112 | 112 | | `Authentication failed` once after the editor was closed for hours | Git used a saved token that had expired; it has now forgotten it. Run the command again, from the editor. | |
| 113 | -| An editor that isn't listed (another VS Code fork) | Sign-in still works: the editor receives the browser's answer on a one-time address on your computer instead of its own link. | | |
| 113 | +| An editor that isn't listed (another VS Code fork) | It works the same way: the extension needs only the editor's built-in Git support. | | |
| 114 | 114 | |
| 115 | 115 | ## For AI agents reading this page |
| 116 | 116 |
modifiedapps/diffinery/scripts/docs-screenshots.mjs+10−4View file
| @@ -4,7 +4,9 @@ | ||
| 4 | 4 | // |
| 5 | 5 | // pnpm --filter @diffinery/app build && node scripts/docs-screenshots.mjs |
| 6 | 6 | // |
| 7 | -// Writes docs/images/consent-claude-code.webp, consent-codex.webp and connected-apps.webp. | |
| 7 | +// Writes docs/images/consent-claude-code.webp, consent-codex.webp, editor-consent.webp and connected-apps.webp; name | |
| 8 | +// some of them as arguments to write only those. The editor extension's own pictures (the clone picker) come from | |
| 9 | +// test/e2e/editor-smoke.mjs. | |
| 8 | 10 | import { spawn, execFileSync } from "node:child_process"; |
| 9 | 11 | import { createHash, randomBytes } from "node:crypto"; |
| 10 | 12 | import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; |
| @@ -26,6 +28,7 @@ | ||
| 26 | 28 | }); |
| 27 | 29 | // The browser talks to the Worker here; the pages themselves say diffinery.com. |
| 28 | 30 | const ORIGIN = `http://localhost:${port}`; |
| 31 | +const only = process.argv.slice(2); | |
| 29 | 32 | const work = mkdtempSync(join(tmpdir(), "diffinery-shots-")); |
| 30 | 33 | const persist = join(work, "state"); |
| 31 | 34 | const SESSION = "docs-screenshots-session-0123456789abcdef"; |
| @@ -79,6 +82,7 @@ | ||
| 79 | 82 | |
| 80 | 83 | /** Encodes the screenshot as WebP in the browser itself (no image library needed). */ |
| 81 | 84 | async function save(name, png) { |
| 85 | + if (only.length && !only.includes(name)) return; | |
| 82 | 86 | const data = await page.evaluate(async (b64) => { |
| 83 | 87 | const img = new Image(); |
| 84 | 88 | img.src = `data:image/png;base64,${b64}`; |
| @@ -94,13 +98,13 @@ | ||
| 94 | 98 | } |
| 95 | 99 | |
| 96 | 100 | // Connects an app identified by its client metadata document, as Claude Code and Codex do. |
| 97 | - async function connect(clientId, redirect, file) { | |
| 101 | + async function connect(clientId, redirect, file, { actAs = "ada-bot", scope = "read write", resource = `${PUBLIC}/mcp` } = {}) { | |
| 98 | 102 | const verifier = b64url(randomBytes(32)); |
| 99 | 103 | const challenge = b64url(createHash("sha256").update(verifier).digest()); |
| 100 | - const q = new URLSearchParams({ response_type: "code", client_id: clientId, redirect_uri: redirect, state: b64url(randomBytes(8)), code_challenge: challenge, code_challenge_method: "S256", scope: "read write", resource: `${PUBLIC}/mcp` }); | |
| 104 | + const q = new URLSearchParams({ response_type: "code", client_id: clientId, redirect_uri: redirect, state: b64url(randomBytes(8)), code_challenge: challenge, code_challenge_method: "S256", scope, ...(resource ? { resource } : {}) }); | |
| 101 | 105 | await page.goto(`${ORIGIN}/oauth/authorize?${q}`); |
| 102 | 106 | await page.waitForLoadState("networkidle"); |
| 103 | - await page.getByText("ada-bot", { exact: false }).first().click(); | |
| 107 | + if (actAs) await page.getByText(actAs, { exact: false }).first().click(); | |
| 104 | 108 | await sleep(300); |
| 105 | 109 | const card = page.locator("main .box").first(); |
| 106 | 110 | await save(file, await card.screenshot()); |
| @@ -130,6 +134,8 @@ | ||
| 130 | 134 | |
| 131 | 135 | await connect("https://claude.ai/oauth/claude-code-client-metadata", "http://localhost:53682/callback", "consent-claude-code.webp"); |
| 132 | 136 | await connect("https://chatgpt.com/oauth/codex/client.json", "http://127.0.0.1:1455/callback", "consent-codex.webp"); |
| 137 | + // The editor extension, as it signs in from VS Code or Cursor: as the person, for Git, without the MCP resource. | |
| 138 | + await connect(`${PUBLIC}/oauth/clients/editor.json`, "http://127.0.0.1:52817/callback", "editor-consent.webp", { actAs: null, scope: "write", resource: null }); | |
| 133 | 139 | |
| 134 | 140 | await page.goto(`${ORIGIN}/settings/tokens`); |
| 135 | 141 | await page.waitForLoadState("networkidle"); |
modifiedapps/diffinery/test/e2e/editor-smoke.mjs+45−9View file
| @@ -8,7 +8,9 @@ | ||
| 8 | 8 | // VSIX=/tmp/vsix/diffinery-0.1.0.vsix xvfb-run -a pnpm --filter @diffinery/app exec node test/e2e/editor-smoke.mjs |
| 9 | 9 | // |
| 10 | 10 | // EDITOR_BIN is the editor's Electron binary, EDITOR_CLI its command-line tool (Cursor: usr/share/cursor/cursor and |
| 11 | -// usr/share/cursor/bin/cursor in the extracted AppImage). SHOTS=dir saves screenshots of each step. | |
| 11 | +// usr/share/cursor/bin/cursor in the extracted AppImage; Cursor also needs --skip-onboarding, which this passes last: VS Code would read the next argument as its value). SHOTS=dir | |
| 12 | +// saves screenshots of each step, plus the clone picker on its own (*-box.png, for the docs); THEME sets the color | |
| 13 | +// theme, such as "Default Light Modern", and SCALE the device scale factor. | |
| 12 | 14 | import { spawn, execFileSync } from "node:child_process"; |
| 13 | 15 | import { createHash, randomBytes } from "node:crypto"; |
| 14 | 16 | import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, chmodSync } from "node:fs"; |
| @@ -50,6 +52,7 @@ | ||
| 50 | 52 | throw new Error(`timed out waiting for ${what}\n--- wrangler log tail ---\n${devLog.slice(-3000)}`); |
| 51 | 53 | } |
| 52 | 54 | const shot = async (page, name) => SHOTS && (mkdirSync(SHOTS, { recursive: true }), await page.screenshot({ path: join(SHOTS, `${name}.png`) })); |
| 55 | +const shotPicker = async (page, name) => SHOTS && (await page.locator(".quick-input-widget:not([style*='display: none'])").screenshot({ path: join(SHOTS, `${name}-box.png`) })); | |
| 53 | 56 | const wrangler = (...args) => execFileSync("pnpm", ["exec", "wrangler", ...args], { cwd: APP, stdio: "pipe", env: { ...process.env, CI: "1" } }); |
| 54 | 57 | |
| 55 | 58 | // ---------------------------------------------------------------- a local Diffinery with ada and two repositories |
| @@ -84,8 +87,8 @@ | ||
| 84 | 87 | console.log("worker up at", base); |
| 85 | 88 | |
| 86 | 89 | const api = (method, path, body) => fetch(`${base}/api/v3${path}`, { method, headers: { authorization: `Bearer ${ADA_TOKEN}`, "content-type": "application/json" }, body: body && JSON.stringify(body) }).then(async (r) => (r.ok ? r.json() : Promise.reject(new Error(`${method} ${path}: ${r.status} ${await r.text()}`)))); |
| 87 | -await api("POST", "/user/repos", { name: "hello-editor", private: true, description: "Private repository the editor clones" }); | |
| 88 | -await api("POST", "/user/repos", { name: "notes", private: false, description: "Another one" }); | |
| 90 | +await api("POST", "/user/repos", { name: "hello-editor", private: true, description: "The analytical engine, in TypeScript" }); | |
| 91 | +await api("POST", "/user/repos", { name: "notes", private: false, description: "Notes on the engine, and drafts of the next paper" }); | |
| 89 | 92 | const seed = join(work, "seed"); |
| 90 | 93 | mkdirSync(seed); |
| 91 | 94 | const git = (cwd, ...args) => execFileSync("git", args, { cwd, stdio: "pipe", env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } }).toString(); |
| @@ -101,6 +104,14 @@ | ||
| 101 | 104 | const userData = join(work, "user-data"); |
| 102 | 105 | const exts = join(work, "extensions"); |
| 103 | 106 | mkdirSync(clones, { recursive: true }); |
| 107 | +// Opened on a folder: Cursor shows its agent window instead of the editor until you log in to Cursor, unless a folder is open. | |
| 108 | +// The folder the editor opens: a local repository with a commit and no remote, for "Publish to Diffinery". | |
| 109 | +const workspace = join(home, "difference-engine"); | |
| 110 | +mkdirSync(workspace, { recursive: true }); | |
| 111 | +git(workspace, "init", "-q", "-b", "main"); | |
| 112 | +writeFileSync(join(workspace, "engine.ts"), "export const add = (a: number, b: number) => a + b;\n"); | |
| 113 | +git(workspace, "add", "."); | |
| 114 | +git(workspace, "-c", "user.name=Ada", "-c", "user.email=ada@example.com", "commit", "-qm", "Start the engine"); | |
| 104 | 115 | mkdirSync(join(userData, "User"), { recursive: true }); |
| 105 | 116 | // Git saves the password the editor gave it, as a keychain would; the extension keeps that copy current. |
| 106 | 117 | writeFileSync(join(home, ".gitconfig"), `[credential]\n\thelper = store\n[user]\n\tname = Ada\n\temail = ada@example.com\n`); |
| @@ -110,7 +121,7 @@ | ||
| 110 | 121 | "diffinery.serverUrl": base, |
| 111 | 122 | "diffinery.checkForUpdates": false, |
| 112 | 123 | "files.simpleDialog.enable": true, |
| 113 | - "git.openAfterClone": "always", | |
| 124 | + "git.openAfterClone": "alwaysNewWindow", | |
| 114 | 125 | "git.defaultCloneDirectory": clones, |
| 115 | 126 | "security.workspace.trust.enabled": false, |
| 116 | 127 | "workbench.startupEditor": "none", |
| @@ -119,6 +130,7 @@ | ||
| 119 | 130 | "telemetry.telemetryLevel": "off", |
| 120 | 131 | "update.mode": "none", |
| 121 | 132 | "extensions.autoCheckUpdates": false, |
| 133 | + ...(process.env.THEME ? { "workbench.colorTheme": process.env.THEME } : {}), | |
| 122 | 134 | }), |
| 123 | 135 | ); |
| 124 | 136 | // The browser the editor opens: record the URL, as a person's browser would receive it. |
| @@ -131,11 +143,11 @@ | ||
| 131 | 143 | chmodSync(browserScript, 0o755); |
| 132 | 144 | const env = { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}`, BROWSER: browserScript, XDG_CURRENT_DESKTOP: "", DONT_PROMPT_WSL_INSTALL: "1" }; |
| 133 | 145 | // No OS keyring in a test container: the editor keeps secrets with its own weaker encryption instead. |
| 134 | -const common = ["--user-data-dir", userData, "--extensions-dir", exts, "--no-sandbox", "--disable-gpu", "--password-store=basic"]; | |
| 146 | +const common = ["--user-data-dir", userData, "--extensions-dir", exts, "--no-sandbox", "--disable-gpu", "--password-store=basic", ...(process.env.SCALE ? [`--force-device-scale-factor=${process.env.SCALE}`] : [])]; | |
| 135 | 147 | execFileSync(CLI, [...common, "--install-extension", VSIX, "--force"], { env, stdio: "pipe", timeout: 120_000 }); |
| 136 | 148 | console.log("extension installed"); |
| 137 | 149 | |
| 138 | -const app = await _electron.launch({ executablePath: EDITOR, args: [...common, "--disable-workspace-trust", "--skip-welcome", "--skip-release-notes", "--new-window"], env, timeout: 120_000 }); | |
| 150 | +const app = await _electron.launch({ executablePath: EDITOR, args: [workspace, ...common, "--disable-workspace-trust", "--skip-welcome", "--skip-release-notes", "--skip-onboarding"], env, timeout: 120_000 }); | |
| 139 | 151 | const win = await app.firstWindow(); |
| 140 | 152 | // On a failure, a screenshot of the editor as it was. |
| 141 | 153 | async function fail(e) { |
| @@ -151,7 +163,7 @@ | ||
| 151 | 163 | await sleep(4000); |
| 152 | 164 | const palette = async (cmd) => { |
| 153 | 165 | await win.keyboard.press("Control+Shift+P"); |
| 154 | - await win.waitForSelector(".quick-input-widget:not([style*='display: none']) input"); | |
| 166 | + await win.waitForSelector(".quick-input-widget:not([style*='display: none']) .quick-input-box input"); | |
| 155 | 167 | await win.keyboard.type(cmd); |
| 156 | 168 | await sleep(500); |
| 157 | 169 | await win.keyboard.press("Enter"); |
| @@ -171,6 +183,7 @@ | ||
| 171 | 183 | }), |
| 172 | 184 | ); |
| 173 | 185 | await shot(win, "1-clone-picker"); |
| 186 | +await shotPicker(win, "1-clone-picker"); | |
| 174 | 187 | await win.keyboard.type("Clone from Diffinery"); |
| 175 | 188 | await sleep(300); |
| 176 | 189 | await win.keyboard.press("Enter"); |
| @@ -211,6 +224,7 @@ | ||
| 211 | 224 | await until("ada's repositories in the picker", async () => (await quickItems()).some((t) => t.includes("ada/hello-editor")), 60_000); |
| 212 | 225 | console.log("repositories:", await quickItems()); |
| 213 | 226 | await shot(win, "4-repositories"); |
| 227 | +await shotPicker(win, "4-repositories"); | |
| 214 | 228 | await win.keyboard.type("hello"); |
| 215 | 229 | await until("the search result", async () => { |
| 216 | 230 | const items = await quickItems(); |
| @@ -236,10 +250,32 @@ | ||
| 236 | 250 | assert.match(stored, new RegExp(`^http://ada:dfy_[^@]+@localhost%3a${port}$|^http://ada:dfy_[^@]+@localhost:${port}$`, "m")); |
| 237 | 251 | assert.ok(!stored.includes(ADA_TOKEN), "the saved credential is the editor's own token"); |
| 238 | 252 | console.log("git credential saved for ada"); |
| 253 | +await sleep(4000); | |
| 254 | +const cloneWin = app.windows().at(-1); | |
| 255 | +await cloneWin.waitForSelector(".monaco-workbench", { timeout: 60_000 }); | |
| 239 | 256 | await sleep(3000); |
| 240 | -await shot(win, "7-opened"); | |
| 257 | +await shot(cloneWin, "7-opened"); | |
| 241 | 258 | |
| 242 | -// 5. Signing out removes Git's saved copy and disconnects the editor on the server. | |
| 259 | +// 5. Publishing the open folder: Git's "Publish Branch" offers Diffinery, which creates the repository and pushes. | |
| 260 | +await palette("Git: Publish Branch"); | |
| 261 | +await until("Publish to Diffinery in the picker", async () => (await quickItems()).some((t) => t.includes("Publish to Diffinery")), 30_000); | |
| 262 | +console.log("publish picker:", await quickItems()); | |
| 263 | +await win.keyboard.type("Diffinery"); | |
| 264 | +await sleep(300); | |
| 265 | +await win.keyboard.press("Enter"); | |
| 266 | +await until("the repository name box", async () => (await win.inputValue(".quick-input-widget:not([style*='display: none']) .quick-input-box input").catch(() => "")) === "difference-engine", 30_000); | |
| 267 | +await shot(win, "8-publish-name"); | |
| 268 | +await win.keyboard.press("Enter"); | |
| 269 | +await until("the visibility choice", async () => (await quickItems()).some((t) => t.includes("Private")), 20_000); | |
| 270 | +await win.keyboard.press("Enter"); | |
| 271 | +const published = await until("the pushed branch on Diffinery", async () => (await api("GET", "/repos/ada/difference-engine/branches").catch(() => null))?.find((b) => b.name === "main"), 60_000); | |
| 272 | +assert.equal(published.commit.sha, git(workspace, "rev-parse", "HEAD").trim()); | |
| 273 | +assert.equal((await api("GET", "/repos/ada/difference-engine")).private, true); | |
| 274 | +assert.match(git(workspace, "remote", "get-url", "origin"), /\/ada\/difference-engine\.git/); | |
| 275 | +await shot(win, "9-published"); | |
| 276 | +console.log("published: ada/difference-engine, private, main pushed"); | |
| 277 | + | |
| 278 | +// 6. Signing out removes Git's saved copy and disconnects the editor on the server. | |
| 243 | 279 | await palette("Diffinery: Sign Out"); |
| 244 | 280 | await until("the saved credential to go", async () => !readFileSync(join(home, ".git-credentials"), "utf8").includes("ada:"), 20_000); |
| 245 | 281 | const grants = JSON.parse(wrangler("d1", "execute", "diffinery-dev", "--local", "-e", "dev", "--persist-to", persist, "--json", "--command", "SELECT revoked_at FROM oauth_grants").toString())[0].results; |
addedapps/vscode/PUBLISHING.md+38−0View file
| @@ -1,0 +1,38 @@ | ||
| 1 | +# Publishing to the VS Code Marketplace and Open VSX | |
| 2 | + | |
| 3 | +Not done yet: for now the extension is served only from diffinery.com (`/-/vscode/diffinery.vsix`, built by | |
| 4 | +`apps/diffinery/scripts/build-docs.mjs`). These are the steps for when Diffinery goes public. Cursor, Windsurf and | |
| 5 | +VSCodium install from Open VSX; VS Code installs from the Visual Studio Marketplace, so both are needed. | |
| 6 | + | |
| 7 | +## Before the first upload | |
| 8 | + | |
| 9 | +1. Add a `LICENSE` file and a `license` field in `package.json` (both registries show it; `vsce` warns without it). | |
| 10 | +2. Create the `codician` publisher on the Marketplace (https://marketplace.visualstudio.com/manage, needs an Azure | |
| 11 | + DevOps organization for the personal access token) and the `codician` namespace on Open VSX | |
| 12 | + (`npx ovsx create-namespace codician`, after signing the Eclipse publisher agreement). The extension id | |
| 13 | + `codician.diffinery` is what the server's OAuth client (`EDITOR_EXTENSION_ID` in `apps/diffinery/src/auth/oauth.ts`) | |
| 14 | + and the docs use, so keep it. | |
| 15 | +3. Marketplace builds update through the marketplace: ship them with `diffinery.checkForUpdates` defaulting to | |
| 16 | + `false`, or have `src/updates.ts` skip the check when the extension was installed from a gallery, so the two | |
| 17 | + update paths don't fight. | |
| 18 | + | |
| 19 | +## Listing | |
| 20 | + | |
| 21 | +| Field | Value | | |
| 22 | +| --- | --- | | |
| 23 | +| Name | Diffinery | | |
| 24 | +| Short description | `description` in `package.json` | | |
| 25 | +| Categories | SCM Providers, Other | | |
| 26 | +| Icon | `media/icon.png` (128×128) | | |
| 27 | +| Banner | `galleryBanner` in `package.json` | | |
| 28 | +| Details page | `README.md` (relative image links are rewritten to the repository) | | |
| 29 | +| Changelog | `CHANGELOG.md` | | |
| 30 | +| Q&A / issues | `bugs.url` in `package.json` | | |
| 31 | + | |
| 32 | +## Each release | |
| 33 | + | |
| 34 | +1. Bump `version` in `package.json` and add a `CHANGELOG.md` entry. | |
| 35 | +2. `node scripts/package.mjs --out dist` builds `dist/diffinery-<version>.vsix`, the same file diffinery.com serves. | |
| 36 | +3. `npx @vscode/vsce publish --packagePath dist/diffinery-<version>.vsix` and | |
| 37 | + `npx ovsx publish dist/diffinery-<version>.vsix`, with the tokens from steps 2 above in `VSCE_PAT` and `OVSX_PAT`. | |
| 38 | +4. Deploying Diffinery keeps diffinery.com serving the same version for people who installed from there. |
modifiedapps/vscode/src/git.ts+1−1View file
| @@ -76,7 +76,7 @@ | ||
| 76 | 76 | } |
| 77 | 77 | const [me, orgs] = await Promise.all([api.user(), api.orgs().catch(() => [])]); |
| 78 | 78 | const owners = [ |
| 79 | - { label: me.login, description: me.type === "Bot" ? "the agent's owner's account" : "your account", owner: null as string | null }, | |
| 79 | + { label: me.login, description: "your account", owner: null as string | null }, | |
| 80 | 80 | ...orgs.map((o) => ({ label: o.login, description: "organization", owner: o.login as string | null })), |
| 81 | 81 | ]; |
| 82 | 82 | const owner = owners.length === 1 ? owners[0] : await vscode.window.showQuickPick(owners, { title: "Publish to Diffinery", placeHolder: "Owner of the new repository" }); |