@astrale-os/cli 1.0.0-beta.11 → 1.0.0-beta.13
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 +15432 -14745
- package/dist/types/admin/contract.d.ts +26 -0
- package/dist/types/admin/instance/client.d.ts +2 -4
- package/dist/types/admin/instance/model.d.ts +3 -0
- package/package.json +1 -1
- package/src/admin/.spec/architecture.md +12 -5
- package/src/admin/__tests__/fixture.ts +19 -95
- package/src/admin/catalog/.spec/api.d.ts +0 -2
- package/src/admin/catalog/.spec/architecture.md +5 -4
- package/src/admin/catalog/__tests__/client.test.ts +136 -33
- package/src/admin/catalog/client.ts +38 -61
- package/src/admin/contract.ts +46 -0
- package/src/admin/instance/.spec/api.d.ts +4 -8
- package/src/admin/instance/.spec/architecture.md +7 -7
- package/src/admin/instance/__tests__/client.test.ts +138 -31
- package/src/admin/instance/client.ts +32 -37
- package/src/admin/instance/model.ts +3 -0
- package/src/commands/__tests__/call.test.ts +34 -0
- package/src/commands/__tests__/read-commands.test.ts +27 -0
- package/src/commands/__tests__/token-ttl.test.ts +49 -4
- package/src/commands/call.ts +28 -3
- package/src/commands/query.ts +8 -2
- package/src/commands/token.ts +34 -20
- package/src/commands/ui/__tests__/commands.test.ts +129 -0
- package/src/commands/ui/add.ts +51 -0
- package/src/commands/ui/doctor.ts +13 -0
- package/src/commands/ui/init.ts +38 -0
- package/src/commands/ui/list.ts +26 -0
- package/src/commands/ui/preset-apply.ts +19 -0
- package/src/commands/ui/preset-list.ts +12 -0
- package/src/commands/ui/shared.ts +25 -0
- package/src/lib/__tests__/binary.test.ts +16 -1
- package/src/lib/binary.ts +22 -5
- package/src/lib/proc.ts +7 -2
- package/src/program/.spec/api.d.ts +1 -0
- package/src/program/__tests__/program.test.ts +32 -2
- package/src/program/build.ts +23 -1
- package/src/program/command.ts +1 -0
- package/src/program/registry.ts +2 -1
- package/src/ui/.spec/api.d.ts +14 -0
- package/src/ui/.spec/architecture.md +10 -0
- package/src/ui/.spec/laws.ts +36 -0
- package/src/ui/.spec/layout.ts +16 -0
- package/src/ui/__tests__/ui.test.ts +542 -0
- package/src/ui/index.ts +13 -0
- package/src/ui/lock.ts +87 -0
- package/src/ui/model.ts +83 -0
- package/src/ui/operations.ts +539 -0
- package/src/ui/project.ts +146 -0
- package/src/ui/release.ts +267 -0
- package/src/ui/runner.ts +18 -0
- package/studio/server/agent/harness/gateway/token.test.ts +1 -1
- package/studio/server/agent/harness/gateway/token.ts +3 -3
- package/dist/types/admin/binding.d.ts +0 -19
- package/src/admin/__tests__/binding.test.ts +0 -31
- package/src/admin/binding.ts +0 -98
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { doctorUi } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'doctor',
|
|
8
|
+
description: 'Verify Astrale UI package, CSS, config, lock, and installed source integrity',
|
|
9
|
+
arguments: [{ name: 'path', description: 'Application directory', required: false }],
|
|
10
|
+
options: [UI_JSON_OPTION],
|
|
11
|
+
action: async (path: string | undefined, options: UiCommandOptions) =>
|
|
12
|
+
runUiCommand(options, () => doctorUi(path)),
|
|
13
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { initUi, type UiPreset } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & {
|
|
7
|
+
preset?: UiPreset
|
|
8
|
+
version?: string
|
|
9
|
+
dryRun?: boolean
|
|
10
|
+
force?: boolean
|
|
11
|
+
install?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'init',
|
|
16
|
+
description: 'Initialize Astrale UI in an existing React and Tailwind v4 application',
|
|
17
|
+
arguments: [{ name: 'path', description: 'Application directory', required: false }],
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
flags: '--preset <name>',
|
|
21
|
+
description: 'Visual preset',
|
|
22
|
+
choices: ['astrale', 'compact', 'expressive'],
|
|
23
|
+
default: 'astrale',
|
|
24
|
+
},
|
|
25
|
+
{ flags: '--version <version>', description: 'Exact @astrale-os/ui version or release tag' },
|
|
26
|
+
{ flags: '--dry-run', description: 'Print the complete plan without writing or installing' },
|
|
27
|
+
{ flags: '--force', description: 'Replace an existing Astrale UI lock intentionally' },
|
|
28
|
+
{
|
|
29
|
+
flags: '--no-install',
|
|
30
|
+
description: 'Write configuration without running the package manager',
|
|
31
|
+
},
|
|
32
|
+
UI_JSON_OPTION,
|
|
33
|
+
],
|
|
34
|
+
afterHelpText:
|
|
35
|
+
'\nExamples:\n $ astrale ui init --preset astrale\n $ astrale ui init ./app --dry-run --json\n',
|
|
36
|
+
action: async (path: string | undefined, options: Options) =>
|
|
37
|
+
runUiCommand(options, () => initUi({ path, ...options })),
|
|
38
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { listUi } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & { type?: string; limit?: string; version?: string }
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'list',
|
|
10
|
+
description: 'List or search Astrale patterns and blocks',
|
|
11
|
+
arguments: [{ name: 'query', description: 'Optional search text', required: false }],
|
|
12
|
+
options: [
|
|
13
|
+
{
|
|
14
|
+
flags: '--type <type>',
|
|
15
|
+
description: 'Restrict to pattern or block',
|
|
16
|
+
choices: ['pattern', 'block'],
|
|
17
|
+
},
|
|
18
|
+
{ flags: '--limit <n>', description: 'Maximum results', default: '100' },
|
|
19
|
+
{ flags: '--version <version>', description: 'Exact release to inspect' },
|
|
20
|
+
UI_JSON_OPTION,
|
|
21
|
+
],
|
|
22
|
+
action: async (query: string | undefined, options: Options) =>
|
|
23
|
+
runUiCommand(options, () =>
|
|
24
|
+
listUi(query, { type: options.type, limit: Number(options.limit), version: options.version }),
|
|
25
|
+
),
|
|
26
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { applyPreset, type UiPreset } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, UI_PROJECT_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
type Options = UiCommandOptions & { dryRun?: boolean }
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'apply',
|
|
10
|
+
description: 'Change the preset CSS import without rewriting component source',
|
|
11
|
+
arguments: [{ name: 'name', description: 'Preset name' }],
|
|
12
|
+
options: [
|
|
13
|
+
UI_PROJECT_OPTION,
|
|
14
|
+
{ flags: '--dry-run', description: 'Show the CSS and lock change without writing' },
|
|
15
|
+
UI_JSON_OPTION,
|
|
16
|
+
],
|
|
17
|
+
action: async (name: UiPreset, options: Options) =>
|
|
18
|
+
runUiCommand(options, () => applyPreset(name, options)),
|
|
19
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../program'
|
|
2
|
+
|
|
3
|
+
import { UI_PRESETS } from '../../ui'
|
|
4
|
+
import { UI_JSON_OPTION, runUiCommand, type UiCommandOptions } from './shared'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
name: 'list',
|
|
8
|
+
description: 'List qualified Astrale UI visual presets',
|
|
9
|
+
options: [UI_JSON_OPTION],
|
|
10
|
+
action: async (options: UiCommandOptions) =>
|
|
11
|
+
runUiCommand(options, async () => UI_PRESETS.map((name) => ({ name }))),
|
|
12
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { fatal } from '../../lib/log'
|
|
2
|
+
import { output } from '../../lib/output'
|
|
3
|
+
|
|
4
|
+
export type UiCommandOptions = { json?: boolean; project?: string }
|
|
5
|
+
|
|
6
|
+
export async function runUiCommand(
|
|
7
|
+
options: UiCommandOptions,
|
|
8
|
+
operation: () => Promise<unknown>,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
output(await operation(), { json: options.json })
|
|
12
|
+
} catch (error) {
|
|
13
|
+
fatal(error, { json: options.json })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const UI_PROJECT_OPTION = {
|
|
18
|
+
flags: '--project <path>',
|
|
19
|
+
description: 'Existing application root (defaults to the current project)',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export const UI_JSON_OPTION = {
|
|
23
|
+
flags: '--json',
|
|
24
|
+
description: 'Emit one machine-readable JSON value',
|
|
25
|
+
} as const
|
|
@@ -39,11 +39,26 @@ describe('presentBinary', () => {
|
|
|
39
39
|
|
|
40
40
|
test('--json inlines text for text-like content types', async () => {
|
|
41
41
|
await presentBinary(
|
|
42
|
-
{ mediaType: 'text/plain', body: new TextEncoder().encode('hi') },
|
|
42
|
+
{ mediaType: 'text/plain', status: 202, body: new TextEncoder().encode('hi') },
|
|
43
43
|
{ json: true },
|
|
44
44
|
)
|
|
45
45
|
const obj = JSON.parse(Buffer.concat(chunks).toString())
|
|
46
46
|
expect(obj.body).toBe('hi')
|
|
47
|
+
expect(obj.status).toBe(202)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('--raw drains streaming binary chunks in exact order', async () => {
|
|
51
|
+
await presentBinary(
|
|
52
|
+
{
|
|
53
|
+
mediaType: 'application/octet-stream',
|
|
54
|
+
body: (async function* () {
|
|
55
|
+
yield new Uint8Array([1, 2])
|
|
56
|
+
yield new Uint8Array([3, 4])
|
|
57
|
+
})(),
|
|
58
|
+
},
|
|
59
|
+
{ raw: true },
|
|
60
|
+
)
|
|
61
|
+
expect(Array.from(Buffer.concat(chunks))).toEqual([1, 2, 3, 4])
|
|
47
62
|
})
|
|
48
63
|
|
|
49
64
|
test('-o writes raw bytes to a file', async () => {
|
package/src/lib/binary.ts
CHANGED
|
@@ -7,14 +7,31 @@ import { output } from './output'
|
|
|
7
7
|
|
|
8
8
|
/** A binary kernel result: bytes (or a stream of them) plus its content-type. */
|
|
9
9
|
export type BinaryLike = {
|
|
10
|
-
readonly body: Uint8Array
|
|
10
|
+
readonly body: Uint8Array | AsyncIterable<Uint8Array>
|
|
11
11
|
readonly mediaType: string
|
|
12
|
+
readonly status?: number
|
|
12
13
|
readonly headers?: Readonly<Record<string, string>>
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/** Collapse a (possibly streamed) binary body into a single byte array. */
|
|
16
|
-
export async function readBinaryBody(
|
|
17
|
-
|
|
17
|
+
export async function readBinaryBody(
|
|
18
|
+
body: Uint8Array | AsyncIterable<Uint8Array>,
|
|
19
|
+
): Promise<Uint8Array> {
|
|
20
|
+
if (body instanceof Uint8Array) return new Uint8Array(body)
|
|
21
|
+
const chunks: Uint8Array[] = []
|
|
22
|
+
let size = 0
|
|
23
|
+
for await (const chunk of body) {
|
|
24
|
+
const detached = new Uint8Array(chunk)
|
|
25
|
+
chunks.push(detached)
|
|
26
|
+
size += detached.byteLength
|
|
27
|
+
}
|
|
28
|
+
const bytes = new Uint8Array(size)
|
|
29
|
+
let offset = 0
|
|
30
|
+
for (const chunk of chunks) {
|
|
31
|
+
bytes.set(chunk, offset)
|
|
32
|
+
offset += chunk.byteLength
|
|
33
|
+
}
|
|
34
|
+
return bytes
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
function isTextLike(contentType: string): boolean {
|
|
@@ -43,13 +60,13 @@ function humanSize(bytes: number): string {
|
|
|
43
60
|
function jsonEnvelope(resp: BinaryLike, bytes: Uint8Array): Record<string, unknown> {
|
|
44
61
|
if (isTextLike(resp.mediaType)) {
|
|
45
62
|
return {
|
|
46
|
-
status: 200,
|
|
63
|
+
status: resp.status ?? 200,
|
|
47
64
|
contentType: resp.mediaType,
|
|
48
65
|
body: new TextDecoder().decode(bytes),
|
|
49
66
|
}
|
|
50
67
|
}
|
|
51
68
|
return {
|
|
52
|
-
status: 200,
|
|
69
|
+
status: resp.status ?? 200,
|
|
53
70
|
contentType: resp.mediaType,
|
|
54
71
|
bodyBase64: Buffer.from(bytes).toString('base64'),
|
|
55
72
|
}
|
package/src/lib/proc.ts
CHANGED
|
@@ -22,7 +22,11 @@ export function run(
|
|
|
22
22
|
opts: { cwd?: string } = {},
|
|
23
23
|
): Promise<RunResult> {
|
|
24
24
|
return new Promise((resolve, reject) => {
|
|
25
|
-
const child = spawn(file, args, {
|
|
25
|
+
const child = spawn(file, args, {
|
|
26
|
+
cwd: opts.cwd,
|
|
27
|
+
shell: false,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
})
|
|
26
30
|
let stdout = ''
|
|
27
31
|
let stderr = ''
|
|
28
32
|
child.stdout?.setEncoding('utf8')
|
|
@@ -50,7 +54,7 @@ export function runInherit(
|
|
|
50
54
|
opts: { cwd?: string } = {},
|
|
51
55
|
): Promise<number> {
|
|
52
56
|
return new Promise((resolve, reject) => {
|
|
53
|
-
const child = spawn(file, args, { cwd: opts.cwd, stdio: 'inherit' })
|
|
57
|
+
const child = spawn(file, args, { cwd: opts.cwd, shell: false, stdio: 'inherit' })
|
|
54
58
|
child.on('error', reject)
|
|
55
59
|
child.on('close', (code) => resolve(code ?? -1))
|
|
56
60
|
})
|
|
@@ -73,6 +77,7 @@ export function spawnHandle(
|
|
|
73
77
|
return spawn(file, args, {
|
|
74
78
|
cwd: opts.cwd,
|
|
75
79
|
env: opts.env,
|
|
80
|
+
shell: false,
|
|
76
81
|
// `detached` makes the child its own process-group leader so the caller can
|
|
77
82
|
// tear down the whole tree (the child AND its grandchildren) with a single
|
|
78
83
|
// group signal — `process.kill(-child.pid, …)`.
|
|
@@ -181,13 +181,21 @@ describe('program composition', () => {
|
|
|
181
181
|
'status',
|
|
182
182
|
'studio',
|
|
183
183
|
'token',
|
|
184
|
+
'ui',
|
|
185
|
+
'ui add',
|
|
186
|
+
'ui doctor',
|
|
187
|
+
'ui init',
|
|
188
|
+
'ui list',
|
|
189
|
+
'ui preset',
|
|
190
|
+
'ui preset apply',
|
|
191
|
+
'ui preset list',
|
|
184
192
|
'update',
|
|
185
193
|
'use',
|
|
186
194
|
'view',
|
|
187
195
|
'whoami',
|
|
188
196
|
])
|
|
189
197
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
190
|
-
'
|
|
198
|
+
'873d096c4e99f29ab1db17bec0abc4ad2de613f5e44f9cba97c95b052c8b5a98',
|
|
191
199
|
)
|
|
192
200
|
})
|
|
193
201
|
|
|
@@ -281,7 +289,7 @@ describe('help contract — admin target surface is registered', () => {
|
|
|
281
289
|
describe('help contract — connect-only command surface', () => {
|
|
282
290
|
test('runtime management commands are not registered', async () => {
|
|
283
291
|
const program = await buildProgram()
|
|
284
|
-
const names =
|
|
292
|
+
const names = program.commands.map((command) => command.name())
|
|
285
293
|
|
|
286
294
|
// `logs` and `domain` have managed/admin meanings, so only retired verbs are absent.
|
|
287
295
|
for (const removed of [
|
|
@@ -303,6 +311,28 @@ describe('help contract — connect-only command surface', () => {
|
|
|
303
311
|
})
|
|
304
312
|
})
|
|
305
313
|
|
|
314
|
+
describe('help contract — UI is project tooling', () => {
|
|
315
|
+
test('UI commands are local-only and add accepts zero or more canonical addresses', async () => {
|
|
316
|
+
const program = await buildProgram()
|
|
317
|
+
const ui = program.commands.find((command) => command.name() === 'ui')
|
|
318
|
+
const add = ui?.commands.find((command) => command.name() === 'add')
|
|
319
|
+
|
|
320
|
+
expect(ui?.commands.map((command) => command.name())).toEqual([
|
|
321
|
+
'init',
|
|
322
|
+
'list',
|
|
323
|
+
'add',
|
|
324
|
+
'doctor',
|
|
325
|
+
'preset',
|
|
326
|
+
])
|
|
327
|
+
expect(add?.helpInformation()).toContain('[items...]')
|
|
328
|
+
for (const command of ui?.commands ?? []) {
|
|
329
|
+
const help = command.helpInformation()
|
|
330
|
+
expect(help).not.toContain('--url <url>')
|
|
331
|
+
expect(help).not.toContain('--anonymous')
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
306
336
|
describe('help contract — read command split', () => {
|
|
307
337
|
test('get is point-read only and query owns structured read flags', async () => {
|
|
308
338
|
const program = await buildProgram()
|
package/src/program/build.ts
CHANGED
|
@@ -63,6 +63,27 @@ export async function buildProgram(): Promise<Command> {
|
|
|
63
63
|
registerCommand(program, (await import('../commands/view-serve')).default)
|
|
64
64
|
registerCommand(program, (await import('../commands/studio')).default)
|
|
65
65
|
|
|
66
|
+
registerGroup(program, {
|
|
67
|
+
name: 'ui',
|
|
68
|
+
description: 'Initialize and install Astrale UI in local applications',
|
|
69
|
+
commands: [
|
|
70
|
+
(await import('../commands/ui/init')).default,
|
|
71
|
+
(await import('../commands/ui/list')).default,
|
|
72
|
+
(await import('../commands/ui/add')).default,
|
|
73
|
+
(await import('../commands/ui/doctor')).default,
|
|
74
|
+
],
|
|
75
|
+
subgroups: [
|
|
76
|
+
{
|
|
77
|
+
name: 'preset',
|
|
78
|
+
description: 'List and apply qualified Astrale UI presets',
|
|
79
|
+
commands: [
|
|
80
|
+
(await import('../commands/ui/preset-list')).default,
|
|
81
|
+
(await import('../commands/ui/preset-apply')).default,
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
|
|
66
87
|
registerGroup(program, {
|
|
67
88
|
name: 'instance',
|
|
68
89
|
description: 'Manage admin-provisioned instances and local bookmarks',
|
|
@@ -154,6 +175,7 @@ Command groups:
|
|
|
154
175
|
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
155
176
|
Kernel get, mutate, call, query, introspect, logs, view, token
|
|
156
177
|
Management admin, instance, domain, identity, auth, idp, update
|
|
178
|
+
Application ui (initialize, inspect, and install Astrale UI source)
|
|
157
179
|
Agent browser (drive the GUI via agent-browser)
|
|
158
180
|
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
159
181
|
|
|
@@ -172,7 +194,7 @@ Examples:
|
|
|
172
194
|
$ astrale instance bookmark staging --url https://kernel.example.com
|
|
173
195
|
$ astrale instance create my-app
|
|
174
196
|
$ astrale instance status staging
|
|
175
|
-
$ astrale token --audience shell.astrale.ai
|
|
197
|
+
$ astrale token --audience shell.astrale.ai
|
|
176
198
|
$ astrale query /:notes.example.dev:class.Note --limit 50
|
|
177
199
|
$ astrale introspect /:kernel.astrale.ai:class.Identity:whois
|
|
178
200
|
$ astrale query --file query.v6.json --cursor "$CURSOR"
|
package/src/program/command.ts
CHANGED
package/src/program/registry.ts
CHANGED
|
@@ -18,7 +18,8 @@ export function registerCommand(parent: Command, def: CommandDefinition): void {
|
|
|
18
18
|
|
|
19
19
|
if (def.arguments) {
|
|
20
20
|
for (const arg of def.arguments) {
|
|
21
|
-
const
|
|
21
|
+
const name = arg.variadic ? `${arg.name}...` : arg.name
|
|
22
|
+
const bracket = arg.required !== false ? `<${name}>` : `[${name}]`
|
|
22
23
|
cmd.argument(bracket, arg.description)
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
addUi,
|
|
3
|
+
applyPreset,
|
|
4
|
+
diffUi,
|
|
5
|
+
doctorUi,
|
|
6
|
+
initUi,
|
|
7
|
+
listUi,
|
|
8
|
+
viewUi,
|
|
9
|
+
type InitUiOptions,
|
|
10
|
+
} from '../operations.js'
|
|
11
|
+
export { UI_PRESETS, UiError, type UiLock, type UiPreset } from '../model.js'
|
|
12
|
+
export { discoverUiProject, type UiProject } from '../project.js'
|
|
13
|
+
export { resolveUiRelease } from '../release.js'
|
|
14
|
+
export { shadcnInvocation, type UiRunner } from '../runner.js'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# UI local tooling
|
|
2
|
+
|
|
3
|
+
This private CLI owner manages an existing local React project. It does not connect to a Kernel and
|
|
4
|
+
does not add React, Base UI, Tailwind, shadcn, or the UI runtime to the installed CLI dependency
|
|
5
|
+
graph.
|
|
6
|
+
|
|
7
|
+
One operation resolves one immutable UI commit. Package compatibility and registry reads use that
|
|
8
|
+
commit, and the detected package manager invokes the release-qualified shadcn CLI on demand.
|
|
9
|
+
Application writes complete before the lock advances. Dry-run, list, view, diff, and doctor do not
|
|
10
|
+
claim a file mutation.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineLaw } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export const CLI_UI_ONE_SNAPSHOT = defineLaw({
|
|
4
|
+
id: 'CLI-UI-ONE-SNAPSHOT',
|
|
5
|
+
statement:
|
|
6
|
+
'Each UI operation resolves one immutable repository commit and reads compatibility, registry, and item source only from that snapshot.',
|
|
7
|
+
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-ONE-SNAPSHOT' }],
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const CLI_UI_LOCK_AFTER_SUCCESS = defineLaw({
|
|
11
|
+
id: 'CLI-UI-LOCK-AFTER-SUCCESS',
|
|
12
|
+
statement:
|
|
13
|
+
'Dry runs and failed package or source operations do not advance astrale-ui.lock.json.',
|
|
14
|
+
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-LOCK-AFTER-SUCCESS' }],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const CLI_UI_BOUNDED_REMOTE_DOCUMENTS = defineLaw({
|
|
18
|
+
id: 'CLI-UI-BOUNDED-REMOTE-DOCUMENTS',
|
|
19
|
+
statement:
|
|
20
|
+
'Release metadata and registry documents are size-bounded, include-bounded, and normalized to stable UI registry errors before any project mutation.',
|
|
21
|
+
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-BOUNDED-REMOTE-DOCUMENTS' }],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export const CLI_UI_EXACT_ITEM_SOURCE = defineLaw({
|
|
25
|
+
id: 'CLI-UI-EXACT-ITEM-SOURCE',
|
|
26
|
+
statement:
|
|
27
|
+
'An add operation admits the exact built item document against the release index and records a content-bearing source digest before invoking project tooling.',
|
|
28
|
+
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-EXACT-ITEM-SOURCE' }],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const CLI_UI_SEMANTIC_DIFF = defineLaw({
|
|
32
|
+
id: 'CLI-UI-SEMANTIC-DIFF',
|
|
33
|
+
statement:
|
|
34
|
+
'Diff is read-only, path-contained, and classifies locked source and files as upstream changed, unchanged, modified, or deleted without delegating truth to tool output.',
|
|
35
|
+
tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-SEMANTIC-DIFF' }],
|
|
36
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineLayout } from '@astrale-os/spec/authoring'
|
|
2
|
+
|
|
3
|
+
export default defineLayout({
|
|
4
|
+
entries: [
|
|
5
|
+
'.spec/',
|
|
6
|
+
'__tests__/',
|
|
7
|
+
'index.ts',
|
|
8
|
+
'lock.ts',
|
|
9
|
+
'model.ts',
|
|
10
|
+
'operations.ts',
|
|
11
|
+
'project.ts',
|
|
12
|
+
'release.ts',
|
|
13
|
+
'runner.ts',
|
|
14
|
+
],
|
|
15
|
+
exact: true,
|
|
16
|
+
})
|