| 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,6 +30,7 @@ |
| 30 | "@effect/language-service": "0.75.1", | 30 | "@effect/language-service": "0.75.1", |
| 31 | "@effect/vitest": "catalog:", | 31 | "@effect/vitest": "catalog:", |
| 32 | "@t3tools/contracts": "workspace:*", | 32 | "@t3tools/contracts": "workspace:*", |
| 33 | "@t3tools/shared": "workspace:*", | ||
| 33 | "@t3tools/web": "workspace:*", | 34 | "@t3tools/web": "workspace:*", |
| 34 | "@types/bun": "^1.3.9", | 35 | "@types/bun": "^1.3.9", |
| 35 | "@types/node": "^24.10.13", | 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,7 +3,7 @@ import { randomUUID } from "node:crypto"; |
| 3 | import { Effect, FileSystem, Layer, Option, Path, Schema, Stream } from "effect"; | 3 | import { Effect, FileSystem, Layer, Option, Path, Schema, Stream } from "effect"; |
| 4 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 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 | import { resolveAttachmentPath } from "../../attachmentStore.ts"; | 8 | import { resolveAttachmentPath } from "../../attachmentStore.ts"; |
| 9 | import { ServerConfig } from "../../config.ts"; | 9 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/git/Layers/GitManager.ts+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | import { randomUUID } from "node:crypto"; | 1 | import { randomUUID } from "node:crypto"; |
| 2 | 2 | ||
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import { Effect, FileSystem, Layer, Path } from "effect"; |
| 4 | import { sanitizeFeatureBranchName } from "@t3tools/contracts"; | 4 | import { sanitizeFeatureBranchName } from "@t3tools/shared/git"; |
| 5 | 5 | ||
| 6 | import { GitManagerError } from "../Errors.ts"; | 6 | import { GitManagerError } from "../Errors.ts"; |
| 7 | import { GitManager, type GitManagerShape } from "../Services/GitManager.ts"; | 7 | import { GitManager, type GitManagerShape } from "../Services/GitManager.ts"; |
apps/web/package.json+1| ... | @@ -15,6 +15,7 @@ | ... | @@ -15,6 +15,7 @@ |
| 15 | "@lexical/react": "^0.41.0", | 15 | "@lexical/react": "^0.41.0", |
| 16 | "@pierre/diffs": "^1.1.0-beta.16", | 16 | "@pierre/diffs": "^1.1.0-beta.16", |
| 17 | "@t3tools/contracts": "workspace:*", | 17 | "@t3tools/contracts": "workspace:*", |
| 18 | "@t3tools/shared": "workspace:*", | ||
| 18 | "@tanstack/react-pacer": "^0.19.4", | 19 | "@tanstack/react-pacer": "^0.19.4", |
| 19 | "@tanstack/react-query": "^5.90.0", | 20 | "@tanstack/react-query": "^5.90.0", |
| 20 | "@tanstack/react-router": "^1.160.2", | 21 | "@tanstack/react-router": "^1.160.2", |
apps/web/src/components/GitActionsControl.logic.ts+5-5| ... | @@ -1,9 +1,9 @@ | ... | @@ -1,9 +1,9 @@ |
| 1 | import { | 1 | import type { |
| 2 | type GitRunStackedActionResult, | 2 | GitRunStackedActionResult, |
| 3 | type GitStackedAction, | 3 | GitStackedAction, |
| 4 | type GitStatusResult, | 4 | GitStatusResult, |
| 5 | sanitizeBranchFragment, | ||
| 6 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { sanitizeBranchFragment } from "@t3tools/shared/git"; | ||
| 7 | 7 | ||
| 8 | export type GitActionIconName = "commit" | "push" | "pr"; | 8 | export type GitActionIconName = "commit" | "push" | "pr"; |
| 9 | 9 |
bun.lock+11| ... | @@ -50,6 +50,7 @@ | ... | @@ -50,6 +50,7 @@ |
| 50 | "@effect/language-service": "0.75.1", | 50 | "@effect/language-service": "0.75.1", |
| 51 | "@effect/vitest": "catalog:", | 51 | "@effect/vitest": "catalog:", |
| 52 | "@t3tools/contracts": "workspace:*", | 52 | "@t3tools/contracts": "workspace:*", |
| 53 | "@t3tools/shared": "workspace:*", | ||
| 53 | "@t3tools/web": "workspace:*", | 54 | "@t3tools/web": "workspace:*", |
| 54 | "@types/bun": "^1.3.9", | 55 | "@types/bun": "^1.3.9", |
| 55 | "@types/node": "^24.10.13", | 56 | "@types/node": "^24.10.13", |
| ... | @@ -67,6 +68,7 @@ | ... | @@ -67,6 +68,7 @@ |
| 67 | "@lexical/react": "^0.41.0", | 68 | "@lexical/react": "^0.41.0", |
| 68 | "@pierre/diffs": "^1.1.0-beta.16", | 69 | "@pierre/diffs": "^1.1.0-beta.16", |
| 69 | "@t3tools/contracts": "workspace:*", | 70 | "@t3tools/contracts": "workspace:*", |
| 71 | "@t3tools/shared": "workspace:*", | ||
| 70 | "@tanstack/react-pacer": "^0.19.4", | 72 | "@tanstack/react-pacer": "^0.19.4", |
| 71 | "@tanstack/react-query": "^5.90.0", | 73 | "@tanstack/react-query": "^5.90.0", |
| 72 | "@tanstack/react-router": "^1.160.2", | 74 | "@tanstack/react-router": "^1.160.2", |
| ... | @@ -110,6 +112,13 @@ | ... | @@ -110,6 +112,13 @@ |
| 110 | "typescript": "^5.7.3", | 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 | "catalog": { | 123 | "catalog": { |
| 115 | "@effect/language-service": "0.75.1", | 124 | "@effect/language-service": "0.75.1", |
| ... | @@ -537,6 +546,8 @@ | ... | @@ -537,6 +546,8 @@ |
| 537 | 546 | ||
| 538 | "@t3tools/desktop": ["@t3tools/desktop@workspace:apps/desktop"], | 547 | "@t3tools/desktop": ["@t3tools/desktop@workspace:apps/desktop"], |
| 539 | 548 | ||
| 549 | "@t3tools/shared": ["@t3tools/shared@workspace:packages/shared"], | ||
| 550 | |||
| 540 | "@t3tools/web": ["@t3tools/web@workspace:apps/web"], | 551 | "@t3tools/web": ["@t3tools/web@workspace:apps/web"], |
| 541 | 552 | ||
| 542 | "@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=="], | 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,39 +179,3 @@ export const GitPullResult = Schema.Struct({ |
| 179 | upstreamBranch: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), | 179 | upstreamBranch: TrimmedNonEmptyStringSchema.pipe(Schema.NullOr), |
| 180 | }); | 180 | }); |
| 181 | export type GitPullResult = typeof GitPullResult.Type; | 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 | } | ||