@amulet-laboratories/astrolabe 0.4.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,116 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, extname, join, resolve } from 'node:path'
3
+
4
+ import { buildStack, pkgOf } from '../shared.mjs'
5
+
6
+ const VITE_CONFIGS = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']
7
+
8
+ // Auto-imports that come from the framework itself — drawing them as nodes
9
+ // would bury the app's own composables and stores in library noise.
10
+ const FRAMEWORK = /^(vue|vue-router|pinia|@vue\/|@vueuse\/|#)/
11
+
12
+ /**
13
+ * A plain Vue app: a Vite config plus a `vue` dependency, and *not* Nuxt. The
14
+ * registry tries the Nuxt adapter first, so a Nuxt app (which also has `vue`)
15
+ * never reaches here.
16
+ */
17
+ export function detect(dir) {
18
+ const root = resolve(dir)
19
+ if (!VITE_CONFIGS.some((n) => existsSync(join(root, n)))) return false
20
+ try {
21
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
22
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
23
+ return Boolean(deps.vue) && !deps.nuxt
24
+ } catch {
25
+ return false
26
+ }
27
+ }
28
+
29
+ export function loadProject(dir) {
30
+ const root = resolve(dir)
31
+ const srcDir = existsSync(join(root, 'src')) ? join(root, 'src') : root
32
+
33
+ let pkg = {}
34
+ try {
35
+ pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
36
+ } catch {
37
+ // Unusual but not fatal.
38
+ }
39
+
40
+ return {
41
+ root,
42
+ srcDir,
43
+ name: pkg.name || root.split('/').pop(),
44
+ description: pkg.description || '',
45
+ modules: [],
46
+ stack: buildStack(pkg),
47
+ // The unplugin ecosystem commits these manifests, so a bare clone already
48
+ // has exact auto-import names — no build/prepare needed (unlike Nuxt's .nuxt).
49
+ components: readComponentManifest(root),
50
+ autoImports: readAutoImports(root),
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Classify an import specifier from a manifest: local file vs. library.
56
+ * Relative specifiers resolve against `base` — the manifest's own directory,
57
+ * not the project root, since that's what the `./…` paths are relative to.
58
+ */
59
+ function classify(base, spec) {
60
+ if (spec.startsWith('.')) {
61
+ const file = resolveLocal(resolve(base, spec))
62
+ return file.includes('node_modules')
63
+ ? { file, origin: 'library', pkg: pkgOf(file) }
64
+ : { file, origin: 'local', pkg: null }
65
+ }
66
+ const pkg = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : spec.split('/')[0]
67
+ return { file: spec, origin: 'library', pkg }
68
+ }
69
+
70
+ // TS import specifiers drop the extension (`./stores/user` → user.ts), so a
71
+ // resolved path that doesn't exist as-is gets the source extensions tried.
72
+ const SOURCE_EXTS = ['.ts', '.js', '.mts', '.vue']
73
+ function resolveLocal(file) {
74
+ if (extname(file) && existsSync(file)) return file
75
+ return SOURCE_EXTS.map((e) => file + e).find(existsSync) ?? file
76
+ }
77
+
78
+ const firstExisting = (root, names) => names.map((n) => join(root, n)).find(existsSync)
79
+
80
+ /**
81
+ * unplugin-vue-components writes `Name: typeof import('./…')['default']` inside a
82
+ * GlobalComponents interface. Pages (routes) are auto-registered here too, so
83
+ * we drop `src/pages/**` — those become route nodes, not component nodes.
84
+ */
85
+ function readComponentManifest(root) {
86
+ const file = firstExisting(root, ['components.d.ts', 'src/components.d.ts'])
87
+ if (!file) return null
88
+ const base = dirname(file)
89
+ const raw = readFileSync(file, 'utf8')
90
+ const map = new Map()
91
+ const re = /^\s*['"]?([A-Za-z][\w.]*)['"]?\s*:\s*typeof import\('([^']+)'\)\['default'\]/gm
92
+ for (const [, name, spec] of raw.matchAll(re)) {
93
+ if (map.has(name) || /(^|\/)pages\//.test(spec)) continue
94
+ map.set(name, classify(base, spec))
95
+ }
96
+ return map.size ? map : null
97
+ }
98
+
99
+ /**
100
+ * unplugin-auto-import writes `const useFoo: typeof import('./…')['useFoo']`.
101
+ * Keep only the app's own `use*` auto-imports; the rest is vue/vueuse plumbing.
102
+ */
103
+ function readAutoImports(root) {
104
+ const file = firstExisting(root, ['auto-imports.d.ts', 'src/auto-imports.d.ts'])
105
+ if (!file) return null
106
+ const base = dirname(file)
107
+ const map = new Map()
108
+ const raw = readFileSync(file, 'utf8')
109
+ for (const [, name, spec] of raw.matchAll(/const (\w+):\s*typeof import\('([^']+)'\)/g)) {
110
+ if (map.has(name) || !name.startsWith('use')) continue
111
+ const info = classify(base, spec)
112
+ if (info.origin === 'library' && FRAMEWORK.test(info.pkg ?? '')) continue
113
+ map.set(name, info)
114
+ }
115
+ return map.size ? map : null
116
+ }