@shvmgyl15/tsgraph 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/changes/index.test.js +2 -6
  2. package/dist/changes/index.test.js.map +1 -1
  3. package/dist/cli/index.js +184 -4
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/git/index.test.js +4 -6
  6. package/dist/git/index.test.js.map +1 -1
  7. package/dist/opencode/index.js +1 -1
  8. package/dist/opencode/index.js.map +1 -1
  9. package/dist/opencode/index.test.js +2 -2
  10. package/dist/opencode/index.test.js.map +1 -1
  11. package/dist/search/index.d.ts.map +1 -1
  12. package/dist/search/index.js +12 -4
  13. package/dist/search/index.js.map +1 -1
  14. package/package.json +16 -1
  15. package/AGENTS.md +0 -64
  16. package/TODOS.md +0 -61
  17. package/opencode.json +0 -24
  18. package/src/analysis/analysis.test.ts +0 -405
  19. package/src/analysis/complexity.ts +0 -107
  20. package/src/analysis/coupling.ts +0 -106
  21. package/src/analysis/hotspot.ts +0 -52
  22. package/src/analysis/index.ts +0 -17
  23. package/src/boundaries/index.test.ts +0 -335
  24. package/src/boundaries/index.ts +0 -137
  25. package/src/changes/index.test.ts +0 -114
  26. package/src/changes/index.ts +0 -95
  27. package/src/cli/index.ts +0 -736
  28. package/src/git/index.test.ts +0 -92
  29. package/src/git/index.ts +0 -86
  30. package/src/graph/types.test.ts +0 -383
  31. package/src/graph/types.ts +0 -353
  32. package/src/mcp/mcp.test.ts +0 -176
  33. package/src/mcp/server.ts +0 -217
  34. package/src/nextjs/index.ts +0 -23
  35. package/src/nextjs/nextjs.test.ts +0 -233
  36. package/src/nextjs/pages.ts +0 -43
  37. package/src/nextjs/react.ts +0 -100
  38. package/src/nextjs/router.ts +0 -102
  39. package/src/nextjs/routes.ts +0 -69
  40. package/src/opencode/index.test.ts +0 -90
  41. package/src/opencode/index.ts +0 -83
  42. package/src/parser/index.ts +0 -339
  43. package/src/parser/parser.test.ts +0 -282
  44. package/src/plan/index.test.ts +0 -162
  45. package/src/plan/index.ts +0 -161
  46. package/src/report/index.ts +0 -128
  47. package/src/scanner/index.ts +0 -97
  48. package/src/scanner/scanner.test.ts +0 -135
  49. package/src/search/index.ts +0 -163
  50. package/src/search/search.test.ts +0 -512
  51. package/src/traversal/index.ts +0 -5
  52. package/src/traversal/traversal.test.ts +0 -266
  53. package/src/traversal/traversal.ts +0 -185
  54. package/tsconfig.json +0 -20
  55. package/vitest.config.ts +0 -7
@@ -1,92 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import path from "node:path";
3
- import fs from "node:fs";
4
- import os from "node:os";
5
- import { execSync } from "node:child_process";
6
- import {
7
- isGitRepo,
8
- getCurrentBranch,
9
- getDiffFiles,
10
- getStaleFiles,
11
- getCommitHistory,
12
- } from "./index.js";
13
-
14
- let tmpDir: string;
15
-
16
- function run(cmd: string) {
17
- execSync(cmd, { cwd: tmpDir, stdio: "pipe" });
18
- }
19
-
20
- function writeFile(relativePath: string, content: string) {
21
- const fullPath = path.join(tmpDir, relativePath);
22
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
23
- fs.writeFileSync(fullPath, content, "utf-8");
24
- }
25
-
26
- beforeAll(() => {
27
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-git-test-"));
28
- run("git init");
29
- run('git config user.email "test@test.com"');
30
- run('git config user.name "Test"');
31
- writeFile(".gitkeep", "");
32
- run("git add . && git commit -m 'initial'");
33
- });
34
-
35
- afterAll(() => {
36
- fs.rmSync(tmpDir, { recursive: true });
37
- });
38
-
39
- describe("isGitRepo", () => {
40
- it("returns true inside a git repo", () => {
41
- expect(isGitRepo(tmpDir)).toBe(true);
42
- });
43
-
44
- it("returns false outside a git repo", () => {
45
- const nonRepo = fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-non-git-"));
46
- expect(isGitRepo(nonRepo)).toBe(false);
47
- fs.rmSync(nonRepo, { recursive: true });
48
- });
49
- });
50
-
51
- describe("getCurrentBranch", () => {
52
- it("returns current branch name", () => {
53
- const branch = getCurrentBranch(tmpDir);
54
- expect(branch).toBeTruthy();
55
- });
56
- });
57
-
58
- describe("getDiffFiles", () => {
59
- it("returns changed files vs base branch", () => {
60
- writeFile("file1.ts", "content");
61
- run("git add . && git commit -m 'initial'");
62
- run("git checkout -b feature");
63
- writeFile("file2.ts", "content");
64
- run("git add . && git commit -m 'add file2'");
65
- writeFile("file3.ts", "content");
66
- run("git add . && git commit -m 'add file3'");
67
- const results = getDiffFiles(tmpDir, "main");
68
- expect(results.length).toBeGreaterThanOrEqual(2);
69
- const paths = results.map((r) => r.path);
70
- expect(paths).toContain("file2.ts");
71
- expect(paths).toContain("file3.ts");
72
- run("git checkout main");
73
- });
74
- });
75
-
76
- describe("getCommitHistory", () => {
77
- it("returns recent commit history", () => {
78
- const history = getCommitHistory(tmpDir, 5);
79
- expect(history.length).toBeGreaterThan(0);
80
- expect(history[0]).toHaveProperty("hash");
81
- expect(history[0]).toHaveProperty("message");
82
- });
83
- });
84
-
85
- describe("getStaleFiles", () => {
86
- it("does not return recently committed files", () => {
87
- writeFile("fresh.ts", "fresh content");
88
- run("git add . && git commit -m 'add fresh'");
89
- const stale = getStaleFiles(tmpDir, 0);
90
- expect(stale).not.toContain("fresh.ts");
91
- });
92
- });
package/src/git/index.ts DELETED
@@ -1,86 +0,0 @@
1
- import { execSync } from "node:child_process";
2
-
3
- export interface ChangedFile {
4
- path: string;
5
- status: "added" | "modified" | "deleted" | "renamed";
6
- }
7
-
8
- export interface CommitInfo {
9
- hash: string;
10
- date: string;
11
- message: string;
12
- author: string;
13
- }
14
-
15
- export function isGitRepo(root: string): boolean {
16
- try {
17
- execSync("git rev-parse --git-dir", { cwd: root, stdio: "ignore" });
18
- return true;
19
- } catch {
20
- return false;
21
- }
22
- }
23
-
24
- export function getCurrentBranch(root: string): string {
25
- return execSync("git rev-parse --abbrev-ref HEAD", {
26
- cwd: root,
27
- encoding: "utf-8",
28
- }).trim();
29
- }
30
-
31
- export function getDiffFiles(root: string, base: string = "main"): ChangedFile[] {
32
- const output = execSync(`git diff --name-status ${base}...HEAD`, {
33
- cwd: root,
34
- encoding: "utf-8",
35
- }).trim();
36
- if (!output) return [];
37
- return output.split("\n").map((line) => {
38
- const parts = line.split(/\s+/);
39
- const status = parts[0];
40
- let fileStatus: ChangedFile["status"];
41
- let filePath = parts[1] ?? "";
42
- if (status === "A") fileStatus = "added";
43
- else if (status === "D") fileStatus = "deleted";
44
- else if (status === "R" || status.startsWith("R")) fileStatus = "renamed";
45
- else fileStatus = "modified";
46
- if (fileStatus === "renamed" && parts[2]) filePath = parts[2];
47
- return { path: filePath, status: fileStatus };
48
- });
49
- }
50
-
51
- export function getStaleFiles(root: string, thresholdDays: number = 90): string[] {
52
- const cutoff = new Date();
53
- cutoff.setDate(cutoff.getDate() - thresholdDays);
54
- const cutoffStr = cutoff.toISOString().split("T")[0];
55
- try {
56
- const output = execSync(
57
- `git log --pretty=format: --name-only --diff-filter=AM --since=${cutoffStr}`,
58
- { cwd: root, encoding: "utf-8" },
59
- ).trim();
60
- const recentFiles = new Set(output.split("\n").filter(Boolean));
61
- const allFiles = execSync("git ls-files", {
62
- cwd: root,
63
- encoding: "utf-8",
64
- })
65
- .trim()
66
- .split("\n")
67
- .filter(Boolean);
68
- return allFiles.filter((f) => !recentFiles.has(f));
69
- } catch {
70
- return [];
71
- }
72
- }
73
-
74
- export function getCommitHistory(root: string, count: number = 10): CommitInfo[] {
75
- const output = execSync(
76
- `git log --max-count=${count} --format="%H|%ad|%s|%an" --date=short`,
77
- { cwd: root, encoding: "utf-8" },
78
- ).trim();
79
- if (!output) return [];
80
- return output.split("\n").map((line) => {
81
- const [hash, date, ...msgParts] = line.split("|");
82
- const message = msgParts.slice(0, -1).join("|");
83
- const author = msgParts[msgParts.length - 1] ?? "";
84
- return { hash, date, message, author };
85
- });
86
- }
@@ -1,383 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- GRAPH_VERSION,
4
- makeGraph,
5
- makePackageNode,
6
- makeFileNode,
7
- makeSymbolNode,
8
- makeCallEdge,
9
- makeImportEdge,
10
- makeDependency,
11
- makeHTTPRoute,
12
- makeEnvRead,
13
- makeConcurrencyNode,
14
- makeTestEdge,
15
- makeImplementsEdge,
16
- makeMutationEdge,
17
- makeErrorEdge,
18
- makeAppRouterNode,
19
- makeStructField,
20
- serialize,
21
- deserialize,
22
- } from "./types.js";
23
-
24
- describe("GRAPH_VERSION", () => {
25
- it("is 1", () => {
26
- expect(GRAPH_VERSION).toBe("1");
27
- });
28
- });
29
-
30
- describe("factory functions", () => {
31
- describe("makeGraph", () => {
32
- it("returns a graph with defaults", () => {
33
- const g = makeGraph();
34
- expect(g.version).toBe(GRAPH_VERSION);
35
- expect(g.generatedAt).toBeTruthy();
36
- expect(g.root).toBe("");
37
- expect(g.packages).toEqual([]);
38
- expect(g.files).toEqual([]);
39
- expect(g.symbols).toEqual([]);
40
- expect(g.imports).toEqual([]);
41
- expect(g.calls).toEqual([]);
42
- expect(g.envReads).toEqual([]);
43
- expect(g.dependencies).toEqual([]);
44
- expect(g.routes).toEqual([]);
45
- expect(g.concurrency).toEqual([]);
46
- expect(g.testEdges).toEqual([]);
47
- expect(g.implements).toEqual([]);
48
- expect(g.mutations).toEqual([]);
49
- expect(g.errors).toEqual([]);
50
- expect(g.appRouter).toBeUndefined();
51
- });
52
-
53
- it("merges overrides", () => {
54
- const g = makeGraph({ root: "/project", version: "2" });
55
- expect(g.root).toBe("/project");
56
- expect(g.version).toBe("2");
57
- });
58
-
59
- it("generates an ISO timestamp", () => {
60
- const g = makeGraph();
61
- expect(() => new Date(g.generatedAt)).not.toThrow();
62
- expect(new Date(g.generatedAt).toISOString()).toBe(g.generatedAt);
63
- });
64
- });
65
-
66
- describe("makePackageNode", () => {
67
- it("returns a package node with defaults", () => {
68
- const p = makePackageNode();
69
- expect(p.id).toBeTruthy();
70
- expect(p.name).toBe("");
71
- expect(p.importPathBestEffort).toBe("");
72
- expect(p.dir).toBe("");
73
- expect(p.files).toEqual([]);
74
- });
75
-
76
- it("merges overrides", () => {
77
- const p = makePackageNode({ name: "utils", dir: "./utils" });
78
- expect(p.name).toBe("utils");
79
- expect(p.dir).toBe("./utils");
80
- expect(p.files).toEqual([]);
81
- });
82
- });
83
-
84
- describe("makeFileNode", () => {
85
- it("returns a file node with defaults", () => {
86
- const f = makeFileNode();
87
- expect(f.id).toBeTruthy();
88
- expect(f.path).toBe("");
89
- expect(f.packageName).toBe("");
90
- expect(f.lines).toBe(0);
91
- expect(f.generated).toBe(false);
92
- });
93
-
94
- it("merges overrides", () => {
95
- const f = makeFileNode({ path: "src/index.ts", generated: true, lines: 42 });
96
- expect(f.path).toBe("src/index.ts");
97
- expect(f.generated).toBe(true);
98
- expect(f.lines).toBe(42);
99
- });
100
- });
101
-
102
- describe("makeSymbolNode", () => {
103
- it("returns a symbol node with defaults", () => {
104
- const s = makeSymbolNode();
105
- expect(s.id).toBeTruthy();
106
- expect(s.kind).toBe("function");
107
- expect(s.name).toBe("");
108
- expect(s.packageName).toBe("");
109
- expect(s.file).toBe("");
110
- expect(s.line).toBe(0);
111
- expect(s.endLine).toBe(0);
112
- expect(s.isExported).toBe(false);
113
- });
114
-
115
- it("merges overrides", () => {
116
- const s = makeSymbolNode({ name: "Foo", kind: "class", isExported: true, line: 10, endLine: 30 });
117
- expect(s.name).toBe("Foo");
118
- expect(s.kind).toBe("class");
119
- expect(s.isExported).toBe(true);
120
- expect(s.line).toBe(10);
121
- expect(s.endLine).toBe(30);
122
- });
123
-
124
- it("preserves optional fields", () => {
125
- const s = makeSymbolNode({
126
- doc: "does foo",
127
- signature: "Foo() => void",
128
- structFields: [{ name: "bar", type: "string" }],
129
- embeddedTypes: ["Mixin"],
130
- arity: 2,
131
- isClientComponent: true,
132
- isServerComponent: false,
133
- });
134
- expect(s.doc).toBe("does foo");
135
- expect(s.signature).toBe("Foo() => void");
136
- expect(s.structFields).toHaveLength(1);
137
- expect(s.structFields![0].name).toBe("bar");
138
- expect(s.embeddedTypes).toEqual(["Mixin"]);
139
- expect(s.arity).toBe(2);
140
- expect(s.isClientComponent).toBe(true);
141
- expect(s.isServerComponent).toBe(false);
142
- });
143
- });
144
-
145
- describe("makeCallEdge", () => {
146
- it("returns defaults", () => {
147
- const e = makeCallEdge();
148
- expect(e.callerSymbolId).toBe("");
149
- expect(e.callerName).toBe("");
150
- expect(e.calleeRaw).toBe("");
151
- expect(e.file).toBe("");
152
- expect(e.line).toBe(0);
153
- });
154
- });
155
-
156
- describe("makeImportEdge", () => {
157
- it("returns defaults", () => {
158
- const e = makeImportEdge();
159
- expect(e.fromFile).toBe("");
160
- expect(e.fromPackage).toBe("");
161
- expect(e.importPath).toBe("");
162
- expect(e.isDefault).toBe(false);
163
- });
164
-
165
- it("sets isDefault", () => {
166
- const e = makeImportEdge({ isDefault: true });
167
- expect(e.isDefault).toBe(true);
168
- });
169
- });
170
-
171
- describe("makeDependency", () => {
172
- it("returns defaults", () => {
173
- const d = makeDependency();
174
- expect(d.module).toBe("");
175
- expect(d.version).toBe("");
176
- });
177
- });
178
-
179
- describe("makeHTTPRoute", () => {
180
- it("returns defaults", () => {
181
- const r = makeHTTPRoute();
182
- expect(r.method).toBe("");
183
- expect(r.path).toBe("");
184
- expect(r.handler).toBe("");
185
- expect(r.file).toBe("");
186
- expect(r.line).toBe(0);
187
- });
188
- });
189
-
190
- describe("makeEnvRead", () => {
191
- it("returns defaults", () => {
192
- const e = makeEnvRead();
193
- expect(e.key).toBe("");
194
- expect(e.accessor).toBe("");
195
- expect(e.file).toBe("");
196
- expect(e.line).toBe(0);
197
- });
198
- });
199
-
200
- describe("makeConcurrencyNode", () => {
201
- it("returns defaults", () => {
202
- const c = makeConcurrencyNode();
203
- expect(c.kind).toBe("async_function");
204
- expect(c.functionName).toBe("");
205
- expect(c.file).toBe("");
206
- expect(c.line).toBe(0);
207
- });
208
- });
209
-
210
- describe("makeTestEdge", () => {
211
- it("returns defaults", () => {
212
- const t = makeTestEdge();
213
- expect(t.testFunc).toBe("");
214
- expect(t.target).toBe("");
215
- expect(t.file).toBe("");
216
- expect(t.line).toBe(0);
217
- });
218
- });
219
-
220
- describe("makeImplementsEdge", () => {
221
- it("returns defaults", () => {
222
- const e = makeImplementsEdge();
223
- expect(e.interface).toBe("");
224
- expect(e.concrete).toBe("");
225
- });
226
- });
227
-
228
- describe("makeMutationEdge", () => {
229
- it("returns defaults", () => {
230
- const m = makeMutationEdge();
231
- expect(m.field).toBe("");
232
- expect(m.functionName).toBe("");
233
- expect(m.file).toBe("");
234
- expect(m.line).toBe(0);
235
- });
236
- });
237
-
238
- describe("makeErrorEdge", () => {
239
- it("returns defaults", () => {
240
- const e = makeErrorEdge();
241
- expect(e.message).toBe("");
242
- expect(e.functionName).toBe("");
243
- expect(e.file).toBe("");
244
- expect(e.line).toBe(0);
245
- });
246
- });
247
-
248
- describe("makeAppRouterNode", () => {
249
- it("returns defaults", () => {
250
- const n = makeAppRouterNode();
251
- expect(n.path).toBe("");
252
- expect(n.dir).toBe("");
253
- expect(n.files).toEqual({});
254
- expect(n.children).toEqual([]);
255
- });
256
-
257
- it("supports nested children", () => {
258
- const child = makeAppRouterNode({ path: "/child" });
259
- const parent = makeAppRouterNode({ path: "/", children: [child] });
260
- expect(parent.children).toHaveLength(1);
261
- expect(parent.children[0].path).toBe("/child");
262
- });
263
- });
264
-
265
- describe("makeStructField", () => {
266
- it("returns defaults", () => {
267
- const f = makeStructField();
268
- expect(f.name).toBe("");
269
- expect(f.type).toBe("");
270
- });
271
-
272
- it("merges overrides", () => {
273
- const f = makeStructField({ name: "id", type: "string", tag: "json:\"id\"" });
274
- expect(f.name).toBe("id");
275
- expect(f.type).toBe("string");
276
- expect(f.tag).toBe("json:\"id\"");
277
- });
278
- });
279
- });
280
-
281
- describe("serialize / deserialize", () => {
282
- describe("serialize", () => {
283
- it("produces valid JSON with 2-space indentation", () => {
284
- const g = makeGraph();
285
- const json = serialize(g);
286
- expect(json).toBeTruthy();
287
- expect(json.startsWith("{")).toBe(true);
288
- const parsed = JSON.parse(json);
289
- expect(parsed.version).toBe(GRAPH_VERSION);
290
- });
291
-
292
- it("throws on version mismatch", () => {
293
- const g = makeGraph({ version: "999" });
294
- expect(() => serialize(g)).toThrow("version mismatch");
295
- });
296
- });
297
-
298
- describe("deserialize", () => {
299
- it("round-trips a graph with all fields", () => {
300
- const original = makeGraph({
301
- root: "/test",
302
- packages: [makePackageNode({ name: "main", dir: "." })],
303
- files: [makeFileNode({ path: "index.ts", packageName: "main" })],
304
- symbols: [makeSymbolNode({ name: "run", kind: "function", file: "index.ts", line: 1, endLine: 5 })],
305
- calls: [makeCallEdge({ callerSymbolId: "s1", callerName: "run", calleeRaw: "log", file: "index.ts", line: 2 })],
306
- imports: [makeImportEdge({ fromFile: "index.ts", importPath: "fs" })],
307
- dependencies: [makeDependency({ module: "react", version: "^18" })],
308
- routes: [makeHTTPRoute({ method: "GET", path: "/api", handler: "getHandler", file: "route.ts", line: 3 })],
309
- envReads: [makeEnvRead({ key: "PORT", accessor: "process.env.PORT", file: "config.ts", line: 5 })],
310
- concurrency: [makeConcurrencyNode({ kind: "promise_all", functionName: "fetchAll", file: "fetch.ts", line: 10 })],
311
- testEdges: [makeTestEdge({ testFunc: "TestRun", target: "run", file: "index_test.ts", line: 1 })],
312
- implements: [makeImplementsEdge({ interface: "Runner", concrete: "FastRunner" })],
313
- mutations: [makeMutationEdge({ field: "User.name", functionName: "rename", file: "user.ts", line: 8 })],
314
- errors: [makeErrorEdge({ message: "not found", functionName: "findUser", file: "user.ts", line: 12 })],
315
- appRouter: makeAppRouterNode({
316
- path: "/",
317
- dir: "app",
318
- files: { page: "page.tsx", layout: "layout.tsx" },
319
- children: [makeAppRouterNode({ path: "/about", dir: "about", files: { page: "page.tsx" } })],
320
- }),
321
- });
322
-
323
- const json = serialize(original);
324
- const restored = deserialize(json);
325
-
326
- expect(restored.version).toBe(original.version);
327
- expect(restored.root).toBe(original.root);
328
- expect(restored.packages).toHaveLength(1);
329
- expect(restored.files).toHaveLength(1);
330
- expect(restored.symbols).toHaveLength(1);
331
- expect(restored.calls).toHaveLength(1);
332
- expect(restored.imports).toHaveLength(1);
333
- expect(restored.dependencies).toHaveLength(1);
334
- expect(restored.routes).toHaveLength(1);
335
- expect(restored.envReads).toHaveLength(1);
336
- expect(restored.concurrency).toHaveLength(1);
337
- expect(restored.testEdges).toHaveLength(1);
338
- expect(restored.implements).toHaveLength(1);
339
- expect(restored.mutations).toHaveLength(1);
340
- expect(restored.errors).toHaveLength(1);
341
- expect(restored.appRouter).toBeTruthy();
342
- expect(restored.appRouter!.children).toHaveLength(1);
343
- });
344
-
345
- it("round-trips an empty graph", () => {
346
- const original = makeGraph({ root: "/empty" });
347
- const json = serialize(original);
348
- const restored = deserialize(json);
349
- expect(restored.root).toBe("/empty");
350
- expect(restored.packages).toEqual([]);
351
- });
352
-
353
- it("rejects malformed JSON", () => {
354
- expect(() => deserialize("not json")).toThrow("Invalid JSON");
355
- });
356
-
357
- it("rejects null", () => {
358
- expect(() => deserialize("null")).toThrow("Invalid graph structure");
359
- });
360
-
361
- it("rejects missing version", () => {
362
- const json = JSON.stringify({ root: "/x", generatedAt: "", packages: [], files: [], symbols: [], imports: [], calls: [], envReads: [], dependencies: [], routes: [], concurrency: [], testEdges: [], implements: [], mutations: [], errors: [] });
363
- expect(() => deserialize(json)).toThrow("Invalid graph structure");
364
- });
365
-
366
- it("rejects version mismatch", () => {
367
- const g = makeGraph({ version: "2" });
368
- expect(() => deserialize(serialize(g))).toThrow("version mismatch");
369
- });
370
-
371
- it("rejects missing array fields", () => {
372
- const json = JSON.stringify({ version: GRAPH_VERSION, generatedAt: "", root: "" });
373
- expect(() => deserialize(json)).toThrow("Invalid graph structure");
374
- });
375
-
376
- it("accepts extra unknown fields (forward compat)", () => {
377
- const g = makeGraph({ root: "/future" });
378
- const json = JSON.stringify({ ...JSON.parse(serialize(g)), futureField: "hello" });
379
- const restored = deserialize(json);
380
- expect(restored.root).toBe("/future");
381
- });
382
- });
383
- });