From 68d145528d57d7b8e49c0a7968c94b912bc7108a Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 11:15:42 -0600 Subject: [PATCH 01/10] [cross-module] first try --- Readme.md | 2 + schema.json | 41 +++++----- src/parser/__test__/parse.test.ts | 117 ++++++++++++++++++++++++++++- src/parser/parse.ts | 26 +++++-- src/parser/utils.ts | 121 ++++++++++++++++++++++++++++++ src/types.ts | 5 ++ 6 files changed, 284 insertions(+), 28 deletions(-) diff --git a/Readme.md b/Readme.md index 48db833..3df9d03 100644 --- a/Readme.md +++ b/Readme.md @@ -44,6 +44,8 @@ module.exports = { crawl: { root: "../../", }, + // Allow resolving fragments imported from workspace packages. + moduleRoots: ["../../"], generate: [ { ...options, diff --git a/schema.json b/schema.json index 3c98219..afa2c6b 100644 --- a/schema.json +++ b/schema.json @@ -1,5 +1,5 @@ { - "$schema":"http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema", "type": "object", "additionalProperties": false, "definitions": { @@ -13,19 +13,13 @@ "match": { "type": "array", "items": { - "oneOf": [ - { "type": "object" }, - { "type": "string" } - ] + "oneOf": [{"type": "object"}, {"type": "string"}] } }, "exclude": { "type": "array", "items": { - "oneOf": [ - { "type": "object" }, - { "type": "string" } - ] + "oneOf": [{"type": "object"}, {"type": "string"}] } }, "scalars": { @@ -65,9 +59,7 @@ "type": "boolean" } }, - "required": [ - "schemaFilePath" - ] + "required": ["schemaFilePath"] } }, "properties": { @@ -89,12 +81,17 @@ "type": "string" } }, - "required": [ "root" ] + "required": ["root"] + }, + "generate": { + "oneOf": [ + {"$ref": "#/definitions/GenerateConfig"}, + { + "type": "array", + "items": {"$ref": "#/definitions/GenerateConfig"} + } + ] }, - "generate": {"oneOf": [ - {"$ref": "#/definitions/GenerateConfig"}, - {"type": "array", "items": {"$ref": "#/definitions/GenerateConfig"}} - ]}, "alias": { "type": "array", "items": { @@ -108,9 +105,15 @@ "type": "string" } }, - "required": [ "find", "replacement" ] + "required": ["find", "replacement"] + } + }, + "moduleRoots": { + "type": "array", + "items": { + "type": "string" } } }, - "required": [ "crawl", "generate" ] + "required": ["crawl", "generate"] } diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index f4a5644..02c1fa3 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -1,4 +1,5 @@ -import {describe, it, expect} from "@jest/globals"; +import {describe, it, expect, jest} from "@jest/globals"; +import fs from "fs"; import {Config} from "../../types"; import {processFiles} from "../parse"; @@ -14,6 +15,27 @@ const fixtureFiles: { resolvedPath: string; }; } = { + "/repo/node_modules/monorepo-package/fragment.js": ` + import gql from 'graphql-tag'; + + export const sharedFragment = gql\` + fragment SharedFields on Something { + id + } + \`; + `, + "/repo/packages/app/App.js": ` + import gql from 'graphql-tag'; + import {sharedFragment} from 'monorepo-package/fragment'; + export const appQuery = gql\` + query AppQuery { + viewer { + ...SharedFields + } + } + \${sharedFragment} + \`; + `, "/firstFile.js": ` // Note that you can import graphql-tag as // something other than gql. @@ -338,4 +360,97 @@ describe("processing fragments in various ways", () => { ); expect(printed).toMatchInlineSnapshot(`Object {}`); }); + + it("should resolve fragments imported from monorepo packages", () => { + const config: Config = { + crawl: { + root: "/here/we/crawl", + }, + generate: { + match: [/\.fixture\.js$/], + exclude: [ + "_test\\.js$", + "\\bcourse-editor-package\\b", + "\\.fixture\\.js$", + "\\b__flowtests__\\b", + "\\bcourse-editor\\b", + ], + readOnlyArray: false, + regenerateCommand: "make gqlflow", + scalars: { + JSONString: "string", + KALocale: "string", + NaiveDateTime: "string", + }, + splitTypes: true, + generatedDirectory: "__graphql-types__", + exportAllObjectTypes: true, + schemaFilePath: "./composed_schema.graphql", + }, + moduleRoots: ["/repo"], + }; + + const existsSpy = jest + .spyOn(fs, "existsSync") + .mockImplementation((path) => { + if (typeof path !== "string") { + return false; + } + return ( + path === + "/repo/node_modules/monorepo-package/package.json" || + path === "/repo/node_modules/monorepo-package/fragment.js" + ); + }); + const readSpy = jest + .spyOn(fs, "readFileSync") + .mockImplementation((path) => { + if ( + path === "/repo/node_modules/monorepo-package/package.json" + ) { + return JSON.stringify({name: "monorepo-package"}); + } + throw new Error(`Unexpected readFileSync for ${path}`); + }); + const realpathSpy = jest + .spyOn(fs, "realpathSync") + .mockImplementation((value) => value.toString()); + + try { + const files = processFiles( + ["/repo/packages/app/App.js"], + config, + getFileSource, + ); + Object.keys(files).forEach((k: any) => { + expect(files[k].errors).toEqual([]); + }); + const {resolved, errors} = resolveDocuments(files, config); + expect(errors).toEqual([]); + const printed: Record = {}; + Object.keys(resolved).map( + (k: any) => (printed[k] = print(resolved[k].document).trim()), + ); + expect(printed).toMatchInlineSnapshot(` + Object { + "/repo/node_modules/monorepo-package/fragment.js:4": "fragment SharedFields on Something { + id + }", + "/repo/packages/app/App.js:4": "query AppQuery { + viewer { + ...SharedFields + } + } + + fragment SharedFields on Something { + id + }", + } + `); + } finally { + existsSpy.mockRestore(); + readSpy.mockRestore(); + realpathSpy.mockRestore(); + } + }); }); diff --git a/src/parser/parse.ts b/src/parser/parse.ts index a0e2229..b921b88 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -11,7 +11,11 @@ import traverse from "@babel/traverse"; import path from "path"; -import {fixPathResolution, getPathWithExtension} from "./utils"; +import { + fixPathResolution, + getPathWithExtension, + resolveImportPath, +} from "./utils"; import {Config} from "../types"; /** @@ -182,7 +186,7 @@ export const processFile = ( if (newLocals) { Object.keys(newLocals).forEach((k) => { const local = newLocals[k]; - if (local.path.startsWith("/")) { + if (path.isAbsolute(local.path)) { result.locals[k] = local; } if ( @@ -197,9 +201,13 @@ export const processFile = ( if (toplevel.type === "ExportNamedDeclaration") { if (toplevel.source) { const source = toplevel.source; - const importPath = source.value.startsWith(".") - ? path.resolve(path.join(dir, source.value)) - : source.value; + const resolvedPath = resolveImportPath( + source.value, + dir, + config, + ); + const importPath = + resolvedPath ?? fixPathResolution(source.value, config); toplevel.specifiers?.forEach((spec) => { if ( spec.type === "ExportSpecifier" && @@ -398,9 +406,11 @@ const getLocals = ( return null; } const fixedPath = fixPathResolution(toplevel.source.value, config); - const importPath = fixedPath.startsWith(".") - ? path.resolve(path.join(dir, fixedPath)) - : fixedPath; + const resolvedPath = + fixedPath === "graphql-tag" + ? null + : resolveImportPath(toplevel.source.value, dir, config); + const importPath = resolvedPath ?? fixedPath; const locals: Record = {}; toplevel.specifiers.forEach((spec) => { if (spec.type === "ImportDefaultSpecifier") { diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 620157c..b26e042 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,4 +1,5 @@ import fs from "fs"; +import path from "path"; import {Config} from "../types"; export const fixPathResolution = (path: string, config: Config) => { @@ -10,6 +11,126 @@ export const fixPathResolution = (path: string, config: Config) => { return path; }; +const parseModuleSpecifier = (specifier: string) => { + if (specifier.startsWith("@")) { + const parts = specifier.split("/"); + return { + moduleName: parts.slice(0, 2).join("/"), + subpath: parts.slice(2).join("/"), + }; + } + const parts = specifier.split("/"); + return { + moduleName: parts[0], + subpath: parts.slice(1).join("/"), + }; +}; + +const tryReadPackageJson = (packageJsonPath: string) => { + if (!fs.existsSync(packageJsonPath)) { + return null; + } + try { + return JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + } catch (err) { + return null; + } +}; + +const resolvePackageRoot = (moduleName: string, moduleRoots: Array) => { + for (const root of moduleRoots) { + const nodeModulesPath = path.join(root, "node_modules", moduleName); + const nodeModulesPkg = path.join(nodeModulesPath, "package.json"); + const nodeModulesJson = tryReadPackageJson(nodeModulesPkg); + if (nodeModulesJson?.name === moduleName) { + return fs.realpathSync(nodeModulesPath); + } + + const directPackageJson = path.join(root, "package.json"); + const directJson = tryReadPackageJson(directPackageJson); + if (directJson?.name === moduleName) { + return fs.realpathSync(root); + } + + if (moduleName.startsWith("@")) { + const [scope, name] = moduleName.split("/"); + const scopedRoot = path.join(root, scope, name); + const scopedPackageJson = path.join(scopedRoot, "package.json"); + const scopedJson = tryReadPackageJson(scopedPackageJson); + if (scopedJson?.name === moduleName) { + return fs.realpathSync(scopedRoot); + } + } else { + const namedRoot = path.join(root, moduleName); + const namedPackageJson = path.join(namedRoot, "package.json"); + const namedJson = tryReadPackageJson(namedPackageJson); + if (namedJson?.name === moduleName) { + return fs.realpathSync(namedRoot); + } + } + } + return null; +}; + +const resolvePackageEntry = ( + packageRoot: string, + config: Config, + packageJson: any, +) => { + const candidates: Array = []; + const pushIfString = (value: unknown) => { + if (typeof value === "string") { + candidates.push(value); + } + }; + pushIfString(packageJson?.source); + pushIfString(packageJson?.module); + pushIfString(packageJson?.main); + pushIfString(packageJson?.types); + if (typeof packageJson?.exports === "string") { + candidates.push(packageJson.exports); + } + for (const entry of candidates) { + const resolved = getPathWithExtension( + path.resolve(packageRoot, entry), + config, + ); + if (resolved) { + return resolved; + } + } + return getPathWithExtension(path.join(packageRoot, "index"), config); +}; + +export const resolveImportPath = ( + rawImportPath: string, + fromDir: string, + config: Config, +) => { + const fixedPath = fixPathResolution(rawImportPath, config); + if (fixedPath.startsWith(".")) { + return path.resolve(path.join(fromDir, fixedPath)); + } + if (path.isAbsolute(fixedPath)) { + return fixedPath; + } + if (!config.moduleRoots || config.moduleRoots.length === 0) { + return null; + } + const {moduleName, subpath} = parseModuleSpecifier(fixedPath); + const packageRoot = resolvePackageRoot(moduleName, config.moduleRoots); + if (!packageRoot) { + return null; + } + if (subpath) { + return getPathWithExtension(path.join(packageRoot, subpath), config); + } + const packageJson = tryReadPackageJson( + path.join(packageRoot, "package.json"), + ); + return resolvePackageEntry(packageRoot, config, packageJson); +}; + export const getPathWithExtension = ( pathWithoutExtension: string, config: Config, diff --git a/src/types.ts b/src/types.ts index 66e3619..259755e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -54,6 +54,11 @@ export type Config = { find: RegExp | string; replacement: string; }>; + /** + * Absolute paths to monorepo roots or package roots used to resolve + * non-relative fragment imports (e.g. workspace packages). + */ + moduleRoots?: Array; }; export type Schema = { From d888a55148d94593b00ed500477a4e4c74410489 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 13:28:38 -0600 Subject: [PATCH 02/10] [cross-module] closer --- Readme.md | 1 + src/cli/run.ts | 7 ++- src/parser/__test__/parse.test.ts | 3 ++ src/parser/__test__/utils.test.ts | 80 ++++++++++++++++++++++++++++++- src/parser/utils.ts | 54 +++++++++++++++++++-- src/types.ts | 5 ++ 6 files changed, 144 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index 3df9d03..392d2b7 100644 --- a/Readme.md +++ b/Readme.md @@ -45,6 +45,7 @@ module.exports = { root: "../../", }, // Allow resolving fragments imported from workspace packages. + // moduleRoots are crawled once at startup to map package names. moduleRoots: ["../../"], generate: [ { diff --git a/src/cli/run.ts b/src/cli/run.ts index f8568e4..4024903 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -6,7 +6,7 @@ import type {GraphQLSchema} from "graphql/type/schema"; import {generateTypeFiles, processPragmas} from "../generateTypeFiles"; import {processFiles} from "../parser/parse"; import {resolveDocuments} from "../parser/resolve"; -import {getPathWithExtension} from "../parser/utils"; +import {buildModuleMap, getPathWithExtension} from "../parser/utils"; import { findApplicableConfig, getInputFiles, @@ -55,6 +55,11 @@ const inputFiles = getInputFiles(options, config); /** Step (2) */ +if (config.moduleRoots && config.moduleRoots.length > 0) { + config.moduleMap = buildModuleMap(config.moduleRoots); + console.log(`module map`, config.moduleMap, config.moduleRoots); +} + const files = processFiles(inputFiles, config, (f) => { const resolvedPath = getPathWithExtension(f, config); if (!resolvedPath) { diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index 02c1fa3..c3cdb43 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -388,6 +388,9 @@ describe("processing fragments in various ways", () => { schemaFilePath: "./composed_schema.graphql", }, moduleRoots: ["/repo"], + moduleMap: { + "monorepo-package": "/repo/node_modules/monorepo-package", + }, }; const existsSpy = jest diff --git a/src/parser/__test__/utils.test.ts b/src/parser/__test__/utils.test.ts index a8da61d..c02d19f 100644 --- a/src/parser/__test__/utils.test.ts +++ b/src/parser/__test__/utils.test.ts @@ -2,7 +2,7 @@ import fs from "fs"; import {describe, it, expect, jest} from "@jest/globals"; import type {Config} from "../../types"; -import {getPathWithExtension} from "../utils"; +import {buildModuleMap, getPathWithExtension} from "../utils"; const generate = { match: [/\.fixture\.js$/], @@ -78,3 +78,81 @@ describe("getPathWithExtension", () => { expect(result).toBe("../../some/prefix/dir/file.js"); }); }); + +describe("buildModuleMap", () => { + const makeDirent = (name: string, isDir: boolean) => ({ + name, + isDirectory: () => isDir, + isFile: () => !isDir, + }); + + it("collects package.json names and ignores node_modules", () => { + const existsSpy = jest + .spyOn(fs, "existsSync") + .mockImplementation((value) => { + return ( + value === "/repo/package.json" || + value === "/repo/packages/app/package.json" || + value === "/repo/packages/shared/package.json" || + value === "/repo/node_modules/ignore-me/package.json" + ); + }); + const readSpy = jest + .spyOn(fs, "readFileSync") + .mockImplementation((value) => { + if (value === "/repo/package.json") { + return JSON.stringify({name: "root-package"}); + } + if (value === "/repo/packages/app/package.json") { + return JSON.stringify({name: "app-package"}); + } + if (value === "/repo/packages/shared/package.json") { + return JSON.stringify({name: "@scope/shared"}); + } + if (value === "/repo/node_modules/ignore-me/package.json") { + return JSON.stringify({name: "ignore-me"}); + } + throw new Error(`Unexpected readFileSync for ${value}`); + }); + const readdirSpy = jest + .spyOn(fs, "readdirSync") + .mockImplementation((value) => { + if (value === "/repo") { + return [ + makeDirent("packages", true), + makeDirent("node_modules", true), + ] as Array; + } + if (value === "/repo/packages") { + return [ + makeDirent("app", true), + makeDirent("shared", true), + ] as Array; + } + if ( + value === "/repo/packages/app" || + value === "/repo/packages/shared" + ) { + return [] as Array; + } + return [] as Array; + }); + const realpathSpy = jest + .spyOn(fs, "realpathSync") + .mockImplementation((value) => value.toString()); + + try { + const result = buildModuleMap(["/repo"]); + expect(result).toEqual({ + "root-package": "/repo", + "app-package": "/repo/packages/app", + "@scope/shared": "/repo/packages/shared", + }); + } finally { + existsSpy.mockRestore(); + readSpy.mockRestore(); + readdirSpy.mockRestore(); + realpathSpy.mockRestore(); + } + }); +}); diff --git a/src/parser/utils.ts b/src/parser/utils.ts index b26e042..a674725 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -2,6 +2,8 @@ import fs from "fs"; import path from "path"; import {Config} from "../types"; +export type ModuleMap = {[key: string]: string}; + export const fixPathResolution = (path: string, config: Config) => { if (config.alias) { for (const {find, replacement} of config.alias) { @@ -72,6 +74,50 @@ const resolvePackageRoot = (moduleName: string, moduleRoots: Array) => { return null; }; +const getRealPath = (candidate: string) => { + try { + return fs.realpathSync(candidate); + } catch (err) { + return candidate; + } +}; + +export const buildModuleMap = (moduleRoots: Array): ModuleMap => { + const moduleMap: ModuleMap = {}; + const visited = new Set(); + const stack = moduleRoots.map((root) => getRealPath(root)); + while (stack.length) { + const current = stack.pop(); + if (!current || visited.has(current)) { + continue; + } + visited.add(current); + const packageJsonPath = path.join(current, "package.json"); + const packageJson = tryReadPackageJson(packageJsonPath); + if (packageJson?.name && typeof packageJson.name === "string") { + if (!moduleMap[packageJson.name]) { + moduleMap[packageJson.name] = current; + } + } + let entries: Array = []; + try { + entries = fs.readdirSync(current, {withFileTypes: true}); + } catch (err) { + continue; + } + entries.forEach((entry) => { + if (!entry.isDirectory()) { + return; + } + if (entry.name === "node_modules") { + return; + } + stack.push(path.join(current, entry.name)); + }); + } + return moduleMap; +}; + const resolvePackageEntry = ( packageRoot: string, config: Config, @@ -114,11 +160,11 @@ export const resolveImportPath = ( if (path.isAbsolute(fixedPath)) { return fixedPath; } - if (!config.moduleRoots || config.moduleRoots.length === 0) { - return null; - } const {moduleName, subpath} = parseModuleSpecifier(fixedPath); - const packageRoot = resolvePackageRoot(moduleName, config.moduleRoots); + let packageRoot = config.moduleMap?.[moduleName] ?? null; + if (!packageRoot && config.moduleRoots && config.moduleRoots.length > 0) { + packageRoot = resolvePackageRoot(moduleName, config.moduleRoots); + } if (!packageRoot) { return null; } diff --git a/src/types.ts b/src/types.ts index 259755e..c4dc902 100644 --- a/src/types.ts +++ b/src/types.ts @@ -59,6 +59,11 @@ export type Config = { * non-relative fragment imports (e.g. workspace packages). */ moduleRoots?: Array; + /** + * Internal map of package name to package root path. + * Populated from moduleRoots before processing files. + */ + moduleMap?: {[key: string]: string}; }; export type Schema = { From 0a1968c4c23ceba04f4edd4995d88a816397e424 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 13:31:34 -0600 Subject: [PATCH 03/10] [cross-module] handle export-from --- src/cli/run.ts | 1 - src/parser/__test__/parse.test.ts | 83 +++++++++++++++++++++++++++++++ src/parser/parse.ts | 19 +++++++ src/parser/resolve.ts | 17 +++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index 4024903..643f958 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -57,7 +57,6 @@ const inputFiles = getInputFiles(options, config); if (config.moduleRoots && config.moduleRoots.length > 0) { config.moduleMap = buildModuleMap(config.moduleRoots); - console.log(`module map`, config.moduleMap, config.moduleRoots); } const files = processFiles(inputFiles, config, (f) => { diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index c3cdb43..0573052 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -78,6 +78,30 @@ const fixtureFiles: { } \`; export {secondFragment};`, + "/starExportSource.js": ` + import gql from 'graphql-tag'; + export const starFragment = gql\` + fragment StarFragment on Star { + id + } + \`; + `, + "/starExportReexport.js": ` + export * from './starExportSource.js'; + `, + "/starExportConsumer.js": ` + import gql from 'graphql-tag'; + import {starFragment} from './starExportReexport.js'; + + export const starQuery = gql\` + query StarQuery { + stars { + ...StarFragment + } + } + \${starFragment} + \`; + `, "/thirdFile.js": ` import {fromFirstFile, alsoFirst, secondFragment} from './secondFile.js'; @@ -361,6 +385,65 @@ describe("processing fragments in various ways", () => { expect(printed).toMatchInlineSnapshot(`Object {}`); }); + it("should resolve fragments re-exported via export all", () => { + const config: Config = { + crawl: { + root: "/here/we/crawl", + }, + generate: { + match: [/\.fixture\.js$/], + exclude: [ + "_test\\.js$", + "\\bcourse-editor-package\\b", + "\\.fixture\\.js$", + "\\b__flowtests__\\b", + "\\bcourse-editor\\b", + ], + readOnlyArray: false, + regenerateCommand: "make gqlflow", + scalars: { + JSONString: "string", + KALocale: "string", + NaiveDateTime: "string", + }, + splitTypes: true, + generatedDirectory: "__graphql-types__", + exportAllObjectTypes: true, + schemaFilePath: "./composed_schema.graphql", + }, + }; + const files = processFiles( + ["/starExportConsumer.js"], + config, + getFileSource, + ); + Object.keys(files).forEach((k: any) => { + expect(files[k].errors).toEqual([]); + }); + const {resolved, errors} = resolveDocuments(files, config); + expect(errors).toEqual([]); + const printed: Record = {}; + Object.keys(resolved).map( + (k: any) => (printed[k] = print(resolved[k].document).trim()), + ); + expect(printed).toMatchInlineSnapshot(` + Object { + "/starExportConsumer.js:4": "query StarQuery { + stars { + ...StarFragment + } + } + + fragment StarFragment on Star { + id + }", + "/starExportSource.js:3": "fragment StarFragment on Star { + id + }", + } + `); + }); + it("should resolve fragments imported from monorepo packages", () => { const config: Config = { crawl: { diff --git a/src/parser/parse.ts b/src/parser/parse.ts index b921b88..22d1a5e 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -83,6 +83,7 @@ export type FileResult = { exports: { [key: string]: Document | Import; }; + exportAlls: Array; locals: { [key: string]: Document | Import; }; @@ -163,6 +164,7 @@ export const processFile = ( path: filePath, operations: [], exports: {}, + exportAlls: [], locals: {}, errors: [], }; @@ -237,6 +239,23 @@ export const processFile = ( }); } } + if (toplevel.type === "ExportAllDeclaration" && toplevel.source) { + const source = toplevel.source; + const resolvedPath = resolveImportPath(source.value, dir, config); + const importPath = + resolvedPath ?? fixPathResolution(source.value, config); + result.exportAlls.push({ + type: "import", + name: "*", + path: importPath, + loc: { + start: toplevel.start ?? -1, + end: toplevel.end ?? -1, + line: toplevel.loc?.start.line ?? -1, + path: filePath, + }, + }); + } const processDeclarator = ( decl: VariableDeclarator, diff --git a/src/parser/resolve.ts b/src/parser/resolve.ts index f530a3d..9adfb00 100644 --- a/src/parser/resolve.ts +++ b/src/parser/resolve.ts @@ -71,6 +71,23 @@ const resolveImport = ( return null; } if (!res.exports[expr.name]) { + if (expr.name !== "*" && res.exportAlls.length) { + for (const exportAll of res.exportAlls) { + const value = resolveImport( + { + ...exportAll, + name: expr.name, + }, + files, + errors, + {...seen}, + config, + ); + if (value) { + return value; + } + } + } errors.push({ loc: expr.loc, message: `${absPath} has no valid gql export ${expr.name}`, From a1e7f6dc20d58006ded2cb924b6de5b16a5fc535 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 13:34:38 -0600 Subject: [PATCH 04/10] [cross-module] better error message --- src/parser/__test__/parse.test.ts | 2 +- src/parser/parse.ts | 55 ++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index 0573052..1825451 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -327,7 +327,7 @@ describe("processing fragments in various ways", () => { expect(files["/invalidThings.js"].errors.map((m: any) => m.message)) .toMatchInlineSnapshot(` Array [ - "Unable to resolve someExternalFragment", + "Unable to resolve import someExternalFragment from \"somewhere\" at /invalidThings.js:4. If this is a local package, add it to moduleRoots.", "Unable to resolve someUndefinedFragment", "Template literal interpolation must be an identifier", ] diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 22d1a5e..0bd63d3 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -84,6 +84,12 @@ export type FileResult = { [key: string]: Document | Import; }; exportAlls: Array; + unresolvedImports?: { + [key: string]: { + source: string; + loc: Loc; + }; + }; locals: { [key: string]: Document | Import; }; @@ -165,6 +171,7 @@ export const processFile = ( operations: [], exports: {}, exportAlls: [], + unresolvedImports: {}, locals: {}, errors: [], }; @@ -184,6 +191,38 @@ export const processFile = ( ast.program.body.forEach((toplevel) => { if (toplevel.type === "ImportDeclaration") { + const fixedSource = fixPathResolution( + toplevel.source.value, + config, + ); + const resolvedSource = resolveImportPath( + toplevel.source.value, + dir, + config, + ); + const isUnresolvedModule = + !resolvedSource && + !fixedSource.startsWith(".") && + !path.isAbsolute(fixedSource) && + fixedSource !== "graphql-tag"; + if (isUnresolvedModule) { + toplevel.specifiers.forEach((spec) => { + if ( + spec.type === "ImportSpecifier" || + spec.type === "ImportDefaultSpecifier" + ) { + result.unresolvedImports![spec.local.name] = { + source: fixedSource, + loc: { + start: spec.start ?? -1, + end: spec.end ?? -1, + line: spec.loc?.start.line ?? -1, + path: filePath, + }, + }; + } + }); + } const newLocals = getLocals(dir, toplevel, filePath, config); if (newLocals) { Object.keys(newLocals).forEach((k) => { @@ -385,10 +424,18 @@ const processTemplate = ( const found = getTemplate(expr.name); return found; } - result.errors.push({ - loc, - message: `Unable to resolve ${expr.name}`, - }); + const unresolved = result.unresolvedImports?.[expr.name]; + if (unresolved) { + result.errors.push({ + loc: unresolved.loc, + message: `Unable to resolve import ${expr.name} from "${unresolved.source}" at ${unresolved.loc.path}:${unresolved.loc.line}. If this is a local package, add it to moduleRoots.`, + }); + } else { + result.errors.push({ + loc, + message: `Unable to resolve ${expr.name}`, + }); + } return null; } return result.locals[expr.name]; From cdd4ef326c57102991666086dd6dfb4735a4d5ff Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 13:45:07 -0600 Subject: [PATCH 05/10] [cross-module] arrange act assert --- src/parser/__test__/parse.test.ts | 29 ++++++++++++++++++++--------- src/parser/__test__/utils.test.ts | 9 ++++++++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index 1825451..51d5b8a 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -1,4 +1,7 @@ -import {describe, it, expect, jest} from "@jest/globals"; +/** + * @jest-environment node + */ +import {describe, it, expect} from "@jest/globals"; import fs from "fs"; import {Config} from "../../types"; @@ -386,6 +389,7 @@ describe("processing fragments in various ways", () => { }); it("should resolve fragments re-exported via export all", () => { + // Arrange const config: Config = { crawl: { root: "/here/we/crawl", @@ -412,20 +416,23 @@ describe("processing fragments in various ways", () => { schemaFilePath: "./composed_schema.graphql", }, }; + // Act const files = processFiles( ["/starExportConsumer.js"], config, getFileSource, ); - Object.keys(files).forEach((k: any) => { - expect(files[k].errors).toEqual([]); - }); const {resolved, errors} = resolveDocuments(files, config); - expect(errors).toEqual([]); const printed: Record = {}; Object.keys(resolved).map( (k: any) => (printed[k] = print(resolved[k].document).trim()), ); + + // Assert + Object.keys(files).forEach((k: any) => { + expect(files[k].errors).toEqual([]); + }); + expect(errors).toEqual([]); expect(printed).toMatchInlineSnapshot(` Object { "/starExportConsumer.js:4": "query StarQuery { @@ -445,6 +452,7 @@ describe("processing fragments in various ways", () => { }); it("should resolve fragments imported from monorepo packages", () => { + // Arrange const config: Config = { crawl: { root: "/here/we/crawl", @@ -503,20 +511,23 @@ describe("processing fragments in various ways", () => { .mockImplementation((value) => value.toString()); try { + // Act const files = processFiles( ["/repo/packages/app/App.js"], config, getFileSource, ); - Object.keys(files).forEach((k: any) => { - expect(files[k].errors).toEqual([]); - }); const {resolved, errors} = resolveDocuments(files, config); - expect(errors).toEqual([]); const printed: Record = {}; Object.keys(resolved).map( (k: any) => (printed[k] = print(resolved[k].document).trim()), ); + + // Assert + Object.keys(files).forEach((k: any) => { + expect(files[k].errors).toEqual([]); + }); + expect(errors).toEqual([]); expect(printed).toMatchInlineSnapshot(` Object { "/repo/node_modules/monorepo-package/fragment.js:4": "fragment SharedFields on Something { diff --git a/src/parser/__test__/utils.test.ts b/src/parser/__test__/utils.test.ts index c02d19f..aa9b86f 100644 --- a/src/parser/__test__/utils.test.ts +++ b/src/parser/__test__/utils.test.ts @@ -1,5 +1,8 @@ +/** + * @jest-environment node + */ import fs from "fs"; -import {describe, it, expect, jest} from "@jest/globals"; +import {describe, it, expect} from "@jest/globals"; import type {Config} from "../../types"; import {buildModuleMap, getPathWithExtension} from "../utils"; @@ -87,6 +90,7 @@ describe("buildModuleMap", () => { }); it("collects package.json names and ignores node_modules", () => { + // Arrange const existsSpy = jest .spyOn(fs, "existsSync") .mockImplementation((value) => { @@ -142,7 +146,10 @@ describe("buildModuleMap", () => { .mockImplementation((value) => value.toString()); try { + // Act const result = buildModuleMap(["/repo"]); + + // Assert expect(result).toEqual({ "root-package": "/repo", "app-package": "/repo/packages/app", From 7a6d8d03944606684c4738b6e786428599014e5d Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 15:07:21 -0600 Subject: [PATCH 06/10] [cross-module] crackle --- .changeset/modern-items-itch.md | 5 + package.json | 1 + pnpm-lock.yaml | 796 +++++++++++++++++++++++++++++++- 3 files changed, 787 insertions(+), 15 deletions(-) create mode 100644 .changeset/modern-items-itch.md diff --git a/.changeset/modern-items-itch.md b/.changeset/modern-items-itch.md new file mode 100644 index 0000000..34cfdb8 --- /dev/null +++ b/.changeset/modern-items-itch.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/graphql-flow": minor +--- + +Add support for cross-package fragment imports in monorepos diff --git a/package.json b/package.json index 53791ff..7aba420 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@babel/preset-env": "^7.24.5", "@babel/preset-typescript": "^7.24.1", "@changesets/cli": "^2.21.1", + "@jest/globals": "^30.2.0", "@khanacademy/eslint-config": "^4.0.0", "@types/jest": "^29.5.3", "@types/prop-types": "^15.7.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0bf2c0..4255664 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: '@changesets/cli': specifier: ^2.21.1 version: 2.21.1 + '@jest/globals': + specifier: ^30.2.0 + version: 30.2.0 '@khanacademy/eslint-config': specifier: ^4.0.0 version: 4.0.0(@khanacademy/eslint-plugin@3.2.2(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint-config-prettier@7.0.0(eslint@8.57.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-prettier@4.0.0(eslint-config-prettier@7.0.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.5.1))(eslint-plugin-react@7.37.5(eslint@8.57.0))(eslint@8.57.0) @@ -126,14 +129,26 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.26.8': resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.26.10': resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + '@babel/eslint-parser@7.17.0': resolution: {integrity: sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} @@ -145,6 +160,10 @@ packages: resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} @@ -153,6 +172,10 @@ packages: resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.27.0': resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==} engines: {node: '>=6.9.0'} @@ -170,6 +193,10 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.25.9': resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} @@ -178,12 +205,22 @@ packages: resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.26.0': resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.25.9': resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} @@ -192,6 +229,10 @@ packages: resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.25.9': resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} @@ -228,6 +269,10 @@ packages: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.25.9': resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} @@ -236,6 +281,10 @@ packages: resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.16.10': resolution: {integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==} engines: {node: '>=6.9.0'} @@ -245,6 +294,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} @@ -296,6 +350,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-assertions@7.26.0': resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} engines: {node: '>=6.9.0'} @@ -324,6 +384,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -354,6 +420,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -366,6 +438,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -707,10 +785,18 @@ packages: resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.27.0': resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.27.0': resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} @@ -719,6 +805,10 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -851,22 +941,54 @@ packages: node-notifier: optional: true + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment@27.5.1': resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@jest/environment@30.2.0': + resolution: {integrity: sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect-utils@29.6.1': resolution: {integrity: sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@30.2.0': + resolution: {integrity: sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.2.0': + resolution: {integrity: sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/fake-timers@27.5.1': resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@jest/fake-timers@30.2.0': + resolution: {integrity: sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/globals@27.5.1': resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@jest/globals@30.2.0': + resolution: {integrity: sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/reporters@27.5.1': resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -880,6 +1002,14 @@ packages: resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.2.0': + resolution: {integrity: sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/source-map@27.5.1': resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -896,6 +1026,10 @@ packages: resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@jest/transform@30.2.0': + resolution: {integrity: sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/types@27.5.1': resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -904,10 +1038,20 @@ packages: resolution: {integrity: sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@30.2.0': + resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -922,6 +1066,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@khanacademy/eslint-config@4.0.0': resolution: {integrity: sha512-4FHz/TyU+HIwlVUJ/MHyQ4poob2wnyA4x6XHzvMHj5zMvHcIaDPDDzbnbeAX4dtNQY5suHIFStBPwxhJZQghZg==} peerDependencies: @@ -971,15 +1118,28 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.34.48': + resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==} + '@sinonjs/commons@1.8.3': resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==} + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@sinonjs/fake-timers@8.1.0': resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} @@ -1014,12 +1174,18 @@ packages: '@types/istanbul-lib-coverage@2.0.4': resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + '@types/istanbul-lib-report@3.0.0': resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} '@types/istanbul-reports@3.0.1': resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jest@29.5.3': resolution: {integrity: sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==} @@ -1056,6 +1222,9 @@ packages: '@types/stack-utils@2.0.1': resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/yargs-parser@21.0.0': resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -1065,6 +1234,9 @@ packages: '@types/yargs@17.0.24': resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@7.17.0': resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1196,6 +1368,9 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -1374,6 +1549,10 @@ packages: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} engines: {node: '>= 8'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + apollo-utilities@1.3.4: resolution: {integrity: sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==} peerDependencies: @@ -1500,6 +1679,10 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} + engines: {node: '>=12'} + babel-plugin-jest-hoist@23.2.0: resolution: {integrity: sha512-N0MlMjZtahXK0yb0K3V9hWPrq5e7tThbghvDr0k3X75UuOOqwsWW6mk8XHD2QvEC0Ca9dLIfTgNU36TeJD6Hnw==} @@ -1530,6 +1713,11 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + babel-preset-jest@23.2.0: resolution: {integrity: sha512-AdfWwc0PYvDtwr009yyVNh72Ev68os7SsPmOFVX7zSA+STXuk5CV2iMVazZU01bEoHCSwTkgv4E4HOOcODPkPg==} @@ -1670,6 +1858,10 @@ packages: ci-info@3.3.0: resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==} + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + cjs-module-lexer@1.2.2: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} @@ -2160,6 +2352,10 @@ packages: resolution: {integrity: sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + expect@30.2.0: + resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -2197,6 +2393,9 @@ packages: fb-watchman@2.0.1: resolution: {integrity: sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==} + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2287,6 +2486,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} @@ -2381,6 +2585,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graceful-fs@4.2.9: resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==} @@ -2738,6 +2945,10 @@ packages: resolution: {integrity: sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==} engines: {node: '>=8'} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + istanbul-lib-report@3.0.0: resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} engines: {node: '>=8'} @@ -2789,6 +3000,10 @@ packages: resolution: {integrity: sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-diff@30.2.0: + resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-docblock@27.5.1: resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2817,6 +3032,10 @@ packages: resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + jest-haste-map@30.2.0: + resolution: {integrity: sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-jasmine2@27.5.1: resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2833,6 +3052,10 @@ packages: resolution: {integrity: sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@30.2.0: + resolution: {integrity: sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@27.5.1: resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2841,10 +3064,18 @@ packages: resolution: {integrity: sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@30.2.0: + resolution: {integrity: sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@27.5.1: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + jest-mock@30.2.0: + resolution: {integrity: sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-pnp-resolver@1.2.2: resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} engines: {node: '>=6'} @@ -2858,6 +3089,10 @@ packages: resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-resolve-dependencies@27.5.1: resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2882,6 +3117,10 @@ packages: resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + jest-snapshot@30.2.0: + resolution: {integrity: sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-util@27.5.1: resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2890,6 +3129,10 @@ packages: resolution: {integrity: sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-util@30.2.0: + resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-validate@27.5.1: resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -2902,6 +3145,10 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} + jest-worker@30.2.0: + resolution: {integrity: sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest@27.5.1: resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -3385,6 +3632,10 @@ packages: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -3438,6 +3689,10 @@ packages: resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@30.2.0: + resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + private@0.1.8: resolution: {integrity: sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==} engines: {node: '>= 0.6'} @@ -3483,6 +3738,9 @@ packages: react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + read-pkg-up@1.0.1: resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} engines: {node: '>=0.10.0'} @@ -3720,6 +3978,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3787,6 +4049,10 @@ packages: resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==} engines: {node: '>=10'} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -3884,6 +4150,10 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + table@6.8.0: resolution: {integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==} engines: {node: '>=10.0.0'} @@ -4116,6 +4386,7 @@ packages: whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@2.3.0: resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} @@ -4174,6 +4445,10 @@ packages: write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ws@7.5.7: resolution: {integrity: sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==} engines: {node: '>=8.3.0'} @@ -4257,8 +4532,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.26.8': {} + '@babel/compat-data@7.28.6': {} + '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 @@ -4279,6 +4562,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/eslint-parser@7.17.0(@babel/core@7.26.10)(eslint@8.57.0)': dependencies: '@babel/core': 7.26.10 @@ -4295,6 +4598,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.25.9': dependencies: '@babel/types': 7.27.0 @@ -4307,6 +4618,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4338,6 +4657,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.25.9': dependencies: '@babel/traverse': 7.27.0 @@ -4352,6 +4673,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4361,12 +4689,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.25.9': dependencies: '@babel/types': 7.27.0 '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4402,11 +4741,13 @@ snapshots: '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-wrap-function@7.25.9': dependencies: '@babel/template': 7.27.0 '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -4415,6 +4756,11 @@ snapshots: '@babel/template': 7.27.0 '@babel/types': 7.27.0 + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + '@babel/highlight@7.16.10': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -4425,6 +4771,10 @@ snapshots: dependencies: '@babel/types': 7.27.0 + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4469,16 +4819,36 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4489,61 +4859,126 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4988,6 +5423,12 @@ snapshots: '@babel/parser': 7.27.0 '@babel/types': 7.27.0 + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@babel/traverse@7.27.0': dependencies: '@babel/code-frame': 7.26.2 @@ -5000,6 +5441,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.27.0': dependencies: '@babel/helper-string-parser': 7.25.9 @@ -5010,6 +5463,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@0.2.3': {} '@changesets/apply-release-plan@5.0.5': @@ -5292,6 +5750,8 @@ snapshots: - ts-node - utf-8-validate + '@jest/diff-sequences@30.0.1': {} + '@jest/environment@27.5.1': dependencies: '@jest/fake-timers': 27.5.1 @@ -5299,10 +5759,28 @@ snapshots: '@types/node': 17.0.21 jest-mock: 27.5.1 + '@jest/environment@30.2.0': + dependencies: + '@jest/fake-timers': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 17.0.21 + jest-mock: 30.2.0 + '@jest/expect-utils@29.6.1': dependencies: jest-get-type: 29.4.3 + '@jest/expect-utils@30.2.0': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.2.0': + dependencies: + expect: 30.2.0 + jest-snapshot: 30.2.0 + transitivePeerDependencies: + - supports-color + '@jest/fake-timers@27.5.1': dependencies: '@jest/types': 27.5.1 @@ -5312,12 +5790,37 @@ snapshots: jest-mock: 27.5.1 jest-util: 27.5.1 + '@jest/fake-timers@30.2.0': + dependencies: + '@jest/types': 30.2.0 + '@sinonjs/fake-timers': 13.0.5 + '@types/node': 17.0.21 + jest-message-util: 30.2.0 + jest-mock: 30.2.0 + jest-util: 30.2.0 + + '@jest/get-type@30.1.0': {} + '@jest/globals@27.5.1': dependencies: '@jest/environment': 27.5.1 '@jest/types': 27.5.1 expect: 27.5.1 + '@jest/globals@30.2.0': + dependencies: + '@jest/environment': 30.2.0 + '@jest/expect': 30.2.0 + '@jest/types': 30.2.0 + jest-mock: 30.2.0 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 17.0.21 + jest-regex-util: 30.0.1 + '@jest/reporters@27.5.1': dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -5352,6 +5855,17 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.8 + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.48 + + '@jest/snapshot-utils@30.2.0': + dependencies: + '@jest/types': 30.2.0 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + '@jest/source-map@27.5.1': dependencies: callsites: 3.1.0 @@ -5394,6 +5908,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/transform@30.2.0': + dependencies: + '@babel/core': 7.28.6 + '@jest/types': 30.2.0 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.2.0 + jest-regex-util: 30.0.1 + jest-util: 30.2.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + '@jest/types@27.5.1': dependencies: '@types/istanbul-lib-coverage': 2.0.4 @@ -5411,12 +5945,32 @@ snapshots: '@types/yargs': 17.0.24 chalk: 4.1.2 + '@jest/types@30.2.0': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 17.0.21 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} @@ -5428,6 +5982,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@khanacademy/eslint-config@4.0.0(@khanacademy/eslint-plugin@3.2.2(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6))(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint-config-prettier@7.0.0(eslint@8.57.0))(eslint-import-resolver-typescript@3.10.1)(eslint-plugin-import@2.32.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0))(eslint-plugin-prettier@4.0.0(eslint-config-prettier@7.0.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.5.1))(eslint-plugin-react@7.37.5(eslint@8.57.0))(eslint@8.57.0)': dependencies: '@khanacademy/eslint-plugin': 3.2.2(eslint@8.57.0)(typescript@5.1.6) @@ -5443,7 +6002,7 @@ snapshots: '@khanacademy/eslint-plugin@3.2.2(eslint@8.57.0)(typescript@5.1.6)': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@typescript-eslint/utils': 8.46.4(eslint@8.57.0)(typescript@5.1.6) ancesdir: 7.2.0 checksync: 10.0.2 @@ -5494,14 +6053,26 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@pkgr/core@0.2.9': {} + '@rtsao/scc@1.1.0': {} '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.34.48': {} + '@sinonjs/commons@1.8.3': dependencies: type-detect: 4.0.8 + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers@8.1.0': dependencies: '@sinonjs/commons': 1.8.3 @@ -5516,23 +6087,23 @@ snapshots: '@types/babel__core@7.1.18': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.14.2 '@types/babel__generator@7.6.4': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 '@types/babel__template@7.4.1': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 '@types/babel__traverse@7.14.2': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 '@types/eslint-visitor-keys@1.0.0': {} @@ -5546,14 +6117,20 @@ snapshots: '@types/istanbul-lib-coverage@2.0.4': {} + '@types/istanbul-lib-coverage@2.0.6': {} + '@types/istanbul-lib-report@3.0.0': dependencies: - '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports@3.0.1': dependencies: '@types/istanbul-lib-report': 3.0.0 + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.0 + '@types/jest@29.5.3': dependencies: expect: 29.6.1 @@ -5584,6 +6161,8 @@ snapshots: '@types/stack-utils@2.0.1': {} + '@types/stack-utils@2.0.3': {} + '@types/yargs-parser@21.0.0': {} '@types/yargs@16.0.4': @@ -5594,6 +6173,10 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.0 + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.0 + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6)': dependencies: '@eslint-community/regexpp': 4.11.0 @@ -5654,7 +6237,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.1.6) '@typescript-eslint/types': 8.46.4 - debug: 4.3.5 + debug: 4.4.3 typescript: 5.1.6 transitivePeerDependencies: - supports-color @@ -5727,11 +6310,11 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.1.6) '@typescript-eslint/types': 8.46.4 '@typescript-eslint/visitor-keys': 8.46.4 - debug: 4.3.5 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 2.1.0(typescript@5.1.6) typescript: 5.1.6 transitivePeerDependencies: @@ -5775,6 +6358,8 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -5861,7 +6446,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.5 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -5910,6 +6495,11 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + apollo-utilities@1.3.4(graphql@16.9.0): dependencies: '@wry/equality': 0.1.11 @@ -6114,12 +6704,22 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.26.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + babel-plugin-jest-hoist@23.2.0: {} babel-plugin-jest-hoist@27.5.1: dependencies: '@babel/template': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.5 '@types/babel__core': 7.1.18 '@types/babel__traverse': 7.14.2 @@ -6165,6 +6765,25 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.28.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6) + babel-preset-jest@23.2.0: dependencies: babel-plugin-jest-hoist: 23.2.0 @@ -6353,6 +6972,8 @@ snapshots: ci-info@3.3.0: {} + ci-info@4.3.1: {} + cjs-module-lexer@1.2.2: {} cliui@6.0.0: @@ -6474,7 +7095,7 @@ snapshots: debug@3.2.7: dependencies: - ms: 2.1.2 + ms: 2.1.3 debug@4.3.5: dependencies: @@ -6996,6 +7617,15 @@ snapshots: jest-message-util: 29.6.1 jest-util: 29.6.1 + expect@30.2.0: + dependencies: + '@jest/expect-utils': 30.2.0 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.2.0 + jest-message-util: 30.2.0 + jest-mock: 30.2.0 + jest-util: 30.2.0 + extendable-error@0.1.7: {} external-editor@3.1.0: @@ -7040,6 +7670,10 @@ snapshots: dependencies: bser: 2.1.1 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7132,6 +7766,9 @@ snapshots: fsevents@2.3.2: optional: true + fsevents@2.3.3: + optional: true + function-bind@1.1.1: {} function-bind@1.1.2: {} @@ -7237,6 +7874,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + graceful-fs@4.2.9: {} grapheme-splitter@1.0.4: {} @@ -7300,14 +7939,14 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@5.0.0: dependencies: agent-base: 6.0.2 - debug: 4.3.5 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -7567,6 +8206,16 @@ snapshots: transitivePeerDependencies: - supports-color + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.27.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + istanbul-lib-report@3.0.0: dependencies: istanbul-lib-coverage: 3.2.0 @@ -7692,6 +8341,13 @@ snapshots: jest-get-type: 29.4.3 pretty-format: 29.6.1 + jest-diff@30.2.0: + dependencies: + '@jest/diff-sequences': 30.0.1 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.2.0 + jest-docblock@27.5.1: dependencies: detect-newline: 3.1.0 @@ -7749,6 +8405,21 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + jest-haste-map@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 17.0.21 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.2.0 + jest-worker: 30.2.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + jest-jasmine2@27.5.1: dependencies: '@jest/environment': 27.5.1 @@ -7790,6 +8461,13 @@ snapshots: jest-get-type: 29.4.3 pretty-format: 29.6.1 + jest-matcher-utils@30.2.0: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.2.0 + pretty-format: 30.2.0 + jest-message-util@27.5.1: dependencies: '@babel/code-frame': 7.26.2 @@ -7814,17 +8492,37 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.5 + jest-message-util@30.2.0: + dependencies: + '@babel/code-frame': 7.28.6 + '@jest/types': 30.2.0 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 30.2.0 + slash: 3.0.0 + stack-utils: 2.0.6 + jest-mock@27.5.1: dependencies: '@jest/types': 27.5.1 '@types/node': 17.0.21 + jest-mock@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 17.0.21 + jest-util: 30.2.0 + jest-pnp-resolver@1.2.2(jest-resolve@27.5.1): optionalDependencies: jest-resolve: 27.5.1 jest-regex-util@27.5.1: {} + jest-regex-util@30.0.1: {} + jest-resolve-dependencies@27.5.1: dependencies: '@jest/types': 27.5.1 @@ -7934,6 +8632,32 @@ snapshots: transitivePeerDependencies: - supports-color + jest-snapshot@30.2.0: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/types': 7.28.5 + '@jest/expect-utils': 30.2.0 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + chalk: 4.1.2 + expect: 30.2.0 + graceful-fs: 4.2.11 + jest-diff: 30.2.0 + jest-matcher-utils: 30.2.0 + jest-message-util: 30.2.0 + jest-util: 30.2.0 + pretty-format: 30.2.0 + semver: 7.7.3 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color + jest-util@27.5.1: dependencies: '@jest/types': 27.5.1 @@ -7952,6 +8676,15 @@ snapshots: graceful-fs: 4.2.9 picomatch: 2.3.1 + jest-util@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 17.0.21 + chalk: 4.1.2 + ci-info: 4.3.1 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + jest-validate@27.5.1: dependencies: '@jest/types': 27.5.1 @@ -7977,6 +8710,14 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jest-worker@30.2.0: + dependencies: + '@types/node': 17.0.21 + '@ungap/structured-clone': 1.3.0 + jest-util: 30.2.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + jest@27.5.1: dependencies: '@jest/core': 27.5.1 @@ -8470,6 +9211,8 @@ snapshots: pirates@4.0.5: {} + pirates@4.0.7: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -8531,6 +9274,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 + pretty-format@30.2.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + private@0.1.8: {} progress@2.0.3: {} @@ -8568,6 +9317,8 @@ snapshots: react-is@18.2.0: {} + react-is@18.3.1: {} + read-pkg-up@1.0.1: dependencies: find-up: 1.1.2 @@ -8825,6 +9576,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + sisteransi@1.0.5: {} slash@1.0.0: {} @@ -8889,6 +9642,10 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -9006,6 +9763,10 @@ snapshots: symbol-tree@3.2.4: {} + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + table@6.8.0: dependencies: ajv: 8.10.0 @@ -9359,6 +10120,11 @@ snapshots: signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + ws@7.5.7: {} xml-name-validator@3.0.0: {} From e6b4f8e13931e344b66fe36f2ed6d4ebb3024b21 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 16:33:26 -0600 Subject: [PATCH 07/10] [cross-module] tap --- package.json | 1 + pnpm-lock.yaml | 72 +++++++++++++++++++++------------------------ src/parser/parse.ts | 3 +- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 7aba420..284cfa0 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@jest/globals": "^30.2.0", "@khanacademy/eslint-config": "^4.0.0", "@types/jest": "^29.5.3", + "@types/minimist": "^1.2.5", "@types/prop-types": "^15.7.12", "@types/react": "^18.3.3", "@typescript-eslint/eslint-plugin": "^7.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4255664..499a379 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@types/jest': specifier: ^29.5.3 version: 29.5.3 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 '@types/prop-types': specifier: ^15.7.12 version: 15.7.12 @@ -801,10 +804,6 @@ packages: resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.6': resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} @@ -1153,14 +1152,14 @@ packages: '@types/babel__core@7.1.18': resolution: {integrity: sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==} - '@types/babel__generator@7.6.4': - resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.1': resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} - '@types/babel__traverse@7.14.2': - resolution: {integrity: sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==} + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} '@types/eslint-visitor-keys@1.0.0': resolution: {integrity: sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==} @@ -1195,8 +1194,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/minimist@1.2.2': - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} '@types/node@12.20.47': resolution: {integrity: sha512-BzcaRsnFuznzOItW1WpQrDHM7plAa7GIDMZ6b5pnMbkqEtM/6WCOhvZar39oeMQP79gwvFUWjjptE7/KGcNqFg==} @@ -4608,7 +4607,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 '@babel/helper-compilation-targets@7.27.0': dependencies: @@ -4662,14 +4661,14 @@ snapshots: '@babel/helper-member-expression-to-functions@7.25.9': dependencies: '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color @@ -4700,7 +4699,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 '@babel/helper-plugin-utils@7.26.5': {} @@ -4727,7 +4726,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color @@ -4747,14 +4746,14 @@ snapshots: dependencies: '@babel/template': 7.27.0 '@babel/traverse': 7.27.0 - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color '@babel/helpers@7.27.0': dependencies: '@babel/template': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 '@babel/helpers@7.28.6': dependencies: @@ -4763,7 +4762,7 @@ snapshots: '@babel/highlight@7.16.10': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.28.5 chalk: 2.4.2 js-tokens: 4.0.0 @@ -5421,7 +5420,7 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 '@babel/template@7.28.6': dependencies: @@ -5458,11 +5457,6 @@ snapshots: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.28.6': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -6087,23 +6081,23 @@ snapshots: '@types/babel__core@7.1.18': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.28.5 - '@types/babel__generator': 7.6.4 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.14.2 + '@types/babel__traverse': 7.28.0 - '@types/babel__generator@7.6.4': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@types/babel__template@7.4.1': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 - '@types/babel__traverse@7.14.2': + '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@types/eslint-visitor-keys@1.0.0': {} @@ -6140,7 +6134,7 @@ snapshots: '@types/json5@0.0.29': {} - '@types/minimist@1.2.2': {} + '@types/minimist@1.2.5': {} '@types/node@12.20.47': {} @@ -6719,9 +6713,9 @@ snapshots: babel-plugin-jest-hoist@27.5.1: dependencies: '@babel/template': 7.27.0 - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@types/babel__core': 7.1.18 - '@types/babel__traverse': 7.14.2 + '@types/babel__traverse': 7.28.0 babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): dependencies: @@ -8611,10 +8605,10 @@ snapshots: '@babel/generator': 7.27.0 '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) '@babel/traverse': 7.27.0 - '@babel/types': 7.27.0 + '@babel/types': 7.28.6 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/babel__traverse': 7.14.2 + '@types/babel__traverse': 7.28.0 '@types/prettier': 2.4.4 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) chalk: 4.1.2 @@ -8638,7 +8632,7 @@ snapshots: '@babel/generator': 7.28.6 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 '@jest/expect-utils': 30.2.0 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.2.0 @@ -8921,7 +8915,7 @@ snapshots: meow@6.1.1: dependencies: - '@types/minimist': 1.2.2 + '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 decamelize-keys: 1.1.0 hard-rejection: 2.1.0 diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 0bd63d3..8a116cd 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -8,6 +8,7 @@ import type { import {parse, ParserPlugin} from "@babel/parser"; import traverse from "@babel/traverse"; +import type {NodePath} from "@babel/traverse"; import path from "path"; @@ -375,7 +376,7 @@ export const processFile = ( traverse(ast as any, { TaggedTemplateExpression(path) { - visitTpl(path.node as any, (name) => { + visitTpl(path.node, (name) => { const binding = path.scope.getBinding(name); if (!binding) { return null; From 4a369b9301002c10dad74523081dd3f6ea55fe07 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Fri, 23 Jan 2026 16:40:27 -0600 Subject: [PATCH 08/10] [cross-module] hm, --- src/parser/parse.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 8a116cd..54f84f6 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -375,8 +375,9 @@ export const processFile = ( }; traverse(ast as any, { - TaggedTemplateExpression(path) { - visitTpl(path.node, (name) => { + TaggedTemplateExpression(path: NodePath) { + const node = path.node as TaggedTemplateExpression; + visitTpl(node, (name) => { const binding = path.scope.getBinding(name); if (!binding) { return null; From 59cd5051f4dcf1217c1482618992a2a2ee206c09 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 4 Feb 2026 13:23:26 -0600 Subject: [PATCH 09/10] [cross-module-2] better --- Readme.md | 4 +- package.json | 4 +- pnpm-lock.yaml | 47 +++------ schema.json | 6 -- src/cli/run.ts | 6 +- src/parser/__test__/parse.test.ts | 48 +++------ src/parser/__test__/utils.test.ts | 110 ++++++-------------- src/parser/parse.ts | 3 +- src/parser/utils.ts | 161 +++--------------------------- src/types.ts | 10 -- 10 files changed, 83 insertions(+), 316 deletions(-) diff --git a/Readme.md b/Readme.md index 392d2b7..04fbfe3 100644 --- a/Readme.md +++ b/Readme.md @@ -45,8 +45,7 @@ module.exports = { root: "../../", }, // Allow resolving fragments imported from workspace packages. - // moduleRoots are crawled once at startup to map package names. - moduleRoots: ["../../"], + // Uses Node-style resolution (workspace packages via node_modules/symlinks). generate: [ { ...options, @@ -64,6 +63,7 @@ module.exports = { ``` ## Introspecting your backend's graphql schema + Here's how to get your backend's schema in the way that this tool expects, using the builtin 'graphql introspection query': ```js diff --git a/package.json b/package.json index 284cfa0..8118371 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@types/minimist": "^1.2.5", "@types/prop-types": "^15.7.12", "@types/react": "^18.3.3", + "@types/resolve": "^1.20.6", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "babel-jest": "23.4.2", @@ -63,7 +64,8 @@ "apollo-utilities": "^1.3.4", "graphql": "^16.9.0", "jsonschema": "^1.4.1", - "minimist": "^1.2.8" + "minimist": "^1.2.8", + "resolve": "^1.22.8" }, "packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 499a379..0dbda40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: minimist: specifier: ^1.2.8 version: 1.2.8 + resolve: + specifier: ^1.22.8 + version: 1.22.11 devDependencies: '@babel/cli': specifier: ^7.17.6 @@ -75,6 +78,9 @@ importers: '@types/react': specifier: ^18.3.3 version: 18.3.3 + '@types/resolve': + specifier: ^1.20.6 + version: 1.20.6 '@typescript-eslint/eslint-plugin': specifier: ^7.17.0 version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6) @@ -1215,6 +1221,9 @@ packages: '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/semver@6.2.3': resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==} @@ -2490,9 +2499,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2640,10 +2646,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -2761,9 +2763,6 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-core-module@2.8.1: - resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} - is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} @@ -3862,10 +3861,6 @@ packages: resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} engines: {node: '>=10'} - resolve@1.22.0: - resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} - hasBin: true - resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -4652,7 +4647,7 @@ snapshots: '@babel/helper-plugin-utils': 7.26.5 debug: 4.3.5 lodash.debounce: 4.0.8 - resolve: 1.22.0 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -6151,6 +6146,8 @@ snapshots: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/resolve@1.20.6': {} + '@types/semver@6.2.3': {} '@types/stack-utils@2.0.1': {} @@ -7763,8 +7760,6 @@ snapshots: fsevents@2.3.3: optional: true - function-bind@1.1.1: {} - function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -7908,10 +7903,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - has@1.0.3: - dependencies: - function-bind: 1.1.1 - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -8033,10 +8024,6 @@ snapshots: dependencies: hasown: 2.0.2 - is-core-module@2.8.1: - dependencies: - has: 1.0.3 - is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -8534,7 +8521,7 @@ snapshots: jest-pnp-resolver: 1.2.2(jest-resolve@27.5.1) jest-util: 27.5.1 jest-validate: 27.5.1 - resolve: 1.22.0 + resolve: 1.22.11 resolve.exports: 1.1.0 slash: 3.0.0 @@ -9006,7 +8993,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.0 + resolve: 1.22.11 semver: 5.7.1 validate-npm-package-license: 3.0.4 @@ -9441,12 +9428,6 @@ snapshots: resolve.exports@1.1.0: {} - resolve@1.22.0: - dependencies: - is-core-module: 2.8.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.11: dependencies: is-core-module: 2.16.1 diff --git a/schema.json b/schema.json index afa2c6b..3762cec 100644 --- a/schema.json +++ b/schema.json @@ -107,12 +107,6 @@ }, "required": ["find", "replacement"] } - }, - "moduleRoots": { - "type": "array", - "items": { - "type": "string" - } } }, "required": ["crawl", "generate"] diff --git a/src/cli/run.ts b/src/cli/run.ts index 643f958..f8568e4 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -6,7 +6,7 @@ import type {GraphQLSchema} from "graphql/type/schema"; import {generateTypeFiles, processPragmas} from "../generateTypeFiles"; import {processFiles} from "../parser/parse"; import {resolveDocuments} from "../parser/resolve"; -import {buildModuleMap, getPathWithExtension} from "../parser/utils"; +import {getPathWithExtension} from "../parser/utils"; import { findApplicableConfig, getInputFiles, @@ -55,10 +55,6 @@ const inputFiles = getInputFiles(options, config); /** Step (2) */ -if (config.moduleRoots && config.moduleRoots.length > 0) { - config.moduleMap = buildModuleMap(config.moduleRoots); -} - const files = processFiles(inputFiles, config, (f) => { const resolvedPath = getPathWithExtension(f, config); if (!resolvedPath) { diff --git a/src/parser/__test__/parse.test.ts b/src/parser/__test__/parse.test.ts index 51d5b8a..1f3e2bd 100644 --- a/src/parser/__test__/parse.test.ts +++ b/src/parser/__test__/parse.test.ts @@ -2,7 +2,7 @@ * @jest-environment node */ import {describe, it, expect} from "@jest/globals"; -import fs from "fs"; +import resolve from "resolve"; import {Config} from "../../types"; import {processFiles} from "../parse"; @@ -10,6 +10,8 @@ import {resolveDocuments} from "../resolve"; import {print} from "graphql/language/printer"; +jest.mock("resolve"); + const fixtureFiles: { [key: string]: | string @@ -330,7 +332,7 @@ describe("processing fragments in various ways", () => { expect(files["/invalidThings.js"].errors.map((m: any) => m.message)) .toMatchInlineSnapshot(` Array [ - "Unable to resolve import someExternalFragment from \"somewhere\" at /invalidThings.js:4. If this is a local package, add it to moduleRoots.", + "Unable to resolve import someExternalFragment from \\\"somewhere\\\" at /invalidThings.js:4.", "Unable to resolve someUndefinedFragment", "Template literal interpolation must be an identifier", ] @@ -435,7 +437,7 @@ describe("processing fragments in various ways", () => { expect(errors).toEqual([]); expect(printed).toMatchInlineSnapshot(` Object { - "/starExportConsumer.js:4": "query StarQuery { + "/starExportConsumer.js:5": "query StarQuery { stars { ...StarFragment } @@ -478,37 +480,15 @@ describe("processing fragments in various ways", () => { exportAllObjectTypes: true, schemaFilePath: "./composed_schema.graphql", }, - moduleRoots: ["/repo"], - moduleMap: { - "monorepo-package": "/repo/node_modules/monorepo-package", - }, }; - const existsSpy = jest - .spyOn(fs, "existsSync") - .mockImplementation((path) => { - if (typeof path !== "string") { - return false; - } - return ( - path === - "/repo/node_modules/monorepo-package/package.json" || - path === "/repo/node_modules/monorepo-package/fragment.js" - ); - }); - const readSpy = jest - .spyOn(fs, "readFileSync") - .mockImplementation((path) => { - if ( - path === "/repo/node_modules/monorepo-package/package.json" - ) { - return JSON.stringify({name: "monorepo-package"}); - } - throw new Error(`Unexpected readFileSync for ${path}`); - }); - const realpathSpy = jest - .spyOn(fs, "realpathSync") - .mockImplementation((value) => value.toString()); + const resolveSync = resolve.sync as jest.Mock; + resolveSync.mockImplementation((specifier: string) => { + if (specifier === "monorepo-package/fragment") { + return "/repo/node_modules/monorepo-package/fragment.js"; + } + throw new Error(`Unexpected resolve for ${specifier}`); + }); try { // Act @@ -545,9 +525,7 @@ describe("processing fragments in various ways", () => { } `); } finally { - existsSpy.mockRestore(); - readSpy.mockRestore(); - realpathSpy.mockRestore(); + resolveSync.mockReset(); } }); }); diff --git a/src/parser/__test__/utils.test.ts b/src/parser/__test__/utils.test.ts index aa9b86f..b495a05 100644 --- a/src/parser/__test__/utils.test.ts +++ b/src/parser/__test__/utils.test.ts @@ -5,7 +5,10 @@ import fs from "fs"; import {describe, it, expect} from "@jest/globals"; import type {Config} from "../../types"; -import {buildModuleMap, getPathWithExtension} from "../utils"; +import resolve from "resolve"; +import {getPathWithExtension, resolveImportPath} from "../utils"; + +jest.mock("resolve"); const generate = { match: [/\.fixture\.js$/], @@ -82,84 +85,35 @@ describe("getPathWithExtension", () => { }); }); -describe("buildModuleMap", () => { - const makeDirent = (name: string, isDir: boolean) => ({ - name, - isDirectory: () => isDir, - isFile: () => !isDir, +describe("resolveImportPath", () => { + const resolveSync = resolve.sync as jest.Mock; + + beforeEach(() => { + resolveSync.mockReset(); }); - it("collects package.json names and ignores node_modules", () => { - // Arrange - const existsSpy = jest - .spyOn(fs, "existsSync") - .mockImplementation((value) => { - return ( - value === "/repo/package.json" || - value === "/repo/packages/app/package.json" || - value === "/repo/packages/shared/package.json" || - value === "/repo/node_modules/ignore-me/package.json" - ); - }); - const readSpy = jest - .spyOn(fs, "readFileSync") - .mockImplementation((value) => { - if (value === "/repo/package.json") { - return JSON.stringify({name: "root-package"}); - } - if (value === "/repo/packages/app/package.json") { - return JSON.stringify({name: "app-package"}); - } - if (value === "/repo/packages/shared/package.json") { - return JSON.stringify({name: "@scope/shared"}); - } - if (value === "/repo/node_modules/ignore-me/package.json") { - return JSON.stringify({name: "ignore-me"}); - } - throw new Error(`Unexpected readFileSync for ${value}`); - }); - const readdirSpy = jest - .spyOn(fs, "readdirSync") - .mockImplementation((value) => { - if (value === "/repo") { - return [ - makeDirent("packages", true), - makeDirent("node_modules", true), - ] as Array; - } - if (value === "/repo/packages") { - return [ - makeDirent("app", true), - makeDirent("shared", true), - ] as Array; - } - if ( - value === "/repo/packages/app" || - value === "/repo/packages/shared" - ) { - return [] as Array; - } - return [] as Array; - }); - const realpathSpy = jest - .spyOn(fs, "realpathSync") - .mockImplementation((value) => value.toString()); - - try { - // Act - const result = buildModuleMap(["/repo"]); - - // Assert - expect(result).toEqual({ - "root-package": "/repo", - "app-package": "/repo/packages/app", - "@scope/shared": "/repo/packages/shared", - }); - } finally { - existsSpy.mockRestore(); - readSpy.mockRestore(); - readdirSpy.mockRestore(); - realpathSpy.mockRestore(); - } + it("returns null for graphql-tag without invoking resolve", () => { + const result = resolveImportPath("graphql-tag", "/from", config); + expect(result).toBeNull(); + expect(resolveSync).not.toHaveBeenCalled(); + }); + + it("resolves relative paths via node resolution", () => { + resolveSync.mockReturnValue("/from/fragment.ts"); + const result = resolveImportPath("./fragment", "/from", config); + expect(result).toBe("/from/fragment"); + expect(resolveSync).not.toHaveBeenCalled(); + }); + + it("resolves package specifiers via node resolution", () => { + resolveSync.mockReturnValue("/repo/node_modules/pkg/fragment.js"); + const result = resolveImportPath("pkg/fragment", "/from", config); + expect(result).toBe("/repo/node_modules/pkg/fragment.js"); + expect(resolveSync).toHaveBeenCalledWith("pkg/fragment", { + basedir: "/from", + extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"], + paths: undefined, + preserveSymlinks: false, + }); }); }); diff --git a/src/parser/parse.ts b/src/parser/parse.ts index 54f84f6..d4d49a5 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -153,6 +153,7 @@ const listExternalReferences = ( ), ), ); + file.exportAlls.forEach((expr) => add(expr, true)); return Object.keys(paths); }; @@ -430,7 +431,7 @@ const processTemplate = ( if (unresolved) { result.errors.push({ loc: unresolved.loc, - message: `Unable to resolve import ${expr.name} from "${unresolved.source}" at ${unresolved.loc.path}:${unresolved.loc.line}. If this is a local package, add it to moduleRoots.`, + message: `Unable to resolve import ${expr.name} from "${unresolved.source}" at ${unresolved.loc.path}:${unresolved.loc.line}.`, }); } else { result.errors.push({ diff --git a/src/parser/utils.ts b/src/parser/utils.ts index a674725..bef07d4 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,8 +1,9 @@ import fs from "fs"; import path from "path"; +import resolve from "resolve"; import {Config} from "../types"; -export type ModuleMap = {[key: string]: string}; +const RESOLVE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"]; export const fixPathResolution = (path: string, config: Config) => { if (config.alias) { @@ -13,168 +14,38 @@ export const fixPathResolution = (path: string, config: Config) => { return path; }; -const parseModuleSpecifier = (specifier: string) => { - if (specifier.startsWith("@")) { - const parts = specifier.split("/"); - return { - moduleName: parts.slice(0, 2).join("/"), - subpath: parts.slice(2).join("/"), - }; - } - const parts = specifier.split("/"); - return { - moduleName: parts[0], - subpath: parts.slice(1).join("/"), - }; -}; - -const tryReadPackageJson = (packageJsonPath: string) => { - if (!fs.existsSync(packageJsonPath)) { - return null; - } +const resolveWithNode = ( + specifier: string, + fromDir: string, + config: Config, +): string | null => { try { - return JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + return resolve.sync(specifier, { + basedir: fromDir, + extensions: RESOLVE_EXTENSIONS, + preserveSymlinks: false, + }); } catch (err) { return null; } }; -const resolvePackageRoot = (moduleName: string, moduleRoots: Array) => { - for (const root of moduleRoots) { - const nodeModulesPath = path.join(root, "node_modules", moduleName); - const nodeModulesPkg = path.join(nodeModulesPath, "package.json"); - const nodeModulesJson = tryReadPackageJson(nodeModulesPkg); - if (nodeModulesJson?.name === moduleName) { - return fs.realpathSync(nodeModulesPath); - } - - const directPackageJson = path.join(root, "package.json"); - const directJson = tryReadPackageJson(directPackageJson); - if (directJson?.name === moduleName) { - return fs.realpathSync(root); - } - - if (moduleName.startsWith("@")) { - const [scope, name] = moduleName.split("/"); - const scopedRoot = path.join(root, scope, name); - const scopedPackageJson = path.join(scopedRoot, "package.json"); - const scopedJson = tryReadPackageJson(scopedPackageJson); - if (scopedJson?.name === moduleName) { - return fs.realpathSync(scopedRoot); - } - } else { - const namedRoot = path.join(root, moduleName); - const namedPackageJson = path.join(namedRoot, "package.json"); - const namedJson = tryReadPackageJson(namedPackageJson); - if (namedJson?.name === moduleName) { - return fs.realpathSync(namedRoot); - } - } - } - return null; -}; - -const getRealPath = (candidate: string) => { - try { - return fs.realpathSync(candidate); - } catch (err) { - return candidate; - } -}; - -export const buildModuleMap = (moduleRoots: Array): ModuleMap => { - const moduleMap: ModuleMap = {}; - const visited = new Set(); - const stack = moduleRoots.map((root) => getRealPath(root)); - while (stack.length) { - const current = stack.pop(); - if (!current || visited.has(current)) { - continue; - } - visited.add(current); - const packageJsonPath = path.join(current, "package.json"); - const packageJson = tryReadPackageJson(packageJsonPath); - if (packageJson?.name && typeof packageJson.name === "string") { - if (!moduleMap[packageJson.name]) { - moduleMap[packageJson.name] = current; - } - } - let entries: Array = []; - try { - entries = fs.readdirSync(current, {withFileTypes: true}); - } catch (err) { - continue; - } - entries.forEach((entry) => { - if (!entry.isDirectory()) { - return; - } - if (entry.name === "node_modules") { - return; - } - stack.push(path.join(current, entry.name)); - }); - } - return moduleMap; -}; - -const resolvePackageEntry = ( - packageRoot: string, - config: Config, - packageJson: any, -) => { - const candidates: Array = []; - const pushIfString = (value: unknown) => { - if (typeof value === "string") { - candidates.push(value); - } - }; - pushIfString(packageJson?.source); - pushIfString(packageJson?.module); - pushIfString(packageJson?.main); - pushIfString(packageJson?.types); - if (typeof packageJson?.exports === "string") { - candidates.push(packageJson.exports); - } - for (const entry of candidates) { - const resolved = getPathWithExtension( - path.resolve(packageRoot, entry), - config, - ); - if (resolved) { - return resolved; - } - } - return getPathWithExtension(path.join(packageRoot, "index"), config); -}; - export const resolveImportPath = ( rawImportPath: string, fromDir: string, config: Config, ) => { const fixedPath = fixPathResolution(rawImportPath, config); + if (fixedPath === "graphql-tag") { + return null; + } if (fixedPath.startsWith(".")) { return path.resolve(path.join(fromDir, fixedPath)); } if (path.isAbsolute(fixedPath)) { return fixedPath; } - const {moduleName, subpath} = parseModuleSpecifier(fixedPath); - let packageRoot = config.moduleMap?.[moduleName] ?? null; - if (!packageRoot && config.moduleRoots && config.moduleRoots.length > 0) { - packageRoot = resolvePackageRoot(moduleName, config.moduleRoots); - } - if (!packageRoot) { - return null; - } - if (subpath) { - return getPathWithExtension(path.join(packageRoot, subpath), config); - } - const packageJson = tryReadPackageJson( - path.join(packageRoot, "package.json"), - ); - return resolvePackageEntry(packageRoot, config, packageJson); + return resolveWithNode(fixedPath, fromDir, config); }; export const getPathWithExtension = ( diff --git a/src/types.ts b/src/types.ts index c4dc902..66e3619 100644 --- a/src/types.ts +++ b/src/types.ts @@ -54,16 +54,6 @@ export type Config = { find: RegExp | string; replacement: string; }>; - /** - * Absolute paths to monorepo roots or package roots used to resolve - * non-relative fragment imports (e.g. workspace packages). - */ - moduleRoots?: Array; - /** - * Internal map of package name to package root path. - * Populated from moduleRoots before processing files. - */ - moduleMap?: {[key: string]: string}; }; export type Schema = { From 1b1b9791061e212c93b6b9ebc0cd09ca647b9857 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 4 Feb 2026 15:06:52 -0600 Subject: [PATCH 10/10] [cross-module-2] extra --- Readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Readme.md b/Readme.md index 04fbfe3..5058576 100644 --- a/Readme.md +++ b/Readme.md @@ -44,8 +44,6 @@ module.exports = { crawl: { root: "../../", }, - // Allow resolving fragments imported from workspace packages. - // Uses Node-style resolution (workspace packages via node_modules/symlinks). generate: [ { ...options,