mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-08 23:32:36 -05:00
* AH: replace agentSdks {url, sha256} with {urlTemplate}
product.agentSdks.<sdk> now ships {version, urlTemplate} instead of
{version, url, sha256}. The runtime substitutes {sdkTarget} into the
template per-launch via a new IAgentSdkPackage.currentSdkTarget()
hook — Claude appends -musl on musl Linux hosts (detected from Node's
process.report.header.glibcVersionRuntime, no subprocess), Codex
never does (statically musl-linked, single Linux SKU).
Why the shape change:
1. macOS Universal bundles ship arm64 + x64 binaries sharing one
product.json — a fixed per-platform {url, sha256} could only be
correct for one of the two halves. The template lets the same
bundle serve both.
2. The sha256 was belt-and-suspenders: product.json is covered by
product.checksums inside the signed app bundle, URLs are HTTPS
to a Microsoft-controlled CDN. The sha only guarded "trusted URL
string, tampered edge bytes" — a much harder attack than
tampering with product.json itself.
Downloader changes: sdkTarget joins the cache key path
(<userDataPath>/agent-host/sdk-cache/<pkg>/<sdkVersion>/<sdkTarget>/)
so Universal launches with different resolved targets get independent
caches. .complete sentinel content is now the source URL (debug-only;
the file's existence is the integrity signal).
isAvailable() now gates on both product config AND currentSdkTarget()
resolving, so the provider doesn't register on hosts with no SKU
(armhf) even if some future product.json carries an agentSdks block.
Tests: drop sha-mismatch + stale-cache-by-sha tests; add coverage for
{sdkTarget} substitution, separate-cache-dirs-for-different-targets
(the Universal motivating case), currentSdkTarget-undefined gating
isAvailable, and CodexSdkPackage.currentSdkTarget agreeing with the
existing codexPackageSuffix table.
Pairs with the build PR (#321012), which will be rebased to emit the
new shape once this lands. See build/agent-sdk/TODO.md on that branch.
* AH: simplify per-package SDK target resolution
Replaces per-package `currentSdkTarget()` (one method per SDK,
re-implementing the same platform/arch table modulo a musl branch)
with a single boolean `hasSeparateMuslLinuxPackage` on the package
descriptor and a shared `resolveSdkTarget(pkg, host)` in the
downloader. Claude sets it true; Codex sets it false. The supported-
platforms whitelist collapses from three copies (claudeSdkTarget,
codexPackageSuffix, build's getSdkTargetForBuild) to one runtime
resolver paired with the build helper.
Host injection: AgentSdkDownloader gains an optional leading
`ISdkTargetHost` constructor param (per project convention, non-DI
before DI). Production call sites pass `undefined` to derive from
`process`; tests pass synthetic hosts to exercise Universal launches
and musl Linux without touching `process`.
Other simplifications applied from review:
- `_cacheHit` was a one-line passthrough → inlined to
`_fileService.exists(sentinel)` at both call sites.
- `_pendingDownloads` key now uses `cacheDir` directly (already
unique per pkg/version/target) instead of allocating a parallel
`<pkg>/<version>/<target>` key string.
- `.complete` sentinel content is now empty — the file's existence
is the integrity signal, the cache dir path already encodes
`<pkg>/<version>/<sdkTarget>` for debugging.
- `detectLibc()` returns `LibcFamily | undefined` on non-Linux
instead of `'glibc'`-by-convention (drops consumer-specific
phrasing from the primitive).
- Test's `listLeftovers` recursive walker replaced with a direct
`readdir` of the known version dir (the only level where scratch
dirs can land).
- Tests collapse 4 direct `new AgentSdkDownloader(...)` blocks
through `makeDownloader(null, host)`.
- `IAgentSdkProductConfig` JSDoc trimmed to interface contract;
rationale lives in roadmap.md Phase 15.
- `_failureLatch` doc explains why per-id (not per-target) granularity
is intentional.
Tests: 25 passing (3 libc + 3 resolveSdkTarget + 13 downloader +
6 codex paths). New `resolveSdkTarget` suite covers the cross-product
of {claude, codex} × {linux glibc, linux musl, darwin, win32} that
previously lived as scattered table tests.
* AH: drop test-only host injection on AgentSdkDownloader
The previous commit added a constructor param to inject a synthetic
`(platform, arch, libc)` into the downloader so tests could exercise
Universal launches and musl Linux from any CI host. Production passed
`undefined` and the body fell back to a derived host — a test-only
ceremony in production code.
Restructured so the runtime stays clean:
- `resolveSdkTarget(pkg, host?)` keeps its optional `host` param,
defaulting to the real process. Cross-host coverage lives in
dedicated unit tests that call it directly.
- `AgentSdkDownloader` no longer takes a host. Both call sites
revert to `createInstance(AgentSdkDownloader)` with no extras.
- Integration suite `suiteSetup` skips on hosts the downloader
can't target (e.g. linux-armhf), and pins `hostSdkTarget` for
path assertions. The "two-host cache key" assertion becomes a
direct path check on the host's resolved target instead of an
artificial second-host download.
Tests: 23 passing (3 libc + 3 resolveSdkTarget unit + 11 downloader
integration + 6 codex paths).
* AH: address PR review — validate urlTemplate placeholders + honor backpressure
Two findings from #321078 review:
1. `format2()` silently leaves unknown placeholders untouched, so a
vscode-distro typo like `{sdkTaret}` would produce a 404 from the
CDN with no hint at the real cause. Add a `{...}` scan after
substitution that throws an actionable error pointing at the
suspect product.json field. Covered by a new test.
2. The hand-rolled `_fetch` pipe ignored `out.write()`'s return value,
so a slow disk (Windows AV scan, network home dir) could buffer
the entire 70-95MB tarball in memory. Pause the source stream on
write-buffer full, resume on drain. Can't use `stream/promises
.pipeline()` here because `IRequestContext.stream` is a
`VSBufferReadableStream`, not a Node Readable — the source's own
`pause()`/`resume()` is what we have to work with.
Cancellation test still passes; backpressure change is transparent to
the cancel teardown.
* AH: per-platform agent SDK build + CDN upload (#7885)
Per-platform VS Code build jobs now produce + upload the Claude and
Codex agent SDK tarballs to main.vscode-cdn.net and stamp the resulting
url/sha into `product.agentSdks` of their own packaged product.json.
The build step (`build/azure-pipelines/common/agent-sdk-produce.yml`)
runs inline in each existing platform job (darwin/linux/win32/alpine),
before the gulp packaging step. It always builds the tarballs. The
AzureCLI credential fetch and the CDN upload are gated on
`VSCODE_PUBLISH=true` — test pipeline runs leave the tarballs as a
pipeline artifact (`agent_sdk_<platform>_<arch>_tarballs`) for
inspection but don't touch the CDN, and ship product.json without
`agentSdks` (same shape as a local dev build).
The REH gulpfile only stamps `agentSdks` for `type === 'reh'`; REH-web
skips it because the agent host is node-only.
* AH: use npm.cmd on Windows in agent SDK build
`spawnSync('npm', ...)` fails on Windows because npm ships as a `.cmd`
shim and Node's child_process doesn't resolve PATHEXT without an
explicit suffix. The Windows pipeline jobs were dying with `exited
null` and no further context.
Also surface `result.error` so a future spawn-resolution failure shows
the actual ENOENT instead of a bare exit-code message.
* AH: pass shell:true when spawning npm.cmd on Windows
Node 20+ (CVE-2024-27980) refuses to spawn `.cmd`/`.bat` files without
`shell: true` and fails with `EINVAL`. The Windows pipeline jobs hit
this after the previous fix swapped `npm` for `npm.cmd`.
* AH: emit {version, urlTemplate} per the runtime shape change
Stacked on top of the runtime PR (tyler/agent-sdk-url-template). With
the runtime now consuming `{version, urlTemplate}` and substituting
`{sdkTarget}` per launch, the build pipeline emits the matching shape:
- `IAgentSdkResults[<sdk>]` drops `{url, sha256}` for
`{version, urlTemplate}`.
- `produce.ts` still uploads its platform's tarballs (idempotent
HEAD-then-skip in upload.ts is unchanged), but the results JSON
every job writes is identical per SDK — only the version differs.
That's the whole point: macOS Universal can ship one product.json
that covers both arm64 and x64 launches because the runtime
resolves {sdkTarget} per launch.
- New `buildCdnUrlTemplate(sdk, version)` mirrors `buildCdnUrl`'s
path but leaves `{sdkTarget}` as the format2 placeholder.
- README updated; TODO.md (the placeholder note left while the
runtime PR was pending) deleted.
Tarballs at the existing CDN paths (e.g.
`agent-sdk/claude/0.3.168/darwin-arm64.tgz`) remain valid and reachable
— no re-upload needed, just a re-stamp of product.json on the next
publish run.
* AH: address PR review on build/agent-sdk (Copilot)
Five comments from the build PR review:
1. common.ts header named drift-check.ts (deleted during simplification)
and missed produce.ts. Updated.
2. common.ts "single source of truth is package.json optionalDependencies"
was aspirational — getSdkTargetForBuild is a hardcoded table. Reframed
the comment to describe what we actually do (hardcoded table kept in
lockstep by convention) and why (no runtime npm metadata lookup).
3. package.ts header said the library form is consumed by gulpfile
packaging tasks — actually called from produce.ts as its own pipeline
step. Updated.
4. + 5. isCliInvocation() in package.ts and upload.ts compared
import.meta.url to a manually constructed `file://${process.argv[1]}`,
which breaks on Windows (drive letters URL-encoded, spaces escaped).
Repo already established the cleaner `import.meta.filename ===
process.argv[1]` pattern (see build/npm/installStateHash.ts:143).
Pure portability fix — only affects the dev-mode CLIs, the production
pipeline calls these as library functions.
* AH: per-SDK agents/<sdk>/{package.json,package-lock.json} for byte determinism
Build 447090 surfaced sha drift across pipeline runs: same exact-pinned
SDK version, but transitive deps unlocked (`npm install
--no-package-lock`) → different bytes → CDN HEAD-then-fail rejected
the re-upload. Determinism is load-bearing for the security model
(content-addressed CDN URLs, no runtime sha verification — the bytes
at a given URL must be stable).
Fix: ship pinned lockfiles per SDK and use `npm ci`. New layout:
build/agent-sdk/agents/
claude/
package.json # exact one dep: @anthropic-ai/claude-agent-sdk@0.3.168
package-lock.json # full transitive graph
codex/
package.json # exact one dep: @openai/codex@0.134.0
package-lock.json
Bonus: the folder set IS the SDK list. Drops the hardcoded
`SDKS: readonly Sdk[]` and `PACKAGE_NAME: Record<Sdk, string>` from
common.ts; replaced with `getSdks()` (discovers from `agents/`) and
`getAgentMeta(sdk)` (reads from the agent's own package.json). Adding
a new SDK is now one folder + lockfile gen + commit.
Verified reproducible locally: two back-to-back runs of `package.ts
--sdk=codex --target=darwin-arm64` produce byte-identical tarballs.
NOTE: existing CDN blobs from build 446990 carry the old drifted
shas. The next publish will fail HEAD-then-skip against them. Need to
delete `agent-sdk/{claude,codex}/{0.3.168,0.134.0}/*.tgz` from the
vscodeweb storage account's $web container before re-publishing, or
the upload step will refuse with "blob already present with DIFFERENT
sha256".
* AH: bump pinned SDK versions to sidestep stale CDN blobs
claude 0.3.168 → 0.3.169 (one point release; 0.3.170/172/173/174/175
all exist upstream, sticking to the next bump for risk minimisation).
codex 0.134.0 → 0.135.0 (next stable; 0.135-0.139 are all stable
releases, picking the immediate successor).
Bumping versions changes the CDN URL path (`agent-sdk/<sdk>/<version>/...`)
so the next publish lands at fresh, never-uploaded blob URLs. Avoids
having to delete the drift-shaped blobs from build 446990 that would
otherwise trip HEAD-then-fail.
Bumped both the per-SDK `agents/<sdk>/package.json` (the build's pin)
and repo-root `package.json` devDeps (the runtime's type-import pin)
in lockstep, with all four lockfiles regenerated. Local reproducibility
re-verified: two back-to-back runs of `package.ts --sdk=codex
--target=darwin-arm64` produce byte-identical tarballs at the new pin.
Runtime typecheck clean — no API changes to either SDK in these point
releases.
* AH: stub usage_EXPERIMENTAL on test Query fakes (SDK 0.3.169)
Claude SDK 0.3.169 added `usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET`
as a required method on `Query`. Three test files implement the
interface as fakes (FakeQuery, ImmediatelyDoneQuery, RoundTripQuery)
and broke the type-check on tsgo.
Stubbed each as `throw 'not modeled'` matching the existing pattern
for every other method these fakes don't exercise. The field name
makes it clear the SDK doesn't expect anyone to rely on it yet, so a
"not modeled" stub is honest.
* AH: authenticate npmrc before agent SDK `npm ci`
Build 447232 hit E401 from the private npm mirror: the platform job's
existing "Setup NPM Authentication" step is gated on the node_modules
cache being a miss (it lives in the cache-warming path), so on a cache
hit the user's ~/.npmrc has no auth token, and our agent-sdk `npm ci`
inherits the global registry override + missing auth → E401.
Fix: add an always-on auth step at the top of agent-sdk-produce.yml.
Captures the user's npmrc path, runs npmAuthenticate@0 against it. Now
runs independent of the node_modules cache state.
The previous npm install --no-package-lock path tolerated this because
it fell back to anonymous resolution against npmjs.org. `npm ci`
strictly resolves through the configured (private) registry, which
needs auth. The lockfile + private mirror combination is what we want
for supply-chain auditing — the fix is to make sure auth is set up
unconditionally rather than bypass the mirror.
Also reverts a brief stop-along-the-way that added
`--registry=https://registry.npmjs.org/` to the npm ci call — wrong
direction (would bypass the supply-chain mirror).
* AH: create ~/.npmrc with `npm config set` before authenticating
npmAuthenticate@0 errored on cache-hit runs: the .npmrc path returned
by `npm config get userconfig` is just where npm WOULD write — the
file doesn't exist until something actually writes to it. The platform
job's "Setup NPM" step creates it via `npm config set registry`, but
is skipped on cache hits.
Mirror that pattern in our prep step: run `npm config set registry`
ourselves (idempotent — rewrites the same value the existing config
already has on cache misses) so npmAuthenticate@0 has a real file to
edit.
* AH: move agent SDK step ahead of Download Copilot VSIX
Was: install-builtin → VSIX-background → Compile → … → VSIX-attach → agent-sdk → Build client.
Now: install-builtin → agent-sdk → VSIX-background → Compile → … → VSIX-attach → Build client.
No data dependency between the agent SDK step and the VSIX download
(or Compile, for that matter — agent SDK uses its own scratch dir,
its own npmrc, doesn't read node_modules or anything from out-build).
Benefit: fail-fast. The agent SDK step previously ran after Compile +
both VSIX wait points, so a CDN auth failure or a sha-mismatch
would only surface ~10 minutes into the job. Moving it earlier
catches those failures in seconds, before any heavy work runs.
Applied consistently across darwin/linux/win32/alpine (linux still
gated on `ne(VSCODE_ARCH, 'armhf')`).
253 lines
9.6 KiB
TypeScript
253 lines
9.6 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Shared helpers for the per-platform agent SDK build pipeline. Called
|
|
* from `package.ts`, `upload.ts`, and `produce.ts`, plus the gulpfiles'
|
|
* `packageTask` (via `readAgentSdkResults`) so each VS Code build can
|
|
* stamp its own `product.agentSdks.<sdk>` into the per-platform
|
|
* `product.json` at packaging time.
|
|
*
|
|
* Source of truth for the SDK list and version pins:
|
|
* `build/agent-sdk/agents/<sdk>/{package.json,package-lock.json}`.
|
|
* Each subdirectory under `agents/` is one SDK. The folder name is the
|
|
* SDK id (the key under `product.agentSdks`); the `package.json` names
|
|
* exactly one npm dependency (the SDK's own package) and its exact
|
|
* version; the `package-lock.json` pins the full transitive graph for
|
|
* byte-deterministic `npm ci`. Add an SDK by adding a folder, run
|
|
* `npm install` inside it once to generate the lockfile, commit both.
|
|
*
|
|
* `getSdkTargetForBuild()` hard-codes the
|
|
* `(vscodePlatform, arch, sdk) → sdkTarget` table; the SDK's own npm
|
|
* `optionalDependencies` are the canonical SKU set, and the table is
|
|
* kept in lockstep with it by convention.
|
|
*/
|
|
|
|
import * as crypto from 'crypto';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const THIS_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
/** Root of the per-SDK `{package.json, package-lock.json}` directories. */
|
|
export const AGENTS_DIR = path.join(THIS_DIR, 'agents');
|
|
|
|
/**
|
|
* SDK identifier — the folder name under `agents/`, also the key under
|
|
* `product.agentSdks` and the path segment in the CDN URL. Open string
|
|
* type rather than a closed union so adding a new SDK is one folder, no
|
|
* compile-time list change required.
|
|
*/
|
|
export type Sdk = string;
|
|
|
|
let _sdksCache: readonly Sdk[] | undefined;
|
|
|
|
/**
|
|
* All SDKs the pipeline knows about, in stable sort order. Discovered
|
|
* at load time by listing `agents/`. Cached because the set never
|
|
* changes mid-process.
|
|
*/
|
|
export function getSdks(): readonly Sdk[] {
|
|
if (_sdksCache) {
|
|
return _sdksCache;
|
|
}
|
|
const entries = fs.readdirSync(AGENTS_DIR, { withFileTypes: true });
|
|
_sdksCache = entries
|
|
.filter(e => e.isDirectory())
|
|
.map(e => e.name)
|
|
.sort();
|
|
return _sdksCache;
|
|
}
|
|
|
|
/** Path to a given SDK's agents/ subdirectory (the one with package.json). */
|
|
export function getAgentDir(sdk: Sdk): string {
|
|
const dir = path.join(AGENTS_DIR, sdk);
|
|
if (!fs.existsSync(dir)) {
|
|
throw new Error(`Unknown SDK '${sdk}': no directory at ${dir}. Add a folder under build/agent-sdk/agents/ with a package.json + package-lock.json.`);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
interface IAgentPackageJson {
|
|
readonly dependencies?: Readonly<Record<string, string>>;
|
|
}
|
|
|
|
let _agentMetaCache: Map<Sdk, { name: string; version: string }> | undefined;
|
|
|
|
/**
|
|
* Returns the npm package name and pinned version this SDK ships. Read
|
|
* from `agents/<sdk>/package.json`'s single dependency.
|
|
*
|
|
* The agent's `package.json` MUST declare exactly one dependency (the SDK's
|
|
* own npm package) at an exact version — no `^` / `~` ranges. Ranges
|
|
* would let `npm install` resolve different versions across runs, which
|
|
* the CDN's HEAD-then-fail upload rejects.
|
|
*/
|
|
export function getAgentMeta(sdk: Sdk): { name: string; version: string } {
|
|
if (!_agentMetaCache) {
|
|
_agentMetaCache = new Map();
|
|
}
|
|
const cached = _agentMetaCache.get(sdk);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const pkgPath = path.join(getAgentDir(sdk), 'package.json');
|
|
const json = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as IAgentPackageJson;
|
|
const deps = json.dependencies ?? {};
|
|
const entries = Object.entries(deps);
|
|
if (entries.length !== 1) {
|
|
throw new Error(`Expected exactly one dependency in ${pkgPath}, found ${entries.length}: ${entries.map(([k]) => k).join(', ')}`);
|
|
}
|
|
const [name, version] = entries[0];
|
|
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
throw new Error(`Refusing to use ${name}@${version} from ${pkgPath}: must be an exact version (no ^ or ~ ranges)`);
|
|
}
|
|
const meta = { name, version };
|
|
_agentMetaCache.set(sdk, meta);
|
|
return meta;
|
|
}
|
|
|
|
/** Convenience shortcut for `getAgentMeta(sdk).version`. */
|
|
export function getSdkVersion(sdk: Sdk): string {
|
|
return getAgentMeta(sdk).version;
|
|
}
|
|
|
|
/** Strict subset of VS Code build platforms — the SDK pipeline only knows
|
|
* about platforms it can target. `web` is excluded (no SDK on web). */
|
|
export type VscodeBuildPlatform = 'darwin' | 'linux' | 'alpine' | 'win32';
|
|
|
|
/** Runtime whitelist mirroring `VscodeBuildPlatform`. Used by CLI guards so
|
|
* a typo like `--vscode-platform=lnux` fails loud instead of silently
|
|
* emitting an empty results file. */
|
|
export const KNOWN_VSCODE_PLATFORMS: ReadonlySet<VscodeBuildPlatform> = new Set([
|
|
'darwin', 'linux', 'alpine', 'win32',
|
|
]);
|
|
|
|
/** Strict subset of architectures. `armhf` is excluded — no SDK ships armhf. */
|
|
export type VscodeBuildArch = 'x64' | 'arm64';
|
|
|
|
/**
|
|
* Resolves the SDK's npm `optionalDependencies` suffix for a particular VS
|
|
* Code build. Returns undefined when the VS Code build's `(platform, arch)`
|
|
* has no compatible SDK (e.g. armhf, web, any combination we don't ship).
|
|
*
|
|
* - Claude ships `linux-{x64,arm64}-musl` packages separately from the glibc
|
|
* variants. Alpine REH builds get the `-musl` variant; regular Linux gets
|
|
* the plain `linux-*`.
|
|
* - Codex's Linux binaries are statically musl-linked and ship under a
|
|
* single `linux-*` SKU that runs on both glibc and musl hosts. Alpine
|
|
* REH gets the same `linux-*` package as regular Linux.
|
|
*
|
|
* REH uses two encodings for Alpine x64: the modern `{platform: 'alpine',
|
|
* arch: 'x64'}` and the legacy `{platform: 'linux', arch: 'alpine'}`. Both
|
|
* are accepted.
|
|
*/
|
|
export function getSdkTargetForBuild(
|
|
vscodePlatform: string,
|
|
arch: string,
|
|
sdk: Sdk,
|
|
): string | undefined {
|
|
// Normalize the legacy Alpine x64 encoding into the modern one.
|
|
if (vscodePlatform === 'linux' && arch === 'alpine') {
|
|
vscodePlatform = 'alpine';
|
|
arch = 'x64';
|
|
}
|
|
if (arch !== 'x64' && arch !== 'arm64') {
|
|
return undefined;
|
|
}
|
|
switch (vscodePlatform) {
|
|
case 'darwin': return `darwin-${arch}`;
|
|
case 'win32': return `win32-${arch}`;
|
|
case 'linux': return `linux-${arch}`;
|
|
case 'alpine':
|
|
return sdk === 'claude' ? `linux-${arch}-musl` : `linux-${arch}`;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the CDN URL the per-platform `product.agentSdks.<sdk>.url` points at.
|
|
* Content-addressed under `agent-sdk/<sdk>/<version>/<target>.tgz`. Matches
|
|
* the upload path written by `upload.ts`.
|
|
*/
|
|
export function buildCdnUrl(sdk: Sdk, sdkVersion: string, sdkTarget: string): string {
|
|
return `https://main.vscode-cdn.net/agent-sdk/${sdk}/${sdkVersion}/${sdkTarget}.tgz`;
|
|
}
|
|
|
|
/**
|
|
* Builds the `format2`-style URL template stamped into
|
|
* `product.agentSdks.<sdk>.urlTemplate`. The runtime substitutes
|
|
* `{sdkTarget}` per launch via `resolveSdkTarget` in
|
|
* `src/vs/platform/agentHost/node/agentSdkDownloader.ts`. Matches the
|
|
* upload path written by `upload.ts` with `{sdkTarget}` in place of the
|
|
* concrete target suffix.
|
|
*/
|
|
export function buildCdnUrlTemplate(sdk: Sdk, sdkVersion: string): string {
|
|
return `https://main.vscode-cdn.net/agent-sdk/${sdk}/${sdkVersion}/{sdkTarget}.tgz`;
|
|
}
|
|
|
|
/** Streams `filePath` into a sha256 hasher. Avoids reading the whole file
|
|
* into memory — agent SDK tarballs are 50-100MB. */
|
|
export function sha256OfFile(filePath: string): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const hash = crypto.createHash('sha256');
|
|
const stream = fs.createReadStream(filePath);
|
|
stream.on('error', reject);
|
|
stream.on('data', chunk => hash.update(chunk));
|
|
stream.on('end', () => resolve(hash.digest('hex')));
|
|
});
|
|
}
|
|
|
|
/** Parses `--key=value` CLI flags into a Map. Shared by the script-mode
|
|
* entry points in `package.ts`, `upload.ts`, and `produce.ts`. */
|
|
export function parseFlags(argv: readonly string[]): Map<string, string> {
|
|
const flags = new Map<string, string>();
|
|
for (const arg of argv) {
|
|
const m = /^--([a-zA-Z-]+)=(.+)$/.exec(arg);
|
|
if (m) {
|
|
flags.set(m[1], m[2]);
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
/**
|
|
* Per-SDK product.json entry, written by `produce.ts` and read by the
|
|
* gulpfiles' `packageTask`. Shape matches `IAgentSdkProductConfig` in
|
|
* `src/vs/base/common/product.ts` so the values can be dropped straight
|
|
* into `product.agentSdks`.
|
|
*
|
|
* Every platform job emits the SAME `{version, urlTemplate}` per SDK —
|
|
* the `{sdkTarget}` placeholder is resolved at runtime per launch (see
|
|
* `resolveSdkTarget` in `agentSdkDownloader.ts`). This is what lets a
|
|
* macOS Universal bundle share one `product.json` across arm64+x64.
|
|
*/
|
|
export interface IAgentSdkResults {
|
|
[packageId: string]: {
|
|
readonly version: string;
|
|
readonly urlTemplate: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reads the per-platform agent-SDK results file written by `produce.ts`.
|
|
* Returns `{}` when `AGENT_SDK_RESULTS_FILE` is unset or the file doesn't
|
|
* exist — that's the local-dev path (no agent SDKs produced, ship
|
|
* product.json without `agentSdks`, providers don't register).
|
|
*/
|
|
export function readAgentSdkResults(): IAgentSdkResults {
|
|
const filePath = process.env.AGENT_SDK_RESULTS_FILE;
|
|
if (!filePath || !fs.existsSync(filePath)) {
|
|
return {};
|
|
}
|
|
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as unknown;
|
|
if (typeof parsed !== 'object' || parsed === null) {
|
|
throw new Error(`AGENT_SDK_RESULTS_FILE at ${filePath} is not a JSON object`);
|
|
}
|
|
return parsed as IAgentSdkResults;
|
|
}
|