@pyreon/mcp 0.11.4 → 0.11.6

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.
package/src/index.ts CHANGED
@@ -17,20 +17,20 @@
17
17
  * bunx @pyreon/mcp # stdio transport (for IDE integration)
18
18
  */
19
19
 
20
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
21
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
22
- import { detectReactPatterns, diagnoseError, migrateReactCode } from "@pyreon/compiler"
23
- import { z } from "zod"
24
- import packageJson from "../package.json" with { type: "json" }
25
- import { API_REFERENCE } from "./api-reference"
26
- import { generateContext, type ProjectContext } from "./project-scanner"
20
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
21
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
22
+ import { detectReactPatterns, diagnoseError, migrateReactCode } from '@pyreon/compiler'
23
+ import { z } from 'zod'
24
+ import packageJson from '../package.json' with { type: 'json' }
25
+ import { API_REFERENCE } from './api-reference'
26
+ import { generateContext, type ProjectContext } from './project-scanner'
27
27
 
28
28
  // ═══════════════════════════════════════════════════════════════════════════════
29
29
  // Server setup
30
30
  // ═══════════════════════════════════════════════════════════════════════════════
31
31
 
32
32
  const server = new McpServer({
33
- name: "pyreon",
33
+ name: 'pyreon',
34
34
  version: packageJson.version,
35
35
  })
36
36
 
@@ -47,7 +47,7 @@ function getContext(): ProjectContext {
47
47
  }
48
48
 
49
49
  function textResult(text: string) {
50
- return { content: [{ type: "text" as const, text }] }
50
+ return { content: [{ type: 'text' as const, text }] }
51
51
  }
52
52
 
53
53
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -55,7 +55,7 @@ function textResult(text: string) {
55
55
  // ═══════════════════════════════════════════════════════════════════════════════
56
56
 
57
57
  server.tool(
58
- "get_api",
58
+ 'get_api',
59
59
  {
60
60
  package: z.string(),
61
61
  symbol: z.string(),
@@ -73,14 +73,14 @@ server.tool(
73
73
  return textResult(
74
74
  `Symbol '${symbol}' not found in @pyreon/${pkg}.\n\n${
75
75
  suggestions.length > 0
76
- ? `Did you mean one of these?\n${suggestions.map((s) => ` - ${s}`).join("\n")}`
77
- : "No similar symbols found."
76
+ ? `Did you mean one of these?\n${suggestions.map((s) => ` - ${s}`).join('\n')}`
77
+ : 'No similar symbols found.'
78
78
  }`,
79
79
  )
80
80
  }
81
81
 
82
82
  return textResult(
83
- `## @pyreon/${pkg} — ${symbol}\n\n**Signature:**\n\`\`\`typescript\n${entry.signature}\n\`\`\`\n\n**Usage:**\n\`\`\`typescript\n${entry.example}\n\`\`\`\n\n${entry.notes ? `**Notes:** ${entry.notes}\n\n` : ""}${entry.mistakes ? `**Common mistakes:**\n${entry.mistakes}\n` : ""}`,
83
+ `## @pyreon/${pkg} — ${symbol}\n\n**Signature:**\n\`\`\`typescript\n${entry.signature}\n\`\`\`\n\n**Usage:**\n\`\`\`typescript\n${entry.example}\n\`\`\`\n\n${entry.notes ? `**Notes:** ${entry.notes}\n\n` : ''}${entry.mistakes ? `**Common mistakes:**\n${entry.mistakes}\n` : ''}`,
84
84
  )
85
85
  },
86
86
  )
@@ -90,27 +90,27 @@ server.tool(
90
90
  // ═══════════════════════════════════════════════════════════════════════════════
91
91
 
92
92
  server.tool(
93
- "validate",
93
+ 'validate',
94
94
  {
95
95
  code: z.string(),
96
96
  filename: z.string().optional(),
97
97
  },
98
98
  async ({ code, filename }) => {
99
- const diagnostics = detectReactPatterns(code, filename ?? "snippet.tsx")
99
+ const diagnostics = detectReactPatterns(code, filename ?? 'snippet.tsx')
100
100
 
101
101
  if (diagnostics.length === 0) {
102
- return textResult("✓ No issues found. The code follows Pyreon patterns correctly.")
102
+ return textResult('✓ No issues found. The code follows Pyreon patterns correctly.')
103
103
  }
104
104
 
105
105
  const issueText = diagnostics
106
106
  .map(
107
107
  (d, i) =>
108
- `${i + 1}. **${d.code}** (line ${d.line})\n ${d.message}\n Current: \`${d.current}\`\n Fix: \`${d.suggested}\`\n Auto-fixable: ${d.fixable ? "yes" : "no"}`,
108
+ `${i + 1}. **${d.code}** (line ${d.line})\n ${d.message}\n Current: \`${d.current}\`\n Fix: \`${d.suggested}\`\n Auto-fixable: ${d.fixable ? 'yes' : 'no'}`,
109
109
  )
110
- .join("\n\n")
110
+ .join('\n\n')
111
111
 
112
112
  return textResult(
113
- `Found ${diagnostics.length} issue${diagnostics.length === 1 ? "" : "s"}:\n\n${issueText}`,
113
+ `Found ${diagnostics.length} issue${diagnostics.length === 1 ? '' : 's'}:\n\n${issueText}`,
114
114
  )
115
115
  },
116
116
  )
@@ -120,24 +120,24 @@ server.tool(
120
120
  // ═══════════════════════════════════════════════════════════════════════════════
121
121
 
122
122
  server.tool(
123
- "migrate_react",
123
+ 'migrate_react',
124
124
  {
125
125
  code: z.string(),
126
126
  filename: z.string().optional(),
127
127
  },
128
128
  async ({ code, filename }) => {
129
- const result = migrateReactCode(code, filename ?? "component.tsx")
129
+ const result = migrateReactCode(code, filename ?? 'component.tsx')
130
130
 
131
- const changeList = result.changes.map((c) => `- Line ${c.line}: ${c.description}`).join("\n")
131
+ const changeList = result.changes.map((c) => `- Line ${c.line}: ${c.description}`).join('\n')
132
132
 
133
133
  const remainingIssues = result.diagnostics.filter((d) => !d.fixable)
134
134
  const manualText =
135
135
  remainingIssues.length > 0
136
- ? `\n\n**Remaining issues (manual fix needed):**\n${remainingIssues.map((d) => `- Line ${d.line}: ${d.message}\n Suggested: \`${d.suggested}\``).join("\n")}`
137
- : ""
136
+ ? `\n\n**Remaining issues (manual fix needed):**\n${remainingIssues.map((d) => `- Line ${d.line}: ${d.message}\n Suggested: \`${d.suggested}\``).join('\n')}`
137
+ : ''
138
138
 
139
139
  return textResult(
140
- `## Migrated Code\n\n\`\`\`tsx\n${result.code}\n\`\`\`\n\n**Changes applied (${result.changes.length}):**\n${changeList || "No changes needed."}${manualText}`,
140
+ `## Migrated Code\n\n\`\`\`tsx\n${result.code}\n\`\`\`\n\n**Changes applied (${result.changes.length}):**\n${changeList || 'No changes needed.'}${manualText}`,
141
141
  )
142
142
  },
143
143
  )
@@ -147,7 +147,7 @@ server.tool(
147
147
  // ═══════════════════════════════════════════════════════════════════════════════
148
148
 
149
149
  server.tool(
150
- "diagnose",
150
+ 'diagnose',
151
151
  {
152
152
  error: z.string(),
153
153
  },
@@ -176,29 +176,29 @@ server.tool(
176
176
  // Tool: get_routes
177
177
  // ═══════════════════════════════════════════════════════════════════════════════
178
178
 
179
- server.tool("get_routes", {}, async () => {
179
+ server.tool('get_routes', {}, async () => {
180
180
  const ctx = getContext()
181
181
 
182
182
  if (ctx.routes.length === 0) {
183
183
  return textResult(
184
- "No routes detected. Routes are defined via createRouter() or a routes array.",
184
+ 'No routes detected. Routes are defined via createRouter() or a routes array.',
185
185
  )
186
186
  }
187
187
 
188
188
  const routeTable = ctx.routes
189
189
  .map((r) => {
190
190
  const flags = [
191
- r.hasLoader ? "loader" : "",
192
- r.hasGuard ? "guard" : "",
193
- r.params.length > 0 ? `params: ${r.params.join(", ")}` : "",
194
- r.name ? `name: "${r.name}"` : "",
191
+ r.hasLoader ? 'loader' : '',
192
+ r.hasGuard ? 'guard' : '',
193
+ r.params.length > 0 ? `params: ${r.params.join(', ')}` : '',
194
+ r.name ? `name: "${r.name}"` : '',
195
195
  ]
196
196
  .filter(Boolean)
197
- .join(", ")
197
+ .join(', ')
198
198
 
199
- return ` ${r.path}${flags ? ` (${flags})` : ""}`
199
+ return ` ${r.path}${flags ? ` (${flags})` : ''}`
200
200
  })
201
- .join("\n")
201
+ .join('\n')
202
202
 
203
203
  return textResult(`**Routes (${ctx.routes.length}):**\n\n${routeTable}`)
204
204
  })
@@ -207,25 +207,25 @@ server.tool("get_routes", {}, async () => {
207
207
  // Tool: get_components
208
208
  // ═══════════════════════════════════════════════════════════════════════════════
209
209
 
210
- server.tool("get_components", {}, async () => {
210
+ server.tool('get_components', {}, async () => {
211
211
  const ctx = getContext()
212
212
 
213
213
  if (ctx.components.length === 0) {
214
- return textResult("No components detected.")
214
+ return textResult('No components detected.')
215
215
  }
216
216
 
217
217
  const compList = ctx.components
218
218
  .map((c) => {
219
219
  const details = [
220
- c.props.length > 0 ? `props: { ${c.props.join(", ")} }` : "",
221
- c.hasSignals ? `signals: [${c.signalNames.join(", ")}]` : "",
220
+ c.props.length > 0 ? `props: { ${c.props.join(', ')} }` : '',
221
+ c.hasSignals ? `signals: [${c.signalNames.join(', ')}]` : '',
222
222
  ]
223
223
  .filter(Boolean)
224
- .join(", ")
224
+ .join(', ')
225
225
 
226
- return ` ${c.name} — ${c.file}${details ? `\n ${details}` : ""}`
226
+ return ` ${c.name} — ${c.file}${details ? `\n ${details}` : ''}`
227
227
  })
228
- .join("\n")
228
+ .join('\n')
229
229
 
230
230
  return textResult(`**Components (${ctx.components.length}):**\n\n${compList}`)
231
231
  })
@@ -240,6 +240,6 @@ async function main(): Promise<void> {
240
240
  }
241
241
 
242
242
  main().catch((err) => {
243
- console.error("MCP server error:", err)
243
+ console.error('MCP server error:', err)
244
244
  process.exit(1)
245
245
  })
@@ -7,4 +7,4 @@ export {
7
7
  type IslandInfo,
8
8
  type ProjectContext,
9
9
  type RouteInfo,
10
- } from "@pyreon/compiler"
10
+ } from '@pyreon/compiler'
@@ -1,11 +1,11 @@
1
- import { API_REFERENCE } from "../api-reference"
1
+ import { API_REFERENCE } from '../api-reference'
2
2
 
3
- describe("api-reference", () => {
4
- it("has entries", () => {
3
+ describe('api-reference', () => {
4
+ it('has entries', () => {
5
5
  expect(Object.keys(API_REFERENCE).length).toBeGreaterThan(0)
6
6
  })
7
7
 
8
- it("entries have required fields", () => {
8
+ it('entries have required fields', () => {
9
9
  for (const [key, entry] of Object.entries(API_REFERENCE)) {
10
10
  expect(entry.signature, `${key} missing signature`).toBeTruthy()
11
11
  expect(entry.example, `${key} missing example`).toBeTruthy()
@@ -1,19 +1,19 @@
1
- import * as fs from "node:fs"
2
- import * as os from "node:os"
3
- import * as path from "node:path"
4
- import { generateContext } from "../project-scanner"
1
+ import * as fs from 'node:fs'
2
+ import * as os from 'node:os'
3
+ import * as path from 'node:path'
4
+ import { generateContext } from '../project-scanner'
5
5
 
6
6
  function makeTmpDir(): string {
7
- return fs.mkdtempSync(path.join(os.tmpdir(), "pyreon-scanner-"))
7
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'pyreon-scanner-'))
8
8
  }
9
9
 
10
10
  function writeFile(dir: string, relPath: string, content: string): void {
11
11
  const full = path.join(dir, relPath)
12
12
  fs.mkdirSync(path.dirname(full), { recursive: true })
13
- fs.writeFileSync(full, content, "utf-8")
13
+ fs.writeFileSync(full, content, 'utf-8')
14
14
  }
15
15
 
16
- describe("generateContext", () => {
16
+ describe('generateContext', () => {
17
17
  let tmpDir: string
18
18
 
19
19
  beforeEach(() => {
@@ -24,34 +24,34 @@ describe("generateContext", () => {
24
24
  fs.rmSync(tmpDir, { recursive: true, force: true })
25
25
  })
26
26
 
27
- it("returns correct framework and version from @pyreon dependency", () => {
27
+ it('returns correct framework and version from @pyreon dependency', () => {
28
28
  writeFile(
29
29
  tmpDir,
30
- "package.json",
31
- JSON.stringify({ dependencies: { "@pyreon/core": "^1.2.3" } }),
30
+ 'package.json',
31
+ JSON.stringify({ dependencies: { '@pyreon/core': '^1.2.3' } }),
32
32
  )
33
33
  const ctx = generateContext(tmpDir)
34
- expect(ctx.framework).toBe("pyreon")
35
- expect(ctx.version).toBe("1.2.3")
34
+ expect(ctx.framework).toBe('pyreon')
35
+ expect(ctx.version).toBe('1.2.3')
36
36
  expect(ctx.generatedAt).toBeTruthy()
37
37
  })
38
38
 
39
- it("falls back to package version when no @pyreon dep", () => {
40
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "0.5.0" }))
39
+ it('falls back to package version when no @pyreon dep', () => {
40
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '0.5.0' }))
41
41
  const ctx = generateContext(tmpDir)
42
- expect(ctx.version).toBe("0.5.0")
42
+ expect(ctx.version).toBe('0.5.0')
43
43
  })
44
44
 
45
- it("returns unknown version when no package.json", () => {
45
+ it('returns unknown version when no package.json', () => {
46
46
  const ctx = generateContext(tmpDir)
47
- expect(ctx.version).toBe("unknown")
47
+ expect(ctx.version).toBe('unknown')
48
48
  })
49
49
 
50
- it("extracts routes from createRouter calls", () => {
51
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
50
+ it('extracts routes from createRouter calls', () => {
51
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
52
52
  writeFile(
53
53
  tmpDir,
54
- "src/router.ts",
54
+ 'src/router.ts',
55
55
  `
56
56
  import { createRouter } from "@pyreon/router"
57
57
  const router = createRouter([
@@ -62,15 +62,15 @@ const router = createRouter([
62
62
  )
63
63
  const ctx = generateContext(tmpDir)
64
64
  expect(ctx.routes).toHaveLength(2)
65
- expect(ctx.routes[0]?.path).toBe("/")
66
- expect(ctx.routes[1]?.path).toBe("/about")
65
+ expect(ctx.routes[0]?.path).toBe('/')
66
+ expect(ctx.routes[1]?.path).toBe('/about')
67
67
  })
68
68
 
69
- it("extracts routes from routes variable", () => {
70
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
69
+ it('extracts routes from routes variable', () => {
70
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
71
71
  writeFile(
72
72
  tmpDir,
73
- "src/routes.ts",
73
+ 'src/routes.ts',
74
74
  `
75
75
  const routes = [
76
76
  { path: "/home", component: Home },
@@ -79,14 +79,14 @@ const routes = [
79
79
  )
80
80
  const ctx = generateContext(tmpDir)
81
81
  expect(ctx.routes).toHaveLength(1)
82
- expect(ctx.routes[0]?.path).toBe("/home")
82
+ expect(ctx.routes[0]?.path).toBe('/home')
83
83
  })
84
84
 
85
- it("extracts route params", () => {
86
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
85
+ it('extracts route params', () => {
86
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
87
87
  writeFile(
88
88
  tmpDir,
89
- "src/router.ts",
89
+ 'src/router.ts',
90
90
  `
91
91
  const routes = [
92
92
  { path: "/users/:id/posts/:postId" },
@@ -94,14 +94,14 @@ const routes = [
94
94
  `,
95
95
  )
96
96
  const ctx = generateContext(tmpDir)
97
- expect(ctx.routes[0]?.params).toEqual(["id", "postId"])
97
+ expect(ctx.routes[0]?.params).toEqual(['id', 'postId'])
98
98
  })
99
99
 
100
- it("extracts optional route params", () => {
101
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
100
+ it('extracts optional route params', () => {
101
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
102
102
  writeFile(
103
103
  tmpDir,
104
- "src/router.ts",
104
+ 'src/router.ts',
105
105
  `
106
106
  const routes = [
107
107
  { path: "/search/:query?" },
@@ -109,14 +109,14 @@ const routes = [
109
109
  `,
110
110
  )
111
111
  const ctx = generateContext(tmpDir)
112
- expect(ctx.routes[0]?.params).toEqual(["query"])
112
+ expect(ctx.routes[0]?.params).toEqual(['query'])
113
113
  })
114
114
 
115
- it("detects loaders and guards", () => {
116
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
115
+ it('detects loaders and guards', () => {
116
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
117
117
  writeFile(
118
118
  tmpDir,
119
- "src/router.ts",
119
+ 'src/router.ts',
120
120
  `
121
121
  const routes = [
122
122
  { path: "/dash", loader: fetchData, beforeEnter: authGuard },
@@ -128,11 +128,11 @@ const routes = [
128
128
  expect(ctx.routes[0]?.hasGuard).toBe(true)
129
129
  })
130
130
 
131
- it("detects beforeLeave guard", () => {
132
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
131
+ it('detects beforeLeave guard', () => {
132
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
133
133
  writeFile(
134
134
  tmpDir,
135
- "src/router.ts",
135
+ 'src/router.ts',
136
136
  `
137
137
  const routes = [
138
138
  { path: "/form", beforeLeave: confirmLeave },
@@ -143,11 +143,11 @@ const routes = [
143
143
  expect(ctx.routes[0]?.hasGuard).toBe(true)
144
144
  })
145
145
 
146
- it("extracts route names", () => {
147
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
146
+ it('extracts route names', () => {
147
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
148
148
  writeFile(
149
149
  tmpDir,
150
- "src/router.ts",
150
+ 'src/router.ts',
151
151
  `
152
152
  const routes = [
153
153
  { path: "/profile", name: "profile", component: Profile },
@@ -155,14 +155,14 @@ const routes = [
155
155
  `,
156
156
  )
157
157
  const ctx = generateContext(tmpDir)
158
- expect(ctx.routes[0]?.name).toBe("profile")
158
+ expect(ctx.routes[0]?.name).toBe('profile')
159
159
  })
160
160
 
161
- it("extracts component names with props", () => {
162
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
161
+ it('extracts component names with props', () => {
162
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
163
163
  writeFile(
164
164
  tmpDir,
165
- "src/Button.tsx",
165
+ 'src/Button.tsx',
166
166
  `
167
167
  export function Button(props: { label: string; disabled: boolean }) {
168
168
  return <button>{props.label}</button>
@@ -171,15 +171,15 @@ export function Button(props: { label: string; disabled: boolean }) {
171
171
  )
172
172
  const ctx = generateContext(tmpDir)
173
173
  expect(ctx.components).toHaveLength(1)
174
- expect(ctx.components[0]?.name).toBe("Button")
175
- expect(ctx.components[0]?.file).toBe("src/Button.tsx")
174
+ expect(ctx.components[0]?.name).toBe('Button')
175
+ expect(ctx.components[0]?.file).toBe('src/Button.tsx')
176
176
  })
177
177
 
178
- it("detects signals in components", () => {
179
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
178
+ it('detects signals in components', () => {
179
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
180
180
  writeFile(
181
181
  tmpDir,
182
- "src/Counter.tsx",
182
+ 'src/Counter.tsx',
183
183
  `
184
184
  function Counter() {
185
185
  const count = signal<number>(0)
@@ -190,14 +190,14 @@ function Counter() {
190
190
  )
191
191
  const ctx = generateContext(tmpDir)
192
192
  expect(ctx.components[0]?.hasSignals).toBe(true)
193
- expect(ctx.components[0]?.signalNames).toEqual(["count", "name"])
193
+ expect(ctx.components[0]?.signalNames).toEqual(['count', 'name'])
194
194
  })
195
195
 
196
- it("reports no signals when none present", () => {
197
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
196
+ it('reports no signals when none present', () => {
197
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
198
198
  writeFile(
199
199
  tmpDir,
200
- "src/Static.tsx",
200
+ 'src/Static.tsx',
201
201
  `
202
202
  function Static() {
203
203
  return <div>hello</div>
@@ -209,11 +209,11 @@ function Static() {
209
209
  expect(ctx.components[0]?.signalNames).toEqual([])
210
210
  })
211
211
 
212
- it("extracts island declarations with hydrate strategy", () => {
213
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
212
+ it('extracts island declarations with hydrate strategy', () => {
213
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
214
214
  writeFile(
215
215
  tmpDir,
216
- "src/islands.ts",
216
+ 'src/islands.ts',
217
217
  `
218
218
  const Counter = island(() => import("./Counter"), { name: "Counter", hydrate: "visible" })
219
219
  const Nav = island(() => import("./Nav"), { name: "Nav", hydrate: "idle" })
@@ -221,66 +221,66 @@ const Nav = island(() => import("./Nav"), { name: "Nav", hydrate: "idle" })
221
221
  )
222
222
  const ctx = generateContext(tmpDir)
223
223
  expect(ctx.islands).toHaveLength(2)
224
- expect(ctx.islands[0]?.name).toBe("Counter")
225
- expect(ctx.islands[1]?.name).toBe("Nav")
224
+ expect(ctx.islands[0]?.name).toBe('Counter')
225
+ expect(ctx.islands[1]?.name).toBe('Nav')
226
226
  })
227
227
 
228
- it("defaults hydrate to load when not specified", () => {
229
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
228
+ it('defaults hydrate to load when not specified', () => {
229
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
230
230
  writeFile(
231
231
  tmpDir,
232
- "src/islands.ts",
232
+ 'src/islands.ts',
233
233
  `
234
234
  const Widget = island(() => import("./Widget"), { name: "Widget" })
235
235
  `,
236
236
  )
237
237
  const ctx = generateContext(tmpDir)
238
- expect(ctx.islands[0]?.hydrate).toBe("load")
238
+ expect(ctx.islands[0]?.hydrate).toBe('load')
239
239
  })
240
240
 
241
- it("returns empty arrays for empty project", () => {
242
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
241
+ it('returns empty arrays for empty project', () => {
242
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
243
243
  const ctx = generateContext(tmpDir)
244
244
  expect(ctx.routes).toEqual([])
245
245
  expect(ctx.components).toEqual([])
246
246
  expect(ctx.islands).toEqual([])
247
247
  })
248
248
 
249
- it("skips node_modules and dist directories", () => {
250
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
249
+ it('skips node_modules and dist directories', () => {
250
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
251
251
  writeFile(
252
252
  tmpDir,
253
- "node_modules/@pyreon/core/src/index.ts",
253
+ 'node_modules/@pyreon/core/src/index.ts',
254
254
  `function Internal() { return null }`,
255
255
  )
256
- writeFile(tmpDir, "dist/bundle.ts", `function Bundled() { return null }`)
257
- writeFile(tmpDir, "src/App.tsx", `function App() { return <div /> }`)
256
+ writeFile(tmpDir, 'dist/bundle.ts', `function Bundled() { return null }`)
257
+ writeFile(tmpDir, 'src/App.tsx', `function App() { return <div /> }`)
258
258
  const ctx = generateContext(tmpDir)
259
259
  expect(ctx.components).toHaveLength(1)
260
- expect(ctx.components[0]?.name).toBe("App")
260
+ expect(ctx.components[0]?.name).toBe('App')
261
261
  })
262
262
 
263
- it("handles unreadable directories gracefully", () => {
264
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
265
- writeFile(tmpDir, "src/App.tsx", `function App() { return <div /> }`)
266
- const badDir = path.join(tmpDir, "restricted")
263
+ it('handles unreadable directories gracefully', () => {
264
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
265
+ writeFile(tmpDir, 'src/App.tsx', `function App() { return <div /> }`)
266
+ const badDir = path.join(tmpDir, 'restricted')
267
267
  fs.mkdirSync(badDir)
268
268
  fs.chmodSync(badDir, 0o000)
269
269
 
270
270
  try {
271
271
  const ctx = generateContext(tmpDir)
272
272
  expect(ctx.components).toHaveLength(1)
273
- expect(ctx.components[0]?.name).toBe("App")
273
+ expect(ctx.components[0]?.name).toBe('App')
274
274
  } finally {
275
275
  fs.chmodSync(badDir, 0o755)
276
276
  }
277
277
  })
278
278
 
279
- it("extracts const arrow function components", () => {
280
- writeFile(tmpDir, "package.json", JSON.stringify({ version: "1.0.0" }))
279
+ it('extracts const arrow function components', () => {
280
+ writeFile(tmpDir, 'package.json', JSON.stringify({ version: '1.0.0' }))
281
281
  writeFile(
282
282
  tmpDir,
283
- "src/Card.tsx",
283
+ 'src/Card.tsx',
284
284
  `
285
285
  export const Card = (props: CardProps) => {
286
286
  return <div>{props.title}</div>
@@ -289,6 +289,6 @@ export const Card = (props: CardProps) => {
289
289
  )
290
290
  const ctx = generateContext(tmpDir)
291
291
  expect(ctx.components).toHaveLength(1)
292
- expect(ctx.components[0]?.name).toBe("Card")
292
+ expect(ctx.components[0]?.name).toBe('Card')
293
293
  })
294
294
  })