@hypit/hypit 0.1.2 → 0.1.4
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/README.md +1 -1
- package/dist/public/studio-adapter.d.ts +31 -2
- package/package.json +4 -1
- package/packages/audio-track-studio/src/index.ts +7 -4
- package/packages/build-result/README.md +58 -0
- package/packages/build-result/package.json +2 -1
- package/packages/build-result/src/decode.ts +10 -8
- package/packages/build-result/src/file-reference.ts +42 -0
- package/packages/build-result/src/index.ts +3 -0
- package/packages/build-result/src/replace-file-windows.ts +58 -0
- package/packages/build-result/src/replace-file.ts +11 -0
- package/packages/build-result/src/store.ts +44 -13
- package/packages/build-result/src/types.ts +38 -8
- package/packages/build-result/src/writer.ts +12 -1
- package/packages/build-result-fs/README.md +23 -1
- package/packages/build-result-s3/README.md +5 -0
- package/packages/build-result-s3/src/repository.ts +19 -5
- package/packages/caption-fine/README.md +17 -2
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +48 -36
- package/packages/caption-fine-studio/package.json +8 -3
- package/packages/caption-fine-studio/src/index.ts +10 -1
- package/packages/cli/README.md +25 -0
- package/packages/cli/src/arguments.ts +16 -11
- package/packages/cli/src/build-planning.ts +6 -1
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +42 -13
- package/packages/cli/src/machine-view.ts +12 -3
- package/packages/cli/src/main.ts +4 -0
- package/packages/cli/src/output.ts +28 -12
- package/packages/cli/src/result-export.ts +22 -13
- package/packages/cli/src/run-file.ts +19 -8
- package/packages/comment-sticker-studio/src/index.ts +2 -1
- package/packages/compiler-node/src/compiler.ts +1 -0
- package/packages/estimate/README.md +56 -3
- package/packages/estimate/src/program.ts +7 -8
- package/packages/fonts-open/README.md +5 -0
- package/packages/fonts-open/package.json +5 -1
- package/packages/fonts-open/src/studio.ts +21 -0
- package/packages/gpt-image-kits/README.md +17 -12
- package/packages/gpt-image-kits/kits/phone-ugc-v1.svs +8 -8
- package/packages/media-track-studio/src/index.ts +7 -2
- package/packages/provider-whisperx-local/README.md +57 -0
- package/packages/ranking/src/render.ts +11 -9
- package/packages/ranking-studio/README.md +11 -0
- package/packages/ranking-studio/package.json +8 -3
- package/packages/ranking-studio/src/index.ts +11 -4
- package/packages/runtime/src/catalog.ts +2 -0
- package/packages/runtime-host-node/src/index.ts +1 -0
- package/packages/runtime-local/README.md +19 -0
- package/packages/runtime-local/src/programs.ts +39 -24
- package/packages/runtime-local/src/runtime.ts +2 -0
- package/packages/script/README.md +22 -0
- package/packages/script/src/edit.ts +123 -73
- package/packages/script/src/lexical.ts +40 -6
- package/packages/script/src/parser.ts +36 -8
- package/packages/script/src/types.ts +4 -0
- package/packages/seedance-kits/README.md +3 -1
- package/packages/seedance-kits/kits/speaker-v1.svs +5 -5
- package/packages/speech-alignment/src/align.ts +45 -21
- package/packages/studio/INSPECTOR.md +104 -0
- package/packages/studio/README.md +65 -4
- package/packages/studio/src/build-library.ts +99 -84
- package/packages/studio/src/library-media.ts +19 -0
- package/packages/studio/src/parameter-values.ts +41 -1
- package/packages/studio/src/parameters.ts +20 -21
- package/packages/studio/src/server.ts +58 -30
- package/packages/studio/src/shared.ts +20 -6
- package/packages/studio/src/studio-registry.ts +22 -0
- package/packages/studio/src/style.css +135 -144
- package/packages/studio/src/temporal-edit.ts +131 -0
- package/packages/studio/src/ui/artifact-name.ts +80 -0
- package/packages/studio/src/ui/artifact-preview.ts +99 -0
- package/packages/studio/src/ui/icons.ts +5 -1
- package/packages/studio/src/ui/library.ts +373 -192
- package/packages/studio/src/ui/main.ts +153 -31
- package/packages/studio/src/ui/material-preview.ts +1 -1
- package/packages/studio/src/ui/overlay.ts +21 -20
- package/packages/studio/src/ui/sidebar-panel.ts +31 -0
- package/packages/studio/src/ui/stage.ts +124 -8
- package/packages/studio/src/ui/timeline.ts +16 -179
- package/packages/studio/start.ts +15 -3
- package/packages/studio-adapter/README.md +21 -0
- package/packages/studio-adapter/src/index.ts +26 -1
- package/packages/temporal-markup/EDITING.md +183 -0
- package/packages/temporal-markup/README.md +4 -0
- package/packages/typography-track-studio/package.json +8 -3
- package/packages/typography-track-studio/src/index.ts +5 -1
- package/packages/video-cli/README.md +12 -3
- package/packages/video-cli/src/creation.ts +11 -5
- package/packages/whisperx/README.md +7 -0
- package/packages/workspace/src/index.ts +2 -0
- package/packages/workspace-fs-node/README.md +4 -1
- package/packages/workspace-fs-node/src/workspace.ts +3 -1
|
@@ -157,7 +157,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
|
|
|
157
157
|
}
|
|
158
158
|
case "status": {
|
|
159
159
|
const [build, rest] = requiredPositional(tail, "status requires one Build id");
|
|
160
|
-
const options = commandOptions(command, rest, "--runtime", "--watch", "--max-wait-ms", "--limit");
|
|
160
|
+
const options = commandOptions(command, rest, "--runtime", "--workspace", "--watch", "--max-wait-ms", "--limit");
|
|
161
161
|
if (!options.watch && options.maxWaitMs !== undefined) {
|
|
162
162
|
throw new Error("--max-wait-ms applies to status --watch");
|
|
163
163
|
}
|
|
@@ -172,7 +172,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
case "activity": {
|
|
175
|
-
const options = commandOptions(command, tail, "--runtime", "--watch", "--jsonl", "--limit");
|
|
175
|
+
const options = commandOptions(command, tail, "--runtime", "--workspace", "--watch", "--jsonl", "--limit");
|
|
176
176
|
if (options.presentation.jsonl === true && !options.watch) {
|
|
177
177
|
throw new Error("--jsonl applies only to activity --watch");
|
|
178
178
|
}
|
|
@@ -189,7 +189,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
|
|
|
189
189
|
}
|
|
190
190
|
case "cancel": {
|
|
191
191
|
const [build, rest] = requiredPositional(tail, "cancel requires one Build id");
|
|
192
|
-
const options = commandOptions(command, rest, "--runtime", "--reason");
|
|
192
|
+
const options = commandOptions(command, rest, "--runtime", "--workspace", "--reason");
|
|
193
193
|
return {
|
|
194
194
|
command,
|
|
195
195
|
build,
|
|
@@ -205,17 +205,20 @@ export function parseCommand(argv: readonly string[]): CliCommand {
|
|
|
205
205
|
case "auth": return parseAuthCommand(tail);
|
|
206
206
|
case "doctor": {
|
|
207
207
|
const [profile, rest] = optionalPositional(tail);
|
|
208
|
-
const options = commandOptions(command, rest, "--workspace", "--limit");
|
|
208
|
+
const options = commandOptions(command, rest, "--workspace", "--runtime", "--limit");
|
|
209
|
+
if (profile !== undefined && options.runtimeProfile !== undefined) {
|
|
210
|
+
throw new Error("doctor accepts the Runtime Profile either positionally or with --runtime, not both");
|
|
211
|
+
}
|
|
209
212
|
return {
|
|
210
213
|
command,
|
|
211
214
|
presentation: options.presentation,
|
|
212
215
|
limit: options.limit,
|
|
213
216
|
...optionalProject(options),
|
|
214
|
-
...runtimeOption(profile),
|
|
217
|
+
...runtimeOption(profile ?? options.runtimeProfile),
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
220
|
case "paths": {
|
|
218
|
-
const options = commandOptions(command, tail, "--runtime");
|
|
221
|
+
const options = commandOptions(command, tail, "--runtime", "--workspace");
|
|
219
222
|
return { command, presentation: options.presentation, ...optionalRuntime(options) };
|
|
220
223
|
}
|
|
221
224
|
case "_worker": {
|
|
@@ -244,7 +247,7 @@ function parseResultCommand(tail: readonly string[]): ProjectResultCommand | Exe
|
|
|
244
247
|
}
|
|
245
248
|
const [build, rest] = requiredPositional(values, `result ${action} requires one Build id`);
|
|
246
249
|
if (action === "finish" || action === "discard") {
|
|
247
|
-
const options = commandOptions(`result ${action}`, rest, "--runtime");
|
|
250
|
+
const options = commandOptions(`result ${action}`, rest, "--runtime", "--workspace");
|
|
248
251
|
return { command: "result", action, build, presentation: options.presentation, ...optionalRuntime(options) };
|
|
249
252
|
}
|
|
250
253
|
const options = commandOptions("result edit", rest,
|
|
@@ -306,7 +309,7 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
|
|
|
306
309
|
: action === "status"
|
|
307
310
|
? ["--runtime", "--limit"] as const
|
|
308
311
|
: ["--runtime", "--lines"] as const;
|
|
309
|
-
const options = commandOptions(`runtime ${action}`, rest, ...allowed);
|
|
312
|
+
const options = commandOptions(`runtime ${action}`, rest, "--workspace", ...allowed);
|
|
310
313
|
rejectDuplicateProfile("runtime", profile, options.runtimeProfile);
|
|
311
314
|
const runtime = runtimeOption(profile ?? options.runtimeProfile);
|
|
312
315
|
const common = {
|
|
@@ -314,6 +317,7 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
|
|
|
314
317
|
action,
|
|
315
318
|
presentation: options.presentation,
|
|
316
319
|
...runtime,
|
|
320
|
+
...optionalProject(options),
|
|
317
321
|
};
|
|
318
322
|
if (action === "up" || action === "down") {
|
|
319
323
|
return { ...common, action, ...(options.maxWaitMs === undefined ? {} : { maxWaitMs: options.maxWaitMs }) };
|
|
@@ -329,7 +333,7 @@ function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
|
|
|
329
333
|
}
|
|
330
334
|
const [profile, rest] = optionalPositional(values);
|
|
331
335
|
const options = commandOptions(`programs ${action}`, rest,
|
|
332
|
-
"--runtime", "--limit", "--max-wait-ms");
|
|
336
|
+
"--runtime", "--workspace", "--limit", "--max-wait-ms");
|
|
333
337
|
if (action !== "up" && options.maxWaitMs !== undefined) {
|
|
334
338
|
throw new Error("--max-wait-ms applies to programs up");
|
|
335
339
|
}
|
|
@@ -341,6 +345,7 @@ function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
|
|
|
341
345
|
presentation: options.presentation,
|
|
342
346
|
limit: options.limit,
|
|
343
347
|
...runtime,
|
|
348
|
+
...optionalProject(options),
|
|
344
349
|
};
|
|
345
350
|
return action === "up"
|
|
346
351
|
? { ...common, action, ...(options.maxWaitMs === undefined ? {} : { maxWaitMs: options.maxWaitMs }) }
|
|
@@ -362,7 +367,7 @@ function parseAuthCommand(tail: readonly string[]): AuthCommand {
|
|
|
362
367
|
}
|
|
363
368
|
const [endpoint, rest] = requiredPositional(values, `auth ${action} requires one Endpoint instance`);
|
|
364
369
|
const options = commandOptions(`auth ${action}`, rest,
|
|
365
|
-
"--runtime", "--slot", ...(action === "login" ? ["--from"] as const : []),
|
|
370
|
+
"--runtime", "--workspace", "--slot", ...(action === "login" ? ["--from"] as const : []),
|
|
366
371
|
...(action === "status" ? ["--limit"] as const : []));
|
|
367
372
|
const common = {
|
|
368
373
|
command: "auth" as const,
|
|
@@ -576,7 +581,7 @@ function optionalProject(options: RawOptions): ProjectOption {
|
|
|
576
581
|
}
|
|
577
582
|
|
|
578
583
|
function optionalRuntime(options: RawOptions): RuntimeOption {
|
|
579
|
-
return runtimeOption(options.runtimeProfile);
|
|
584
|
+
return { ...runtimeOption(options.runtimeProfile), ...optionalProject(options) };
|
|
580
585
|
}
|
|
581
586
|
|
|
582
587
|
function runtimeOption(profile: string | undefined): RuntimeOption {
|
|
@@ -24,7 +24,12 @@ export function createCatalogDescriptor(options: {
|
|
|
24
24
|
if (item.ref.kind === "operation-result") {
|
|
25
25
|
throw new Error(`public output ${item.name} was not lowered to a stable Record or Logical Output`);
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
if (item.ref.kind !== "logical-output") return [];
|
|
28
|
+
const outputId = item.ref.id;
|
|
29
|
+
const element = options.compilation.provenance.elements.find((element) => element.outputs.some((output) => output.id === outputId));
|
|
30
|
+
const authored = element?.outputs.find((output) => output.id === outputId);
|
|
31
|
+
const displayName = element?.components.find((component) => component.id === authored?.component)?.local;
|
|
32
|
+
return [{ name: item.name, ref: item.ref, ...(displayName === undefined ? {} : { displayName }) }];
|
|
28
33
|
});
|
|
29
34
|
const names = new Set<string>();
|
|
30
35
|
const outputs = new Set<string>();
|
|
@@ -10,9 +10,27 @@ import { acquireOAuthCredential } from "../oauth.js";
|
|
|
10
10
|
import { writeCliOutput } from "../output.js";
|
|
11
11
|
import type { CliIo } from "../output.js";
|
|
12
12
|
import { hypitHostStateRoot, hypitProjectStateRoot } from "../paths.js";
|
|
13
|
-
import type { CliRuntimeController } from "../runtime-port.js";
|
|
13
|
+
import type { CliManagedProgramReport, CliRuntimeController } from "../runtime-port.js";
|
|
14
14
|
import type { OperationalWriter } from "./types.js";
|
|
15
15
|
|
|
16
|
+
function programRecord(item: CliManagedProgramReport) {
|
|
17
|
+
return {
|
|
18
|
+
...item,
|
|
19
|
+
state: item.state.state,
|
|
20
|
+
...(item.state.state === "ready" ? {} : { stateDetail: item.state.detail }),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function programDescription(item: CliManagedProgramReport): string {
|
|
25
|
+
const details = [...new Set([
|
|
26
|
+
...(item.state.state === "ready" ? [] : [item.state.detail]),
|
|
27
|
+
...(item.detail === undefined ? [] : [item.detail]),
|
|
28
|
+
])];
|
|
29
|
+
return `${item.id}: ${item.state.state}${details.length === 0 ? "" : ` — ${details.join("; ")}`}`
|
|
30
|
+
+ (item.pid === undefined ? "" : ` · PID ${item.pid}`)
|
|
31
|
+
+ (item.logPath === undefined ? "" : ` · log ${item.logPath}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
export function isEnvironmentCommand(args: CliCommand): args is EnvironmentCommand {
|
|
17
35
|
return args.command === "paths" || args.command === "packages" || args.command === "doctor"
|
|
18
36
|
|| args.command === "programs" || args.command === "auth"
|
|
@@ -23,6 +41,7 @@ export function isEnvironmentCommand(args: CliCommand): args is EnvironmentComma
|
|
|
23
41
|
export async function runEnvironmentCommand(input: {
|
|
24
42
|
readonly args: EnvironmentCommand;
|
|
25
43
|
readonly runtimeProfile: string | undefined;
|
|
44
|
+
readonly runtimeSelectionFile: string | undefined;
|
|
26
45
|
readonly io: CliIo;
|
|
27
46
|
readonly distribution: CliDistribution;
|
|
28
47
|
readonly projectRoot: string;
|
|
@@ -32,11 +51,16 @@ export async function runEnvironmentCommand(input: {
|
|
|
32
51
|
readonly write: OperationalWriter;
|
|
33
52
|
}): Promise<void> {
|
|
34
53
|
const {
|
|
35
|
-
args, runtimeProfile, io, distribution, projectRoot, packageRootForProject, runtimeHost, runtimeController, write,
|
|
54
|
+
args, runtimeProfile, runtimeSelectionFile, io, distribution, projectRoot, packageRootForProject, runtimeHost, runtimeController, write,
|
|
36
55
|
} = input;
|
|
56
|
+
const profileSource: "none" | "argument" | "project" = runtimeProfile === undefined
|
|
57
|
+
? "none" : runtimeSelectionFile === undefined ? "argument" : "project";
|
|
58
|
+
const selectionDescription = profileSource === "argument"
|
|
59
|
+
? "command argument (this invocation only)"
|
|
60
|
+
: runtimeSelectionFile ?? "none";
|
|
37
61
|
const reportProgramProgress = args.presentation.json
|
|
38
62
|
? undefined
|
|
39
|
-
: (event: { readonly id: string; readonly phase: "checking" | "installing" | "starting" | "waiting" | "ready" }): void => {
|
|
63
|
+
: (event: { readonly id: string; readonly phase: "checking" | "installing" | "starting" | "waiting" | "ready"; readonly logPath?: string }): void => {
|
|
40
64
|
if (!args.presentation.verbose && event.phase !== "installing" && event.phase !== "starting") return;
|
|
41
65
|
const verb = {
|
|
42
66
|
checking: "Checking",
|
|
@@ -45,7 +69,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
45
69
|
waiting: "Waiting for",
|
|
46
70
|
ready: "Ready",
|
|
47
71
|
}[event.phase];
|
|
48
|
-
io.write(` · ${verb} ${event.id}\n`);
|
|
72
|
+
io.write(` · ${verb} ${event.id}${event.logPath === undefined ? "" : ` · log ${event.logPath}`}\n`);
|
|
49
73
|
};
|
|
50
74
|
const reportPackageProgress = args.presentation.json
|
|
51
75
|
? undefined
|
|
@@ -63,6 +87,8 @@ export async function runEnvironmentCommand(input: {
|
|
|
63
87
|
const machine = {
|
|
64
88
|
format: "hypit.cli-paths@1" as const,
|
|
65
89
|
project: projectRoot,
|
|
90
|
+
profileSource,
|
|
91
|
+
selectionFile: resolve(projectRoot, ".hypit/runtime"),
|
|
66
92
|
projectState: hypitProjectStateRoot(projectRoot),
|
|
67
93
|
...(runtimeProfile === undefined ? {} : { profile: runtimeProfile }),
|
|
68
94
|
...(runtimePaths === undefined ? {} : { runtimeData: runtimePaths.runtimeDataRoot }),
|
|
@@ -74,11 +100,14 @@ export async function runEnvironmentCommand(input: {
|
|
|
74
100
|
["Project", machine.project],
|
|
75
101
|
["Project state", machine.projectState],
|
|
76
102
|
["Runtime Profile", machine.profile ?? "not selected"],
|
|
103
|
+
["Runtime selection", selectionDescription],
|
|
77
104
|
["Runtime data", machine.runtimeData ?? "not selected"],
|
|
78
105
|
["Host state", machine.hostState],
|
|
79
106
|
["Machine packages", machine.machinePackages],
|
|
80
107
|
["Distribution", machine.distribution ?? "embedded"],
|
|
81
|
-
]
|
|
108
|
+
], runtimeProfile === undefined
|
|
109
|
+
? ["Select an existing Profile with hypit runtime use <profile> --workspace <project>, or create one with hypit runtime init."]
|
|
110
|
+
: []);
|
|
82
111
|
return;
|
|
83
112
|
}
|
|
84
113
|
|
|
@@ -122,6 +151,8 @@ export async function runEnvironmentCommand(input: {
|
|
|
122
151
|
format: "hypit.cli-doctor@1" as const,
|
|
123
152
|
ok: !diagnostics.some((item) => item.severity === "error"),
|
|
124
153
|
project: projectRoot,
|
|
154
|
+
profileSource,
|
|
155
|
+
...(runtimeSelectionFile === undefined ? {} : { selectionFile: runtimeSelectionFile }),
|
|
125
156
|
...(profile === undefined ? {} : { profile }),
|
|
126
157
|
diagnosticCount: diagnostics.length,
|
|
127
158
|
diagnostics: diagnostics.slice(0, args.limit),
|
|
@@ -151,7 +182,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
151
182
|
const ready = result.programs.every((item) => item.state.state === "ready");
|
|
152
183
|
const desiredState = args.action === "down" ? !result.programs.some((item) => item.state.state === "ready") : ready;
|
|
153
184
|
const lifecycleOk = args.action === "status" || desiredState;
|
|
154
|
-
const shownPrograms = result.programs.filter((item) => item.state.state !== "ready").slice(0, args.limit);
|
|
185
|
+
const shownPrograms = result.programs.filter((item) => args.presentation.verbose || item.state.state !== "ready").slice(0, args.limit);
|
|
155
186
|
const title = args.action === "up"
|
|
156
187
|
? desiredState ? "External programs ready" : "External programs need attention"
|
|
157
188
|
: args.action === "down"
|
|
@@ -161,11 +192,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
161
192
|
format: "hypit.cli-programs@1",
|
|
162
193
|
action: args.action,
|
|
163
194
|
ready,
|
|
164
|
-
programs: result.programs.slice(0, args.limit).map(
|
|
165
|
-
id: item.id,
|
|
166
|
-
state: item.state.state,
|
|
167
|
-
...(args.presentation.verbose && item.action !== undefined ? { action: item.action } : {}),
|
|
168
|
-
})),
|
|
195
|
+
programs: result.programs.slice(0, args.limit).map(programRecord),
|
|
169
196
|
...(result.programs.length <= args.limit ? {} : { omittedPrograms: result.programs.length - args.limit }),
|
|
170
197
|
}, title,
|
|
171
198
|
args.action === "status" ? ready ? "success" : "info" : lifecycleOk ? "success" : "warning", [
|
|
@@ -173,7 +200,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
173
200
|
["Programs", String(result.programs.length)] as const,
|
|
174
201
|
["Ready", String(result.programs.filter((item) => item.state.state === "ready").length)] as const,
|
|
175
202
|
]),
|
|
176
|
-
], shownPrograms.map(
|
|
203
|
+
], shownPrograms.map(programDescription));
|
|
177
204
|
if (!lifecycleOk) io.setExitCode?.(1);
|
|
178
205
|
return;
|
|
179
206
|
}
|
|
@@ -207,6 +234,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
207
234
|
programs: {
|
|
208
235
|
total: external.programs.length,
|
|
209
236
|
ready: external.programs.filter((item) => item.state.state === "ready").length,
|
|
237
|
+
items: external.programs.map(programRecord),
|
|
210
238
|
},
|
|
211
239
|
}, ok ? "Local Runtime ready" : "Local Runtime needs attention", ok ? "success" : "warning", [
|
|
212
240
|
...(!args.presentation.verbose && ok ? [] : [
|
|
@@ -214,7 +242,8 @@ export async function runEnvironmentCommand(input: {
|
|
|
214
242
|
["Worker", processState.state] as const,
|
|
215
243
|
["Managed programs", `${external.programs.filter((item) => item.state.state === "ready").length}/${external.programs.length} ready`] as const,
|
|
216
244
|
]),
|
|
217
|
-
])
|
|
245
|
+
], external.programs.filter((item) => args.presentation.verbose || item.state.state !== "ready")
|
|
246
|
+
.map(programDescription));
|
|
218
247
|
if (!ok) io.setExitCode?.(1);
|
|
219
248
|
return;
|
|
220
249
|
}
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import type { CliBuildResultView, CliBuildStatusView, CliOutputView } from "./view.js";
|
|
2
2
|
|
|
3
|
-
type ProgramStateView = {
|
|
3
|
+
type ProgramStateView = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly state: string;
|
|
6
|
+
readonly action?: string;
|
|
7
|
+
readonly endpoint?: string;
|
|
8
|
+
readonly stateDetail?: string;
|
|
9
|
+
readonly detail?: string;
|
|
10
|
+
readonly pid?: number;
|
|
11
|
+
readonly logPath?: string;
|
|
12
|
+
};
|
|
4
13
|
type AttentionView = { readonly message: string; readonly action?: string };
|
|
5
14
|
|
|
6
15
|
export type OperationalMachineView =
|
|
7
16
|
| { readonly format: "hypit.cli-runtime-init@1"; readonly profile: string; readonly project: string; readonly selected: true }
|
|
8
17
|
| { readonly format: "hypit.cli-runtime-selection@1"; readonly selected: boolean; readonly removed?: boolean; readonly profile?: string; readonly project?: string }
|
|
9
|
-
| { readonly format: "hypit.cli-paths@1"; readonly project: string; readonly projectState: string; readonly profile?: string; readonly runtimeData?: string; readonly hostState: string; readonly machinePackages: string; readonly distribution?: string }
|
|
18
|
+
| { readonly format: "hypit.cli-paths@1"; readonly project: string; readonly projectState: string; readonly profileSource: "argument" | "project" | "none"; readonly selectionFile: string; readonly profile?: string; readonly runtimeData?: string; readonly hostState: string; readonly machinePackages: string; readonly distribution?: string }
|
|
10
19
|
| { readonly format: "hypit.cli-package@1"; readonly action: "install" | "status"; readonly package: string; readonly ready: boolean }
|
|
11
20
|
| { readonly format: "hypit.cli-programs@1"; readonly action: "up" | "down" | "status"; readonly ready: boolean; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
|
|
12
|
-
| { readonly format: "hypit.cli-runtime-up@1"; readonly ready: boolean; readonly worker: string; readonly preparedPackages: number; readonly programs: { readonly total: number; readonly ready: number } }
|
|
21
|
+
| { readonly format: "hypit.cli-runtime-up@1"; readonly ready: boolean; readonly worker: string; readonly preparedPackages: number; readonly programs: { readonly total: number; readonly ready: number; readonly items: readonly ProgramStateView[] } }
|
|
13
22
|
| { readonly format: "hypit.cli-runtime-logs@1"; readonly lines: readonly string[]; readonly totalLines: number; readonly omittedLines: number; readonly path?: string }
|
|
14
23
|
| { readonly format: "hypit.cli-runtime-down@1"; readonly worker: string }
|
|
15
24
|
| { readonly format: "hypit.cli-runtime-status@1"; readonly ready: boolean; readonly attention: boolean; readonly worker: { readonly state: string; readonly configuration?: string }; readonly builds: Readonly<Record<string, number>>; readonly programs: { readonly total: number; readonly ready: number; readonly unavailable: readonly ProgramStateView[] }; readonly capacity: { readonly active: number } }
|
package/packages/cli/src/main.ts
CHANGED
|
@@ -177,12 +177,14 @@ export async function runCli(
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
let runtimeProfile = acceptsRuntimeContext(args) ? args.runtimeProfile : undefined;
|
|
180
|
+
let runtimeSelectionFile: string | undefined;
|
|
180
181
|
const runtimeWasExplicit = runtimeProfile !== undefined;
|
|
181
182
|
let runtimeNeedsHint = runtimeWasExplicit;
|
|
182
183
|
if (acceptsRuntimeContext(args) && runtimeProfile === undefined) {
|
|
183
184
|
const selected = await findRuntimeProfile(await commandProjectRoot());
|
|
184
185
|
if (selected !== undefined) {
|
|
185
186
|
runtimeProfile = selected.profile;
|
|
187
|
+
runtimeSelectionFile = selected.selectionFile;
|
|
186
188
|
runtimeNeedsHint = false;
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -196,6 +198,7 @@ export async function runCli(
|
|
|
196
198
|
await runEnvironmentCommand({
|
|
197
199
|
args,
|
|
198
200
|
runtimeProfile,
|
|
201
|
+
runtimeSelectionFile,
|
|
199
202
|
io,
|
|
200
203
|
distribution,
|
|
201
204
|
projectRoot: await commandProjectRoot(),
|
|
@@ -387,6 +390,7 @@ export async function runCli(
|
|
|
387
390
|
result: {
|
|
388
391
|
repository: buildResults.location,
|
|
389
392
|
...(args.title === undefined ? {} : { title: args.title }),
|
|
393
|
+
resourceReferences: loadedRun.resultResourceReferences,
|
|
390
394
|
forwards: result.resultForwards.filter((forward) =>
|
|
391
395
|
catalog.publishedOutputs.some((published) => published.ref.id === forward.output)),
|
|
392
396
|
},
|
|
@@ -43,6 +43,8 @@ export type DoctorOutput = {
|
|
|
43
43
|
readonly ok: boolean;
|
|
44
44
|
readonly project: string;
|
|
45
45
|
readonly profile?: string;
|
|
46
|
+
readonly profileSource?: "argument" | "project" | "none";
|
|
47
|
+
readonly selectionFile?: string;
|
|
46
48
|
readonly diagnosticCount: number;
|
|
47
49
|
readonly diagnostics: readonly CliDiagnostic[];
|
|
48
50
|
readonly omittedDiagnostics?: number;
|
|
@@ -272,9 +274,16 @@ function renderDoctor(view: Extract<CliPresentation, { kind: "doctor" }>, io: Cl
|
|
|
272
274
|
const lines = [colors.accent(colors.strong("Hypit Doctor")), ""];
|
|
273
275
|
lines.push(...facts([
|
|
274
276
|
["Project", shortPath(view.machine.project)],
|
|
275
|
-
|
|
277
|
+
["Runtime Profile", view.machine.profile === undefined ? "not selected" : shortPath(view.machine.profile)],
|
|
278
|
+
...(view.machine.profileSource === "argument"
|
|
279
|
+
? [["Runtime selection", "command argument (this invocation only)"] as const]
|
|
280
|
+
: view.machine.selectionFile === undefined ? [] : [["Runtime selection", shortPath(view.machine.selectionFile)] as const]),
|
|
281
|
+
["Scope", view.machine.profile === undefined ? "project Results only" : "selected Runtime and project Results"],
|
|
276
282
|
], colors));
|
|
277
283
|
lines.push("");
|
|
284
|
+
if (view.machine.profile === undefined) {
|
|
285
|
+
lines.push("Select an existing Profile with hypit runtime use <profile>, or pass --runtime <profile> to check it.", "");
|
|
286
|
+
}
|
|
278
287
|
if (view.machine.diagnostics.length === 0) {
|
|
279
288
|
lines.push(heading("success", "No problems found", io, colors));
|
|
280
289
|
} else {
|
|
@@ -770,7 +779,8 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
770
779
|
colors.accent(colors.strong("hypit doctor")),
|
|
771
780
|
colors.dim("Diagnose project Results and, when selected or supplied, one Runtime Profile."),
|
|
772
781
|
"",
|
|
773
|
-
" hypit doctor [<
|
|
782
|
+
" hypit doctor [--runtime <profile>] [--workspace <project>]",
|
|
783
|
+
" A positional Profile is also accepted in place of --runtime.",
|
|
774
784
|
" Without a Runtime Profile, checks only the project's selected Result Store.",
|
|
775
785
|
],
|
|
776
786
|
plan: [
|
|
@@ -819,6 +829,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
819
829
|
" hypit runtime down [<profile>] stop the Worker; external programs keep running",
|
|
820
830
|
"",
|
|
821
831
|
"The project is resolved first. Selection is read only from that project's .hypit/runtime.",
|
|
832
|
+
"All runtime actions accept --workspace <project>; operational actions also accept --runtime <profile>.",
|
|
822
833
|
"No Profile filename discovery or parent-project inheritance is performed.",
|
|
823
834
|
"Remote Endpoints such as HypiHub are not started by this command; use doctor to test them.",
|
|
824
835
|
],
|
|
@@ -836,22 +847,26 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
836
847
|
colors.accent(colors.strong("hypit programs")),
|
|
837
848
|
colors.dim("Prepare and operate external programs declared by Endpoints in one Runtime Profile."),
|
|
838
849
|
"",
|
|
839
|
-
" hypit programs up [<profile>] [--max-wait-ms <ms>]",
|
|
840
|
-
" hypit programs status [<profile>]",
|
|
841
|
-
" hypit programs down [<profile>]",
|
|
850
|
+
" hypit programs up [--runtime <profile>] [--workspace <project>] [--max-wait-ms <ms>]",
|
|
851
|
+
" hypit programs status [--runtime <profile>] [--workspace <project>]",
|
|
852
|
+
" hypit programs down [--runtime <profile>] [--workspace <project>]",
|
|
853
|
+
" A positional Profile is also accepted in place of --runtime.",
|
|
842
854
|
],
|
|
843
855
|
activity: [
|
|
844
856
|
colors.accent(colors.strong("hypit activity")),
|
|
845
857
|
colors.dim("Inspect active Builds and Provider pool capacity."),
|
|
846
858
|
"",
|
|
847
|
-
" hypit activity [--runtime <profile>] [--watch]",
|
|
848
|
-
" hypit activity [--runtime <profile>] --watch --jsonl",
|
|
859
|
+
" hypit activity [--workspace <project>] [--runtime <profile>] [--watch]",
|
|
860
|
+
" hypit activity [--workspace <project>] [--runtime <profile>] --watch --jsonl",
|
|
849
861
|
],
|
|
850
862
|
paths: [
|
|
851
863
|
colors.accent(colors.strong("hypit paths")),
|
|
852
864
|
colors.dim("Show project, Runtime and host state locations without creating them."),
|
|
853
865
|
"",
|
|
854
|
-
" hypit paths [--runtime <profile>]",
|
|
866
|
+
" hypit paths [--workspace <project>] [--runtime <profile>]",
|
|
867
|
+
" Shows whether Runtime selection comes from a command argument or the project's .hypit/runtime.",
|
|
868
|
+
" Without --workspace, the nearest package.json above cwd defines the project (otherwise cwd).",
|
|
869
|
+
" Relative command-line paths start at cwd, including when --workspace is supplied.",
|
|
855
870
|
],
|
|
856
871
|
builds: [
|
|
857
872
|
colors.accent(colors.strong("hypit builds")),
|
|
@@ -863,7 +878,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
863
878
|
colors.accent(colors.strong("hypit status")),
|
|
864
879
|
colors.dim("Show one Build now, or keep watching it without owning execution."),
|
|
865
880
|
"",
|
|
866
|
-
" hypit status <build-id> [--runtime <profile>] [--watch]",
|
|
881
|
+
" hypit status <build-id> [--workspace <project>] [--runtime <profile>] [--watch]",
|
|
867
882
|
" --watch observe until a Result outcome or operator attention",
|
|
868
883
|
" --max-wait-ms <ms> stop watching after a bounded wait",
|
|
869
884
|
],
|
|
@@ -892,14 +907,14 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
892
907
|
colors.accent(colors.strong("hypit cancel")),
|
|
893
908
|
colors.dim("Stop one Build and request cancellation of submitted work when supported."),
|
|
894
909
|
"",
|
|
895
|
-
" hypit cancel <build-id> [--runtime <profile>] [--reason <text>]",
|
|
910
|
+
" hypit cancel <build-id> [--workspace <project>] [--runtime <profile>] [--reason <text>]",
|
|
896
911
|
],
|
|
897
912
|
result: [
|
|
898
913
|
colors.accent(colors.strong("hypit result")),
|
|
899
914
|
colors.dim("Edit one Result, finish an interrupted Result write, or discard an incomplete submission."),
|
|
900
915
|
"",
|
|
901
|
-
" hypit result finish <build-id> [--runtime <profile>]",
|
|
902
|
-
" hypit result discard <build-id> [--runtime <profile>]",
|
|
916
|
+
" hypit result finish <build-id> [--workspace <project>] [--runtime <profile>]",
|
|
917
|
+
" hypit result discard <build-id> [--workspace <project>] [--runtime <profile>]",
|
|
903
918
|
" hypit result edit <build-id> [--workspace <project>] [--title <text>] [--note <text>]",
|
|
904
919
|
" [--highlight <output> ...]",
|
|
905
920
|
" --clear-title remove the Result's human title",
|
|
@@ -913,6 +928,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
913
928
|
" hypit auth status <endpoint-instance> [--runtime <profile>] [--slot <name>]",
|
|
914
929
|
" hypit auth login <endpoint-instance> [--runtime <profile>] [--slot <name>] [--from <secret-file>]",
|
|
915
930
|
" hypit auth logout <endpoint-instance> [--runtime <profile>] [--slot <name>]",
|
|
931
|
+
" All auth actions accept --workspace <project> to use that project's selection.",
|
|
916
932
|
],
|
|
917
933
|
};
|
|
918
934
|
const selected = topics[topic];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileReferenceIdentity } from "@hypit/build-result";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
@@ -53,7 +54,7 @@ async function openResultFile(
|
|
|
53
54
|
file: BuildResultFileRef,
|
|
54
55
|
): Promise<AsyncIterable<Uint8Array>> {
|
|
55
56
|
const input = await repository.openFile(build, file);
|
|
56
|
-
if (input === undefined) throw new Error(`
|
|
57
|
+
if (input === undefined) throw new Error(`File ${fileReferenceIdentity(build, file)} is unavailable`);
|
|
57
58
|
return input;
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -83,20 +84,28 @@ async function exportComposite(
|
|
|
83
84
|
const temporary = join(dirname(target), `.${basename(target)}.part-${randomUUID()}`);
|
|
84
85
|
try {
|
|
85
86
|
await mkdir(temporary);
|
|
86
|
-
const copied = new
|
|
87
|
+
const copied = new Map<string, BuildResultFileRef>();
|
|
88
|
+
const paths = new Set<string>();
|
|
89
|
+
const bindings = [];
|
|
87
90
|
for (const binding of resolvedOutput.value.document.resources) {
|
|
88
|
-
const file = binding.file;
|
|
89
|
-
|
|
90
|
-
copied.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const file = await repository.describeFile(resolvedOutput.build, binding.file);
|
|
92
|
+
const identity = fileReferenceIdentity(resolvedOutput.build, file);
|
|
93
|
+
let local = copied.get(identity);
|
|
94
|
+
if (local === undefined) {
|
|
95
|
+
let path = file.kind === "build-file" ? file.path : `files/file-${copied.size + 1}`;
|
|
96
|
+
const name = basename(path);
|
|
97
|
+
let index = copied.size + 1;
|
|
98
|
+
while (paths.has(path) || path === "value.json") path = `files/import-${index++}/${name}`;
|
|
99
|
+
paths.add(path);
|
|
100
|
+
local = { kind: "build-file", path, size: file.size, mediaType: file.mediaType };
|
|
101
|
+
copied.set(identity, local);
|
|
102
|
+
await writeStream(containedPath(temporary, path), await openResultFile(repository, resolvedOutput.build, file));
|
|
103
|
+
}
|
|
104
|
+
bindings.push({ at: binding.at, file: local });
|
|
96
105
|
}
|
|
97
106
|
await writeFile(
|
|
98
107
|
join(temporary, "value.json"),
|
|
99
|
-
`${JSON.stringify(resolvedOutput.value.document, null, 2)}\n`,
|
|
108
|
+
`${JSON.stringify({ ...resolvedOutput.value.document, resources: bindings }, null, 2)}\n`,
|
|
100
109
|
{ flag: "wx" },
|
|
101
110
|
);
|
|
102
111
|
await rename(temporary, target);
|
|
@@ -122,7 +131,7 @@ export async function exportBuildResultOutput(
|
|
|
122
131
|
const target = resolve(destination);
|
|
123
132
|
if (resolvedOutput.value.kind === "value") {
|
|
124
133
|
await exportComposite(repository, { ...resolvedOutput, value: resolvedOutput.value }, target);
|
|
125
|
-
} else if (resolvedOutput.value.kind === "build-file") {
|
|
134
|
+
} else if ((resolvedOutput.value.kind === "build-file" || resolvedOutput.value.kind === "external-file")) {
|
|
126
135
|
const resource = resolvedOutput.value;
|
|
127
136
|
await exportFile(target, async (temporary) => {
|
|
128
137
|
await writeStream(
|
|
@@ -142,7 +151,7 @@ export async function exportBuildResultOutput(
|
|
|
142
151
|
type: publishedOutput.type,
|
|
143
152
|
kind: resolvedOutput.value.kind === "inline"
|
|
144
153
|
? "scalar"
|
|
145
|
-
: resolvedOutput.value.kind === "build-file" ? "resource" : "composite",
|
|
154
|
+
: (resolvedOutput.value.kind === "build-file" || resolvedOutput.value.kind === "external-file") ? "resource" : "composite",
|
|
146
155
|
path: target,
|
|
147
156
|
};
|
|
148
157
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileReferenceIdentity, ownedFileReference } from "@hypit/build-result";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import type {
|
|
3
4
|
BuildResultRepository,
|
|
@@ -24,15 +25,17 @@ import type { RunFrontend } from "@hypit/run";
|
|
|
24
25
|
export type LoadedRunFile = NodeCompiledRun & {
|
|
25
26
|
readonly path: string;
|
|
26
27
|
readonly compiler: NodeRunCompiler;
|
|
28
|
+
readonly resultResourceReferences: Readonly<Record<string, BuildResultFileRef>>;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
type BuildResultResolutionSession = {
|
|
30
32
|
readonly files: Map<string, ArtifactAttachment>;
|
|
33
|
+
readonly references: Map<string, BuildResultFileRef>;
|
|
31
34
|
readonly outputs: Map<string, Promise<RepositoryBuildResultOutput | undefined>>;
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
function createBuildResultResolutionSession(): BuildResultResolutionSession {
|
|
35
|
-
return { files: new Map(), outputs: new Map() };
|
|
38
|
+
return { files: new Map(), outputs: new Map(), references: new Map() };
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export async function checkRunFile(options: {
|
|
@@ -88,9 +91,10 @@ export async function resolveBuildResultValue(
|
|
|
88
91
|
const manifest = await repository.read(build);
|
|
89
92
|
if (manifest?.outcome === undefined) return undefined;
|
|
90
93
|
const resultAttachment = async (owner: string, file: BuildResultFileRef): Promise<ArtifactAttachment> => {
|
|
91
|
-
const address =
|
|
94
|
+
const address = fileReferenceIdentity(owner, file);
|
|
92
95
|
const existing = session.files.get(address);
|
|
93
96
|
if (existing !== undefined) return existing;
|
|
97
|
+
file = await repository.describeFile(owner, file);
|
|
94
98
|
const artifact: BlobRef = {
|
|
95
99
|
kind: "blob",
|
|
96
100
|
resource: `res_${randomUUID()}`,
|
|
@@ -101,11 +105,12 @@ export async function resolveBuildResultValue(
|
|
|
101
105
|
artifact,
|
|
102
106
|
async open() {
|
|
103
107
|
const stream = await repository.openFile(owner, file);
|
|
104
|
-
if (stream === undefined) throw new Error(`
|
|
108
|
+
if (stream === undefined) throw new Error(`File ${address} is unavailable`);
|
|
105
109
|
return stream;
|
|
106
110
|
},
|
|
107
111
|
};
|
|
108
112
|
session.files.set(address, attachment);
|
|
113
|
+
session.references.set(artifact.resource, ownedFileReference(owner, file));
|
|
109
114
|
return attachment;
|
|
110
115
|
};
|
|
111
116
|
const attachments = new Map<string, ArtifactAttachment>();
|
|
@@ -120,7 +125,7 @@ export async function resolveBuildResultValue(
|
|
|
120
125
|
const owner = resolved.build === build ? manifest : await repository.read(resolved.build);
|
|
121
126
|
if (owner?.outcome === undefined) return undefined;
|
|
122
127
|
let value: StoredValue;
|
|
123
|
-
if (resolved.value.kind === "build-file") {
|
|
128
|
+
if (resolved.value.kind === "build-file" || resolved.value.kind === "external-file") {
|
|
124
129
|
const attachment = await resultAttachment(resolved.build, resolved.value);
|
|
125
130
|
attachments.set(attachment.artifact.resource, attachment);
|
|
126
131
|
value = attachment.artifact;
|
|
@@ -147,8 +152,7 @@ function createRunCompiler(options: {
|
|
|
147
152
|
readonly frontends: readonly RunFrontend[];
|
|
148
153
|
readonly packageContributions: readonly NodePackageContribution[];
|
|
149
154
|
readonly results?: BuildResultRepository;
|
|
150
|
-
}): NodeRunCompiler {
|
|
151
|
-
const resultSession = createBuildResultResolutionSession();
|
|
155
|
+
}, resultSession = createBuildResultResolutionSession()): NodeRunCompiler {
|
|
152
156
|
const fragments = new RunFragmentRegistry();
|
|
153
157
|
for (const item of options.packageContributions) {
|
|
154
158
|
installRunFragmentHostFacets(item.hostFacets ?? [], fragments);
|
|
@@ -180,7 +184,14 @@ export async function loadRunFile(options: {
|
|
|
180
184
|
readonly packageContributions: readonly NodePackageContribution[];
|
|
181
185
|
readonly results?: BuildResultRepository;
|
|
182
186
|
}): Promise<LoadedRunFile> {
|
|
183
|
-
const
|
|
187
|
+
const resultSession = createBuildResultResolutionSession();
|
|
188
|
+
const compiler = createRunCompiler(options, resultSession);
|
|
184
189
|
const compiled = await compiler.compileSource(options.workspace.entry, options.workspace);
|
|
185
|
-
|
|
190
|
+
const references = new Map<string, BuildResultFileRef>(compiled.attachments.flatMap((attachment) =>
|
|
191
|
+
attachment.location === undefined ? [] : [[attachment.artifact.resource, {
|
|
192
|
+
kind: "external-file" as const, uri: attachment.location,
|
|
193
|
+
size: attachment.artifact.size, mediaType: attachment.artifact.mediaType,
|
|
194
|
+
}]]));
|
|
195
|
+
for (const [resource, file] of resultSession.references) references.set(resource, file);
|
|
196
|
+
return { path: options.workspace.entry.id, compiler, ...compiled, resultResourceReferences: Object.fromEntries(references) };
|
|
186
197
|
}
|
|
@@ -117,12 +117,13 @@ export const commentStickerStudioTrackCompanions: readonly StudioTrackCompanion[
|
|
|
117
117
|
inspector: [
|
|
118
118
|
...frameParameters.filter(({ writable }) => writable === true).map(({ name }) => ({
|
|
119
119
|
binding: `frame.${name}`, label: title(name), domain: "where" as const,
|
|
120
|
-
page: { id: "frame", label: "Frame" }, section: { id: "frame", label: "Frame" }, control: "
|
|
120
|
+
page: { id: "frame", label: "Frame" }, section: { id: "frame", label: "Frame" }, control: "number" as const, number: { suffixes: ["%", "px"], step: 1 },
|
|
121
121
|
})),
|
|
122
122
|
...commentInspector,
|
|
123
123
|
...["comment", "author", "header", "meta"].map((binding) => ({
|
|
124
124
|
binding, label: title(binding), domain: "how" as const,
|
|
125
125
|
page: { id: "copy", label: "Copy" }, section: { id: "copy", label: "Copy" }, control: "text" as const,
|
|
126
|
+
...(binding === "comment" ? { multiline: true } : {}),
|
|
126
127
|
})),
|
|
127
128
|
],
|
|
128
129
|
requiredValues: ["program"], project: projectComments,
|
|
@@ -51,6 +51,7 @@ export function mergeAttachments(groups: readonly (readonly ArtifactAttachment[]
|
|
|
51
51
|
merged.set(key, {
|
|
52
52
|
artifact: { ...item.artifact },
|
|
53
53
|
open: item.open,
|
|
54
|
+
...(item.location === undefined ? {} : { location: item.location }),
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
return [...merged.values()]
|