@astrale-os/cli 1.0.0-beta.21 → 1.0.0-beta.22
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 +21 -3
- package/package.json +1 -1
- package/src/ui/.spec/architecture.md +4 -3
- package/src/ui/.spec/laws.ts +1 -1
- package/src/ui/__tests__/ui.test.ts +19 -12
- package/src/ui/operations.ts +44 -2
package/dist/astrale.js
CHANGED
|
@@ -2578,7 +2578,7 @@ var package_default;
|
|
|
2578
2578
|
var init_package = __esm(() => {
|
|
2579
2579
|
package_default = {
|
|
2580
2580
|
name: "@astrale-os/cli",
|
|
2581
|
-
version: "1.0.0-beta.
|
|
2581
|
+
version: "1.0.0-beta.22",
|
|
2582
2582
|
description: "Astrale CLI — connect to existing Astrale kernels",
|
|
2583
2583
|
keywords: [
|
|
2584
2584
|
"astrale",
|
|
@@ -85157,6 +85157,9 @@ async function addLocalTheme(address, project2, options) {
|
|
|
85157
85157
|
}
|
|
85158
85158
|
const source2 = await readFile18(sourcePath, "utf8");
|
|
85159
85159
|
admitLocalThemeCss(source2, slug);
|
|
85160
|
+
return installThemeCss(slug, source2, digest5(source2), address, project2, options);
|
|
85161
|
+
}
|
|
85162
|
+
async function installThemeCss(slug, source2, sourceDigest, sourceLabel, project2, options) {
|
|
85160
85163
|
const lock = await readUiLock(project2.uiLockPath);
|
|
85161
85164
|
const canonicalAddress = "theme/" + slug;
|
|
85162
85165
|
const relativeTarget = "components/astrale/theme/" + slug + ".css";
|
|
@@ -85178,7 +85181,7 @@ async function addLocalTheme(address, project2, options) {
|
|
|
85178
85181
|
items: [canonicalAddress],
|
|
85179
85182
|
files: { [canonicalAddress]: { [relativeTarget]: digest5(source2) } },
|
|
85180
85183
|
activation: { file: projectRelative(project2, project2.cssPath), import: statement },
|
|
85181
|
-
source:
|
|
85184
|
+
source: sourceLabel
|
|
85182
85185
|
};
|
|
85183
85186
|
if (options.dryRun)
|
|
85184
85187
|
return result;
|
|
@@ -85193,7 +85196,7 @@ async function addLocalTheme(address, project2, options) {
|
|
|
85193
85196
|
await writeFile6(project2.cssPath, activateTheme(css, statement), "utf8");
|
|
85194
85197
|
lock.items[canonicalAddress] = {
|
|
85195
85198
|
address: canonicalAddress,
|
|
85196
|
-
sourceDigest
|
|
85199
|
+
sourceDigest,
|
|
85197
85200
|
files: { [relativeTarget]: digest5(source2) }
|
|
85198
85201
|
};
|
|
85199
85202
|
await writeJson(project2.uiLockPath, lock);
|
|
@@ -85208,6 +85211,15 @@ async function addLocalTheme(address, project2, options) {
|
|
|
85208
85211
|
throw error52;
|
|
85209
85212
|
}
|
|
85210
85213
|
}
|
|
85214
|
+
async function addReleasedTheme(item, document, project2, options) {
|
|
85215
|
+
const slug = item.meta.canonicalAddress.slice("theme/".length);
|
|
85216
|
+
const source2 = document.files[0]?.content;
|
|
85217
|
+
if (typeof source2 !== "string") {
|
|
85218
|
+
throw new UiError("UI_REGISTRY_UNAVAILABLE", "The released theme has no admitted CSS source.");
|
|
85219
|
+
}
|
|
85220
|
+
admitLocalThemeCss(source2, slug);
|
|
85221
|
+
return installThemeCss(slug, source2, digest5(JSON.stringify(document)), item.meta.canonicalAddress, project2, options);
|
|
85222
|
+
}
|
|
85211
85223
|
async function addUi(addresses, options, dependencies = {}) {
|
|
85212
85224
|
if (addresses.length === 0) {
|
|
85213
85225
|
throw new UiError("UI_ITEM_NOT_FOUND", "No UI item was provided.");
|
|
@@ -85231,6 +85243,12 @@ async function addUi(addresses, options, dependencies = {}) {
|
|
|
85231
85243
|
throw new UiError("UI_ITEM_CONFLICT", "Only one theme can be activated per add operation.");
|
|
85232
85244
|
}
|
|
85233
85245
|
const itemDocuments = await Promise.all(items.map((item) => readUiRegistryItem(release, item, dependencies.fetcher)));
|
|
85246
|
+
if (themes.length === 1) {
|
|
85247
|
+
if (items.length !== 1) {
|
|
85248
|
+
throw new UiError("UI_ITEM_CONFLICT", "Install a released theme separately from patterns and blocks.");
|
|
85249
|
+
}
|
|
85250
|
+
return addReleasedTheme(themes[0], itemDocuments[0], project2, options);
|
|
85251
|
+
}
|
|
85234
85252
|
await rejectLocalChanges(project2, lock, items, options.overwrite === true);
|
|
85235
85253
|
const targets = (await Promise.all(items.flatMap((item) => item.files.filter((file2) => file2.target).map((file2) => assertSafePlannedTarget(project2, file2.target))))).filter((target2, index3, all) => all.indexOf(target2) === index3);
|
|
85236
85254
|
const invocation3 = shadcnInvocation(project2.manager, release.compatibility.shadcn, [
|
package/package.json
CHANGED
|
@@ -5,8 +5,9 @@ does not add React, Base UI, Tailwind, shadcn, or the UI runtime to the installe
|
|
|
5
5
|
graph.
|
|
6
6
|
|
|
7
7
|
One registry operation resolves one immutable UI commit. Package compatibility and registry reads
|
|
8
|
-
use that commit
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
use that commit. Patterns and blocks invoke the release-qualified shadcn CLI on demand through the
|
|
9
|
+
detected package manager. Released themes copy the exact admitted embedded CSS directly, while a
|
|
10
|
+
local playground export is admitted and copied without registry or shadcn. Both theme paths install
|
|
11
|
+
consumer-owned source and activate it through one project-relative CSS
|
|
11
12
|
import, and complete application writes before the lock advances. Dry-run, list, and doctor do not
|
|
12
13
|
claim a file mutation.
|
package/src/ui/.spec/laws.ts
CHANGED
|
@@ -24,7 +24,7 @@ export const CLI_UI_BOUNDED_REMOTE_DOCUMENTS = defineLaw({
|
|
|
24
24
|
export const CLI_UI_EXACT_ITEM_SOURCE = defineLaw({
|
|
25
25
|
id: 'CLI-UI-EXACT-ITEM-SOURCE',
|
|
26
26
|
statement:
|
|
27
|
-
'An add operation admits the exact built item document against the release index and records
|
|
27
|
+
'An add operation admits the exact built item document against the release index and records its content-bearing source digest before any consumer mutation; only patterns and blocks invoke project tooling.',
|
|
28
28
|
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-EXACT-ITEM-SOURCE' }],
|
|
29
29
|
})
|
|
30
30
|
|
|
@@ -183,21 +183,25 @@ function themeFetch(seen: string[] = []): typeof fetch {
|
|
|
183
183
|
}
|
|
184
184
|
if (url.endsWith('/registry/themes/registry.json')) return Response.json(themeRegistry)
|
|
185
185
|
if (url.endsWith('/registry/public/r/theme-observatory.json')) {
|
|
186
|
-
return Response.json(
|
|
187
|
-
...themeRegistry.items[0],
|
|
188
|
-
files: [
|
|
189
|
-
{
|
|
190
|
-
...themeRegistry.items[0]!.files[0],
|
|
191
|
-
path: 'registry/themes/observatory.css',
|
|
192
|
-
content: themeCss,
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
})
|
|
186
|
+
return Response.json(builtThemeItem())
|
|
196
187
|
}
|
|
197
188
|
return new Response('not found', { status: 404 })
|
|
198
189
|
}) as typeof fetch
|
|
199
190
|
}
|
|
200
191
|
|
|
192
|
+
function builtThemeItem() {
|
|
193
|
+
return {
|
|
194
|
+
...themeRegistry.items[0],
|
|
195
|
+
files: [
|
|
196
|
+
{
|
|
197
|
+
...themeRegistry.items[0]!.files[0],
|
|
198
|
+
path: 'registry/themes/observatory.css',
|
|
199
|
+
content: themeCss,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
201
205
|
function builtItem(item: UiRegistry['items'][number]) {
|
|
202
206
|
return {
|
|
203
207
|
...item,
|
|
@@ -887,26 +891,29 @@ describe('UI source operations', () => {
|
|
|
887
891
|
"@import '@astrale-os/ui/theme.css';\n@import '@astrale-os/ui/presets/astrale.css';\n",
|
|
888
892
|
)
|
|
889
893
|
const target = path.join(root, 'components/astrale/theme/observatory.css')
|
|
894
|
+
let invoked = false
|
|
890
895
|
const result = await addUi(
|
|
891
896
|
['theme/observatory'],
|
|
892
897
|
{ project: root, yes: true },
|
|
893
898
|
{
|
|
894
899
|
fetcher: themeFetch(),
|
|
895
900
|
runner: async () => {
|
|
896
|
-
|
|
897
|
-
await writeFile(target, themeCss)
|
|
901
|
+
invoked = true
|
|
898
902
|
return { code: 0, stdout: '', stderr: '' }
|
|
899
903
|
},
|
|
900
904
|
},
|
|
901
905
|
)
|
|
902
906
|
|
|
907
|
+
expect(invoked).toBe(false)
|
|
903
908
|
expect(result).toMatchObject({ status: 'installed', items: ['theme/observatory'] })
|
|
909
|
+
expect(await readFile(target, 'utf8')).toBe(themeCss)
|
|
904
910
|
expect(await readFile(path.join(root, 'src/index.css'), 'utf8')).toContain(
|
|
905
911
|
"@import '../components/astrale/theme/observatory.css';",
|
|
906
912
|
)
|
|
907
913
|
const written = JSON.parse(await readFile(path.join(root, 'astrale-ui.lock.json'), 'utf8'))
|
|
908
914
|
expect(written.items['theme/observatory']).toMatchObject({
|
|
909
915
|
address: 'theme/observatory',
|
|
916
|
+
sourceDigest: digest(JSON.stringify(builtThemeItem())),
|
|
910
917
|
files: { 'components/astrale/theme/observatory.css': digest(themeCss) },
|
|
911
918
|
})
|
|
912
919
|
expect((await doctorUi(root)).healthy).toBe(true)
|
package/src/ui/operations.ts
CHANGED
|
@@ -544,6 +544,17 @@ async function addLocalTheme(
|
|
|
544
544
|
const source = await readFile(sourcePath, 'utf8')
|
|
545
545
|
admitLocalThemeCss(source, slug)
|
|
546
546
|
|
|
547
|
+
return installThemeCss(slug, source, digest(source), address, project, options)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function installThemeCss(
|
|
551
|
+
slug: string,
|
|
552
|
+
source: string,
|
|
553
|
+
sourceDigest: string,
|
|
554
|
+
sourceLabel: string,
|
|
555
|
+
project: UiProject,
|
|
556
|
+
options: { dryRun?: boolean; overwrite?: boolean },
|
|
557
|
+
): Promise<Record<string, unknown>> {
|
|
547
558
|
const lock = await readUiLock(project.uiLockPath)
|
|
548
559
|
const canonicalAddress = 'theme/' + slug
|
|
549
560
|
const relativeTarget = 'components/astrale/theme/' + slug + '.css'
|
|
@@ -573,7 +584,7 @@ async function addLocalTheme(
|
|
|
573
584
|
items: [canonicalAddress],
|
|
574
585
|
files: { [canonicalAddress]: { [relativeTarget]: digest(source) } },
|
|
575
586
|
activation: { file: projectRelative(project, project.cssPath), import: statement },
|
|
576
|
-
source:
|
|
587
|
+
source: sourceLabel,
|
|
577
588
|
}
|
|
578
589
|
if (options.dryRun) return result
|
|
579
590
|
|
|
@@ -588,7 +599,7 @@ async function addLocalTheme(
|
|
|
588
599
|
await writeFile(project.cssPath, activateTheme(css, statement), 'utf8')
|
|
589
600
|
lock.items[canonicalAddress] = {
|
|
590
601
|
address: canonicalAddress,
|
|
591
|
-
sourceDigest
|
|
602
|
+
sourceDigest,
|
|
592
603
|
files: { [relativeTarget]: digest(source) },
|
|
593
604
|
}
|
|
594
605
|
await writeJson(project.uiLockPath, lock)
|
|
@@ -602,6 +613,28 @@ async function addLocalTheme(
|
|
|
602
613
|
}
|
|
603
614
|
}
|
|
604
615
|
|
|
616
|
+
async function addReleasedTheme(
|
|
617
|
+
item: UiRegistryItem,
|
|
618
|
+
document: UiRegistryItem,
|
|
619
|
+
project: UiProject,
|
|
620
|
+
options: { dryRun?: boolean; overwrite?: boolean },
|
|
621
|
+
): Promise<Record<string, unknown>> {
|
|
622
|
+
const slug = item.meta.canonicalAddress.slice('theme/'.length)
|
|
623
|
+
const source = document.files[0]?.content
|
|
624
|
+
if (typeof source !== 'string') {
|
|
625
|
+
throw new UiError('UI_REGISTRY_UNAVAILABLE', 'The released theme has no admitted CSS source.')
|
|
626
|
+
}
|
|
627
|
+
admitLocalThemeCss(source, slug)
|
|
628
|
+
return installThemeCss(
|
|
629
|
+
slug,
|
|
630
|
+
source,
|
|
631
|
+
digest(JSON.stringify(document)),
|
|
632
|
+
item.meta.canonicalAddress,
|
|
633
|
+
project,
|
|
634
|
+
options,
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
|
|
605
638
|
export async function addUi(
|
|
606
639
|
addresses: string[],
|
|
607
640
|
options: { project?: string; dryRun?: boolean; overwrite?: boolean; yes?: boolean },
|
|
@@ -638,6 +671,15 @@ export async function addUi(
|
|
|
638
671
|
const itemDocuments = await Promise.all(
|
|
639
672
|
items.map((item) => readUiRegistryItem(release, item, dependencies.fetcher)),
|
|
640
673
|
)
|
|
674
|
+
if (themes.length === 1) {
|
|
675
|
+
if (items.length !== 1) {
|
|
676
|
+
throw new UiError(
|
|
677
|
+
'UI_ITEM_CONFLICT',
|
|
678
|
+
'Install a released theme separately from patterns and blocks.',
|
|
679
|
+
)
|
|
680
|
+
}
|
|
681
|
+
return addReleasedTheme(themes[0]!, itemDocuments[0]!, project, options)
|
|
682
|
+
}
|
|
641
683
|
await rejectLocalChanges(project, lock, items, options.overwrite === true)
|
|
642
684
|
const targets = (
|
|
643
685
|
await Promise.all(
|