@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30

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.
Files changed (343) hide show
  1. package/README.md +80 -6
  2. package/dist/astrale.js +55471 -58674
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +13 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +118 -10
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +35 -32
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +89 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +12 -5
  138. package/src/graph/.spec/layout.ts +9 -1
  139. package/src/graph/__tests__/mutation.test.ts +2 -2
  140. package/src/graph/__tests__/query.test.ts +25 -6
  141. package/src/graph/class.ts +30 -0
  142. package/src/graph/index.ts +1 -0
  143. package/src/graph/query.ts +21 -32
  144. package/src/identity/.spec/api.d.ts +2 -2
  145. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  146. package/src/identity/__tests__/registration.test.ts +1 -1
  147. package/src/identity/__tests__/registry.test.ts +4 -0
  148. package/src/identity/registration.ts +3 -3
  149. package/src/identity/registry.ts +2 -0
  150. package/src/keys/__tests__/keys.test.ts +4 -0
  151. package/src/keys/credential.ts +1 -0
  152. package/src/lib/__tests__/admin-target.test.ts +12 -0
  153. package/src/lib/__tests__/binary.test.ts +16 -1
  154. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  155. package/src/lib/__tests__/command-dx.test.ts +1 -1
  156. package/src/lib/__tests__/config.test.ts +27 -0
  157. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  158. package/src/lib/__tests__/idp.test.ts +34 -4
  159. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  160. package/src/lib/__tests__/instance-target.test.ts +7 -39
  161. package/src/lib/__tests__/log-errors.test.ts +64 -0
  162. package/src/lib/__tests__/skills.test.ts +809 -0
  163. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  164. package/src/lib/__tests__/update.test.ts +264 -11
  165. package/src/lib/__tests__/view-assets.test.ts +25 -0
  166. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  167. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  168. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  169. package/src/lib/__tests__/view-server.test.ts +26 -3
  170. package/src/lib/__tests__/view-session.test.ts +2 -1
  171. package/src/lib/admin-instance.ts +14 -8
  172. package/src/lib/admin-target.ts +21 -2
  173. package/src/lib/binary.ts +22 -5
  174. package/src/lib/browser-retention.ts +210 -0
  175. package/src/lib/command-dx.ts +9 -9
  176. package/src/lib/config.ts +12 -1
  177. package/src/lib/domain-publication.ts +9 -7
  178. package/src/lib/failure-debug.ts +26 -0
  179. package/src/lib/idp.ts +34 -3
  180. package/src/lib/instance-candidates.ts +2 -2
  181. package/src/lib/instance-target.ts +3 -14
  182. package/src/lib/instance.ts +16 -11
  183. package/src/lib/log.ts +16 -9
  184. package/src/lib/output.ts +1 -1
  185. package/src/lib/proc.ts +7 -2
  186. package/src/lib/provision-instance.ts +34 -57
  187. package/src/lib/skills/lock.ts +117 -0
  188. package/src/lib/skills/sync.ts +814 -0
  189. package/src/lib/skills.ts +1 -52
  190. package/src/lib/update.ts +298 -45
  191. package/src/lib/validation.ts +2 -2
  192. package/src/lib/view/assets.ts +11 -2
  193. package/src/lib/view/external-open-intent.ts +41 -0
  194. package/src/lib/view/external-open-origins.ts +37 -0
  195. package/src/lib/view/host-capabilities.ts +17 -0
  196. package/src/lib/view/resolve.ts +1 -1
  197. package/src/lib/view/server.ts +18 -3
  198. package/src/lib/view/session.ts +2 -0
  199. package/src/program/.spec/api.d.ts +1 -0
  200. package/src/program/__tests__/program.test.ts +163 -6
  201. package/src/program/argv.ts +14 -0
  202. package/src/program/build.ts +26 -4
  203. package/src/program/command.ts +1 -0
  204. package/src/program/index.ts +1 -0
  205. package/src/program/registry.ts +2 -1
  206. package/src/setup/__tests__/instance-step.test.ts +27 -10
  207. package/src/setup/steps/instance.ts +22 -15
  208. package/src/setup/steps/skills.ts +40 -38
  209. package/src/state/.spec/api.d.ts +21 -2
  210. package/src/state/.spec/architecture.md +18 -4
  211. package/src/state/.spec/layout.ts +1 -0
  212. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  213. package/src/state/__tests__/files.test.ts +24 -1
  214. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  215. package/src/state/__tests__/paths.test.ts +1 -0
  216. package/src/state/__tests__/session-routes.test.ts +138 -0
  217. package/src/state/exchange-credentials.ts +22 -9
  218. package/src/state/files.ts +41 -0
  219. package/src/state/index.ts +3 -1
  220. package/src/state/paths.ts +3 -0
  221. package/src/state/session-routes.ts +34 -0
  222. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  223. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  224. package/src/telemetry/__tests__/retention.test.ts +267 -0
  225. package/src/telemetry/__tests__/settings.test.ts +102 -0
  226. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  227. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  228. package/src/telemetry/analyze.ts +24 -2
  229. package/src/telemetry/recorder.ts +15 -4
  230. package/src/telemetry/retention.ts +176 -0
  231. package/src/telemetry/session.ts +18 -12
  232. package/src/telemetry/settings.ts +64 -11
  233. package/src/telemetry/store.ts +92 -9
  234. package/src/telemetry/trigger.ts +16 -28
  235. package/src/ui/.spec/api.d.ts +14 -0
  236. package/src/ui/.spec/architecture.md +14 -0
  237. package/src/ui/.spec/laws.ts +50 -0
  238. package/src/ui/.spec/layout.ts +16 -0
  239. package/src/ui/__tests__/ui.test.ts +1742 -0
  240. package/src/ui/index.ts +13 -0
  241. package/src/ui/lock.ts +89 -0
  242. package/src/ui/model.ts +83 -0
  243. package/src/ui/operations.ts +1044 -0
  244. package/src/ui/project.ts +333 -0
  245. package/src/ui/release.ts +398 -0
  246. package/src/ui/runner.ts +18 -0
  247. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  248. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  249. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  250. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  251. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  252. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  253. package/studio/client/dist/index.html +4 -3
  254. package/studio/package.json +8 -14
  255. package/studio/server/agent/ask.test.ts +1 -1
  256. package/studio/server/agent/bridge/grant.test.ts +1 -1
  257. package/studio/server/agent/bridge/routes.test.ts +1 -1
  258. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  259. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  260. package/studio/server/agent/harness/gateway/token.ts +3 -3
  261. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  262. package/studio/server/agent/prompts/anchors.ts +23 -51
  263. package/studio/server/agent/prompts/system.test.ts +4 -4
  264. package/studio/server/agent/prompts/system.ts +5 -5
  265. package/studio/server/agent/routes.test.ts +1 -1
  266. package/studio/server/agent/run/coordinator.test.ts +1 -1
  267. package/studio/server/agent/run/preparation.ts +1 -1
  268. package/studio/server/api/canvas.ts +0 -1
  269. package/studio/server/api/context.ts +1 -1
  270. package/studio/server/api/deployment.ts +1 -1
  271. package/studio/server/api/views.ts +2 -2
  272. package/studio/server/api/workspace.ts +1 -1
  273. package/studio/server/api-agent-loadout.test.ts +1 -1
  274. package/studio/server/api.test.ts +1 -1
  275. package/studio/server/cache.test.ts +24 -18
  276. package/studio/server/cache.ts +10 -54
  277. package/studio/server/cli-consumers.test.ts +31 -3
  278. package/studio/server/domain.test.ts +50 -68
  279. package/studio/server/domain.ts +82 -63
  280. package/studio/server/handoff/copy.ts +2 -2
  281. package/studio/server/index.ts +1 -1
  282. package/studio/server/instances/probe.test.ts +1 -1
  283. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  284. package/studio/server/introspect/anatomy/views.ts +10 -16
  285. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  286. package/studio/server/introspect/anatomy.test.ts +22 -14
  287. package/studio/server/introspect/anatomy.ts +6 -9
  288. package/studio/server/introspect/bundle.ts +0 -3
  289. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  290. package/studio/server/introspect/canonical-schema.ts +248 -601
  291. package/studio/server/introspect/core.ts +14 -22
  292. package/studio/server/introspect/diff.test.ts +21 -9
  293. package/studio/server/introspect/diff.ts +19 -64
  294. package/studio/server/introspect/extractor.ts +23 -122
  295. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  296. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  297. package/studio/server/introspect/overlay.ts +3 -51
  298. package/studio/server/introspect/revision.test.ts +24 -28
  299. package/studio/server/introspect/revision.ts +11 -22
  300. package/studio/server/introspect/runtime.test.ts +77 -195
  301. package/studio/server/introspect/runtime.ts +5 -67
  302. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  303. package/studio/server/introspect/schema-ir-json.ts +39 -69
  304. package/studio/server/introspect/schema-refs.test.ts +43 -88
  305. package/studio/server/introspect/schema-refs.ts +14 -49
  306. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  307. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  308. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  309. package/studio/server/lifecycle.ts +4 -1
  310. package/studio/server/state/baseline.test.ts +14 -9
  311. package/studio/server/state/baseline.ts +3 -5
  312. package/studio/server/state/documents.test.ts +69 -0
  313. package/studio/server/state/documents.ts +57 -10
  314. package/studio/server/state/visibility.ts +1 -5
  315. package/studio/server/views/model.ts +3 -3
  316. package/studio/server/views/target.test.ts +18 -76
  317. package/studio/server/views/target.ts +24 -50
  318. package/studio/server/watch.test.ts +11 -11
  319. package/studio/server/watch.ts +8 -4
  320. package/studio/server/workspace/updates.ts +23 -1
  321. package/studio/server/workspace-watch.ts +2 -2
  322. package/studio/shared/contracts/runtime.ts +4 -0
  323. package/studio/shared/contracts/schema.ts +44 -131
  324. package/studio/shared/contracts/surface.test.ts +15 -17
  325. package/studio/shared/contracts/workspace.ts +5 -11
  326. package/studio/shared/schema/identity.test.ts +20 -44
  327. package/studio/shared/schema/identity.ts +9 -24
  328. package/studio/tsconfig.json +2 -1
  329. package/tsconfig.json +1 -1
  330. package/viewer/dist/index.html +21 -0
  331. package/viewer/dist/main.js +41 -62
  332. package/.check-workspace.cjs +0 -40
  333. package/src/admin/binding.ts +0 -168
  334. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  335. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  336. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  337. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  338. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  339. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  340. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  341. package/studio/server/introspect/core-extractor.ts +0 -139
  342. package/studio/server/introspect/overlay.test.ts +0 -72
  343. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -0,0 +1,333 @@
1
+ import { access, lstat, readFile, realpath } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { loadConfig } from 'tsconfig-paths'
4
+
5
+ import { UiError, type PackageManager } from './model'
6
+
7
+ export type UiProject = {
8
+ root: string
9
+ packageJsonPath: string
10
+ packageJson: Record<string, unknown>
11
+ manager: PackageManager
12
+ lockPath?: string
13
+ cssPath: string
14
+ componentsPath: string
15
+ uiLockPath: string
16
+ isAstraleDomain: boolean
17
+ }
18
+
19
+ const MANAGERS: readonly [string, PackageManager][] = [
20
+ ['pnpm-lock.yaml', 'pnpm'],
21
+ ['bun.lock', 'bun'],
22
+ ['bun.lockb', 'bun'],
23
+ ['yarn.lock', 'yarn'],
24
+ ['package-lock.json', 'npm'],
25
+ ]
26
+
27
+ async function exists(target: string): Promise<boolean> {
28
+ return access(target).then(
29
+ () => true,
30
+ () => false,
31
+ )
32
+ }
33
+
34
+ async function readManifest(target: string): Promise<Record<string, unknown>> {
35
+ try {
36
+ return JSON.parse(await readFile(target, 'utf8')) as Record<string, unknown>
37
+ } catch (cause) {
38
+ throw new UiError('UI_PROJECT_UNSUPPORTED', 'package.json is not valid JSON.', undefined, {
39
+ cause,
40
+ })
41
+ }
42
+ }
43
+
44
+ function pathPatternMatch(pattern: string, candidate: string): string | undefined {
45
+ const wildcard = pattern.indexOf('*')
46
+ if (wildcard === -1) return pattern === candidate ? '' : undefined
47
+ const prefix = pattern.slice(0, wildcard)
48
+ const suffix = pattern.slice(wildcard + 1)
49
+ if (!candidate.startsWith(prefix) || !candidate.endsWith(suffix)) return undefined
50
+ return candidate.slice(prefix.length, candidate.length - suffix.length)
51
+ }
52
+
53
+ function resolvePackageImport(project: UiProject, candidate: string): string | undefined {
54
+ const imports = project.packageJson.imports
55
+ if (!imports || typeof imports !== 'object' || Array.isArray(imports)) return undefined
56
+ const matches = Object.entries(imports as Record<string, unknown>)
57
+ .flatMap(([pattern, target]) => {
58
+ const wildcard = pathPatternMatch(pattern, candidate)
59
+ if (wildcard === undefined || typeof target !== 'string' || !target.startsWith('./'))
60
+ return []
61
+ const wildcardIndex = pattern.indexOf('*')
62
+ return [
63
+ {
64
+ exact: wildcardIndex === -1,
65
+ prefixLength: wildcardIndex === -1 ? pattern.length : wildcardIndex,
66
+ suffixLength: wildcardIndex === -1 ? 0 : pattern.length - wildcardIndex - 1,
67
+ target: target.replaceAll('*', wildcard),
68
+ },
69
+ ]
70
+ })
71
+ .sort(
72
+ (left, right) =>
73
+ Number(right.exact) - Number(left.exact) ||
74
+ right.prefixLength - left.prefixLength ||
75
+ right.suffixLength - left.suffixLength,
76
+ )
77
+ return matches[0] ? path.resolve(project.root, matches[0].target) : undefined
78
+ }
79
+
80
+ async function resolveAlias(project: UiProject, candidate: string): Promise<string | undefined> {
81
+ const config = loadConfig(project.root)
82
+ if (config.resultType === 'failed') return undefined
83
+ const matches: Array<{
84
+ exact: boolean
85
+ prefixLength: number
86
+ suffixLength: number
87
+ resolved: string
88
+ }> = []
89
+ for (const [pattern, replacements] of Object.entries(config.paths)) {
90
+ const wildcard = pathPatternMatch(pattern, candidate)
91
+ if (wildcard === undefined) continue
92
+ const replacement = replacements?.[0]
93
+ if (!replacement) continue
94
+ const wildcardIndex = pattern.indexOf('*')
95
+ matches.push({
96
+ exact: wildcardIndex === -1,
97
+ prefixLength: wildcardIndex === -1 ? pattern.length : wildcardIndex,
98
+ suffixLength: wildcardIndex === -1 ? 0 : pattern.length - wildcardIndex - 1,
99
+ resolved: path.resolve(config.absoluteBaseUrl, replacement.replaceAll('*', wildcard)),
100
+ })
101
+ }
102
+ matches.sort(
103
+ (left, right) =>
104
+ Number(right.exact) - Number(left.exact) ||
105
+ right.prefixLength - left.prefixLength ||
106
+ right.suffixLength - left.suffixLength,
107
+ )
108
+ const best = matches[0]
109
+ if (!best) return undefined
110
+ const ambiguous = matches.some(
111
+ (match) =>
112
+ match.exact === best.exact &&
113
+ match.prefixLength === best.prefixLength &&
114
+ match.suffixLength === best.suffixLength &&
115
+ match.resolved !== best.resolved,
116
+ )
117
+ if (ambiguous) {
118
+ throw new UiError(
119
+ 'UI_PROJECT_UNSUPPORTED',
120
+ 'The components alias resolves to conflicting project paths.',
121
+ 'Keep one authoritative compilerOptions.paths mapping for the components alias.',
122
+ )
123
+ }
124
+ return best.resolved
125
+ }
126
+
127
+ export async function resolveUiRegistryTarget(
128
+ project: UiProject,
129
+ declaredTarget: string,
130
+ ): Promise<string> {
131
+ if (!declaredTarget.startsWith('components/')) return declaredTarget
132
+ const components = await readFile(project.componentsPath, 'utf8')
133
+ .then((value) => JSON.parse(value) as { aliases?: { components?: unknown } })
134
+ .catch(() => undefined)
135
+ const componentsAlias = components?.aliases?.components
136
+ if (typeof componentsAlias !== 'string' || componentsAlias.length === 0) return declaredTarget
137
+ const suffix = declaredTarget.slice('components/'.length)
138
+ const directAlias = /^(?:\.?\.?\/|src\/|app\/|frontend\/|components\/)/u.test(componentsAlias)
139
+ const resolved =
140
+ resolvePackageImport(project, componentsAlias) ??
141
+ (await resolveAlias(project, componentsAlias)) ??
142
+ (directAlias ? path.resolve(project.root, componentsAlias) : undefined)
143
+ if (!resolved) {
144
+ throw new UiError(
145
+ 'UI_PROJECT_UNSUPPORTED',
146
+ 'components.json alias cannot be resolved through tsconfig.json or jsconfig.json.',
147
+ 'Define a matching compilerOptions.paths entry for ' + componentsAlias + '.',
148
+ )
149
+ }
150
+ const relative = path.relative(project.root, path.join(resolved, suffix))
151
+ if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
152
+ throw new UiError('UI_PROJECT_UNSUPPORTED', 'components.json alias escapes the project.')
153
+ }
154
+ return relative.split(path.sep).join('/')
155
+ }
156
+
157
+ function hasReactTailwind(manifest: Record<string, unknown>): boolean {
158
+ const dependencies = {
159
+ ...(manifest.dependencies as Record<string, string> | undefined),
160
+ ...(manifest.devDependencies as Record<string, string> | undefined),
161
+ ...(manifest.peerDependencies as Record<string, string> | undefined),
162
+ }
163
+ return Boolean(dependencies.react && dependencies['react-dom'] && dependencies.tailwindcss)
164
+ }
165
+
166
+ async function assertPhysicalProjectPath(root: string, target: string): Promise<void> {
167
+ const physicalRoot = await realpath(root)
168
+ let existing = target
169
+ while (!(await exists(existing))) {
170
+ const parent = path.dirname(existing)
171
+ if (parent === existing) break
172
+ existing = parent
173
+ }
174
+ const physicalTarget = await realpath(existing)
175
+ const relative = path.relative(physicalRoot, physicalTarget)
176
+ if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
177
+ throw new UiError('UI_PROJECT_UNSUPPORTED', 'components.json CSS path escapes the project.')
178
+ }
179
+ }
180
+
181
+ export async function discoverUiProject(input = process.cwd()): Promise<UiProject> {
182
+ let root = path.resolve(input)
183
+ if (!(await exists(root))) {
184
+ throw new UiError('UI_PROJECT_UNSUPPORTED', 'Project path does not exist: ' + root)
185
+ }
186
+ if (!(await lstat(root)).isDirectory()) root = path.dirname(root)
187
+
188
+ while (true) {
189
+ const manifestPath = path.join(root, 'package.json')
190
+ if (await exists(manifestPath)) {
191
+ const manifest = await readManifest(manifestPath)
192
+ const parent = path.dirname(root)
193
+ if (hasReactTailwind(manifest) || parent === root) break
194
+ if (await exists(path.join(parent, 'package.json'))) {
195
+ root = parent
196
+ continue
197
+ }
198
+ break
199
+ }
200
+ const parent = path.dirname(root)
201
+ if (parent === root) {
202
+ throw new UiError(
203
+ 'UI_PROJECT_UNSUPPORTED',
204
+ 'No package.json found from ' + path.resolve(input),
205
+ 'Run the command inside an existing React application or pass its path.',
206
+ )
207
+ }
208
+ root = parent
209
+ }
210
+
211
+ const packageJsonPath = path.join(root, 'package.json')
212
+ const packageJson = await readManifest(packageJsonPath)
213
+
214
+ let manager: PackageManager = 'npm'
215
+ let lockPath: string | undefined
216
+ for (const [file, candidate] of MANAGERS) {
217
+ const target = path.join(root, file)
218
+ if (await exists(target)) {
219
+ manager = candidate
220
+ lockPath = target
221
+ break
222
+ }
223
+ }
224
+ const declared = packageJson.packageManager
225
+ if (!lockPath && typeof declared === 'string') {
226
+ const candidate = declared.split('@')[0]
227
+ if (
228
+ candidate === 'pnpm' ||
229
+ candidate === 'npm' ||
230
+ candidate === 'yarn' ||
231
+ candidate === 'bun'
232
+ ) {
233
+ manager = candidate
234
+ }
235
+ }
236
+ if (!lockPath) {
237
+ const expectedLock = {
238
+ pnpm: 'pnpm-lock.yaml',
239
+ npm: 'package-lock.json',
240
+ yarn: 'yarn.lock',
241
+ bun: 'bun.lock',
242
+ }[manager]
243
+ lockPath = path.join(root, expectedLock)
244
+ }
245
+
246
+ const rootCssCandidates = ['src/index.css', 'src/app.css', 'app/globals.css', 'src/styles.css']
247
+ const frontendCssCandidates = [
248
+ 'frontend/src/index.css',
249
+ 'frontend/src/app.css',
250
+ 'frontend/src/styles.css',
251
+ ]
252
+ const componentsPath = path.join(root, 'components.json')
253
+ const configuredCss = await readFile(componentsPath, 'utf8')
254
+ .then((value) => {
255
+ const components = JSON.parse(value) as { tailwind?: { css?: unknown } }
256
+ const css = components.tailwind?.css
257
+ if (typeof css !== 'string' || css.length === 0) return undefined
258
+ const target = path.resolve(root, css)
259
+ const relative = path.relative(root, target)
260
+ if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
261
+ throw new UiError('UI_PROJECT_UNSUPPORTED', 'components.json CSS path escapes the project.')
262
+ }
263
+ return { relative: relative.split(path.sep).join('/'), target }
264
+ })
265
+ .catch((error: unknown) => {
266
+ if (error instanceof UiError) throw error
267
+ return undefined
268
+ })
269
+ const configuredCssRelative = configuredCss?.relative
270
+ const cssCandidates =
271
+ configuredCssRelative !== undefined
272
+ ? [configuredCssRelative]
273
+ : (await exists(path.join(root, 'frontend/package.json')))
274
+ ? [...frontendCssCandidates, ...rootCssCandidates]
275
+ : [...rootCssCandidates, ...frontendCssCandidates]
276
+ const resolvedCss = await Promise.all(
277
+ cssCandidates.map(async (file) => ((await exists(path.join(root, file))) ? file : undefined)),
278
+ )
279
+ const cssRelative = configuredCssRelative ?? resolvedCss.find(Boolean) ?? 'src/astrale-ui.css'
280
+ await assertPhysicalProjectPath(root, path.join(root, cssRelative))
281
+
282
+ return {
283
+ root,
284
+ packageJsonPath,
285
+ packageJson,
286
+ manager,
287
+ lockPath,
288
+ cssPath: path.join(root, cssRelative),
289
+ componentsPath,
290
+ uiLockPath: path.join(root, 'astrale-ui.lock.json'),
291
+ isAstraleDomain:
292
+ (await exists(path.join(root, 'astrale.config.ts'))) &&
293
+ (await exists(path.join(root, 'ui/index.ts'))) &&
294
+ (await exists(path.join(root, 'frontend/package.json'))),
295
+ }
296
+ }
297
+
298
+ export function assertSupportedUiProject(project: UiProject): void {
299
+ const manifest = project.packageJson
300
+ const dependencies = {
301
+ ...(manifest.dependencies as Record<string, string> | undefined),
302
+ ...(manifest.devDependencies as Record<string, string> | undefined),
303
+ ...(manifest.peerDependencies as Record<string, string> | undefined),
304
+ }
305
+ if (!dependencies.react || !dependencies['react-dom']) {
306
+ throw new UiError(
307
+ 'UI_PROJECT_UNSUPPORTED',
308
+ 'Astrale UI requires an existing React application.',
309
+ 'Install React first or choose a non-UI project scaffold.',
310
+ )
311
+ }
312
+ const tailwind = dependencies.tailwindcss
313
+ if (!tailwind || !/(?:^|[^0-9])4(?:\.|$)/u.test(tailwind)) {
314
+ throw new UiError(
315
+ 'UI_PROJECT_UNSUPPORTED',
316
+ 'Astrale UI V1 requires Tailwind CSS 4.',
317
+ 'Upgrade Tailwind to v4 before initializing Astrale UI.',
318
+ )
319
+ }
320
+ }
321
+
322
+ export function projectRelative(project: UiProject, target: string): string {
323
+ const relative = path.relative(project.root, target)
324
+ if (
325
+ relative === '' ||
326
+ relative === '..' ||
327
+ relative.startsWith('..' + path.sep) ||
328
+ path.isAbsolute(relative)
329
+ ) {
330
+ throw new UiError('UI_LOCK_INVALID', 'Path escapes the project root: ' + target)
331
+ }
332
+ return relative.split(path.sep).join('/')
333
+ }