| author | |
| committer | |
| log | dde48e94141370c38166e20dde0cec3c347164d5 |
| tree | 5c4795b68a3a104899d088dc9ef87bce56452e17 |
| parent | b1608f41adff7ac9670c5a762624e13984d820c8 |
Create @t3tools/shared with explicit subpath exports (no barrel) for
shared utility logic. Move sanitizeBranchFragment and
sanitizeFeatureBranchName to @t3tools/shared/git, keeping contracts
focused on schemas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>10 files changed, 75 insertions(+), 43 deletions(-)
apps/server/package.json+1| ... | ... | @@ -30,6 +30,7 @@ |
| 30 | 30 | "@effect/language-service": "0.75.1", |
| 31 | 31 | "@effect/vitest": "catalog:", |
| 32 | 32 | "@t3tools/contracts": "workspace:*", |
| 33 | "@t3tools/shared": "workspace:*", | |
| 33 | 34 | "@t3tools/web": "workspace:*", |
| 34 | 35 | "@types/bun": "^1.3.9", |
| 35 | 36 | "@types/node": "^24.10.13", |
apps/server/src/git/Layers/CodexTextGeneration.ts+1-1| ... | ... | @@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto"; |
| 3 | 3 | import { Effect, FileSystem, Layer, Option, Path, Schema, Stream } from "effect"; |
| 4 | 4 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 5 | 5 | |
| 6 | import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/contracts"; | |
| 6 | import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git"; | |
| 7 | 7 | |
| 8 | 8 | import { resolveAttachmentPath } from "../../attachmentStore.ts"; |
| 9 | 9 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/git/Layers/GitManager.ts+1-1| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | import { randomUUID } from "node:crypto"; |
| 2 | 2 | |
| 3 | 3 | import { Effect, FileSystem, Layer, Path } from "effect"; |
| 4 | import { sanitizeFeatureBranchName } from "@t3tools/contracts"; | |
| 4 | import { sanitizeFeatureBranchName } from "@t3tools/shared/git"; | |
| 5 | 5 | |
| 6 | 6 | import { GitManagerError } from "../Errors.ts"; |
| 7 | 7 | import { GitManager, type GitManagerShape } from "../Services/GitManager.ts"; |
apps/web/package.json+1| ... | ... | @@ -15,6 +15,7 @@ |
| 15 | 15 | "@lexical/react": "^0.41.0", |
| 16 | 16 | "@pierre/diffs": "^1.1.0-beta.16", |
| 17 | 17 | "@t3tools/contracts": "workspace:*", |
| 18 | "@t3tools/shared": "workspace:*", | |
| 18 | 19 | "@tanstack/react-pacer": "^0.19.4", |
| 19 | 20 | "@tanstack/react-query": "^5.90.0", |
| 20 | 21 | "@tanstack/react-router": "^1.160.2", |
apps/web/src/components/GitActionsControl.logic.ts+5-5| ... | ... | @@ -1,9 +1,9 @@ |
| 1 | import { | |
| 2 | type GitRunStackedActionResult, | |
| 3 | type GitStackedAction, | |
| 4 | type GitStatusResult, | |
| 5 | sanitizeBranchFragment, | |
| 1 | import type { | |
| 2 | GitRunStackedActionResult, | |
| 3 | GitStackedAction, | |
| 4 | GitStatusResult, | |
| 6 | 5 | } from "@t3tools/contracts"; |
| 6 | import { sanitizeBranchFragment } from "@t3tools/shared/git"; | |
| 7 | 7 | |
| 8 | 8 | export type GitActionIconName = "commit" | "push" | "pr"; |
| 9 | 9 |
bun.lock+11| ... | ... | @@ -50,6 +50,7 @@ |
| 50 | 50 | "@effect/language-service": "0.75.1", |
| 51 | 51 | "@effect/vitest": "catalog:", |
| 52 | 52 | "@t3tools/contracts": "workspace:*", |
| 53 | "@t3tools/shared": "workspace:*", | |
| 53 | 54 | "@t3tools/web": "workspace:*", |
| 54 | 55 | "@types/bun": "^1.3.9", |
| 55 | 56 | "@types/node": "^24.10.13", |
| ... | ... | @@ -67,6 +68,7 @@ |
| 67 | 68 | "@lexical/react": "^0.41.0", |
| 68 | 69 | "@pierre/diffs": "^1.1.0-beta.16", |
| 69 | 70 | "@t3tools/contracts": "workspace:*", |
| 71 | "@t3tools/shared": "workspace:*", | |
| 70 | 72 | "@tanstack/react-pacer": "^0.19.4", |
| 71 | 73 | "@tanstack/react-query": "^5.90.0", |
| 72 | 74 | "@tanstack/react-router": "^1.160.2", |
| ... | ... | @@ -110,6 +112,13 @@ |
| 110 | 112 | "typescript": "^5.7.3", |
| 111 | 113 | }, |
| 112 | 114 | }, |
| 115 | "packages/shared": { | |
| 116 | "name": "@t3tools/shared", | |
| 117 | "version": "0.0.0-alpha.1", | |
| 118 | "devDependencies": { | |
| 119 | "typescript": "^5.7.3", | |
| 120 | }, | |
| 121 | }, | |
| 113 | 122 | }, |
| 114 | 123 | "catalog": { |
| 115 | 124 | "@effect/language-service": "0.75.1", |
| ... | ... | @@ -537,6 +546,8 @@ |
| 537 | 546 | |
| 538 | 547 | "@t3tools/desktop": ["@t3tools/desktop@workspace:apps/desktop"], |
| 539 | 548 | |
| 549 | "@t3tools/shared": ["@t3tools/shared@workspace:packages/shared"], | |
| 550 | ||
| 540 | 551 | "@t3tools/web": ["@t3tools/web@workspace:apps/web"], |
| 541 | 552 | |
| 542 | 553 | "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], |
packages/contracts/src/git.ts-36| ... | ... | @@ -179,39 +179,3 @@ export const GitPullResult = Schema.Struct({ |
| 179 | 179 | upstreamBranch: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), |
| 180 | 180 | }); |
| 181 | 181 | export type GitPullResult = typeof GitPullResult.Type; |
| 182 | ||
| 183 | // Branch name utilities | |
| 184 | ||
| 185 | /** | |
| 186 | * Sanitize an arbitrary string into a valid, lowercase git branch fragment. | |
| 187 | * Strips quotes, collapses separators, limits to 64 chars. | |
| 188 | */ | |
| 189 | export function sanitizeBranchFragment(raw: string): string { | |
| 190 | const normalized = raw | |
| 191 | .trim() | |
| 192 | .toLowerCase() | |
| 193 | .replace(/['"`]/g, "") | |
| 194 | .replace(/^[./\s_-]+|[./\s_-]+$/g, ""); | |
| 195 | ||
| 196 | const branchFragment = normalized | |
| 197 | .replace(/[^a-z0-9/_-]+/g, "-") | |
| 198 | .replace(/\/+/g, "/") | |
| 199 | .replace(/-+/g, "-") | |
| 200 | .replace(/^[./_-]+|[./_-]+$/g, "") | |
| 201 | .slice(0, 64) | |
| 202 | .replace(/[./_-]+$/g, ""); | |
| 203 | ||
| 204 | return branchFragment.length > 0 ? branchFragment : "update"; | |
| 205 | } | |
| 206 | ||
| 207 | /** | |
| 208 | * Sanitize a string into a `feature/…` branch name. | |
| 209 | * Preserves an existing `feature/` prefix or slash-separated namespace. | |
| 210 | */ | |
| 211 | export function sanitizeFeatureBranchName(raw: string): string { | |
| 212 | const sanitized = sanitizeBranchFragment(raw); | |
| 213 | if (sanitized.includes("/")) { | |
| 214 | return sanitized.startsWith("feature/") ? sanitized : `feature/${sanitized}`; | |
| 215 | } | |
| 216 | return `feature/${sanitized}`; | |
| 217 | } |
packages/shared/package.json created+18| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | { | |
| 2 | "name": "@t3tools/shared", | |
| 3 | "version": "0.0.0-alpha.1", | |
| 4 | "private": true, | |
| 5 | "type": "module", | |
| 6 | "exports": { | |
| 7 | "./git": { | |
| 8 | "types": "./src/git.ts", | |
| 9 | "import": "./src/git.ts" | |
| 10 | } | |
| 11 | }, | |
| 12 | "scripts": { | |
| 13 | "typecheck": "tsc --noEmit" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "typescript": "^5.7.3" | |
| 17 | } | |
| 18 | } |
packages/shared/src/git.ts created+33| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | /** | |
| 2 | * Sanitize an arbitrary string into a valid, lowercase git branch fragment. | |
| 3 | * Strips quotes, collapses separators, limits to 64 chars. | |
| 4 | */ | |
| 5 | export function sanitizeBranchFragment(raw: string): string { | |
| 6 | const normalized = raw | |
| 7 | .trim() | |
| 8 | .toLowerCase() | |
| 9 | .replace(/['"`]/g, "") | |
| 10 | .replace(/^[./\s_-]+|[./\s_-]+$/g, ""); | |
| 11 | ||
| 12 | const branchFragment = normalized | |
| 13 | .replace(/[^a-z0-9/_-]+/g, "-") | |
| 14 | .replace(/\/+/g, "/") | |
| 15 | .replace(/-+/g, "-") | |
| 16 | .replace(/^[./_-]+|[./_-]+$/g, "") | |
| 17 | .slice(0, 64) | |
| 18 | .replace(/[./_-]+$/g, ""); | |
| 19 | ||
| 20 | return branchFragment.length > 0 ? branchFragment : "update"; | |
| 21 | } | |
| 22 | ||
| 23 | /** | |
| 24 | * Sanitize a string into a `feature/…` branch name. | |
| 25 | * Preserves an existing `feature/` prefix or slash-separated namespace. | |
| 26 | */ | |
| 27 | export function sanitizeFeatureBranchName(raw: string): string { | |
| 28 | const sanitized = sanitizeBranchFragment(raw); | |
| 29 | if (sanitized.includes("/")) { | |
| 30 | return sanitized.startsWith("feature/") ? sanitized : `feature/${sanitized}`; | |
| 31 | } | |
| 32 | return `feature/${sanitized}`; | |
| 33 | } |
packages/shared/tsconfig.json created+4| ... | ... | @@ -0,0 +1,4 @@ |
| 1 | { | |
| 2 | "extends": "../../tsconfig.base.json", | |
| 3 | "include": ["src"] | |
| 4 | } |