1import {
2 T3_PROJECT_FILE_NAME,
3 type EnvironmentId,
4 type T3ProjectFile,
5 type T3ProjectFileScript,
6} from "@t3tools/contracts";
7import { parseT3ProjectFile } from "@t3tools/shared/t3ProjectFile";
8import { useMemo } from "react";
9
10import { useProjectFileQuery } from "~/components/files/projectFilesQueryState";
11
12const NO_SCRIPTS: ReadonlyArray<T3ProjectFileScript> = [];
13
14export interface T3ProjectFileState {
15 /**
16 * - `valid`: t3.json exists and decoded.
17 * - `invalid`: t3.json exists but fails to decode (the server then ignores
18 * the whole file, including `iconPath` and every script).
19 * - `missing`: no readable t3.json at the workspace root.
20 * - `loading`: the file query has not settled yet.
21 */
22 status: "loading" | "missing" | "invalid" | "valid";
23 /** The decoded file when status is `valid`, null otherwise. */
24 file: T3ProjectFile | null;
25 scripts: ReadonlyArray<T3ProjectFileScript>;
26}
27
28/**
29 * Decoded state of the project's checked-in `t3.json`, including whether the
30 * file exists but is broken — which the runtime otherwise swallows silently.
31 */
32export function useT3ProjectFileState(
33 environmentId: EnvironmentId,
34 cwd: string | null,
35): T3ProjectFileState {
36 const query = useProjectFileQuery(environmentId, cwd ?? "", T3_PROJECT_FILE_NAME, cwd !== null);
37 const contents = query.data && !query.data.truncated ? query.data.contents : null;
38 const isPending = query.isPending;
39 return useMemo(() => {
40 if (contents === null) {
41 return {
42 status: isPending ? "loading" : "missing",
43 file: null,
44 scripts: NO_SCRIPTS,
45 } as const;
46 }
47 const file = parseT3ProjectFile(contents);
48 if (file === null) {
49 return { status: "invalid", file: null, scripts: NO_SCRIPTS } as const;
50 }
51 return { status: "valid", file, scripts: file.scripts ?? NO_SCRIPTS } as const;
52 }, [contents, isPending]);
53}
54
55/**
56 * Scripts declared in the project's checked-in `t3.json`, offered in the
57 * scripts menu for import. Missing, truncated, or invalid files resolve to
58 * an empty list.
59 */
60export function useT3ProjectFileScripts(
61 environmentId: EnvironmentId,
62 cwd: string | null,
63): ReadonlyArray<T3ProjectFileScript> {
64 return useT3ProjectFileState(environmentId, cwd).scripts;
65}