@astrale-os/cli 1.0.0-beta.21 → 1.0.0-beta.23
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/dist/astrale.js +670 -622
- package/dist/public/connect-core.js +4 -4
- package/dist/types/admin/contract.d.ts +2 -2
- package/package.json +4 -4
- package/src/admin/contract.ts +3 -2
- package/src/commands/__tests__/read-commands.test.ts +56 -0
- package/src/commands/__tests__/view.test.ts +1 -2
- package/src/commands/introspect.ts +8 -9
- package/src/ui/.spec/architecture.md +8 -6
- package/src/ui/.spec/laws.ts +8 -1
- package/src/ui/__tests__/ui.test.ts +166 -14
- package/src/ui/operations.ts +117 -14
- package/src/ui/release.ts +9 -2
- package/studio/client/dist/assets/{index-7YHdeCxp.js → index-OeJK1TjG.js} +2 -2
- package/studio/client/dist/assets/{schema-studio-CABpTfvH.js → schema-studio-DRdfu_cQ.js} +2 -2
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +3 -3
- package/studio/server/cli-consumers.test.ts +0 -1
- package/studio/server/introspect/anatomy/views.ts +1 -2
- package/studio/server/introspect/anatomy-extras.test.ts +3 -14
- package/studio/server/introspect/canonical-schema.test.ts +1 -1
- package/studio/server/introspect/canonical-schema.ts +0 -4
- package/studio/server/introspect/diff.test.ts +10 -2
- package/studio/server/introspect/diff.ts +1 -9
- package/studio/server/introspect/runtime.test.ts +1 -1
- package/studio/server/introspect/schema-ir-json.test.ts +12 -1
- package/studio/server/introspect/schema-ir-json.ts +1 -1
- package/studio/server/views/target.test.ts +1 -2
- package/studio/shared/contracts/schema.ts +1 -3
- package/studio/tsconfig.json +2 -1
- package/viewer/dist/main.js +13 -13
package/src/ui/operations.ts
CHANGED
|
@@ -134,8 +134,45 @@ function activateTheme(current: string, statement: string): string {
|
|
|
134
134
|
.replace(/^\n/u, '')
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
type ManifestDependencySection = 'dependencies' | 'devDependencies'
|
|
138
|
+
|
|
139
|
+
function manifestDependencies(
|
|
140
|
+
manifest: Record<string, unknown>,
|
|
141
|
+
section: ManifestDependencySection = 'dependencies',
|
|
142
|
+
): Record<string, string> {
|
|
143
|
+
return { ...(manifest[section] as Record<string, string> | undefined) }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function uiDependencySection(
|
|
147
|
+
manifest: Record<string, unknown>,
|
|
148
|
+
fallback: ManifestDependencySection = 'dependencies',
|
|
149
|
+
): ManifestDependencySection {
|
|
150
|
+
const runtime = Object.hasOwn(manifestDependencies(manifest), UI_PACKAGE)
|
|
151
|
+
const development = Object.hasOwn(manifestDependencies(manifest, 'devDependencies'), UI_PACKAGE)
|
|
152
|
+
if (runtime && development) {
|
|
153
|
+
throw new UiError(
|
|
154
|
+
'UI_PROJECT_UNSUPPORTED',
|
|
155
|
+
UI_PACKAGE + ' must be declared in exactly one dependency section.',
|
|
156
|
+
'Remove it from either dependencies or devDependencies, then retry.',
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
if (development) return 'devDependencies'
|
|
160
|
+
if (runtime) return 'dependencies'
|
|
161
|
+
return fallback
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function setUiDependency(
|
|
165
|
+
manifest: Record<string, unknown>,
|
|
166
|
+
section: ManifestDependencySection,
|
|
167
|
+
version: string,
|
|
168
|
+
): void {
|
|
169
|
+
const other = section === 'dependencies' ? 'devDependencies' : 'dependencies'
|
|
170
|
+
manifest[section] = { ...manifestDependencies(manifest, section), [UI_PACKAGE]: version }
|
|
171
|
+
const otherDependencies = manifestDependencies(manifest, other)
|
|
172
|
+
if (Object.hasOwn(otherDependencies, UI_PACKAGE)) {
|
|
173
|
+
delete otherDependencies[UI_PACKAGE]
|
|
174
|
+
manifest[other] = otherDependencies
|
|
175
|
+
}
|
|
139
176
|
}
|
|
140
177
|
|
|
141
178
|
function domainRegistryPackagePath(project: UiProject): string {
|
|
@@ -294,6 +331,10 @@ export async function initUi(
|
|
|
294
331
|
}
|
|
295
332
|
if ((await exists(project.uiLockPath)) && !options.force) {
|
|
296
333
|
const lock = await readUiLock(project.uiLockPath)
|
|
334
|
+
const dependencySection = uiDependencySection(project.packageJson)
|
|
335
|
+
const dependencyVersion = manifestDependencies(project.packageJson, dependencySection)[
|
|
336
|
+
UI_PACKAGE
|
|
337
|
+
]
|
|
297
338
|
const css = (await readOptional(project.cssPath)) ?? ''
|
|
298
339
|
const components = await readOptional(project.componentsPath)
|
|
299
340
|
.then((value) => (value ? (JSON.parse(value) as Record<string, unknown>) : undefined))
|
|
@@ -301,6 +342,7 @@ export async function initUi(
|
|
|
301
342
|
const requestedVersion = options.version?.replace(/^v/u, '')
|
|
302
343
|
const desired =
|
|
303
344
|
(!requestedVersion || requestedVersion === lock.package.version) &&
|
|
345
|
+
dependencyVersion === lock.package.version &&
|
|
304
346
|
(!options.preset || options.preset === lock.preset) &&
|
|
305
347
|
css.includes(UI_PACKAGE + '/theme.css') &&
|
|
306
348
|
css.includes(UI_PACKAGE + '/presets/' + lock.preset + '.css') &&
|
|
@@ -362,10 +404,7 @@ export async function initUi(
|
|
|
362
404
|
|
|
363
405
|
try {
|
|
364
406
|
const manifest = { ...project.packageJson }
|
|
365
|
-
manifest
|
|
366
|
-
...manifestDependencies(manifest),
|
|
367
|
-
[UI_PACKAGE]: release.version,
|
|
368
|
-
}
|
|
407
|
+
setUiDependency(manifest, uiDependencySection(manifest), release.version)
|
|
369
408
|
await writeDomainRegistryWorkspace(project, manifest)
|
|
370
409
|
await writeJson(project.packageJsonPath, manifest)
|
|
371
410
|
|
|
@@ -461,14 +500,17 @@ export async function initUi(
|
|
|
461
500
|
async function pinUiDependency(
|
|
462
501
|
project: UiProject,
|
|
463
502
|
version: string,
|
|
503
|
+
section: ManifestDependencySection,
|
|
464
504
|
runner: UiRunner,
|
|
465
505
|
): Promise<void> {
|
|
466
506
|
const manifest = JSON.parse(await readFile(project.packageJsonPath, 'utf8')) as Record<
|
|
467
507
|
string,
|
|
468
508
|
unknown
|
|
469
509
|
>
|
|
470
|
-
|
|
471
|
-
|
|
510
|
+
const current = manifestDependencies(manifest, section)[UI_PACKAGE]
|
|
511
|
+
const other = section === 'dependencies' ? 'devDependencies' : 'dependencies'
|
|
512
|
+
if (current === version && manifestDependencies(manifest, other)[UI_PACKAGE] === undefined) return
|
|
513
|
+
setUiDependency(manifest, section, version)
|
|
472
514
|
await writeJson(project.packageJsonPath, manifest)
|
|
473
515
|
const result = await runner(project.manager, ['install'], project.root)
|
|
474
516
|
if (result.code !== 0) {
|
|
@@ -544,6 +586,17 @@ async function addLocalTheme(
|
|
|
544
586
|
const source = await readFile(sourcePath, 'utf8')
|
|
545
587
|
admitLocalThemeCss(source, slug)
|
|
546
588
|
|
|
589
|
+
return installThemeCss(slug, source, digest(source), address, project, options)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function installThemeCss(
|
|
593
|
+
slug: string,
|
|
594
|
+
source: string,
|
|
595
|
+
sourceDigest: string,
|
|
596
|
+
sourceLabel: string,
|
|
597
|
+
project: UiProject,
|
|
598
|
+
options: { dryRun?: boolean; overwrite?: boolean },
|
|
599
|
+
): Promise<Record<string, unknown>> {
|
|
547
600
|
const lock = await readUiLock(project.uiLockPath)
|
|
548
601
|
const canonicalAddress = 'theme/' + slug
|
|
549
602
|
const relativeTarget = 'components/astrale/theme/' + slug + '.css'
|
|
@@ -573,7 +626,7 @@ async function addLocalTheme(
|
|
|
573
626
|
items: [canonicalAddress],
|
|
574
627
|
files: { [canonicalAddress]: { [relativeTarget]: digest(source) } },
|
|
575
628
|
activation: { file: projectRelative(project, project.cssPath), import: statement },
|
|
576
|
-
source:
|
|
629
|
+
source: sourceLabel,
|
|
577
630
|
}
|
|
578
631
|
if (options.dryRun) return result
|
|
579
632
|
|
|
@@ -588,7 +641,7 @@ async function addLocalTheme(
|
|
|
588
641
|
await writeFile(project.cssPath, activateTheme(css, statement), 'utf8')
|
|
589
642
|
lock.items[canonicalAddress] = {
|
|
590
643
|
address: canonicalAddress,
|
|
591
|
-
sourceDigest
|
|
644
|
+
sourceDigest,
|
|
592
645
|
files: { [relativeTarget]: digest(source) },
|
|
593
646
|
}
|
|
594
647
|
await writeJson(project.uiLockPath, lock)
|
|
@@ -602,6 +655,28 @@ async function addLocalTheme(
|
|
|
602
655
|
}
|
|
603
656
|
}
|
|
604
657
|
|
|
658
|
+
async function addReleasedTheme(
|
|
659
|
+
item: UiRegistryItem,
|
|
660
|
+
document: UiRegistryItem,
|
|
661
|
+
project: UiProject,
|
|
662
|
+
options: { dryRun?: boolean; overwrite?: boolean },
|
|
663
|
+
): Promise<Record<string, unknown>> {
|
|
664
|
+
const slug = item.meta.canonicalAddress.slice('theme/'.length)
|
|
665
|
+
const source = document.files[0]?.content
|
|
666
|
+
if (typeof source !== 'string') {
|
|
667
|
+
throw new UiError('UI_REGISTRY_UNAVAILABLE', 'The released theme has no admitted CSS source.')
|
|
668
|
+
}
|
|
669
|
+
admitLocalThemeCss(source, slug)
|
|
670
|
+
return installThemeCss(
|
|
671
|
+
slug,
|
|
672
|
+
source,
|
|
673
|
+
digest(JSON.stringify(document)),
|
|
674
|
+
item.meta.canonicalAddress,
|
|
675
|
+
project,
|
|
676
|
+
options,
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
605
680
|
export async function addUi(
|
|
606
681
|
addresses: string[],
|
|
607
682
|
options: { project?: string; dryRun?: boolean; overwrite?: boolean; yes?: boolean },
|
|
@@ -630,6 +705,7 @@ export async function addUi(
|
|
|
630
705
|
return addLocalTheme(localThemes[0]!, project, options)
|
|
631
706
|
}
|
|
632
707
|
const { lock, release } = await lockedRelease(project, dependencies.fetcher)
|
|
708
|
+
const dependencySection = uiDependencySection(project.packageJson)
|
|
633
709
|
const items = addresses.map((address) => findItem(release, address))
|
|
634
710
|
const themes = items.filter((item) => item.type === 'registry:theme')
|
|
635
711
|
if (themes.length > 1) {
|
|
@@ -638,6 +714,15 @@ export async function addUi(
|
|
|
638
714
|
const itemDocuments = await Promise.all(
|
|
639
715
|
items.map((item) => readUiRegistryItem(release, item, dependencies.fetcher)),
|
|
640
716
|
)
|
|
717
|
+
if (themes.length === 1) {
|
|
718
|
+
if (items.length !== 1) {
|
|
719
|
+
throw new UiError(
|
|
720
|
+
'UI_ITEM_CONFLICT',
|
|
721
|
+
'Install a released theme separately from patterns and blocks.',
|
|
722
|
+
)
|
|
723
|
+
}
|
|
724
|
+
return addReleasedTheme(themes[0]!, itemDocuments[0]!, project, options)
|
|
725
|
+
}
|
|
641
726
|
await rejectLocalChanges(project, lock, items, options.overwrite === true)
|
|
642
727
|
const targets = (
|
|
643
728
|
await Promise.all(
|
|
@@ -689,7 +774,12 @@ export async function addUi(
|
|
|
689
774
|
throw new UiError('UI_TOOL_FAILED', 'The pinned shadcn operation omitted an item file.')
|
|
690
775
|
}
|
|
691
776
|
}
|
|
692
|
-
await pinUiDependency(
|
|
777
|
+
await pinUiDependency(
|
|
778
|
+
project,
|
|
779
|
+
lock.package.version,
|
|
780
|
+
dependencySection,
|
|
781
|
+
dependencies.runner ?? defaultUiRunner,
|
|
782
|
+
)
|
|
693
783
|
const theme = themes[0]
|
|
694
784
|
const themeTarget = theme?.files[0]?.target
|
|
695
785
|
if (themeTarget) {
|
|
@@ -779,11 +869,24 @@ export async function doctorUi(
|
|
|
779
869
|
detail: error instanceof Error ? error.message : String(error),
|
|
780
870
|
})
|
|
781
871
|
}
|
|
782
|
-
const
|
|
872
|
+
const runtimeVersion = manifestDependencies(project.packageJson)[UI_PACKAGE]
|
|
873
|
+
const developmentVersion = manifestDependencies(project.packageJson, 'devDependencies')[
|
|
874
|
+
UI_PACKAGE
|
|
875
|
+
]
|
|
876
|
+
const declarations = [runtimeVersion, developmentVersion].filter(
|
|
877
|
+
(version): version is string => typeof version === 'string',
|
|
878
|
+
)
|
|
879
|
+
const packageVersion = declarations.length === 1 ? declarations[0] : undefined
|
|
783
880
|
checks.push({
|
|
784
881
|
check: 'package',
|
|
785
|
-
ok:
|
|
786
|
-
detail:
|
|
882
|
+
ok: declarations.length === 1 && (!lock || packageVersion === lock.package.version),
|
|
883
|
+
detail:
|
|
884
|
+
declarations.length > 1
|
|
885
|
+
? 'declared in dependencies and devDependencies'
|
|
886
|
+
: packageVersion === undefined
|
|
887
|
+
? 'missing'
|
|
888
|
+
: (runtimeVersion === packageVersion ? 'dependencies:' : 'devDependencies:') +
|
|
889
|
+
packageVersion,
|
|
787
890
|
})
|
|
788
891
|
const css = (await readOptional(project.cssPath)) ?? ''
|
|
789
892
|
checks.push({ check: 'theme', ok: css.includes(UI_PACKAGE + '/theme.css') })
|
package/src/ui/release.ts
CHANGED
|
@@ -250,8 +250,14 @@ function isInstallableItem(item: unknown): item is UiRegistry['items'][number] {
|
|
|
250
250
|
/^(?:pattern|block)-[a-z0-9-]+$/u.test(candidate.name) &&
|
|
251
251
|
typeof address === 'string' &&
|
|
252
252
|
/^(?:pattern|block)\/[a-z0-9-]+\/[a-z0-9-/]+$/u.test(address)
|
|
253
|
+
const isComponent =
|
|
254
|
+
candidate.type === 'registry:component' &&
|
|
255
|
+
typeof candidate.name === 'string' &&
|
|
256
|
+
/^component-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(candidate.name) &&
|
|
257
|
+
typeof address === 'string' &&
|
|
258
|
+
/^component\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(address)
|
|
253
259
|
return (
|
|
254
|
-
(isTheme || isComposition) &&
|
|
260
|
+
(isTheme || isComposition || isComponent) &&
|
|
255
261
|
(candidate.dependencies === undefined ||
|
|
256
262
|
(Array.isArray(candidate.dependencies) &&
|
|
257
263
|
candidate.dependencies.every(
|
|
@@ -274,7 +280,8 @@ function isInstallableItem(item: unknown): item is UiRegistry['items'][number] {
|
|
|
274
280
|
(candidate.files?.length === 1 &&
|
|
275
281
|
file.type === 'registry:file' &&
|
|
276
282
|
file.target ===
|
|
277
|
-
'components/astrale/theme/' + address!.slice('theme/'.length) + '.css'))
|
|
283
|
+
'components/astrale/theme/' + address!.slice('theme/'.length) + '.css')) &&
|
|
284
|
+
(!isComponent || file.target.startsWith('components/astrale/component/')),
|
|
278
285
|
) &&
|
|
279
286
|
typeof address === 'string'
|
|
280
287
|
)
|