@sightmap/react 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/plugin/adapters/rr7-framework.ts","../../../src/plugin/router-adapter.ts"],"sourcesContent":["// src/plugin/adapters/rr7-framework.ts\n//\n// React Router 7 \"framework mode\" adapter — discovers routes from the\n// programmatic config in `app/routes.ts` (the API exported by\n// `@react-router/dev/routes`: `route`, `index`, `layout`, `prefix`).\n//\n// v0.1 scope:\n// - Reads `app/routes.ts` (or `.tsx`/`.js`/`.jsx`); the `app/` directory is\n// hardcoded. Custom `appDirectory` from `react-router.config.ts` is not yet\n// honored — projects using a different app-dir name fall through to \"no\n// supported router\".\n// - Recognizes top-level helpers by direct identifier name: `route`,\n// `index`, `layout`, `prefix`. Namespaced (`routes.route(...)`) or\n// re-exported / aliased helpers are not parsed.\n// - String-literal arguments only. Template-expression paths or computed\n// file specs are ignored.\n// - `flatRoutes()` (filesystem-based routing) is not supported in v0.1 —\n// projects relying on it will return zero routes.\n//\n// Layout semantics: `layout(file, [children])` adds the layout's component\n// tree to every descendant route's `sourceFiles`, so markers inside a layout\n// surface on every child route. The orchestrator's multi-route promotion then\n// hoists those markers into `shared.yaml`, matching the runtime where the\n// layout wraps each child via `<Outlet />`.\n\nimport { promises as fs, existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { parse as parseAst } from \"@swc/core\";\nimport type { RouterAdapter, AdapterContext, RouteDescriptor } from \"../router-adapter.js\";\nimport { deriveFeatureName } from \"../router-adapter.js\";\n\nconst ROUTES_CONFIG_CANDIDATES = [\n \"app/routes.ts\",\n \"app/routes.tsx\",\n \"app/routes.js\",\n \"app/routes.jsx\",\n];\n\nconst APP_DIR = \"app\";\n\nexport function rr7FrameworkAdapter(): RouterAdapter {\n return {\n name: \"react-router-7-framework\",\n detect(projectRoot: string): boolean {\n return findRoutesConfigSync(projectRoot) !== null;\n },\n async discoverRoutes(ctx: AdapterContext): Promise<RouteDescriptor[]> {\n const routesFile = await findRoutesConfig(ctx.projectRoot);\n if (!routesFile) return [];\n const appDir = join(ctx.projectRoot, APP_DIR);\n\n const source = await fs.readFile(routesFile, \"utf-8\");\n const ast = await parseAst(source, { syntax: \"typescript\", tsx: true, target: \"es2022\" });\n\n const defaultArray = findDefaultExportArray(ast);\n if (!defaultArray) return [];\n\n const collected: Array<{ pattern: string; sourceFiles: Set<string> }> = [];\n await walkRouteArray(defaultArray, { parentPattern: \"\", inheritedFiles: new Set() }, appDir, collected);\n\n return collected.map(({ pattern, sourceFiles }) => ({\n pattern,\n featureName: deriveFeatureName(pattern),\n sourceFiles: Array.from(sourceFiles),\n }));\n },\n };\n}\n\nfunction findRoutesConfigSync(projectRoot: string): string | null {\n for (const candidate of ROUTES_CONFIG_CANDIDATES) {\n const path = join(projectRoot, candidate);\n if (existsSync(path)) return path;\n }\n return null;\n}\n\nasync function findRoutesConfig(projectRoot: string): Promise<string | null> {\n for (const candidate of ROUTES_CONFIG_CANDIDATES) {\n const path = join(projectRoot, candidate);\n try {\n await fs.access(path);\n return path;\n } catch {\n /* not present */\n }\n }\n return null;\n}\n\n// Locate `export default [ ... ]` and return the inner ArrayExpression node.\n// Also handles `export default [...] satisfies RouteConfig` (TS satisfies wraps\n// the array in a TsSatisfiesExpression / TsAsExpression in swc) and the\n// `export default <expr>` -> ArrayExpression direct case.\nfunction findDefaultExportArray(ast: any): any | null {\n for (const stmt of ast.body ?? []) {\n if (stmt.type === \"ExportDefaultExpression\") {\n return unwrapArray(stmt.expression);\n }\n if (stmt.type === \"ExportDefaultDeclaration\") {\n const decl = stmt.decl ?? stmt.declaration;\n if (decl) return unwrapArray(decl);\n }\n }\n return null;\n}\n\nfunction unwrapArray(node: any): any | null {\n if (!node) return null;\n if (node.type === \"ArrayExpression\") return node;\n // Unwrap `satisfies` / `as` casts and parens.\n if (node.type === \"TsSatisfiesExpression\" || node.type === \"TsAsExpression\" || node.type === \"TsTypeAssertion\") {\n return unwrapArray(node.expression);\n }\n if (node.type === \"ParenthesisExpression\") return unwrapArray(node.expression);\n return null;\n}\n\ntype Frame = {\n parentPattern: string;\n inheritedFiles: Set<string>;\n};\n\nasync function walkRouteArray(\n arrayNode: any,\n frame: Frame,\n appDir: string,\n out: Array<{ pattern: string; sourceFiles: Set<string> }>,\n): Promise<void> {\n for (const elem of arrayNode.elements ?? []) {\n if (!elem) continue;\n // ArrayExpression elements in swc are wrapped: `{ spread: null, expression: <expr> }`\n // when in array literal context. Spread elements have `spread` set to a span.\n const expr = elem.expression ?? elem;\n const isSpread = !!elem.spread;\n if (isSpread) {\n // Only spread of a `prefix(...)` call is meaningful in v0.1.\n if (expr?.type === \"CallExpression\" && callName(expr) === \"prefix\") {\n const args = expr.arguments ?? [];\n const prefixArg = stringArg(args[0]);\n const childrenArr = arrayArg(args[1]);\n if (prefixArg !== null && childrenArr) {\n const childFrame: Frame = {\n parentPattern: joinPath(frame.parentPattern, prefixArg),\n inheritedFiles: frame.inheritedFiles,\n };\n await walkRouteArray(childrenArr, childFrame, appDir, out);\n }\n }\n continue;\n }\n if (expr?.type !== \"CallExpression\") continue;\n await processRouteCall(expr, frame, appDir, out);\n }\n}\n\nasync function processRouteCall(\n call: any,\n frame: Frame,\n appDir: string,\n out: Array<{ pattern: string; sourceFiles: Set<string> }>,\n): Promise<void> {\n const name = callName(call);\n const args = call.arguments ?? [];\n\n if (name === \"route\") {\n const pathArg = stringArg(args[0]);\n const fileArg = stringArg(args[1]);\n if (pathArg === null || fileArg === null) return;\n const pattern = joinPath(frame.parentPattern, pathArg);\n const sourceFiles = new Set<string>(frame.inheritedFiles);\n const resolvedFile = resolveAppFile(appDir, fileArg);\n if (resolvedFile) await walkComponentTree(resolvedFile, sourceFiles, new Set());\n out.push({ pattern, sourceFiles });\n\n // Children inherit the parent route's component tree. At runtime, child\n // routes render inside the parent's `<Outlet />`, so markers in the\n // parent's component tree appear on every descendant view.\n const childrenArr = arrayArg(args[2]);\n if (childrenArr) {\n const childFrame: Frame = {\n parentPattern: pattern,\n inheritedFiles: union(frame.inheritedFiles, sourceFiles),\n };\n await walkRouteArray(childrenArr, childFrame, appDir, out);\n }\n return;\n }\n\n if (name === \"index\") {\n const fileArg = stringArg(args[0]);\n if (fileArg === null) return;\n // Index routes match the parent pattern exactly. At the top level that's \"/\".\n const pattern = frame.parentPattern || \"/\";\n const sourceFiles = new Set<string>(frame.inheritedFiles);\n const resolvedFile = resolveAppFile(appDir, fileArg);\n if (resolvedFile) await walkComponentTree(resolvedFile, sourceFiles, new Set());\n out.push({ pattern, sourceFiles });\n return;\n }\n\n if (name === \"layout\") {\n const fileArg = stringArg(args[0]);\n const childrenArr = arrayArg(args[1]);\n if (fileArg === null || !childrenArr) return;\n const layoutFiles = new Set<string>();\n const resolvedFile = resolveAppFile(appDir, fileArg);\n if (resolvedFile) await walkComponentTree(resolvedFile, layoutFiles, new Set());\n const childFrame: Frame = {\n parentPattern: frame.parentPattern,\n inheritedFiles: union(frame.inheritedFiles, layoutFiles),\n };\n await walkRouteArray(childrenArr, childFrame, appDir, out);\n return;\n }\n\n // Unknown helper — silently skip. v0.2 may add `flatRoutes()` etc.\n}\n\nfunction callName(call: any): string | null {\n const callee = call?.callee;\n if (!callee) return null;\n if (callee.type === \"Identifier\") return callee.value ?? null;\n return null;\n}\n\nfunction stringArg(arg: any): string | null {\n // ArrayExpression-style argument wrapper: `{ spread: null, expression: <expr> }`.\n const expr = arg?.expression ?? arg;\n if (expr?.type === \"StringLiteral\") return expr.value ?? null;\n return null;\n}\n\nfunction arrayArg(arg: any): any | null {\n const expr = arg?.expression ?? arg;\n if (expr?.type === \"ArrayExpression\") return expr;\n return null;\n}\n\n// Join a child path onto a parent pattern using RR7 semantics:\n// - child starting with \"/\" is absolute and replaces the parent\n// - empty child returns the parent (used for pathless `route(\"\", ...)`)\n// - otherwise the child is appended with a single \"/\" separator\nexport function joinPath(parent: string, child: string): string {\n if (child.startsWith(\"/\")) return child;\n if (!child) return parent || \"/\";\n const base = (parent || \"\").replace(/\\/+$/, \"\");\n const tail = child.replace(/^\\/+/, \"\");\n return base === \"\" ? `/${tail}` : `${base}/${tail}`;\n}\n\nfunction resolveAppFile(appDir: string, fileSpec: string): string | null {\n // RR7 file specs are relative to the app directory and may omit the extension.\n const base = resolve(appDir, fileSpec);\n if (existsSync(base)) return base;\n const stripped = base.replace(/\\.(js|jsx|mjs)$/, \"\");\n for (const ext of [\".tsx\", \".ts\", \".jsx\", \".js\"]) {\n const candidate = `${stripped}${ext}`;\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n\nfunction union<T>(a: Set<T>, b: Set<T>): Set<T> {\n const out = new Set<T>(a);\n for (const v of b) out.add(v);\n return out;\n}\n\n// Walks a route file's local-import tree, adding every reachable file to\n// `sourceFiles`. Mirrors the declarative adapter's walker so marker scanning\n// in the orchestrator finds `data-sightmap` attributes inside imported\n// components, not just the route file itself.\nasync function walkComponentTree(\n file: string,\n sourceFiles: Set<string>,\n visited: Set<string>,\n): Promise<void> {\n if (visited.has(file)) return;\n visited.add(file);\n sourceFiles.add(file);\n\n let source: string;\n try {\n source = await fs.readFile(file, \"utf-8\");\n } catch {\n return;\n }\n\n const ast = await parseAst(source, { syntax: \"typescript\", tsx: true, target: \"es2022\" });\n for (const stmt of ast.body ?? []) {\n if (stmt.type !== \"ImportDeclaration\") continue;\n const sourcePath = stmt.source?.value as string | undefined;\n if (!sourcePath || !sourcePath.startsWith(\".\")) continue;\n const resolved = resolveLocalImport(file, sourcePath);\n if (!resolved) continue;\n await walkComponentTree(resolved, sourceFiles, visited);\n }\n}\n\nfunction resolveLocalImport(fromFile: string, importSpec: string): string | null {\n const dir = dirname(fromFile);\n const stripped = importSpec.replace(/\\.(js|jsx|mjs)$/, \"\");\n for (const ext of [\".tsx\", \".ts\", \".jsx\", \".js\"]) {\n const candidate = resolve(dir, `${stripped}${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n if (stripped !== importSpec) {\n const original = resolve(dir, importSpec);\n if (existsSync(original)) return original;\n }\n return null;\n}\n","// src/plugin/router-adapter.ts\nimport type { Diagnostic } from \"@sightmap/sightmap\";\n\nexport type RouteDescriptor = {\n pattern: string; // normalized to spec route grammar\n featureName: string; // chunking key — derives the YAML filename\n sourceFiles: string[]; // absolute paths reachable from this route's tree\n meta?: Record<string, unknown>; // raw router metadata; informational only in v1\n};\n\nexport type AdapterContext = {\n projectRoot: string;\n resolveSource: (id: string) => string | null;\n /**\n * Optional sink the adapter can push diagnostics into during discovery — e.g.\n * a `<Route>` it found but couldn't fully process. Adapters MUST tolerate\n * this being absent (callers from tests may not pass one). The orchestrator\n * always provides it and forwards the contents into its own diagnostic\n * stream so they reach the CLI summary.\n */\n diagnostics?: Diagnostic[];\n};\n\nexport interface RouterAdapter {\n name: string;\n detect(projectRoot: string): boolean;\n discoverRoutes(ctx: AdapterContext): Promise<RouteDescriptor[]>;\n /** Optional client-side helper. Falls back to `location.pathname` if absent. */\n currentRoute?(): string;\n}\n\n/** Derive the feature name from a route pattern. First non-parameter segment, or \"home\" for \"/\". */\nexport function deriveFeatureName(pattern: string): string {\n const segments = pattern.split(\"/\").filter(Boolean);\n for (const seg of segments) {\n if (!seg.startsWith(\":\") && !seg.startsWith(\"*\")) return sanitize(seg);\n }\n return \"home\";\n}\n\n/**\n * Derive a view name from a route pattern.\n *\n * Picks the LAST non-parameter segment, capitalized — so `/app/dashboard` →\n * `Dashboard`, `/settings/billing` → `Billing`, `/login` → `Login`.\n *\n * Routes ending in a parameter (`/users/:id`) get the preceding segment with\n * `Detail` appended (`UsersDetail`) so they don't collide with the list view.\n *\n * Routes ending in a wildcard (`/files/*`, `/*`) get `Catchall` appended —\n * a different concept from `:param` (catch-all matches multiple segments;\n * named params match one).\n *\n * Falls back to `Home` for `/`.\n */\nexport function deriveViewName(pattern: string): string {\n const segments = pattern.split(\"/\").filter(Boolean);\n if (segments.length === 0) return \"Home\";\n\n const last = segments[segments.length - 1]!;\n const lastIsParam = last.startsWith(\":\");\n const lastIsWildcard = last.startsWith(\"*\");\n\n if (lastIsParam || lastIsWildcard) {\n const suffix = lastIsWildcard ? \"Catchall\" : \"Detail\";\n // Walk back to the last static segment and append the suffix.\n for (let i = segments.length - 2; i >= 0; i--) {\n const seg = segments[i]!;\n if (!seg.startsWith(\":\") && !seg.startsWith(\"*\")) {\n return capitalize(sanitize(seg)) + suffix;\n }\n }\n return suffix;\n }\n return capitalize(sanitize(last));\n}\n\nfunction sanitize(s: string): string {\n return s.replace(/[^a-zA-Z0-9_-]/g, \"-\").toLowerCase();\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,29 @@
1
+ import { Diagnostic } from '@sightmap/sightmap';
2
+
3
+ type RouteDescriptor = {
4
+ pattern: string;
5
+ featureName: string;
6
+ sourceFiles: string[];
7
+ meta?: Record<string, unknown>;
8
+ };
9
+ type AdapterContext = {
10
+ projectRoot: string;
11
+ resolveSource: (id: string) => string | null;
12
+ /**
13
+ * Optional sink the adapter can push diagnostics into during discovery — e.g.
14
+ * a `<Route>` it found but couldn't fully process. Adapters MUST tolerate
15
+ * this being absent (callers from tests may not pass one). The orchestrator
16
+ * always provides it and forwards the contents into its own diagnostic
17
+ * stream so they reach the CLI summary.
18
+ */
19
+ diagnostics?: Diagnostic[];
20
+ };
21
+ interface RouterAdapter {
22
+ name: string;
23
+ detect(projectRoot: string): boolean;
24
+ discoverRoutes(ctx: AdapterContext): Promise<RouteDescriptor[]>;
25
+ /** Optional client-side helper. Falls back to `location.pathname` if absent. */
26
+ currentRoute?(): string;
27
+ }
28
+
29
+ export type { RouterAdapter as R };
@@ -0,0 +1,51 @@
1
+ import { Sightmap, ResolvedView, MatchResult } from '@sightmap/sightmap';
2
+
3
+ type ComponentNode = {
4
+ id: string;
5
+ parentId: string | null;
6
+ depth: number;
7
+ type: string;
8
+ key: string | null;
9
+ dom: {
10
+ tagName: string;
11
+ dataSightmap: string | null;
12
+ testId: string | null;
13
+ classes: string[];
14
+ } | null;
15
+ source: {
16
+ file: string;
17
+ line: number;
18
+ col: number;
19
+ } | null;
20
+ childIds: string[];
21
+ };
22
+ type SelectorProposal = {
23
+ selector: string;
24
+ strategy: "data-sightmap" | "data-testid" | "id" | "role-text" | "path";
25
+ matches: number;
26
+ };
27
+ type SightmapHook = {
28
+ version(): {
29
+ sdk: string;
30
+ spec: string;
31
+ };
32
+ getSightmap(): Sightmap;
33
+ getCurrentView(): ResolvedView | null;
34
+ match(req: {
35
+ url: string;
36
+ method?: string;
37
+ }): MatchResult;
38
+ walkTree(opts?: {
39
+ rootId?: string;
40
+ maxDepth?: number;
41
+ }): ComponentNode[];
42
+ inspectAt(x: number, y: number): ComponentNode | null;
43
+ findByDataSightmap(name: string): ComponentNode[];
44
+ };
45
+ declare global {
46
+ interface Window {
47
+ __SIGHTMAP__?: SightmapHook;
48
+ }
49
+ }
50
+
51
+ export type { ComponentNode, SelectorProposal, SightmapHook };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=runtime-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type SightmapPluginOptions = {
4
+ /** Project root. Defaults to `process.cwd()`. */
5
+ projectRoot?: string;
6
+ /** Output directory relative to projectRoot. Defaults to `.sightmap`. */
7
+ outputDir?: string;
8
+ };
9
+ declare function sightmapVitePlugin(options?: SightmapPluginOptions): Plugin;
10
+
11
+ export { type SightmapPluginOptions, sightmapVitePlugin as sightmap };
package/dist/vite.js ADDED
@@ -0,0 +1,38 @@
1
+ // src/plugin/dev-middleware.ts
2
+ import { join } from "path";
3
+ import { loadDirectory } from "@sightmap/sightmap";
4
+ function sightmapDevMiddleware(opts) {
5
+ return async (req, res, next) => {
6
+ if (req.url !== "/__sightmap__/sightmap.json") {
7
+ next();
8
+ return;
9
+ }
10
+ try {
11
+ const sightmap = await loadDirectory(join(opts.projectRoot, opts.outputDir));
12
+ res.statusCode = 200;
13
+ res.setHeader("content-type", "application/json");
14
+ res.setHeader("cache-control", "no-store");
15
+ res.end(JSON.stringify(sightmap));
16
+ } catch (err) {
17
+ res.statusCode = 500;
18
+ res.end(`sightmap dev middleware: ${err.message}`);
19
+ }
20
+ };
21
+ }
22
+
23
+ // src/plugin/vite.ts
24
+ function sightmapVitePlugin(options = {}) {
25
+ const projectRoot = options.projectRoot ?? process.cwd();
26
+ const outputDir = options.outputDir ?? ".sightmap";
27
+ return {
28
+ name: "@sightmap/react:dev-server",
29
+ apply: "serve",
30
+ configureServer(server) {
31
+ server.middlewares.use(sightmapDevMiddleware({ projectRoot, outputDir }));
32
+ }
33
+ };
34
+ }
35
+ export {
36
+ sightmapVitePlugin as sightmap
37
+ };
38
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/dev-middleware.ts","../src/plugin/vite.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { join } from \"node:path\";\nimport { loadDirectory } from \"@sightmap/sightmap\";\n\nexport function sightmapDevMiddleware(opts: { projectRoot: string; outputDir: string }) {\n return async (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => {\n if (req.url !== \"/__sightmap__/sightmap.json\") {\n next();\n return;\n }\n try {\n const sightmap = await loadDirectory(join(opts.projectRoot, opts.outputDir));\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"application/json\");\n res.setHeader(\"cache-control\", \"no-store\");\n res.end(JSON.stringify(sightmap));\n } catch (err) {\n res.statusCode = 500;\n res.end(`sightmap dev middleware: ${(err as Error).message}`);\n }\n };\n}\n","// src/plugin/vite.ts\n//\n// Vite plugin for @sightmap/react.\n//\n// In v0.1 (mode B — bootstrap-only codegen), this plugin's only job is to\n// register the dev middleware that serves the merged sightmap at\n// `/__sightmap__/sightmap.json`. Codegen is no longer triggered from any Vite\n// hook; users invoke `sightmap-react gen` explicitly when they want\n// `.sightmap/` files refreshed.\n//\n// The dev middleware reads the on-disk `.sightmap/` directory on each request\n// and returns the merged sightmap as JSON. This powers `<SightmapProvider>`'s\n// fetch loop in development.\n\nimport type { Plugin } from \"vite\";\nimport { sightmapDevMiddleware } from \"./dev-middleware.js\";\n\nexport type SightmapPluginOptions = {\n /** Project root. Defaults to `process.cwd()`. */\n projectRoot?: string;\n /** Output directory relative to projectRoot. Defaults to `.sightmap`. */\n outputDir?: string;\n};\n\nexport function sightmapVitePlugin(options: SightmapPluginOptions = {}): Plugin {\n const projectRoot = options.projectRoot ?? process.cwd();\n const outputDir = options.outputDir ?? \".sightmap\";\n\n return {\n name: \"@sightmap/react:dev-server\",\n apply: \"serve\",\n configureServer(server) {\n server.middlewares.use(sightmapDevMiddleware({ projectRoot, outputDir }));\n },\n };\n}\n"],"mappings":";AACA,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAEvB,SAAS,sBAAsB,MAAkD;AACtF,SAAO,OAAO,KAAsB,KAAqB,SAAkC;AACzF,QAAI,IAAI,QAAQ,+BAA+B;AAC7C,WAAK;AACL;AAAA,IACF;AACA,QAAI;AACF,YAAM,WAAW,MAAM,cAAc,KAAK,KAAK,aAAa,KAAK,SAAS,CAAC;AAC3E,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,iBAAiB,UAAU;AACzC,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAAA,IAClC,SAAS,KAAK;AACZ,UAAI,aAAa;AACjB,UAAI,IAAI,4BAA6B,IAAc,OAAO,EAAE;AAAA,IAC9D;AAAA,EACF;AACF;;;ACGO,SAAS,mBAAmB,UAAiC,CAAC,GAAW;AAC9E,QAAM,cAAc,QAAQ,eAAe,QAAQ,IAAI;AACvD,QAAM,YAAY,QAAQ,aAAa;AAEvC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,gBAAgB,QAAQ;AACtB,aAAO,YAAY,IAAI,sBAAsB,EAAE,aAAa,UAAU,CAAC,CAAC;AAAA,IAC1E;AAAA,EACF;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@sightmap/react",
3
+ "version": "0.2.0",
4
+ "description": "React framework adapter for the Sightmap spec — SightmapProvider, runtime introspection hook, Vite plugin, and CLI.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/sightmap/sightmap-js.git",
9
+ "directory": "packages/react"
10
+ },
11
+ "homepage": "https://sightmap.org",
12
+ "keywords": [
13
+ "sightmap",
14
+ "react",
15
+ "vite",
16
+ "agent",
17
+ "yaml",
18
+ "sdk"
19
+ ],
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ },
28
+ "./vite": {
29
+ "types": "./dist/vite.d.ts",
30
+ "import": "./dist/vite.js"
31
+ },
32
+ "./runtime-types": {
33
+ "types": "./dist/runtime-types.d.ts"
34
+ },
35
+ "./adapters/react-router-7": {
36
+ "types": "./dist/plugin/adapters/rr7-declarative.d.ts",
37
+ "import": "./dist/plugin/adapters/rr7-declarative.js"
38
+ },
39
+ "./adapters/react-router-7-framework": {
40
+ "types": "./dist/plugin/adapters/rr7-framework.d.ts",
41
+ "import": "./dist/plugin/adapters/rr7-framework.js"
42
+ }
43
+ },
44
+ "bin": {
45
+ "sightmap-react": "./dist/cli/index.js"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "typecheck": "tsc --noEmit"
60
+ },
61
+ "peerDependencies": {
62
+ "@sightmap/sightmap": ">=0.1.0 <1.0.0",
63
+ "react": "^19.0.0",
64
+ "react-router": "^7.0.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "react-router": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "dependencies": {
72
+ "@swc/core": "^1.10.0",
73
+ "commander": "^12.1.0",
74
+ "diff": "^8.0.3",
75
+ "yaml": "^2.7.0"
76
+ },
77
+ "devDependencies": {
78
+ "@sightmap/sightmap": "workspace:^",
79
+ "@testing-library/react": "^16.1.0",
80
+ "@types/node": "^20.19.39",
81
+ "@types/react": "^19.0.0",
82
+ "@types/react-dom": "^19.0.0",
83
+ "jsdom": "^25.0.0",
84
+ "react": "^19.0.0",
85
+ "react-dom": "^19.0.0",
86
+ "react-router": "^7.0.0",
87
+ "tsup": "^8.5.1",
88
+ "typescript": "~5.7.3",
89
+ "vite": "^5.4.0",
90
+ "vitest": "^2.1.9"
91
+ }
92
+ }