Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 2 additions & 5 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,8 @@ if ! grep -qE '(^rust/)|(^Cargo\.toml$)|(^Cargo\.lock$)|(^\.githooks/)|(^\.githu
fi

if grep -qE '\.(rs)$|(^|/)Cargo\.(toml|lock)$' "${changed_files}"; then
echo "pre-push: Rust source changes detected — running cross-target check…"
if ! bash scripts/check-targets.sh; then
echo "pre-push: cross-target check failed (cfg issue?). Push aborted."
exit 1
fi
echo "pre-push: Rust source changes detected — skipping optional local cross-target check."
echo "pre-push: run 'npm run check:targets' manually if you have cross-compilers installed."
fi

echo "pre-push: running rust verify…"
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ jobs:
const fs = require("fs");
const { execSync } = require("node:child_process");
const { bump, formatVersion, parseVersion, recommendReleaseBump } = require("./scripts/versioning");
const { readWorkspaceVersion } = require("./scripts/version-utils");

const currentVersion = require("./package.json").version;
const currentVersion = readWorkspaceVersion(process.cwd());
const requestedBump = process.env.REQUESTED_BUMP;
const outputPath = process.env.GITHUB_OUTPUT;
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
Expand Down Expand Up @@ -143,7 +144,7 @@ jobs:
run: |
git checkout -b "${BRANCH_NAME}"
npm run "bump:${SELECTED_BUMP}"
actual_version="$(node -p "require('./package.json').version")"
actual_version="$(node -e "process.stdout.write(require('./scripts/version-utils').readWorkspaceVersion(process.cwd()))")"
if [ "${actual_version}" != "${NEXT_VERSION}" ]; then
echo "Expected version ${NEXT_VERSION}, got ${actual_version}" >&2
exit 1
Expand All @@ -158,9 +159,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add package.json package-lock.json packages/win32-x64/package.json \
rust/crates/shared/Cargo.toml rust/crates/host/Cargo.toml \
rust/crates/graphql/Cargo.toml rust/crates/tabctl/Cargo.toml rust/Cargo.lock
git add package.json package-lock.json packages/win32-x64/package.json rust/Cargo.toml rust/Cargo.lock
git commit -m "${PR_TITLE}"
git push -u origin "${BRANCH_NAME}"

Expand Down
52 changes: 31 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,21 @@ jobs:
shell: bash
run: |
tag='${{ needs.resolve-tag.outputs.tag }}'
version="$(node -p "require('./package.json').version")"
version="$(node -e "process.stdout.write(require('./scripts/version-utils').readWorkspaceVersion(process.cwd()))")"
expected_tag="v${version}"
if [ "$tag" != "$expected_tag" ]; then
echo "Release tag ($tag) must match package version tag ($expected_tag)." >&2
echo "Release tag ($tag) must match workspace version tag ($expected_tag)." >&2
exit 1
fi
node - <<'NODE'
const fs = require("fs");
const path = require("path");
const { readCargoPackageVersion, readJson, readWorkspaceVersion } = require("./scripts/version-utils");
const root = process.cwd();
const packageJson = require(path.join(root, "package.json"));
const workspaceVersion = readWorkspaceVersion(root);
const packageJson = readJson(path.join(root, "package.json"));
const packageVersion = packageJson.version;
const winPackageVersion = require(path.join(root, "packages", "win32-x64", "package.json")).version;
const winOptionalDependencyVersion = packageJson.optionalDependencies?.["tabctl-win32-x64"];
const launcherVersion = packageJson.optionalDependencies && packageJson.optionalDependencies["tabctl-win32-x64"];
const winPackageVersion = readJson(path.join(root, "packages", "win32-x64", "package.json")).version;
const cargoFiles = [
"rust/crates/shared/Cargo.toml",
"rust/crates/host/Cargo.toml",
Expand All @@ -73,27 +74,22 @@ jobs:
];
const mismatches = [];

const readCargoPackageVersion = (relativePath) => {
const content = fs.readFileSync(path.join(root, relativePath), "utf8");
const match = content.match(/\[package\][\s\S]*?\nversion\s*=\s*"([^"]+)"/m);
if (!match) {
throw new Error(`Could not find [package].version in ${relativePath}`);
}
return match[1];
};
if (packageVersion !== workspaceVersion) {
mismatches.push(`package.json=${packageVersion} (expected ${workspaceVersion})`);
}

if (winPackageVersion !== packageVersion) {
mismatches.push(`packages/win32-x64/package.json=${winPackageVersion} (expected ${packageVersion})`);
if (launcherVersion !== workspaceVersion) {
mismatches.push(`package.json optionalDependencies.tabctl-win32-x64=${launcherVersion} (expected ${workspaceVersion})`);
}

if (winOptionalDependencyVersion !== packageVersion) {
mismatches.push(`package.json optionalDependencies.tabctl-win32-x64=${winOptionalDependencyVersion} (expected ${packageVersion})`);
if (winPackageVersion !== workspaceVersion) {
mismatches.push(`packages/win32-x64/package.json=${winPackageVersion} (expected ${workspaceVersion})`);
}

for (const file of cargoFiles) {
const cargoVersion = readCargoPackageVersion(file);
if (cargoVersion !== packageVersion) {
mismatches.push(`${file}=${cargoVersion} (expected ${packageVersion})`);
const cargoVersion = readCargoPackageVersion(path.join(root, file), workspaceVersion);
if (cargoVersion !== workspaceVersion) {
mismatches.push(`${file}=${cargoVersion} (expected ${workspaceVersion})`);
}
}

Expand Down Expand Up @@ -247,6 +243,20 @@ jobs:
env:
TABCTL_VERSION_MODE: release

- name: Sync platform package version
run: |
node -e "
const version = require('./scripts/version-utils').readWorkspaceVersion(process.cwd());
const pkg = require('./package.json');
const win = require('./packages/win32-x64/package.json');
pkg.version = version;
if (pkg.optionalDependencies && pkg.optionalDependencies['tabctl-win32-x64']) {
pkg.optionalDependencies['tabctl-win32-x64'] = version;
}
win.version = version;
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
require('fs').writeFileSync('./packages/win32-x64/package.json', JSON.stringify(win, null, 2) + '\n');
"
- name: Publish tabctl-win32-x64
run: npm publish --provenance --access public --tag '${{ needs.validate.outputs.dist_tag }}'
working-directory: packages/win32-x64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tag-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
id: resolve
shell: bash
run: |
version="$(node -p "require('./package.json').version")"
version="$(node -e "process.stdout.write(require('./scripts/version-utils').readWorkspaceVersion(process.cwd()))")"
tag="v${version}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ npm run test:integration # Run integration tests (requires Chrome)
A **split hook gate** is active via `core.hooksPath=.githooks` (set by `npm install`):
- **pre-commit** (`.githooks/pre-commit`) runs fast unit checks (`npm run test:unit`).
- **pre-push** (`.githooks/pre-push`) runs heavier checks (`npm run rust:verify` and `npm run test:integration`) when Rust/build/hook-related files changed.
- **local cross-target checks** are opt-in via `npm run check:targets` / `make dev-check-targets`; they are not mandatory in pre-push because this workspace's SQLite dependency chain compiles C code during cross-target `cargo check`.
- CI `wsl` job validates the WSL -> Windows invocation bridge (setup + Windows command/native-host invocation + integration handoff); it does not compile Rust inside WSL.

## Project architecture
Expand Down Expand Up @@ -94,7 +95,7 @@ tabctl dedupe --window 123 --confirm # Execute after review

Direct pushes to `main` are blocked by a branch ruleset (requires PR + CI checks + Copilot review).

**Version management:** All version files are synced by `scripts/bump-version.js` (exposed as `npm run bump:<kind>`). Never edit version fields manually.
**Version management:** `rust/Cargo.toml` is the Rust version source of truth. `scripts/bump-version.js` (exposed as `npm run bump:<kind>`) updates that workspace version and mirrors it to the npm/package manifests. Never edit version fields manually.

```bash
npm run bump:alpha # next alpha
Expand All @@ -116,7 +117,8 @@ npm run bump:major # major bump

## Scripts

- `scripts/bump-version.js` — syncs all version files (package.json, win32-x64, 3× Cargo.toml, lockfiles)
- `scripts/bump-version.js` — bumps `rust/Cargo.toml`, mirrors package versions, and refreshes lockfiles
- `scripts/check-targets.sh` — optional local cross-target cargo check; requires extra host toolchains for Linux/Windows targets
- `scripts/ci-wait-merge.sh` — waits for CI, merges PR, tags, creates GitHub release
- `scripts/test-mise-release.sh` — integration test comparing npm stable vs mise alpha channels
- `scripts/gen-version.js` — generates extension manifest version at build time
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ PROFILE ?= $(BROWSER)
EXTENSION_DIR ?= dist/extension
TABCTL := $(CARGO) run --manifest-path rust/Cargo.toml -p tabctl --

.PHONY: help dev-build dev-setup dev-up dev-profile-show dev-ping dev-list-all dev-run dev-run-release-like
.PHONY: help dev-build dev-setup dev-up dev-profile-show dev-ping dev-list-all dev-run dev-run-release-like dev-check-targets

help:
@echo "tabctl local development targets"
Expand All @@ -20,6 +20,7 @@ help:
@echo " make dev-list-all [PROFILE=edge]"
@echo " make dev-run CMD='list --all --json' [PROFILE=edge]"
@echo " make dev-run-release-like CMD='list --all --json' [PROFILE=edge]"
@echo " make dev-check-targets"
@echo " (override npm path when needed: NPM=~/.local/share/mise/shims/npm)"

dev-build:
Expand All @@ -46,3 +47,6 @@ dev-run:
dev-run-release-like:
@test -n "$(CMD)" || (echo "Usage: make dev-run-release-like CMD='list --all --json' [PROFILE=edge]" && exit 1)
TABCTL_AUTO_SYNC_MODE=release-like $(TABCTL) --profile $(PROFILE) $(CMD)

dev-check-targets:
$(NPM) run check:targets
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"typescript": "^5.4.5"
},
"optionalDependencies": {
"tabctl-win32-x64": "0.3.0"
"tabctl-win32-x64": "0.6.0-rc.3"
},
"dependencies": {
"normalize-url": "^8.1.1"
Expand Down
3 changes: 3 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ members = [
"crates/tabctl",
]
resolver = "2"

[workspace.package]
version = "0.6.0-rc.3"
2 changes: 1 addition & 1 deletion rust/crates/graphql/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tabctl-graphql"
version = "0.6.0-rc.3"
version.workspace = true
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/host/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tabctl-host"
version = "0.6.0-rc.3"
version.workspace = true
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tabctl-shared"
version = "0.6.0-rc.3"
version.workspace = true
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/tabctl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tabctl"
version = "0.6.0-rc.3"
version.workspace = true
edition = "2021"
publish = false

Expand Down
56 changes: 16 additions & 40 deletions scripts/bump-version.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,42 @@
#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");
const { execSync } = require("node:child_process");
const { bump, formatVersion, parseVersion } = require("./versioning");
const {
readJson,
readWorkspaceVersion,
writeJson,
writeWorkspaceVersion,
} = require("./version-utils");

const root = path.resolve(__dirname, "..");
const pkgPath = path.join(root, "package.json");
const winPkgPath = path.join(root, "packages", "win32-x64", "package.json");
const winPackageName = "tabctl-win32-x64";
const cargoPackagePaths = [
path.join(root, "rust", "crates", "shared", "Cargo.toml"),
path.join(root, "rust", "crates", "host", "Cargo.toml"),
path.join(root, "rust", "crates", "graphql", "Cargo.toml"),
path.join(root, "rust", "crates", "tabctl", "Cargo.toml"),
];

function readPackage() {
const raw = fs.readFileSync(pkgPath, "utf8");
return JSON.parse(raw);
}

function writePackage(pkg) {
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
}

function syncOptionalDependencyVersion(pkg, dependencyName, version) {
pkg.optionalDependencies = pkg.optionalDependencies || {};
pkg.optionalDependencies[dependencyName] = version;
}

function syncJsonPackageVersion(filePath, version) {
const raw = fs.readFileSync(filePath, "utf8");
const parsed = JSON.parse(raw);
const parsed = readJson(filePath);
parsed.version = version;
fs.writeFileSync(filePath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
}

function syncCargoPackageVersion(filePath, version) {
const raw = fs.readFileSync(filePath, "utf8");
const next = raw.replace(
/(\[package\][\s\S]*?\nversion\s*=\s*")([^"]+)(")/m,
`$1${version}$3`,
);
if (next === raw) {
throw new Error(`Could not update [package].version in ${filePath}`);
if (filePath === pkgPath) {
syncOptionalDependencyVersion(parsed, winPackageName, version);
}
fs.writeFileSync(filePath, next, "utf8");
writeJson(filePath, parsed);
}

const kind = process.argv[2] || "patch";
const pkg = readPackage();
const current = parseVersion(pkg.version || "0.0.0");
const current = parseVersion(readWorkspaceVersion(root));
const next = bump(current, kind);
pkg.version = formatVersion(next);
syncOptionalDependencyVersion(pkg, winPackageName, pkg.version);
writePackage(pkg);
syncJsonPackageVersion(winPkgPath, pkg.version);
for (const cargoPath of cargoPackagePaths) {
syncCargoPackageVersion(cargoPath, pkg.version);
}
const nextVersion = formatVersion(next);
syncJsonPackageVersion(pkgPath, nextVersion);
syncJsonPackageVersion(winPkgPath, nextVersion);
writeWorkspaceVersion(root, nextVersion);
Comment thread
ekroon marked this conversation as resolved.

// Sync lockfiles
execSync("npm install --package-lock-only --ignore-scripts", {
Expand All @@ -72,4 +48,4 @@ execSync("cargo generate-lockfile --manifest-path rust/Cargo.toml", {
stdio: "ignore",
});

process.stdout.write(`${pkg.version}\n`);
process.stdout.write(`${nextVersion}\n`);
Loading