-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat(web): share project actions via t3.json #4363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,17 @@ export function getOptimisticProjectFileQueryData( | |
| return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? null; | ||
| } | ||
|
|
||
| export function getProjectFileQueryData( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High
🤖 Copy this AI Prompt to have your agent fix this: |
||
| environmentId: EnvironmentId, | ||
| cwd: string, | ||
| relativePath: string, | ||
| ): ProjectReadFileResult | null { | ||
| const optimistic = getOptimisticProjectFileQueryData(environmentId, cwd, relativePath); | ||
| if (optimistic !== null) return optimistic; | ||
| const result = appAtomRegistry.get(getProjectFileQueryAtom(environmentId, cwd, relativePath)); | ||
| return Option.getOrNull(AsyncResult.value(result)); | ||
| } | ||
|
|
||
| export function confirmProjectFileQueryData( | ||
| environmentId: EnvironmentId, | ||
| cwd: string, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { describe, expect, it } from "vite-plus/test"; | ||
|
|
||
| import { upsertT3ProjectFileScript } from "./t3ProjectFileScripts"; | ||
|
|
||
| const script = { | ||
| id: "script-test", | ||
| name: "Test", | ||
| command: "vp test", | ||
| icon: "test" as const, | ||
| runOnWorktreeCreate: false, | ||
| autoOpenPreview: false, | ||
| }; | ||
|
|
||
| describe("upsertT3ProjectFileScript", () => { | ||
| it("creates a project file for the first shared action", () => { | ||
| const result = JSON.parse( | ||
| upsertT3ProjectFileScript({ | ||
| contents: null, | ||
| script, | ||
| }), | ||
| ); | ||
|
|
||
| expect(result).toEqual({ | ||
| $schema: "https://t3.codes/schema/t3.json", | ||
| scripts: [ | ||
| { | ||
| name: "Test", | ||
| command: "vp test", | ||
| icon: "test", | ||
| runOnWorktreeCreate: false, | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it("appends an action while preserving existing project settings", () => { | ||
| const result = JSON.parse( | ||
| upsertT3ProjectFileScript({ | ||
| contents: JSON.stringify({ | ||
| $schema: "https://t3.codes/schema/t3.json", | ||
| iconPath: "assets/logo.svg", | ||
| futureSetting: { enabled: true }, | ||
| scripts: [{ name: "Dev", command: "vp dev" }], | ||
| }), | ||
| script, | ||
| }), | ||
| ); | ||
|
|
||
| expect(result.iconPath).toBe("assets/logo.svg"); | ||
| expect(result.futureSetting).toEqual({ enabled: true }); | ||
| expect(result.scripts).toEqual([ | ||
| { name: "Dev", command: "vp dev" }, | ||
| { | ||
| name: "Test", | ||
| command: "vp test", | ||
| icon: "test", | ||
| runOnWorktreeCreate: false, | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it("updates an edited shared action instead of duplicating it", () => { | ||
| const previousScript = { ...script, name: "Checks", command: "vp check" }; | ||
| const result = JSON.parse( | ||
| upsertT3ProjectFileScript({ | ||
| contents: JSON.stringify({ | ||
| scripts: [{ name: "Checks", command: "vp check" }], | ||
| }), | ||
| previousScript, | ||
| script, | ||
| }), | ||
| ); | ||
|
|
||
| expect(result.scripts).toHaveLength(1); | ||
| expect(result.scripts[0]).toMatchObject({ name: "Test", command: "vp test" }); | ||
| }); | ||
|
|
||
| it("rejects an invalid existing project file", () => { | ||
| expect(() => | ||
| upsertT3ProjectFileScript({ | ||
| contents: "{ invalid", | ||
| script, | ||
| }), | ||
| ).toThrow("t3.json is invalid"); | ||
| }); | ||
|
|
||
| it("does not exceed the shared action limit", () => { | ||
| expect(() => | ||
| upsertT3ProjectFileScript({ | ||
| contents: JSON.stringify({ | ||
| scripts: Array.from({ length: 50 }, (_, index) => ({ | ||
| name: `Action ${index}`, | ||
| command: `echo ${index}`, | ||
| })), | ||
| }), | ||
| script, | ||
| }), | ||
| ).toThrow("maximum of 50 shared actions"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Share skips unread t3.json
High Severity
Sharing a project script can overwrite the
t3.jsonfile. This happens becausegetProjectFileQueryDatareturnsnullif the file read query is pending or failed.upsertT3ProjectFileScriptthen interprets thisnullas an empty file, leading to a new, minimalt3.jsonbeing written that discards existing shared actions and settings.Reviewed by Cursor Bugbot for commit d98df4f. Configure here.