Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
12 changes: 12 additions & 0 deletions .changeset/vite-plugin-branch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
Comment thread
zeyadkhaled marked this conversation as resolved.
Outdated
"@osdk/vite-plugin-branch": minor
"@osdk/cli": patch
---

Add a Vite plugin that makes local development branch-aware.

On dev server start `branchPlugin()` sets `VITE_FOUNDRY_BRANCH_RID` to the checked out git branch name, so `@osdk/client` scopes objects, actions, and queries to the matching Foundry branch with no change to `createClient`. Previously a repository checked out on a branch locally read and wrote on the default branch unless the branch was threaded through by hand.

An existing value wins, whether it comes from a `.env` file, the shell, or CI, so the in-platform dev server and pull request previews keep supplying their own branch. Nothing is injected on `main`/`master`, on a detached HEAD, or outside a git repository, and the plugin is dev-server only so a local git branch name is never baked into a production build.

The branch CLI now shares the same git branch detection and normalization logic as the plugin. It also detects unborn branches in repositories with no commits; detached HEAD remains equivalent to no branch. Requires git >= 2.22.
2 changes: 2 additions & 0 deletions .lintstagedrc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const CSPELL_CMD = "cspell --quiet --no-must-find-files";
* shared root oxlint.config.ts.
*/
const OXC_PACKAGES = [
"shared.branch",
"shared.net.errors",
"shared.client.impl",
"shared.net.fetch",
Expand Down Expand Up @@ -87,6 +88,7 @@ const OXC_NESTED_CONFIG_PACKAGES = {
"tool.generate-with-mock-ontology":
"packages/tool.generate-with-mock-ontology/oxlint.config.ts",
"version-updater": "packages/version-updater/oxlint.config.ts",
"vite-plugin-branch": "packages/vite-plugin-branch/oxlint.config.ts",
"vite-plugin-oac": "packages/vite-plugin-oac/oxlint.config.ts",
"vite-plugin-superrepo": "packages/vite-plugin-superrepo/oxlint.config.ts",
"vite-plugin-status-reporter":
Expand Down
11 changes: 11 additions & 0 deletions .monorepolint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ const archetypeRules = archetypes(standardPackageRules, {
// 1-file src). The shipped scaffolding under each package's templates/ dir is
// ignored by oxlint + oxfmt and migrates separately.
"@osdk/create-app.react.beta.common",
"@osdk/shared.branch",
"@osdk/create-app.template.*",
"@osdk/create-widget.template.*",
],
Expand Down Expand Up @@ -295,6 +296,16 @@ const archetypeRules = archetypes(standardPackageRules, {
oxcConfig: "./oxlint.config.ts",
},
)
.addArchetype(
"bundled oxc migrated vite plugins with carve-outs",
["@osdk/vite-plugin-branch"],
{
...LIBRARY_RULES,
output: { browser: "bundle", cjs: "bundle", esm: "bundle" },
oxc: true,
oxcConfig: "./oxlint.config.ts",
},
)
// Same as "oxc migrated libraries with carve-outs" but additionally carries
// checkApi (API Extractor reports). These are the core published API-surface
// packages (@osdk/api is the core SDK type surface) that previously lived in
Expand Down
2 changes: 2 additions & 0 deletions dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"**/*.report.api.md",
// The shared.* packages below are formatted by oxfmt, not dprint (oxc migration, see oxfmt.config.ts)
"packages/shared.net.errors/",
"packages/shared.branch/",
"packages/shared.client.impl/",
"packages/shared.net.fetch/",
"packages/shared.net/",
Expand Down Expand Up @@ -81,6 +82,7 @@
"packages/tool.release/",
"packages/tool.generate-with-mock-ontology/",
"packages/version-updater/",
"packages/vite-plugin-branch/",
"packages/vite-plugin-code-workspace-preview/",
"packages/vite-plugin-oac/",
"packages/vite-plugin-superrepo/",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@osdk/monorepo.api-extractor": "workspace:~",
"@osdk/monorepo.tool.check-bundle": "workspace:~",
"@osdk/monorepo.tsconfig": "workspace:~",
"@osdk/shared.branch": "workspace:~",
"@types/archiver": "^6.0.3",
"@types/ngeohash": "^0.6.8",
"@types/node": "^18.19.124",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/branch/sync/syncCommand.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

import { promises as fs } from "node:fs";

import { getGitBranch as defaultGetGitBranch } from "@osdk/shared.branch";
import { consola } from "consola";

import { discoverOsdkPackages } from "../utils/discoverOsdkPackages.mjs";
import { getGitBranch as defaultGetGitBranch } from "../utils/getGitBranch.js";
import { resolveBranch } from "../utils/resolveBranch.js";
import { resolveSdkPackageVersions } from "../utils/resolveSdkPackageVersions.mjs";
import {
Expand Down
8 changes: 2 additions & 6 deletions packages/cli/src/commands/branch/utils/resolveBranch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

const NON_BRANCH = new Set(["main", "master", "HEAD"]);
import { normalizeGitBranch } from "@osdk/shared.branch";

/**
* Resolve the branch context: `argBranchName` wins; else `gitBranchName`, with
Expand All @@ -28,9 +28,5 @@ export function resolveBranch(
if (argBranch != null && argBranch !== "") {
return argBranch;
}
const gitBranch = gitBranchName?.trim();
if (gitBranch == null || gitBranch === "" || NON_BRANCH.has(gitBranch)) {
return undefined;
}
return gitBranch;
return normalizeGitBranch(gitBranchName);
}
1 change: 1 addition & 0 deletions packages/monorepo.tool.transpile/bin/transpile2.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ async function transpileWithTsup(format, target) {
"@osdk/foundry.functions",
"@osdk/shared.client",
"@osdk/shared.client2",
"@osdk/shared.branch",
"oauth4webapi",
"p-defer",
// create-app templates (private, must be bundled)
Expand Down
52 changes: 52 additions & 0 deletions packages/shared.branch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@osdk/shared.branch",
"private": true,
"version": "0.0.0",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/palantir/osdk-ts.git"
},
"exports": {
".": {
"import": {
"types": "./build/types/index.d.ts",
"default": "./build/esm/index.js"
},
"default": "./build/esm/index.js"
},
"./*": {
"import": {
"types": "./build/types/public/*.d.ts",
"default": "./build/esm/public/*.js"
},
"default": "./build/esm/public/*.js"
}
},
"scripts": {
"check-spelling": "cspell --quiet .",
"clean": "rm -rf lib dist types build tsconfig.tsbuildinfo",
"fix-lint": "oxlint -c ../../oxlint.config.ts --fix . && oxfmt -c ../../oxfmt.config.ts .",
"lint": "oxlint -c ../../oxlint.config.ts . && oxfmt -c ../../oxfmt.config.ts --check .",
"test": "vitest run",
"transpileEsm": "monorepo.tool.transpile -f esm -m normal -t node",
"transpileTypes": "monorepo.tool.transpile -f esm -m types -t node",
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"execa": "^9.6.0"
},
"devDependencies": {
"@osdk/monorepo.api-extractor": "workspace:~",
"@osdk/monorepo.tsconfig": "workspace:~",
"typescript": "~5.5.4"
},
"publishConfig": {
"access": "public"
},
"files": [
],
"module": "./build/esm/index.js",
"types": "./build/esm/index.d.ts",
"type": "module"
}
62 changes: 62 additions & 0 deletions packages/shared.branch/src/getGitBranch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { execFileSync } from "node:child_process";
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";

import { afterAll, describe, expect, it } from "vitest";

import { getGitBranch } from "./getGitBranch.js";

const tempDirs: string[] = [];

function makeTempDir(): string {
const dir = mkdtempSync(
path.join(realpathSync(tmpdir()), "osdk-git-branch-"),
);
tempDirs.push(dir);
return dir;
}

afterAll(() => {
for (const dir of tempDirs) {
rmSync(dir, { recursive: true, force: true });
}
});

describe(getGitBranch, () => {
it("reads the branch of a repository", async () => {
const dir = makeTempDir();
execFileSync("git", ["init", "--initial-branch", "zka/some-branch"], {
cwd: dir,
stdio: "ignore",
});

await expect(getGitBranch(dir)).resolves.toBe("zka/some-branch");
});

it("returns undefined outside a repository", async () => {
await expect(getGitBranch(makeTempDir())).resolves.toBeUndefined();
});

it("returns undefined for a directory that does not exist", async () => {
await expect(
getGitBranch(path.join(makeTempDir(), "nope")),
).resolves.toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,12 @@

import { execa } from "execa";

/**
* The current git branch (`git rev-parse --abbrev-ref HEAD`), or `undefined` if
* git fails. Detached HEAD returns the literal "HEAD".
*/
export async function getGitBranch(): Promise<string | undefined> {
/** The current git branch, or `undefined` if git fails. */
export async function getGitBranch(cwd?: string): Promise<string | undefined> {
Comment thread
zeyadkhaled marked this conversation as resolved.
try {
const { stdout } = await execa("git", [
"rev-parse",
"--abbrev-ref",
"HEAD",
]);
const { stdout } = await execa("git", ["branch", "--show-current"], {
Comment thread
zeyadkhaled marked this conversation as resolved.
cwd,
});
return stdout.trim();
} catch {
return undefined;
Expand Down
18 changes: 18 additions & 0 deletions packages/shared.branch/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export { getGitBranch } from "./getGitBranch.js";
export { normalizeGitBranch } from "./normalizeGitBranch.js";
48 changes: 48 additions & 0 deletions packages/shared.branch/src/normalizeGitBranch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { describe, expect, it } from "vitest";

import { normalizeGitBranch } from "./normalizeGitBranch.js";

describe("normalizeGitBranch", () => {
it("returns the branch when on a feature branch", () => {
expect(normalizeGitBranch("my-feature")).toBe("my-feature");
expect(normalizeGitBranch("zka/my-branch")).toBe("zka/my-branch");
});

it("returns undefined on main/master/detached", () => {
expect(normalizeGitBranch("main")).toBeUndefined();
expect(normalizeGitBranch("master")).toBeUndefined();
expect(normalizeGitBranch("HEAD")).toBeUndefined();
});

it("returns undefined when the branch is missing/empty", () => {
expect(normalizeGitBranch(undefined)).toBeUndefined();
expect(normalizeGitBranch("")).toBeUndefined();
expect(normalizeGitBranch(" ")).toBeUndefined();
});

it("trims whitespace", () => {
expect(normalizeGitBranch(" feature/x ")).toBe("feature/x");
expect(normalizeGitBranch(" main ")).toBeUndefined();
});

it("keeps a branch that merely contains a non-branch name", () => {
expect(normalizeGitBranch("zka/main-fix")).toBe("zka/main-fix");
expect(normalizeGitBranch("mainline")).toBe("mainline");
});
});
31 changes: 31 additions & 0 deletions packages/shared.branch/src/normalizeGitBranch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2026 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const NON_BRANCH: ReadonlySet<string> = new Set(["main", "master", "HEAD"]);

/**
* The Foundry branch a git branch corresponds to: `branch` trimmed, or
* `undefined` if it is blank or names no branch (`main`/`master`/detached HEAD).
*/
export function normalizeGitBranch(
branch: string | undefined,
): string | undefined {
const trimmed = branch?.trim();
if (trimmed == null || trimmed === "" || NON_BRANCH.has(trimmed)) {
return undefined;
}
return trimmed;
}
11 changes: 11 additions & 0 deletions packages/shared.branch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@osdk/monorepo.tsconfig/base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build/esm"
},
"include": [
"./src/**/*"
],
"references": []
}
Loading
Loading