@specbook/vite 0.1.0 → 0.3.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,58 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+
4
+ /**
5
+ * Which operation a request file is for, read from its text without importing it:
6
+ * `import { GetPetById } from '#api/petstore'` and `GetPetById.request({...})` make `petstore/GetPetById`. The panel of an
7
+ * embed opens one request, and with this it imports that file instead of every request in the project.
8
+ *
9
+ * `services` are the project's service names; an import counts when the last segment of its address is one of them, so any
10
+ * import prefix works. Anything the text does not settle (no call, two different operations, a computed one) is undefined,
11
+ * and the file is then imported when requests are listed.
12
+ */
13
+ export function parseRequestTarget(src: string, services: ReadonlySet<string>): string | undefined {
14
+ const local = new Map<string, string>()
15
+ for (const m of src.matchAll(/import\s*(?:type\s*)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g)) {
16
+ const service = (m[2] as string).split('/').pop() as string
17
+ if (!services.has(service) || /^import\s*type/.test(m[0])) continue
18
+ for (const part of (m[1] as string).split(',')) {
19
+ const spec = part.trim()
20
+ if (!spec || spec.startsWith('type ')) continue
21
+ const [imported, as] = spec.split(/\s+as\s+/)
22
+ local.set((as ?? imported) as string, `${service}/${imported}`)
23
+ }
24
+ }
25
+ const used = new Set<string>()
26
+ for (const m of src.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*request\s*\(/g)) {
27
+ const key = local.get(m[1] as string)
28
+ if (key) used.add(key)
29
+ }
30
+ return used.size === 1 ? [...used][0] : undefined
31
+ }
32
+
33
+ export interface RequestScan {
34
+ /** project-relative request file → `service/Operation` */
35
+ meta: Record<string, string>
36
+ /** files the text does not settle; the UI imports these */
37
+ unresolved: string[]
38
+ }
39
+
40
+ export async function scanRequests(
41
+ root: string,
42
+ files: string[],
43
+ services: ReadonlySet<string>,
44
+ ): Promise<RequestScan> {
45
+ const meta: Record<string, string> = {}
46
+ const unresolved: string[] = []
47
+ await Promise.all(
48
+ files.map(async (file) => {
49
+ const key = parseRequestTarget(
50
+ await readFile(join(root, file), 'utf8').catch(() => ''),
51
+ services,
52
+ )
53
+ if (key) meta[file] = key
54
+ else unresolved.push(file)
55
+ }),
56
+ )
57
+ return { meta, unresolved: unresolved.sort() }
58
+ }