Skip to content
Closed
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: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,16 @@ T3CODE_DEV_INSTANCE=feature-xyz bun run dev:desktop
bun run build
bun run start

# Publish an alpha build without touching npm "latest"
bun run version:alpha
bun run publish:alpha

# Build a shareable macOS .dmg (arm64 by default)
bun run dist:desktop:dmg

# Or from any project directory after publishing:
npx t3
npx t3@alpha
```

## Scripts
Expand All @@ -75,6 +80,8 @@ npx t3
- `bun run build` — Builds contracts, web app, and server through Turbo.
- `bun run typecheck` — Strict TypeScript checks for all packages.
- `bun run test` — Runs workspace tests.
- `bun run version:alpha` — Sets or increments the `t3` package to an `x.y.z-alpha.n` prerelease.
- `bun run publish:alpha` — Builds `t3` and publishes it with the npm `alpha` dist-tag, leaving `latest` unchanged.
- `bun run dist:desktop:dmg` — Builds a shareable macOS `.dmg` into `./release`.
- `bun run dist:desktop:dmg:x64` — Builds an Intel macOS `.dmg`.

Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.1.0",
"version": "0.1.0-alpha.1",
"bin": {
"t3": "./dist/index.mjs"
},
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/codexAppServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { EventEmitter } from "node:events";
import readline from "node:readline";
import serverPackage from "../package.json";

import {
ApprovalRequestId,
Expand Down Expand Up @@ -200,7 +201,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
clientInfo: {
name: "t3code_desktop",
title: "T3 Code Desktop",
version: "0.1.0",
version: serverPackage.version,
},
capabilities: {
experimentalApi: false,
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@
"test:desktop-smoke": "turbo run smoke-test --filter=@t3tools/desktop",
"fmt": "oxfmt",
"build:contracts": "turbo run build --filter=@t3tools/contracts",
"dist:desktop:dmg": "node scripts/build-desktop-dmg.mjs",
"dist:desktop:dmg:arm64": "node scripts/build-desktop-dmg.mjs --arch arm64",
"dist:desktop:dmg:x64": "node scripts/build-desktop-dmg.mjs --arch x64",
"version:alpha": "node scripts/version-alpha.mjs",
"version:alpha:dry-run": "node scripts/version-alpha.mjs --dry-run",
"publish:alpha": "node scripts/publish-alpha.mjs",
"publish:alpha:dry-run": "node scripts/publish-alpha.mjs --dry-run",
"dist:desktop:dmg": "node --env-file=.env scripts/build-desktop-dmg.mjs",
"dist:desktop:dmg:arm64": "node --env-file=.env scripts/build-desktop-dmg.mjs --arch arm64",
"dist:desktop:dmg:x64": "node --env-file=.env scripts/build-desktop-dmg.mjs --arch x64",
"clean": "rm -rf node_modules apps/*/node_modules packages/*/node_modules apps/*/dist apps/*/dist-electron packages/*/dist .turbo apps/*/.turbo packages/*/.turbo",
"sync:vscode-icons": "node scripts/sync-vscode-icons.mjs"
},
Expand Down
104 changes: 104 additions & 0 deletions scripts/publish-alpha.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { chmodSync, cpSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

const repoRoot = fileURLToPath(new URL("..", import.meta.url));
const packageDir = fileURLToPath(new URL("../apps/server", import.meta.url));
const packageJsonPath = fileURLToPath(new URL("../apps/server/package.json", import.meta.url));
const rootPackageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
const rootPackageJson = JSON.parse(readFileSync(rootPackageJsonPath, "utf8"));
const version = packageJson.version;
const isDryRun = process.argv.includes("--dry-run");

if (!/^\d+\.\d+\.\d+-alpha\.\d+$/.test(version)) {
throw new Error(
`Refusing alpha publish because apps/server/package.json is "${version}". Use bun run version:alpha first.`,
);
}

const build = spawnSync("bun", ["run", "build", "--filter=t3"], {
cwd: repoRoot,
stdio: "inherit",
});

if (build.status !== 0) {
process.exit(build.status ?? 1);
}

function readWorkspaceCatalog(rootManifest) {
const workspaces = rootManifest?.workspaces;
if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) {
return {};
}

const catalog = workspaces.catalog;
if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
return {};
}

return catalog;
}

function resolveCatalogDependencies(dependencies, catalog, dependencySourceLabel) {
return Object.fromEntries(
Object.entries(dependencies ?? {}).map(([dependencyName, spec]) => {
if (typeof spec !== "string" || !spec.startsWith("catalog:")) {
return [dependencyName, spec];
}

const catalogKey = spec.slice("catalog:".length).trim();
const lookupKey = catalogKey.length > 0 ? catalogKey : dependencyName;
const resolvedSpec = catalog[lookupKey];
if (typeof resolvedSpec !== "string" || resolvedSpec.length === 0) {
throw new Error(
`Unable to resolve '${spec}' for ${dependencySourceLabel} dependency '${dependencyName}'.`,
);
}

return [dependencyName, resolvedSpec];
}),
);
}

const publishDir = mkdtempSync(path.join(os.tmpdir(), "t3-alpha-publish-"));
cpSync(path.join(packageDir, "dist"), path.join(publishDir, "dist"), { recursive: true });
mkdirSync(path.join(publishDir, "bin"), { recursive: true });
writeFileSync(
path.join(publishDir, "bin", "t3"),
'#!/usr/bin/env node\nimport "../dist/index.mjs";\n',
);
chmodSync(path.join(publishDir, "bin", "t3"), 0o755);

const publishManifest = {
name: packageJson.name,
version: packageJson.version,
type: packageJson.type,
bin: {
t3: "bin/t3",
},
main: packageJson.main,
files: ["dist", "bin"],
dependencies: resolveCatalogDependencies(
packageJson.dependencies,
readWorkspaceCatalog(rootPackageJson),
"apps/server",
),
};

writeFileSync(path.join(publishDir, "package.json"), `${JSON.stringify(publishManifest, null, 2)}\n`);

const publishArgs = ["publish", "--tag", "alpha"];
if (isDryRun) {
publishArgs.push("--dry-run");
}

const publish = spawnSync("npm", publishArgs, {
cwd: publishDir,
stdio: "inherit",
});

rmSync(publishDir, { recursive: true, force: true });
process.exit(publish.status ?? 1);
46 changes: 46 additions & 0 deletions scripts/version-alpha.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

const packageJsonPath = fileURLToPath(new URL("../apps/server/package.json", import.meta.url));
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const versionArg = args.find((arg) => arg !== "--dry-run");

const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
const currentVersion = packageJson.version;

const alphaVersionPattern = /^\d+\.\d+\.\d+-alpha\.\d+$/;
const alphaCounterPattern = /^(?<base>\d+\.\d+\.\d+)-alpha\.(?<counter>\d+)$/;

function nextAlphaVersion(version) {
const alphaMatch = version.match(alphaCounterPattern);
Comment on lines +15 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low scripts/version-alpha.mjs:15

If version is undefined (missing from package.json), calling version.match() throws a TypeError. Consider adding a guard at the start of the function to check for this.

 function nextAlphaVersion(version) {
+  if (typeof version !== "string") {
+    throw new Error("Missing or invalid version field in package.json.");
+  }
   const alphaMatch = version.match(alphaCounterPattern);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file scripts/version-alpha.mjs around lines 15-16:

If `version` is `undefined` (missing from `package.json`), calling `version.match()` throws a `TypeError`. Consider adding a guard at the start of the function to check for this.

Evidence trail:
scripts/version-alpha.mjs lines 10, 15-16, 31 at REVIEWED_COMMIT - `currentVersion = packageJson.version` (line 10), `nextAlphaVersion(version)` function calls `version.match(alphaCounterPattern)` (line 16), and `nextAlphaVersion(currentVersion)` is called via nullish coalescing (line 31). If package.json lacks a version field, currentVersion is undefined, and undefined.match() throws TypeError.

if (alphaMatch?.groups) {
const counter = Number.parseInt(alphaMatch.groups.counter, 10);
return `${alphaMatch.groups.base}-alpha.${counter + 1}`;
}

if (version.includes("-")) {
throw new Error(
`Cannot derive an alpha version from prerelease version "${version}". Pass an explicit version instead.`,
);
}

return `${version}-alpha.0`;
}

const nextVersion = versionArg ?? nextAlphaVersion(currentVersion);

if (!alphaVersionPattern.test(nextVersion)) {
throw new Error(
`Expected an explicit alpha version like "0.1.0-alpha.0", received "${nextVersion}".`,
);
}

if (dryRun) {
console.log(nextVersion);
process.exit(0);
}

packageJson.version = nextVersion;
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
console.log(`Updated apps/server/package.json to ${nextVersion}`);