Move the duplicated script normalization logic (dedup-by-id + single runOnWorktreeCreate enforcement) from apps/web/src/store.ts and apps/server/src/projectRegistry.ts into packages/contracts/src/project.ts so both consumers share one canonical implementation. Applied via @cursor push command
Showing the changes to packages (1 of 3 files). Show all
Unified · Split
| @@ -84,6 +84,43 @@ export const projectUpdateScriptsResultSchema = z.object({ · 83 unmodified lines | |||
| 84 | 84 | ||
| 85 | export type ProjectScriptIcon = z.infer<typeof projectScriptIconSchema>; | 85 | export type ProjectScriptIcon = z.infer<typeof projectScriptIconSchema>; |
| 86 | export type ProjectScript = z.infer<typeof projectScriptSchema>; | 86 | export type ProjectScript = z.infer<typeof projectScriptSchema>; |
| 87 | |||
| 88 | /** | ||
| 89 | * Deduplicate scripts by ID and enforce at most one `runOnWorktreeCreate: true` | ||
| 90 | * flag (the first one wins). Fields are trimmed defensively so this is safe to | ||
| 91 | * call on data that hasn't been through Zod parsing. | ||
| 92 | */ | ||
| 93 | export function normalizeProjectScripts(scripts: readonly ProjectScript[]): ProjectScript[] { | ||
| 94 | const seenIds = new Set<string>(); | ||
| 95 | const deduped: ProjectScript[] = []; | ||
| 96 | for (const script of scripts) { | ||
| 97 | const id = script.id.trim(); | ||
| 98 | if (id.length === 0 || seenIds.has(id)) continue; | ||
| 99 | seenIds.add(id); | ||
| 100 | deduped.push({ | ||
| 101 | ...script, | ||
| 102 | id, | ||
| 103 | name: script.name.trim(), | ||
| 104 | command: script.command.trim(), | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | |||
| 108 | let setupAssigned = false; | ||
| 109 | const normalized: ProjectScript[] = []; | ||
| 110 | for (const script of deduped) { | ||
| 111 | if (!script.runOnWorktreeCreate) { | ||
| 112 | normalized.push(script); | ||
| 113 | continue; | ||
| 114 | } | ||
| 115 | if (!setupAssigned) { | ||
| 116 | setupAssigned = true; | ||
| 117 | normalized.push(script); | ||
| 118 | continue; | ||
| 119 | } | ||
| 120 | normalized.push({ ...script, runOnWorktreeCreate: false }); | ||
| 121 | } | ||
| 122 | return normalized; | ||
| 123 | } | ||
| 87 | export type ProjectRecord = z.infer<typeof projectRecordSchema>; | 124 | export type ProjectRecord = z.infer<typeof projectRecordSchema>; |
| 88 | export type ProjectListResult = z.infer<typeof projectListResultSchema>; | 125 | export type ProjectListResult = z.infer<typeof projectListResultSchema>; |
| 89 | export type ProjectAddInput = z.input<typeof projectAddInputSchema>; | 126 | export type ProjectAddInput = z.input<typeof projectAddInputSchema>; |