@astrale-os/cli 1.0.0-beta.17 → 1.0.0-beta.19
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 +2089 -1190
- package/dist/public/connect-core.js +4 -4
- package/dist/types/lib/output.d.ts +1 -1
- package/package.json +2 -2
- package/src/commands/__tests__/domain-install-operation.test.ts +4 -8
- package/src/commands/__tests__/install-direct.test.ts +115 -8
- package/src/commands/__tests__/introspect-parse.test.ts +80 -1
- package/src/commands/__tests__/logs.test.ts +250 -2
- package/src/commands/domain/install.ts +54 -35
- package/src/commands/introspect.ts +40 -20
- package/src/commands/logs.ts +104 -13
- package/src/lib/output.ts +1 -1
- package/src/program/__tests__/program.test.ts +1 -1
- package/studio/package.json +1 -1
- package/viewer/dist/main.js +40 -40
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { InstallRequest, InstallResult, OperationId } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
3
3
|
import chalk from 'chalk'
|
|
4
4
|
|
|
5
5
|
import type { KernelCommandOpts } from '../../connection'
|
|
6
6
|
import type { CommandDefinition } from '../../program/index'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { runKernelCommand, withAdminClientSession } from '../../connection'
|
|
9
9
|
import { formatKernelError } from '../../connection/errors'
|
|
10
10
|
import { AstraleError } from '../../errors'
|
|
11
11
|
import {
|
|
@@ -23,35 +23,61 @@ import { confirmWithInput, promptText, selectFrom } from '../../lib/prompt'
|
|
|
23
23
|
import { isHttpUrl } from '../../lib/validation'
|
|
24
24
|
|
|
25
25
|
/** Public Kernel install syscall input for one remote URL. */
|
|
26
|
-
export function directInstallCallInput(
|
|
26
|
+
export function directInstallCallInput(
|
|
27
|
+
url: string,
|
|
28
|
+
operation: string,
|
|
29
|
+
token?: string,
|
|
30
|
+
): InstallRequest {
|
|
31
|
+
const domain = Object.freeze({
|
|
32
|
+
publication: Object.freeze({
|
|
33
|
+
url,
|
|
34
|
+
...(token === undefined ? {} : { token }),
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
27
37
|
return Object.freeze({
|
|
28
|
-
operation,
|
|
29
|
-
domains: [
|
|
30
|
-
Object.freeze({
|
|
31
|
-
source: Object.freeze({
|
|
32
|
-
kind: 'remote' as const,
|
|
33
|
-
url,
|
|
34
|
-
...(token === undefined ? {} : { token }),
|
|
35
|
-
}),
|
|
36
|
-
}),
|
|
37
|
-
],
|
|
38
|
+
operation: acceptOperationId(operation),
|
|
39
|
+
domains: Object.freeze([domain] as const),
|
|
38
40
|
})
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
export interface DirectInstallPresentation {
|
|
42
44
|
readonly operation: string
|
|
43
|
-
readonly
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
readonly origin: string
|
|
46
|
+
readonly revision: string
|
|
47
|
+
readonly status: 'installed' | 'already current'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Stable CLI presentation derived from the canonical binary install result. */
|
|
51
|
+
export function directInstallPresentation(
|
|
52
|
+
result: InstallResult,
|
|
53
|
+
requestedOperation: string,
|
|
54
|
+
): DirectInstallPresentation {
|
|
55
|
+
if (result.changed) {
|
|
56
|
+
const installed = result.receipt.transitions[0]?.intent
|
|
57
|
+
if (!installed) throw new Error('Kernel install returned no committed Domain transition.')
|
|
58
|
+
if (!installed.generation) {
|
|
59
|
+
throw new Error('Kernel install returned a committed transition without a Domain generation.')
|
|
47
60
|
}
|
|
48
|
-
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
operation: result.receipt.operation,
|
|
63
|
+
origin: installed.origin,
|
|
64
|
+
revision: installed.generation.revision,
|
|
65
|
+
status: 'installed' as const,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
const installed = result.domains[0]
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
operation: requestedOperation,
|
|
71
|
+
origin: installed.origin,
|
|
72
|
+
revision: installed.revision,
|
|
73
|
+
status: 'already current' as const,
|
|
74
|
+
})
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
const OPERATION_ID_PATTERN =
|
|
52
78
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u
|
|
53
79
|
|
|
54
|
-
function acceptOperationId(input: unknown):
|
|
80
|
+
function acceptOperationId(input: unknown): OperationId {
|
|
55
81
|
if (typeof input !== 'string' || !OPERATION_ID_PATTERN.test(input)) {
|
|
56
82
|
throw new AstraleError(
|
|
57
83
|
'INVALID_FLAG',
|
|
@@ -59,10 +85,10 @@ function acceptOperationId(input: unknown): string {
|
|
|
59
85
|
'Omit --operation for a fresh install; use it only with the exact UUID printed for recovery.',
|
|
60
86
|
)
|
|
61
87
|
}
|
|
62
|
-
return input
|
|
88
|
+
return input as OperationId
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
function createOperationId():
|
|
91
|
+
function createOperationId(): OperationId {
|
|
66
92
|
return acceptOperationId(globalThis.crypto.randomUUID())
|
|
67
93
|
}
|
|
68
94
|
|
|
@@ -407,27 +433,20 @@ export async function installDirect(
|
|
|
407
433
|
const url = target as string
|
|
408
434
|
const retry = directInstallRetry(url, operation, opts)
|
|
409
435
|
|
|
410
|
-
await direct.runKernelCommand<
|
|
436
|
+
await direct.runKernelCommand<InstallResult>({
|
|
411
437
|
opts,
|
|
412
438
|
label: `Installing domain from ${url} (operation ${operation})`,
|
|
413
439
|
recovery: { operation, retry },
|
|
414
440
|
fn: async ({ session }) =>
|
|
415
|
-
(
|
|
416
|
-
createPathCall(
|
|
417
|
-
Path.project(K.functions.install.ref).raw,
|
|
418
|
-
directInstallCallInput(url, operation, opts.token),
|
|
419
|
-
),
|
|
420
|
-
)) as DirectInstallResult,
|
|
441
|
+
session.schema.install(directInstallCallInput(url, operation, opts.token)),
|
|
421
442
|
format: (result, fmtOpts, isRaw) => {
|
|
422
443
|
if (isRaw) {
|
|
423
444
|
output(result, fmtOpts)
|
|
424
445
|
return
|
|
425
446
|
}
|
|
426
|
-
const installed = result
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
log.success(`Domain installed: ${installed.origin}@${revision}`)
|
|
430
|
-
log.dim(` operation: ${result.operation}`)
|
|
447
|
+
const installed = directInstallPresentation(result, operation)
|
|
448
|
+
log.success(`Domain ${installed.status}: ${installed.origin}@${installed.revision}`)
|
|
449
|
+
log.dim(` operation: ${installed.operation}`)
|
|
431
450
|
// Belt-and-braces: the kernel-confirmed origin is authoritative. If it
|
|
432
451
|
// aliases the host and the pre-install gate never consented to THAT
|
|
433
452
|
// origin (lying or unavailable Publication), say so loudly after the fact.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { DomainBundle, DomainInfo, SchemaApi } from '@astrale-os/sdk/client/schema'
|
|
2
|
+
|
|
1
3
|
import { Path } from '@astrale-os/sdk/graph/path'
|
|
2
4
|
|
|
3
5
|
import type { KernelCommandOpts } from '../connection'
|
|
@@ -11,7 +13,18 @@ import { describeCallableFromBundle, missingCallableDescription } from './call-d
|
|
|
11
13
|
|
|
12
14
|
type IntrospectOpts = KernelCommandOpts & { bundle?: boolean }
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
interface IntrospectDependencies {
|
|
17
|
+
readonly runKernelCommand: typeof runKernelCommand
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const defaultIntrospectDependencies = Object.freeze({ runKernelCommand })
|
|
21
|
+
|
|
22
|
+
export async function introspectCommand(
|
|
23
|
+
target: string,
|
|
24
|
+
opts: IntrospectOpts,
|
|
25
|
+
dependencies: Partial<IntrospectDependencies> = {},
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const introspect = { ...defaultIntrospectDependencies, ...dependencies }
|
|
15
28
|
let origin: string
|
|
16
29
|
let path: Path
|
|
17
30
|
try {
|
|
@@ -21,30 +34,14 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
21
34
|
}
|
|
22
35
|
const wantsCallable = isCallablePath(path)
|
|
23
36
|
|
|
24
|
-
await runKernelCommand({
|
|
37
|
+
await introspect.runKernelCommand({
|
|
25
38
|
opts,
|
|
26
39
|
label: `Introspect ${origin}`,
|
|
27
40
|
fn: async ({ session }) => {
|
|
28
41
|
const includeBundle = wantsCallable || opts.bundle === true
|
|
29
|
-
const result = await session.schema
|
|
30
|
-
from: { kind: 'installation', origin },
|
|
31
|
-
select: {
|
|
32
|
-
state: true,
|
|
33
|
-
target: true,
|
|
34
|
-
source: true,
|
|
35
|
-
readiness: true,
|
|
36
|
-
capabilities: true,
|
|
37
|
-
...(includeBundle ? { bundle: true as const } : {}),
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
if (result === null) {
|
|
41
|
-
throw new AstraleError(
|
|
42
|
-
'DOMAIN_NOT_INSTALLED',
|
|
43
|
-
`Domain ${origin} is not installed on this Kernel.`,
|
|
44
|
-
)
|
|
45
|
-
}
|
|
42
|
+
const result = await readInstalledDomain(session.schema, origin, includeBundle)
|
|
46
43
|
if (wantsCallable) {
|
|
47
|
-
const described = describeCallableFromBundle(path, result.bundle)
|
|
44
|
+
const described = describeCallableFromBundle(path, (result as DomainBundle).bundle)
|
|
48
45
|
if (described === undefined) throw missingCallableDescription(path.raw)
|
|
49
46
|
return described
|
|
50
47
|
}
|
|
@@ -54,6 +51,29 @@ export async function introspectCommand(target: string, opts: IntrospectOpts): P
|
|
|
54
51
|
})
|
|
55
52
|
}
|
|
56
53
|
|
|
54
|
+
export function readInstalledDomain(
|
|
55
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
56
|
+
origin: string,
|
|
57
|
+
includeBundle: true,
|
|
58
|
+
): Promise<DomainBundle>
|
|
59
|
+
export function readInstalledDomain(
|
|
60
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
61
|
+
origin: string,
|
|
62
|
+
includeBundle: false,
|
|
63
|
+
): Promise<DomainInfo>
|
|
64
|
+
export function readInstalledDomain(
|
|
65
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
66
|
+
origin: string,
|
|
67
|
+
includeBundle: boolean,
|
|
68
|
+
): Promise<DomainInfo | DomainBundle>
|
|
69
|
+
export function readInstalledDomain(
|
|
70
|
+
schema: Pick<SchemaApi, 'inspect' | 'bundle'>,
|
|
71
|
+
origin: string,
|
|
72
|
+
includeBundle: boolean,
|
|
73
|
+
): Promise<DomainInfo | DomainBundle> {
|
|
74
|
+
return includeBundle ? schema.bundle(origin) : schema.inspect(origin)
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
export function parseIntrospectTarget(target: string): { origin: string; path: Path } {
|
|
58
78
|
if (target.startsWith('@')) {
|
|
59
79
|
throw new AstraleError(
|
package/src/commands/logs.ts
CHANGED
|
@@ -33,10 +33,21 @@ export interface JournalRecord {
|
|
|
33
33
|
readonly occurredAt?: string
|
|
34
34
|
readonly committedAt?: string
|
|
35
35
|
readonly principal?: string
|
|
36
|
+
readonly correlation?: JournalCorrelation
|
|
36
37
|
readonly correlationId?: string
|
|
37
38
|
readonly causationId?: string
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export interface JournalCorrelation {
|
|
42
|
+
readonly operationId?: string
|
|
43
|
+
readonly parentOperationId?: string
|
|
44
|
+
readonly invocationId?: string
|
|
45
|
+
readonly rootInvocationId?: string
|
|
46
|
+
readonly parentInvocationId?: string
|
|
47
|
+
readonly traceId?: string
|
|
48
|
+
readonly spanId?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
export interface JournalPage {
|
|
41
52
|
readonly records: readonly JournalRecord[]
|
|
42
53
|
readonly cursor?: string
|
|
@@ -133,8 +144,21 @@ async function runOnce(opts: LogsOpts): Promise<void> {
|
|
|
133
144
|
})
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
type FollowDependencies = {
|
|
148
|
+
readonly run: typeof runKernelCommand
|
|
149
|
+
readonly pause: (milliseconds: number) => Promise<void>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Follow one admitted journal stream. Dependencies are explicit so routing is proven at the command boundary. */
|
|
153
|
+
export async function followLogs(
|
|
154
|
+
opts: LogsOpts,
|
|
155
|
+
dependencies: FollowDependencies = {
|
|
156
|
+
run: runKernelCommand,
|
|
157
|
+
pause: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
158
|
+
},
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
validateLogsOpts(opts)
|
|
161
|
+
await dependencies.run({
|
|
138
162
|
opts,
|
|
139
163
|
label: 'Kernel journal',
|
|
140
164
|
fn: async (context): Promise<never> => {
|
|
@@ -142,9 +166,9 @@ async function follow(opts: LogsOpts): Promise<void> {
|
|
|
142
166
|
let cursor = resolved.cursor
|
|
143
167
|
for (;;) {
|
|
144
168
|
const page = await fetchPage(context, { ...resolved, cursor })
|
|
145
|
-
for (const record of page.records) printRecord(record)
|
|
169
|
+
for (const record of page.records) printRecord(record, opts)
|
|
146
170
|
cursor = page.cursor ?? cursor
|
|
147
|
-
await
|
|
171
|
+
await dependencies.pause(FOLLOW_INTERVAL_MS)
|
|
148
172
|
}
|
|
149
173
|
},
|
|
150
174
|
})
|
|
@@ -169,12 +193,28 @@ function journalProjection(records: JournalRecord[]): ListProjection {
|
|
|
169
193
|
}
|
|
170
194
|
}
|
|
171
195
|
|
|
172
|
-
function printRecord(record: JournalRecord): void {
|
|
196
|
+
function printRecord(record: JournalRecord, opts: LogsOpts): void {
|
|
197
|
+
if (isMachine(opts) || opts.format === 'json') {
|
|
198
|
+
process.stdout.write(formatFollowRecord(record))
|
|
199
|
+
return
|
|
200
|
+
}
|
|
173
201
|
process.stdout.write(
|
|
174
202
|
`${chalk.dim(String(record.sequence).padStart(6))} ${chalk.dim(record.timestamp)} ${chalk.cyan(record.topic)} ${chalk.dim(record.principal ?? '')}\n`,
|
|
175
203
|
)
|
|
176
204
|
}
|
|
177
205
|
|
|
206
|
+
/** Machine follow is an NDJSON stream: one complete admitted record per line. */
|
|
207
|
+
export function formatFollowRecord(record: JournalRecord): string {
|
|
208
|
+
return `${JSON.stringify(record)}\n`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function validateLogsOpts(opts: LogsOpts): void {
|
|
212
|
+
buildJournalInput(opts)
|
|
213
|
+
if (opts.follow && opts.format === 'yaml' && !opts.json && !opts.raw) {
|
|
214
|
+
throw new TypeError('--follow does not support YAML; use --json for an NDJSON stream')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
178
218
|
function acceptRecord(input: unknown, index: number): JournalRecord {
|
|
179
219
|
if (
|
|
180
220
|
!isRecord(input) ||
|
|
@@ -188,10 +228,17 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
|
|
|
188
228
|
if (timestamp === undefined) {
|
|
189
229
|
throw new TypeError(`Kernel journal record ${index} is missing occurredAt/timestamp`)
|
|
190
230
|
}
|
|
191
|
-
const correlation =
|
|
192
|
-
const correlationId
|
|
193
|
-
|
|
194
|
-
|
|
231
|
+
const correlation = acceptCorrelation(input.correlation, index)
|
|
232
|
+
const legacyCorrelationId = optionalIdentifier(input.correlationId, index, 'correlationId')
|
|
233
|
+
const structuredCorrelationId = correlation?.invocationId
|
|
234
|
+
if (
|
|
235
|
+
legacyCorrelationId !== undefined &&
|
|
236
|
+
structuredCorrelationId !== undefined &&
|
|
237
|
+
legacyCorrelationId !== structuredCorrelationId
|
|
238
|
+
) {
|
|
239
|
+
throw new TypeError(`Kernel journal record ${index} has conflicting correlation identifiers`)
|
|
240
|
+
}
|
|
241
|
+
const correlationId = structuredCorrelationId ?? legacyCorrelationId
|
|
195
242
|
const principal = optionalText(input.principal, index, 'principal')
|
|
196
243
|
return Object.freeze({
|
|
197
244
|
sequence: input.sequence as number,
|
|
@@ -203,13 +250,56 @@ function acceptRecord(input: unknown, index: number): JournalRecord {
|
|
|
203
250
|
? {}
|
|
204
251
|
: { committedAt: input.committedAt as string }),
|
|
205
252
|
...(principal === undefined ? {} : { principal }),
|
|
253
|
+
...(correlation === undefined ? {} : { correlation }),
|
|
206
254
|
...(correlationId === undefined ? {} : { correlationId }),
|
|
207
|
-
...(
|
|
255
|
+
...(optionalIdentifier(input.causationId, index, 'causationId') === undefined
|
|
208
256
|
? {}
|
|
209
257
|
: { causationId: input.causationId as string }),
|
|
210
258
|
})
|
|
211
259
|
}
|
|
212
260
|
|
|
261
|
+
const correlationFields = [
|
|
262
|
+
'operationId',
|
|
263
|
+
'parentOperationId',
|
|
264
|
+
'invocationId',
|
|
265
|
+
'rootInvocationId',
|
|
266
|
+
'parentInvocationId',
|
|
267
|
+
'traceId',
|
|
268
|
+
'spanId',
|
|
269
|
+
] as const
|
|
270
|
+
|
|
271
|
+
function acceptCorrelation(input: unknown, index: number): JournalCorrelation | undefined {
|
|
272
|
+
if (input === undefined) return undefined
|
|
273
|
+
if (!isRecord(input)) {
|
|
274
|
+
throw new TypeError(`Kernel journal record ${index}.correlation must be an object`)
|
|
275
|
+
}
|
|
276
|
+
const unknown = Object.keys(input).find(
|
|
277
|
+
(field) => !correlationFields.includes(field as (typeof correlationFields)[number]),
|
|
278
|
+
)
|
|
279
|
+
if (unknown !== undefined) {
|
|
280
|
+
throw new TypeError(`Kernel journal record ${index}.correlation.${unknown} is unsupported`)
|
|
281
|
+
}
|
|
282
|
+
return Object.freeze(
|
|
283
|
+
Object.fromEntries(
|
|
284
|
+
correlationFields.flatMap((field) => {
|
|
285
|
+
const value = optionalIdentifier(input[field], index, `correlation.${field}`)
|
|
286
|
+
return value === undefined ? [] : [[field, value]]
|
|
287
|
+
}),
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function optionalIdentifier(input: unknown, index: number, field: string): string | undefined {
|
|
293
|
+
const value = optionalText(input, index, field)
|
|
294
|
+
if (value === undefined) return undefined
|
|
295
|
+
if (value.trim() === '' || new TextEncoder().encode(value).byteLength > 256) {
|
|
296
|
+
throw new TypeError(
|
|
297
|
+
`Kernel journal record ${index}.${field} must be non-empty and at most 256 UTF-8 bytes`,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
return value
|
|
301
|
+
}
|
|
302
|
+
|
|
213
303
|
function optionalText(input: unknown, index: number, field: string): string | undefined {
|
|
214
304
|
if (input === undefined) return undefined
|
|
215
305
|
if (typeof input !== 'string') {
|
|
@@ -251,7 +341,8 @@ Behavior:
|
|
|
251
341
|
Calls the public Kernel journal syscall and emits its { records, cursor }
|
|
252
342
|
page. Topic selection is exact or prefix-based; cursors and timestamps are
|
|
253
343
|
opaque strings owned by the journal backend. --follow reuses one Client Session
|
|
254
|
-
and advances only with the returned cursor.
|
|
344
|
+
and advances only with the returned cursor. Machine follow output is NDJSON:
|
|
345
|
+
one complete admitted record per line; YAML follow is unsupported.
|
|
255
346
|
|
|
256
347
|
Historical event-glob lowering and the application-specific services-domain
|
|
257
348
|
log buffer are not part of the Kernel V2 journal contract.
|
|
@@ -273,11 +364,11 @@ Examples:
|
|
|
273
364
|
],
|
|
274
365
|
action: async (opts: LogsOpts) => {
|
|
275
366
|
try {
|
|
276
|
-
|
|
367
|
+
validateLogsOpts(opts)
|
|
277
368
|
} catch (error) {
|
|
278
369
|
failInput(error, opts)
|
|
279
370
|
}
|
|
280
|
-
if (opts.follow) await
|
|
371
|
+
if (opts.follow) await followLogs(opts)
|
|
281
372
|
else await runOnce(opts)
|
|
282
373
|
},
|
|
283
374
|
} satisfies CommandDefinition
|
package/src/lib/output.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type RawOutputOpts = Pick<OutputOpts, 'raw' | 'json'>
|
|
|
17
17
|
export type MachineOpts = RawOutputOpts & { readonly ci?: boolean }
|
|
18
18
|
|
|
19
19
|
export const RAW_OUTPUT_OPTIONS = [
|
|
20
|
-
{ flags: '--json', description: '
|
|
20
|
+
{ flags: '--json', description: 'JSON; streaming commands emit one JSON value per line' },
|
|
21
21
|
{ flags: '--raw', description: 'Unwrapped: bare scalar / raw bytes / JSON for objects' },
|
|
22
22
|
] as const
|
|
23
23
|
|
|
@@ -195,7 +195,7 @@ describe('program composition', () => {
|
|
|
195
195
|
'whoami',
|
|
196
196
|
])
|
|
197
197
|
expect(createHash('sha256').update(JSON.stringify(surface)).digest('hex')).toBe(
|
|
198
|
-
'
|
|
198
|
+
'958b54c4eeee1bab77efeac734bd11fe5a0b103aae69788de7db5418e5652f64',
|
|
199
199
|
)
|
|
200
200
|
})
|
|
201
201
|
|
package/studio/package.json
CHANGED