@hypit/hypit 0.1.12 → 0.1.14
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 +36 -2
- package/bin/hypit.mjs +0 -2
- package/dist/public/model-kit.d.ts +16 -0
- package/package.json +3 -1
- package/packages/cli/README.md +3 -0
- package/packages/cli/package.json +1 -0
- package/packages/cli/src/commands/environment.ts +12 -7
- package/packages/cli/src/commands/results.ts +19 -3
- package/packages/cli/src/main.ts +2 -1
- package/packages/cli/src/oauth.ts +50 -7
- package/packages/cli/src/output.ts +2 -2
- package/packages/cli/src/source-discovery.ts +2 -2
- package/packages/credential-store-file/README.md +60 -0
- package/packages/credential-store-file/package.json +21 -0
- package/packages/credential-store-file/src/activation.ts +29 -0
- package/packages/credential-store-file/src/index.ts +1 -0
- package/packages/credential-store-file/src/store.ts +89 -0
- package/packages/fonts-open/src/surface.ts +8 -2
- package/packages/hyperframes/README.md +16 -2
- package/packages/hyperframes/src/browser-program.ts +6 -1
- package/packages/hyperframes/src/document.ts +31 -21
- package/packages/hyperframes/src/project.ts +11 -20
- package/packages/media-execution/README.md +7 -0
- package/packages/media-execution/src/execute.ts +5 -5
- package/packages/media-execution/src/index.ts +1 -1
- package/packages/media-execution/src/process-env.ts +20 -0
- package/packages/media-execution/src/surface.ts +83 -76
- package/packages/model-kit/README.md +28 -0
- package/packages/model-kit/src/index.ts +58 -19
- package/packages/package-loader-node/README.md +10 -0
- package/packages/package-loader-node/src/index.ts +1 -0
- package/packages/package-loader-node/src/loader.ts +25 -5
- package/packages/package-loader-node/src/location.ts +11 -2
- package/packages/project-context-node/README.md +2 -0
- package/packages/project-context-node/src/project-context.ts +5 -3
- package/packages/provider-hyperframes-local/README.md +95 -13
- package/packages/provider-hyperframes-local/package.json +13 -3
- package/packages/provider-hyperframes-local/src/activation.ts +20 -6
- package/packages/provider-hyperframes-local/src/browser-install.ts +5 -0
- package/packages/provider-hyperframes-local/src/browser.ts +118 -0
- package/packages/provider-hyperframes-local/src/capture-bootstrap.ts +4 -6
- package/packages/provider-hyperframes-local/src/capture-exit.ts +24 -0
- package/packages/provider-hyperframes-local/src/capture-process.ts +54 -59
- package/packages/provider-hyperframes-local/src/capture-worker.ts +28 -7
- package/packages/provider-hyperframes-local/src/capture.ts +12 -5
- package/packages/provider-hyperframes-local/src/opaque-capture.ts +46 -18
- package/packages/provider-hyperframes-local/src/options.ts +2 -2
- package/packages/provider-hyperframes-local/src/process-tree.ts +82 -0
- package/packages/provider-hyperframes-local/src/process.ts +22 -0
- package/packages/provider-hyperframes-local/src/program.ts +25 -33
- package/packages/provider-hyperframes-local/src/provider.ts +4 -7
- package/packages/provider-hyperframes-local/src/render.ts +9 -4
- package/packages/provider-hypihub/README.md +3 -0
- package/packages/runtime-host-node/README.md +5 -0
- package/packages/runtime-host-node/src/index.ts +7 -2
- package/packages/runtime-host-node/src/packages.ts +13 -7
- package/packages/runtime-local/README.md +10 -0
- package/packages/runtime-local/src/config.ts +1 -1
- package/packages/runtime-local/src/credentials.ts +10 -5
- package/packages/runtime-local/src/runtime.ts +1 -1
- package/packages/seedance/README.md +42 -0
- package/packages/seedance/src/index.ts +5 -22
- package/packages/seedance/src/surface.ts +2 -3
- package/packages/seedance/src/validation.ts +16 -0
- package/packages/studio/src/preview/runtime-shim.ts +17 -3
- package/packages/studio/src/server.ts +2 -2
- package/packages/video-cli/README.md +5 -1
- package/packages/video-cli/package.json +1 -0
- package/packages/video-cli/src/distribution.ts +3 -10
- package/packages/video-cli/src/version.ts +1 -1
- package/packages/workspace-fs-node/src/workspace.ts +2 -2
- package/packages/yt-dlp/README.md +3 -2
- package/packages/yt-dlp/package.json +4 -0
- package/packages/yt-dlp/src/download.ts +10 -14
|
@@ -13,13 +13,13 @@ import type { HostFacet } from "@hypit/host";
|
|
|
13
13
|
import {
|
|
14
14
|
bindGenerationMedia,
|
|
15
15
|
bindGenerationText,
|
|
16
|
-
finalizeGenerationRequestDraft,
|
|
17
16
|
generationModuleRef,
|
|
18
17
|
generationProducers,
|
|
19
18
|
generationTypes,
|
|
20
19
|
mediaBindingSchemaFromPort,
|
|
21
20
|
requestDraftSchemaFromPorts,
|
|
22
21
|
requestSchemaFromPorts,
|
|
22
|
+
sealGenerationPortRequest,
|
|
23
23
|
verifyGenerationMediaBinding,
|
|
24
24
|
verifyRequestDraftAgainstPorts,
|
|
25
25
|
verifyRequestAgainstPorts,
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
GenerationMediaBinding,
|
|
29
29
|
GenerationMediaPort,
|
|
30
30
|
GenerationPortTable,
|
|
31
|
+
GenerationRequest,
|
|
31
32
|
GenerationRequestDraft,
|
|
32
33
|
} from "@hypit/generation";
|
|
33
34
|
import { textDependency, textTypes } from "@hypit/text";
|
|
@@ -76,6 +77,10 @@ export type ExactModelEndpointSpec = {
|
|
|
76
77
|
readonly requestTypeName: string;
|
|
77
78
|
readonly producerName: string;
|
|
78
79
|
readonly ports: GenerationPortTable;
|
|
80
|
+
/** Additional complete-request rules, after port and supplied-input validation. */
|
|
81
|
+
readonly validateRequest?: (request: GenerationRequest) => void;
|
|
82
|
+
/** Pure, synchronous checks of supplied values, in drafts and complete requests; throw to reject. */
|
|
83
|
+
readonly validateInputs?: (inputs: GenerationRequestDraft) => void;
|
|
79
84
|
};
|
|
80
85
|
|
|
81
86
|
export type ExactModelEndpoint = {
|
|
@@ -89,6 +94,9 @@ export type ExactModelEndpoint = {
|
|
|
89
94
|
readonly mediaBindings: Readonly<Record<string, ExactModelMediaBindingEndpoint>>;
|
|
90
95
|
readonly textBindings: Readonly<Record<string, ExactModelTextBindingEndpoint>>;
|
|
91
96
|
readonly ports: GenerationPortTable;
|
|
97
|
+
readonly validateRequest: (request: unknown) => void;
|
|
98
|
+
readonly validateDraft: (draft: unknown) => void;
|
|
99
|
+
readonly sealRequest: (ports: GenerationRequest["ports"]) => GenerationRequest;
|
|
92
100
|
readonly fragment: ReturnType<typeof sealGraphFragment>;
|
|
93
101
|
};
|
|
94
102
|
|
|
@@ -267,7 +275,7 @@ export function plannedExactModelRequest(
|
|
|
267
275
|
|
|
268
276
|
const knownNeed = state.needs.find((need) => need.id === binding.id);
|
|
269
277
|
if (knownNeed !== undefined) {
|
|
270
|
-
|
|
278
|
+
endpoint.validateRequest(knownNeed.constraints);
|
|
271
279
|
return {
|
|
272
280
|
model: endpoint.ports.model,
|
|
273
281
|
ports: structuredClone((knownNeed.constraints as unknown as GenerationRequestDraft).ports),
|
|
@@ -281,6 +289,12 @@ export function plannedExactModelRequest(
|
|
|
281
289
|
Object.values(step.outputs).map((record) => [record, step] as const)));
|
|
282
290
|
const requestRecord = generationStep.inputs.request;
|
|
283
291
|
if (requestRecord === undefined) return undefined;
|
|
292
|
+
const knownRequest = records.get(requestRecord);
|
|
293
|
+
if (knownRequest !== undefined) {
|
|
294
|
+
const request = inlineValue<GenerationRequest>(knownRequest.value, `${endpoint.ports.model} request`);
|
|
295
|
+
endpoint.validateRequest(request);
|
|
296
|
+
return { model: endpoint.ports.model, ports: structuredClone(request.ports), pendingMedia: [], complete: false };
|
|
297
|
+
}
|
|
284
298
|
const finalize = producedBy.get(requestRecord);
|
|
285
299
|
if (finalize === undefined || !sameReference(finalize.producer, endpoint.finalizeProducer)) return undefined;
|
|
286
300
|
const finalDraft = finalize.inputs.draft;
|
|
@@ -295,7 +309,7 @@ export function plannedExactModelRequest(
|
|
|
295
309
|
const record = records.get(recordId);
|
|
296
310
|
if (record !== undefined) {
|
|
297
311
|
const draft = inlineValue<GenerationRequestDraft>(record.value, `${endpoint.ports.model} request draft`);
|
|
298
|
-
|
|
312
|
+
endpoint.validateDraft(draft);
|
|
299
313
|
return draft;
|
|
300
314
|
}
|
|
301
315
|
const step = producedBy.get(recordId);
|
|
@@ -354,6 +368,8 @@ export function plannedExactModelRequest(
|
|
|
354
368
|
};
|
|
355
369
|
|
|
356
370
|
const draft = rebuildDraft(finalDraft);
|
|
371
|
+
endpoint.validateDraft(draft);
|
|
372
|
+
if (pendingMedia.length === 0) endpoint.validateRequest(draft);
|
|
357
373
|
return {
|
|
358
374
|
model: endpoint.ports.model,
|
|
359
375
|
ports: structuredClone(draft.ports),
|
|
@@ -502,6 +518,15 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
502
518
|
};
|
|
503
519
|
|
|
504
520
|
const endpoints = Object.fromEntries(endpointData.map((item): [Key, ExactModelEndpoint] => {
|
|
521
|
+
const validateRequest = (request: unknown): void => {
|
|
522
|
+
verifyRequestAgainstPorts(item.spec.ports, request);
|
|
523
|
+
item.spec.validateInputs?.(request);
|
|
524
|
+
item.spec.validateRequest?.(request);
|
|
525
|
+
};
|
|
526
|
+
const validateDraft = (draft: unknown): void => {
|
|
527
|
+
verifyRequestDraftAgainstPorts(item.spec.ports, draft);
|
|
528
|
+
item.spec.validateInputs?.(draft);
|
|
529
|
+
};
|
|
505
530
|
const fragment = sealGraphFragment({
|
|
506
531
|
inputs: [{ name: "request", type: item.requestType }],
|
|
507
532
|
operations: [{
|
|
@@ -527,6 +552,14 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
527
552
|
mediaBindings: item.mediaBindings,
|
|
528
553
|
textBindings: item.textBindings,
|
|
529
554
|
ports: item.spec.ports,
|
|
555
|
+
validateRequest,
|
|
556
|
+
validateDraft,
|
|
557
|
+
sealRequest(ports) {
|
|
558
|
+
const request = sealGenerationPortRequest(item.spec.ports, ports);
|
|
559
|
+
item.spec.validateInputs?.(request);
|
|
560
|
+
item.spec.validateRequest?.(request);
|
|
561
|
+
return request;
|
|
562
|
+
},
|
|
530
563
|
fragment,
|
|
531
564
|
} satisfies ExactModelEndpoint];
|
|
532
565
|
}));
|
|
@@ -540,12 +573,12 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
540
573
|
validators: endpointData.flatMap((item) => [{
|
|
541
574
|
type: item.requestType,
|
|
542
575
|
handler({ value }) {
|
|
543
|
-
|
|
576
|
+
endpoints[item.spec.key]!.validateRequest(inlineRequest(value, item.spec.key));
|
|
544
577
|
},
|
|
545
578
|
}, {
|
|
546
579
|
type: item.draftType,
|
|
547
580
|
handler({ value }) {
|
|
548
|
-
|
|
581
|
+
endpoints[item.spec.key]!.validateDraft(inlineRequest(value, `${item.spec.key} draft`));
|
|
549
582
|
},
|
|
550
583
|
}]),
|
|
551
584
|
producers: endpointData.flatMap((item) => [
|
|
@@ -555,7 +588,7 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
555
588
|
const requestRecord = inputs.request;
|
|
556
589
|
assert(requestRecord !== undefined, `${item.spec.key} request input is missing`);
|
|
557
590
|
const request = inlineRequest(requestRecord.value, item.spec.key);
|
|
558
|
-
|
|
591
|
+
endpoints[item.spec.key]!.validateRequest(request);
|
|
559
592
|
return { outputs: {}, needs: { generation: request } };
|
|
560
593
|
},
|
|
561
594
|
},
|
|
@@ -570,11 +603,13 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
570
603
|
verifyGenerationMediaBinding(port, value);
|
|
571
604
|
const artifact = inputs.artifact!.value;
|
|
572
605
|
assert(artifact.kind === "blob", `${binding.port} artifact must be a Blob`);
|
|
606
|
+
const bound = bindGenerationMedia(item.spec.ports, draft, binding.port, value, artifact);
|
|
607
|
+
endpoints[item.spec.key]!.validateDraft(bound);
|
|
573
608
|
return {
|
|
574
609
|
outputs: {
|
|
575
610
|
draft: {
|
|
576
611
|
kind: "inline" as const,
|
|
577
|
-
value: canonicalize(
|
|
612
|
+
value: canonicalize(bound),
|
|
578
613
|
},
|
|
579
614
|
},
|
|
580
615
|
needs: {},
|
|
@@ -586,11 +621,13 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
586
621
|
handler: ({ inputs }: ProducerHandlerContext) => {
|
|
587
622
|
const draft = inlineValue<GenerationRequestDraft>(inputs.draft!.value, `${item.spec.key} draft`);
|
|
588
623
|
const text = inlineValue<Text>(inputs.text!.value, `${binding.port} Text`);
|
|
624
|
+
const bound = bindGenerationText(item.spec.ports, draft, binding.port, text);
|
|
625
|
+
endpoints[item.spec.key]!.validateDraft(bound);
|
|
589
626
|
return {
|
|
590
627
|
outputs: {
|
|
591
628
|
draft: {
|
|
592
629
|
kind: "inline" as const,
|
|
593
|
-
value: canonicalize(
|
|
630
|
+
value: canonicalize(bound),
|
|
594
631
|
},
|
|
595
632
|
},
|
|
596
633
|
needs: {},
|
|
@@ -599,18 +636,20 @@ export function defineExactModelModule<const Key extends string>(
|
|
|
599
636
|
})),
|
|
600
637
|
{
|
|
601
638
|
producer: item.finalizeProducer,
|
|
602
|
-
handler: ({ inputs }) =>
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
639
|
+
handler: ({ inputs }) => {
|
|
640
|
+
const endpoint = endpoints[item.spec.key]!;
|
|
641
|
+
const draft = inlineValue<GenerationRequestDraft>(inputs.draft!.value, `${item.spec.key} draft`);
|
|
642
|
+
endpoint.validateDraft(draft);
|
|
643
|
+
return {
|
|
644
|
+
outputs: {
|
|
645
|
+
request: {
|
|
646
|
+
kind: "inline" as const,
|
|
647
|
+
value: canonicalize(endpoint.sealRequest(draft.ports)),
|
|
648
|
+
},
|
|
610
649
|
},
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
650
|
+
needs: {},
|
|
651
|
+
};
|
|
652
|
+
},
|
|
614
653
|
},
|
|
615
654
|
]),
|
|
616
655
|
plannedNeeds: Object.values(endpoints).map((endpoint) => exactModelPlannedNeedFacet(endpoint)),
|
|
@@ -51,3 +51,13 @@ This is trusted execution, not a sandbox. Builtins, native libraries and process
|
|
|
51
51
|
(including `process.cwd()` and `process.env`) remain shared. Existing module bindings do not change
|
|
52
52
|
under an active Build; a file or dependency first
|
|
53
53
|
read later uses ordinary filesystem semantics. Scoped loading does not make a project tree immutable.
|
|
54
|
+
|
|
55
|
+
## Dependency installation options
|
|
56
|
+
|
|
57
|
+
A Distribution package may declare `hypit.dependencyInstallEnv` in its package.json, keyed by its
|
|
58
|
+
ordinary direct external dependency names. Each value is an environment-variable map passed to that
|
|
59
|
+
dependency's explicit npm installation. For example, an owner can disable an SDK's automatic asset
|
|
60
|
+
download and prepare that asset through its ManagedProgram instead. Options do not propagate to
|
|
61
|
+
unrelated installations or the caller's process. Conflicting values from selected packages fail;
|
|
62
|
+
no package wins by discovery order. They are command inputs, not a second dependency inventory,
|
|
63
|
+
readiness record, or new version system. Project packages remain owned by their package manager.
|
|
@@ -16,7 +16,7 @@ import { externalPackageRoots, locateNodePackage } from "./location.js";
|
|
|
16
16
|
type PackageJson = {
|
|
17
17
|
readonly name: string;
|
|
18
18
|
readonly version?: string;
|
|
19
|
-
readonly hypit?: { readonly activation?: string };
|
|
19
|
+
readonly hypit?: { readonly activation?: string; readonly dependencyInstallEnv?: Readonly<Record<string, Readonly<Record<string, string>>>> };
|
|
20
20
|
readonly dependencies: Readonly<Record<string, string>>;
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -43,7 +43,19 @@ function parsePackageJson(value: unknown, subject: string): PackageJson {
|
|
|
43
43
|
return {
|
|
44
44
|
name: text(item.name, `${subject}.name`),
|
|
45
45
|
...(item.version === undefined ? {} : { version: text(item.version, `${subject}.version`) }),
|
|
46
|
-
...(hypit
|
|
46
|
+
...(hypit === undefined ? {} : { hypit: {
|
|
47
|
+
...(hypit.activation === undefined ? {} : { activation: text(hypit.activation, `${subject}.hypit.activation`) }),
|
|
48
|
+
...(hypit.dependencyInstallEnv === undefined ? {} : { dependencyInstallEnv: Object.fromEntries(
|
|
49
|
+
Object.entries(object(hypit.dependencyInstallEnv, `${subject}.hypit.dependencyInstallEnv`)).map(([name, raw]) => {
|
|
50
|
+
assert(Object.hasOwn(dependencies, name) && !name.startsWith("@hypit/"), `${subject}: installation environment must name a direct external dependency: ${name}`);
|
|
51
|
+
const env = object(raw, `${subject}.hypit.dependencyInstallEnv.${name}`);
|
|
52
|
+
for (const [key, value] of Object.entries(env)) {
|
|
53
|
+
assert(/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key) && typeof value === "string", `${subject}: invalid installation environment for ${name}`);
|
|
54
|
+
}
|
|
55
|
+
return [name, env as Record<string, string>];
|
|
56
|
+
}),
|
|
57
|
+
) }),
|
|
58
|
+
} }),
|
|
47
59
|
dependencies: Object.fromEntries(Object.entries(dependencies).map(([name, version]) => [
|
|
48
60
|
name,
|
|
49
61
|
text(version, `${subject}.dependencies.${name}`),
|
|
@@ -183,11 +195,11 @@ export class NodePackageSelectionMissingError extends Error {
|
|
|
183
195
|
export async function distributionExternalPackageRequirements(
|
|
184
196
|
specifiers: readonly string[],
|
|
185
197
|
distributionRoot: string,
|
|
186
|
-
): Promise<readonly { readonly name: string; readonly version: string; readonly specifier: string }[]> {
|
|
198
|
+
): Promise<readonly { readonly name: string; readonly version: string; readonly specifier: string; readonly env?: Readonly<Record<string, string>> }[]> {
|
|
187
199
|
const root = resolve(distributionRoot);
|
|
188
200
|
const queue = [...new Set(specifiers.filter((name) => name.startsWith("@hypit/")))].sort();
|
|
189
201
|
const visited = new Set<string>();
|
|
190
|
-
const external = new Map<string, { readonly name: string; readonly version: string; readonly specifier: string }>();
|
|
202
|
+
const external = new Map<string, { readonly name: string; readonly version: string; readonly specifier: string; readonly env?: Readonly<Record<string, string>> }>();
|
|
191
203
|
while (queue.length > 0) {
|
|
192
204
|
const name = queue.shift()!;
|
|
193
205
|
if (visited.has(name)) continue;
|
|
@@ -199,7 +211,15 @@ export async function distributionExternalPackageRequirements(
|
|
|
199
211
|
continue;
|
|
200
212
|
}
|
|
201
213
|
const specifier = `${dependency}@${version}`;
|
|
202
|
-
|
|
214
|
+
const requested = physical.json.hypit?.dependencyInstallEnv?.[dependency];
|
|
215
|
+
const previous = external.get(specifier);
|
|
216
|
+
const env = { ...previous?.env };
|
|
217
|
+
for (const [key, value] of Object.entries(requested ?? {})) {
|
|
218
|
+
assert(env[key] === undefined || env[key] === value, `Conflicting installation environment ${key} for ${specifier}`);
|
|
219
|
+
env[key] = value;
|
|
220
|
+
}
|
|
221
|
+
external.set(specifier, { name: dependency, version, specifier,
|
|
222
|
+
...(Object.keys(env).length === 0 ? {} : { env }) });
|
|
203
223
|
}
|
|
204
224
|
}
|
|
205
225
|
return [...external.values()].sort((left, right) => left.specifier.localeCompare(right.specifier));
|
|
@@ -2,6 +2,14 @@ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "n
|
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
|
+
/** Only absence is repairable by installing a package; malformed manifests and I/O errors are not. */
|
|
6
|
+
export class NodePackageNotFoundError extends Error {
|
|
7
|
+
constructor(name: string) {
|
|
8
|
+
super(`cannot locate installed package ${name}`);
|
|
9
|
+
this.name = "NodePackageNotFoundError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export type LocatedNodePackage = {
|
|
6
14
|
readonly root: string;
|
|
7
15
|
readonly manifest: {
|
|
@@ -88,7 +96,8 @@ export function declaredExternalPackageRoot(root: string, from: string | URL, na
|
|
|
88
96
|
// An upstream asset a package ships as optional is still selected by one exact version here.
|
|
89
97
|
// `hypit packages install` places it under the machine npm root named by this selection, so
|
|
90
98
|
// reading only the required map makes the documented repair unusable for every optional asset.
|
|
91
|
-
|
|
99
|
+
// npm lets optionalDependencies override dependencies with the same name.
|
|
100
|
+
const version = value.optionalDependencies?.[name] ?? value.dependencies?.[name];
|
|
92
101
|
return version === undefined ? undefined : externalPackageInstallRoot(root, name, version);
|
|
93
102
|
}
|
|
94
103
|
const parent = dirname(cursor);
|
|
@@ -223,7 +232,7 @@ export function locateNodePackage(nameValue: string, options: LocateNodePackageO
|
|
|
223
232
|
if (found !== undefined) return found;
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
|
-
throw new
|
|
235
|
+
throw new NodePackageNotFoundError(name);
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
function packageFile(
|
|
@@ -6,6 +6,8 @@ It does not load Providers, install programs, inspect credentials or select serv
|
|
|
6
6
|
|
|
7
7
|
1. `resolveProjectRoot({ workspaceRoot?, cwd? })` uses the explicit Workspace, otherwise the nearest
|
|
8
8
|
`package.json` above cwd, otherwise cwd itself. Source and Run filenames do not choose the project.
|
|
9
|
+
After selecting the existing directory, it returns its real filesystem path so project and Source
|
|
10
|
+
paths use the same representation. Symlinks do not redirect the preceding parent-project search.
|
|
9
11
|
2. `findRuntimeProfile(projectRoot)` reads only that project's `.hypit/runtime` file and resolves its
|
|
10
12
|
path relative to the project. An entrypoint's explicit `--runtime` overrides this read for that invocation.
|
|
11
13
|
3. The selected Runtime implementation interprets the Profile, including `dataRoot`, Credentials,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stat } from "node:fs/promises";
|
|
1
|
+
import { realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
async function nearestProjectPackageRoot(start: string): Promise<string | undefined> {
|
|
@@ -28,8 +28,10 @@ export async function resolveProjectRoot(options: {
|
|
|
28
28
|
readonly cwd?: string;
|
|
29
29
|
} = {}): Promise<string> {
|
|
30
30
|
const start = resolve(options.workspaceRoot ?? options.cwd ?? process.cwd());
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const selected = options.workspaceRoot !== undefined ? start : await nearestProjectPackageRoot(start) ?? start;
|
|
32
|
+
// Select through the caller's directory first; following a link before discovery
|
|
33
|
+
// could choose a different parent project. Source readers also return real paths.
|
|
34
|
+
return await realpath(selected);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/** Project package discovery cannot escape an already resolved project. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Trusted local Provider for the `@hypit/render-hyperframes#render-visual` capability. It stages the
|
|
4
4
|
Resource dependencies declared by a `HyperframesDocument`, renders a silent MP4 with the
|
|
5
|
-
HyperFrames engine, probes the bytes, and returns a verified `RenderedVisual`. Before
|
|
5
|
+
HyperFrames engine, probes the bytes, and returns a verified `RenderedVisual`. Before capturing a typed
|
|
6
6
|
Surface it decodes the exact bytes and checks declared dimensions, still/frame timing, SDR/sRGB and
|
|
7
7
|
opaque/straight-alpha facts. These checks validate the typed rendering input; they do not create
|
|
8
8
|
content identity or hidden output metadata.
|
|
@@ -64,12 +64,67 @@ GPU, or `auto` to let the engine decide. Capture uses screenshots and independen
|
|
|
64
64
|
the CLI's automatic worker and drawElement policies do not override the count. The opaque screenshot
|
|
65
65
|
adapter uses the engine's public session, video injector and page seek protocol plus Chrome CDP.
|
|
66
66
|
It waits for seek completion, dynamic images/fonts and the page compositor before capturing PNGs.
|
|
67
|
+
Image readiness includes CSS class and pseudo-element images, CSS masks and SVG images. A failed
|
|
68
|
+
image decode reports its URL instead of producing a successful frame with missing media. Failed
|
|
69
|
+
declared fonts also fail capture instead of silently leaving fallback glyphs in the output.
|
|
67
70
|
The pinned engine couples its PNG session setup to transparent export, so this adapter initializes
|
|
68
71
|
an opaque session and chooses PNG separately at capture. It does not patch engine methods or files.
|
|
69
72
|
|
|
70
|
-
The Runtime Adapter
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
The Runtime Adapter declares one managed browser program. Prepare it explicitly:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
hypit programs up --runtime ./hypit.runtime.json --endpoint hyperframes.local
|
|
77
|
+
hypit doctor --runtime ./hypit.runtime.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use the instance name from the Profile. `runtime up` also prepares it and starts the Runtime Worker.
|
|
81
|
+
The Provider's `package.json` declares its recommended Chrome Headless Shell version in
|
|
82
|
+
`hypit.renderBrowser.version`, alongside the engine dependency used to test that release. The
|
|
83
|
+
installer consumes that declaration; it does not have a browser version constant or fetch a
|
|
84
|
+
"latest" channel. `config.browserVersion` explicitly selects another exact four-part version.
|
|
85
|
+
Changing the recommendation belongs to a Provider release and requires real rendering tests; a
|
|
86
|
+
Puppeteer recommendation alone is not a HyperFrames compatibility guarantee.
|
|
87
|
+
|
|
88
|
+
The default cache is `~/.cache/hyperframes/chrome`; `config.browserCacheDirectory` selects another
|
|
89
|
+
location. Only the selected version is used, regardless of other cached or system browsers.
|
|
90
|
+
Projects sharing this cache use the existing ManagedProgram preparation lock/logs under
|
|
91
|
+
`.hypit-render-program`. Readiness comes from the executable and its reported version, not a receipt.
|
|
92
|
+
Explicit preparation reuses a healthy installation or repairs only the selected managed version.
|
|
93
|
+
A failed download reports failure without selecting another version or browser.
|
|
94
|
+
|
|
95
|
+
`config.browserDownloadBaseUrl` selects a Chrome for Testing archive mirror for explicit preparation.
|
|
96
|
+
It is an absolute HTTP(S) base URL, without credentials, a query or a fragment. The browser library
|
|
97
|
+
appends the selected version, platform and archive name; the mirror must serve that same layout.
|
|
98
|
+
For example, a base of `https://mirror.example/chrome-for-testing` serves archives beneath
|
|
99
|
+
`<base>/<version>/<platform>/chrome-headless-shell-<platform>.zip`. Choose a source maintained by
|
|
100
|
+
the user or organization; this Provider does not keep a mirror list or choose one by region.
|
|
101
|
+
Omitting the setting uses the browser library's official Chrome for Testing source. A configured
|
|
102
|
+
source replaces it: failed transfers or invalid archives fail preparation without trying the official
|
|
103
|
+
source or another mirror. Normal HTTP redirects supplied by the selected server are handled by the
|
|
104
|
+
download library. Preparation displays the complete archive URL, version and destination before
|
|
105
|
+
download. An npm registry setting does not redirect this binary download.
|
|
106
|
+
|
|
107
|
+
The download source only determines where missing installation bytes come from. It does not change
|
|
108
|
+
the executable selection, and changing it does not invalidate a healthy cached version. No source
|
|
109
|
+
receipt is stored. To install into an empty location, explicitly choose `browserCacheDirectory`.
|
|
110
|
+
|
|
111
|
+
`config.chromePath` selects a user-managed Chrome/Chromium executable. It cannot be combined with
|
|
112
|
+
`browserVersion` or `browserDownloadBaseUrl`; invalid combinations fail instead of assigning precedence. Relative paths resolve
|
|
113
|
+
from the Runtime Profile root. This mode never downloads or repairs a browser. Its version remains
|
|
114
|
+
under the user's control, including system-browser auto-updates. `HYPERFRAMES_BROWSER_PATH` and
|
|
115
|
+
`PRODUCER_HEADLESS_SHELL_PATH` do not select browsers in this Provider; configure `chromePath`.
|
|
116
|
+
On platforms without a supported managed download, explicitly select an installed browser.
|
|
117
|
+
|
|
118
|
+
`doctor` displays the selected path and its source, and only inspects it. Build preflight, rendering
|
|
119
|
+
and previews never install a browser. `programs up` / `runtime up` display the selected managed
|
|
120
|
+
version, installation location and download URL before running preparation. The probe runs `--version` and checks
|
|
121
|
+
FFmpeg/FFprobe; it does not promise GPU or page compatibility. Capture receives that same selected
|
|
122
|
+
path as the engine's `chromePath`, including its GPU probe. Active Workers keep their loaded package
|
|
123
|
+
recommendation; restart them explicitly after changing Profile or package dependencies.
|
|
124
|
+
|
|
125
|
+
The Provider's `hypit.dependencyInstallEnv` disables Puppeteer's browser download while preparing its
|
|
126
|
+
engine/producer npm dependencies. The repository `.puppeteerrc.cjs` does the same for checkout installs.
|
|
127
|
+
No browser postinstall allowlist is required. The former `hyperframesCliPath` option is removed.
|
|
73
128
|
|
|
74
129
|
The same executor is exported for callers with an already compiled document:
|
|
75
130
|
|
|
@@ -96,11 +151,31 @@ execution earlier. ResourceStore I/O and Surface probes receive the cancellation
|
|
|
96
151
|
ResourceStore must implement the port's cancellation behavior, including streaming reads and writes.
|
|
97
152
|
|
|
98
153
|
Browser launch, source extraction, capture and encoding run in one disposable child process per
|
|
99
|
-
render.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
154
|
+
render. After successful capture closes its resources, the child sends its completion message,
|
|
155
|
+
flushes that message and disconnects IPC so it can exit normally. The owner awaits exit and drains
|
|
156
|
+
diagnostics before returning. Normal completion does not enumerate or forcibly terminate processes.
|
|
157
|
+
On failure, cleanup may be incomplete: the child reports the error and keeps IPC open while the owner
|
|
158
|
+
discovers and terminates the remaining process tree, before it can become orphaned.
|
|
159
|
+
The capture child also installs synchronous exit cleanup before loading the engine. Early
|
|
160
|
+
`process.exit()`, uncaught exceptions, and catchable `SIGINT`/`SIGTERM` exits stop descendants
|
|
161
|
+
while their owner still exists, even if browser initialization never returned a session.
|
|
162
|
+
Successful resource closure removes this exit handler. No historical browser PID list is retained.
|
|
163
|
+
|
|
164
|
+
Uncatchable termination (`SIGKILL`, native crashes, or OS termination on Windows) cannot run that
|
|
165
|
+
cleanup. The owner reports the termination and cannot confirm descendant cleanup; it never searches
|
|
166
|
+
by an already-exited root PID. If an orphan keeps the output pipes open, the owner closes its pipe
|
|
167
|
+
ends after five seconds so the failed invocation can settle. Guaranteed cleanup after an OS hard
|
|
168
|
+
kill requires containment supplied by that deployment (for example a process job or container);
|
|
169
|
+
this local Node implementation does not provide that guarantee.
|
|
170
|
+
|
|
171
|
+
At cancellation the child receives a stop request and has up to five seconds to clean up. A child
|
|
172
|
+
that remains after cancellation or its completion message is forcibly terminated along with its discovered
|
|
173
|
+
process tree, including Chrome's separate process groups. Cleanup problems are reported through the
|
|
174
|
+
existing diagnostic callback; they do not discard a render already reported as completed. If process
|
|
175
|
+
enumeration fails, the owner still terminates the direct child but cannot confirm descendant cleanup.
|
|
176
|
+
This also covers engine calls that do not accept a signal. The deadline initiates shutdown; the call
|
|
177
|
+
may spend additional time closing resources. Completed Outputs in the Build remain available for a
|
|
178
|
+
new Run and Build.
|
|
104
179
|
|
|
105
180
|
Deployments may additionally set `initializationTimeoutMs` or `frameTimeoutMs` when they have a
|
|
106
181
|
measured stage deadline. Initialization here means initializing an already created browser session;
|
|
@@ -109,7 +184,10 @@ more of the shared render budget on a slow initialization or frame.
|
|
|
109
184
|
An explicit stage-timeout error names the worker and stage/frame, aborts sibling workers and awaits
|
|
110
185
|
cleanup. A completed worker closes its Chrome immediately.
|
|
111
186
|
|
|
112
|
-
One call stages the HTML and every declared asset once. Typed Surface
|
|
187
|
+
One call stages the HTML and every declared asset once. Typed Surface inspection reads the completed
|
|
188
|
+
staged file directly, without retaining its chunks, assembling another whole-file buffer, or writing
|
|
189
|
+
a second temporary copy. The caller keeps that file until inspection and capture have settled.
|
|
190
|
+
Typed Surface validation includes a complete
|
|
113
191
|
decoded-frame count, even for a short render interval. The renderer then finds source-frame windows
|
|
114
192
|
needed by that interval, merges overlapping windows, and extracts them one source/window at a time.
|
|
115
193
|
Decoded PNGs are shared by all workers in this call. Each worker initializes its own page, then takes
|
|
@@ -125,9 +203,13 @@ do not share staged files or decoded PNGs. Exact compiler sampling markers retai
|
|
|
125
203
|
fractional playback rates. There is no SVML rewrite, intermediate cut MP4 or repeat normalization.
|
|
126
204
|
Already compiled video documents need the current compiler's frame markers.
|
|
127
205
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
206
|
+
`ffmpegPath` selects both source decoding and final H.264 encoding; `ffprobePath` selects source,
|
|
207
|
+
output and typed Surface inspection. Bare commands resolve through PATH. The capture child passes
|
|
208
|
+
these selected executables to the engine's public binary overrides; inherited
|
|
209
|
+
`HYPERFRAMES_FFMPEG_PATH` / `HYPERFRAMES_FFPROBE_PATH` cannot select a different decoding toolchain.
|
|
210
|
+
`nodePath` selects the managed
|
|
211
|
+
browser installer's Node executable. Capture uses the current Node process's executable.
|
|
212
|
+
The requested frame range travels in the Model's Need; browser paths stay in the Provider.
|
|
131
213
|
|
|
132
214
|
```ts
|
|
133
215
|
import { createLocalHyperframesProvider } from "@hypit/provider-hyperframes-local";
|
|
@@ -8,7 +8,18 @@
|
|
|
8
8
|
".": "./src/index.ts"
|
|
9
9
|
},
|
|
10
10
|
"hypit": {
|
|
11
|
-
"activation": "./src/activation.ts"
|
|
11
|
+
"activation": "./src/activation.ts",
|
|
12
|
+
"renderBrowser": {
|
|
13
|
+
"version": "152.0.7928.2"
|
|
14
|
+
},
|
|
15
|
+
"dependencyInstallEnv": {
|
|
16
|
+
"@hyperframes/engine": {
|
|
17
|
+
"PUPPETEER_SKIP_DOWNLOAD": "true"
|
|
18
|
+
},
|
|
19
|
+
"@hyperframes/producer": {
|
|
20
|
+
"PUPPETEER_SKIP_DOWNLOAD": "true"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
12
23
|
},
|
|
13
24
|
"dependencies": {
|
|
14
25
|
"@hyperframes/engine": "0.7.101",
|
|
@@ -17,13 +28,12 @@
|
|
|
17
28
|
"@hypit/hyperframes": "workspace:*",
|
|
18
29
|
"@hypit/media": "workspace:*",
|
|
19
30
|
"@hypit/media-execution": "workspace:*",
|
|
20
|
-
"@hypit/package-loader-node": "workspace:*",
|
|
21
31
|
"@hypit/protocol": "workspace:*",
|
|
22
32
|
"@hypit/render-hyperframes": "workspace:*",
|
|
23
33
|
"@hypit/runtime": "workspace:*",
|
|
24
34
|
"@hypit/runtime-host-node": "workspace:*",
|
|
25
35
|
"@hypit/runtime-kit": "workspace:*",
|
|
26
|
-
"
|
|
36
|
+
"@puppeteer/browsers": "3.2.2",
|
|
27
37
|
"tsx": "4.21.0"
|
|
28
38
|
},
|
|
29
39
|
"devDependencies": {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import {
|
|
2
3
|
createRuntimeEndpointAdapterFacet,
|
|
3
4
|
runtimeConfigExact,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
} from "@hypit/runtime-host-node";
|
|
12
13
|
|
|
13
14
|
import { createLocalHyperframesProvider } from "./provider.js";
|
|
14
|
-
import {
|
|
15
|
+
import { configuredBrowserPath, browserExecutablePath, selectedBrowserVersion } from "./browser.js";
|
|
15
16
|
import type { HyperframesBrowserGpu, HyperframesQuality, HyperframesWorkers } from "./provider.js";
|
|
16
17
|
import { localHyperframesBrowserProgram } from "./program.js";
|
|
17
18
|
|
|
@@ -21,11 +22,10 @@ const localHyperframesRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
21
22
|
if (context.pool === undefined) throw new Error("local HyperFrames Provider Pool is required");
|
|
22
23
|
const config = runtimeConfigObject(context.config, "local HyperFrames");
|
|
23
24
|
runtimeConfigExact(config, [
|
|
24
|
-
"nodePath", "
|
|
25
|
+
"nodePath", "chromePath", "browserVersion", "browserCacheDirectory", "browserDownloadBaseUrl", "ffprobePath", "ffmpegPath", "workers", "maxWorkers", "quality", "browserGpu",
|
|
25
26
|
"defaultConcurrency", "browserCapacity", "initializationTimeoutMs", "frameTimeoutMs", "processTimeoutMs", "maxProcessOutputBytes", "maxRenderedBytes",
|
|
26
27
|
], "local HyperFrames");
|
|
27
28
|
runtimeConfigString(config.nodePath, "HyperFrames nodePath");
|
|
28
|
-
runtimeConfigString(config.hyperframesCliPath, "HyperFrames hyperframesCliPath");
|
|
29
29
|
runtimeConfigString(config.ffprobePath, "HyperFrames ffprobePath");
|
|
30
30
|
const workers = config.workers;
|
|
31
31
|
if (workers !== undefined && workers !== "auto") {
|
|
@@ -40,10 +40,21 @@ const localHyperframesRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
40
40
|
throw new Error("HyperFrames browserGpu is invalid");
|
|
41
41
|
}
|
|
42
42
|
const configuredNode = runtimeConfigString(config.nodePath, "HyperFrames nodePath");
|
|
43
|
-
const
|
|
43
|
+
const configuredChrome = runtimeConfigString(config.chromePath, "HyperFrames chromePath");
|
|
44
|
+
const browserVersion = runtimeConfigString(config.browserVersion, "HyperFrames browserVersion");
|
|
45
|
+
const browserDownloadBaseUrl = runtimeConfigString(config.browserDownloadBaseUrl, "HyperFrames browserDownloadBaseUrl");
|
|
46
|
+
const configuredCache = runtimeConfigString(config.browserCacheDirectory, "HyperFrames browserCacheDirectory");
|
|
47
|
+
const chromePath = configuredBrowserPath({ ...(configuredChrome === undefined ? {} : {
|
|
48
|
+
chromePath: resolve(context.dataRoot, configuredChrome),
|
|
49
|
+
}) });
|
|
50
|
+
const browser = {
|
|
51
|
+
...(browserVersion === undefined ? {} : { browserVersion }),
|
|
52
|
+
...(browserDownloadBaseUrl === undefined ? {} : { browserDownloadBaseUrl }),
|
|
53
|
+
...(chromePath === undefined ? {} : { chromePath }),
|
|
54
|
+
...(configuredCache === undefined ? {} : { browserCacheDirectory: resolve(context.dataRoot, configuredCache) }),
|
|
55
|
+
};
|
|
44
56
|
const configuredFfprobe = runtimeConfigString(config.ffprobePath, "HyperFrames ffprobePath");
|
|
45
57
|
const nodePath = resolveRuntimeExecutable(context.dataRoot, configuredNode ?? process.execPath);
|
|
46
|
-
const hyperframesCliPath = () => resolveRuntimeExecutable(context.dataRoot, configuredCli ?? defaultHyperframesCliPath());
|
|
47
58
|
const configuredFfmpeg = runtimeConfigString(config.ffmpegPath, "HyperFrames ffmpegPath");
|
|
48
59
|
const ffmpegPath = resolveRuntimeExecutable(context.dataRoot, configuredFfmpeg ?? "ffmpeg");
|
|
49
60
|
const ffprobePath = resolveRuntimeExecutable(context.dataRoot, configuredFfprobe ?? "ffprobe");
|
|
@@ -60,6 +71,7 @@ const localHyperframesRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
60
71
|
instance: context.instance,
|
|
61
72
|
pool: context.pool,
|
|
62
73
|
nodePath,
|
|
74
|
+
...browser,
|
|
63
75
|
ffprobePath,
|
|
64
76
|
ffmpegPath,
|
|
65
77
|
...(workers === undefined ? {} : { workers: workers as HyperframesWorkers }),
|
|
@@ -77,11 +89,13 @@ const localHyperframesRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
77
89
|
program: localHyperframesBrowserProgram({
|
|
78
90
|
id: context.instance,
|
|
79
91
|
nodePath,
|
|
80
|
-
|
|
92
|
+
...browser,
|
|
81
93
|
ffprobePath,
|
|
82
94
|
ffmpegPath,
|
|
83
95
|
}),
|
|
84
96
|
diagnose: async () => [
|
|
97
|
+
{ severity: "info", code: "HYPERFRAMES_BROWSER_SELECTION", subject: context.instance,
|
|
98
|
+
message: `${chromePath === undefined ? `Managed Chrome Headless Shell ${selectedBrowserVersion(browser)} (${browserVersion === undefined ? "Provider recommendation" : "Profile version"})` : "Profile browser"}: ${browserExecutablePath(browser)}` },
|
|
85
99
|
...await diagnoseRuntimeExecutable({ root: context.dataRoot, configured: configuredFfmpeg, fallback: "ffmpeg", subject: "FFmpeg" }),
|
|
86
100
|
...await diagnoseRuntimeExecutable({
|
|
87
101
|
root: context.dataRoot,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { installRenderBrowser } from "./browser.js";
|
|
2
|
+
|
|
3
|
+
const [cacheDir, version, baseUrl] = process.argv.slice(2);
|
|
4
|
+
if (process.argv.length < 4 || process.argv.length > 5 || !cacheDir || !version) throw new Error("Expected the render browser cache directory, exact version and optional archive base URL");
|
|
5
|
+
await installRenderBrowser(cacheDir, version, baseUrl);
|