@hypit/hypit 0.1.13 → 0.2.0
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 +37 -3
- package/bin/hypit.mjs +0 -2
- package/dist/public/browser-capture.d.ts +9 -3
- package/dist/public/caption.d.ts +2 -0
- package/dist/public/narrative.d.ts +2 -0
- package/dist/public/runtime-kit.d.ts +2 -2
- package/dist/public/speech.d.ts +2 -0
- package/examples/semantic-composition/packages/responsive-explainer/README.md +1 -1
- package/package.json +2 -1
- package/packages/browser-capture/README.md +9 -2
- package/packages/browser-capture/package.json +5 -0
- package/packages/browser-capture/src/browser.ts +22 -8
- package/packages/browser-capture/src/index.ts +8 -2
- package/packages/caption/README.md +1 -1
- package/packages/caption/src/display.ts +1 -0
- package/packages/caption-fine/README.md +19 -5
- package/packages/caption-fine/package.json +1 -0
- package/packages/caption-fine/src/joined-box.ts +114 -0
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +99 -92
- package/packages/caption-fine/src/spacing.ts +8 -61
- package/packages/caption-fine-studio/src/index.ts +2 -4
- package/packages/cli/README.md +3 -2
- package/packages/cli/package.json +1 -0
- package/packages/cli/src/arguments.ts +2 -2
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +21 -9
- package/packages/cli/src/machine-view.ts +1 -1
- package/packages/cli/src/main.ts +2 -1
- package/packages/cli/src/oauth.ts +50 -7
- package/packages/cli/src/output.ts +1 -0
- 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/credential-store-os/src/store.ts +5 -1
- 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/narrative/README.md +3 -1
- package/packages/narrative/src/schema.ts +1 -0
- package/packages/narrative/src/types.ts +2 -0
- 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/provider-hyperframes-local/README.md +81 -8
- 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 +29 -52
- package/packages/provider-hyperframes-local/src/capture-worker.ts +2 -0
- 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-whisperx-local/README.md +26 -10
- package/packages/provider-whisperx-local/src/activation.ts +10 -1
- package/packages/provider-whisperx-local/src/program.ts +8 -4
- package/packages/runtime-host-node/README.md +5 -0
- package/packages/runtime-host-node/src/index.ts +8 -2
- package/packages/runtime-host-node/src/packages.ts +13 -7
- package/packages/runtime-kit/README.md +6 -2
- package/packages/runtime-kit/src/index.ts +2 -2
- package/packages/runtime-local/README.md +16 -1
- package/packages/runtime-local/src/config.ts +1 -1
- package/packages/runtime-local/src/credentials.ts +10 -5
- package/packages/runtime-local/src/host.ts +2 -0
- package/packages/runtime-local/src/index.ts +1 -0
- package/packages/runtime-local/src/programs.ts +53 -17
- package/packages/script/README.md +81 -34
- package/packages/script/bin/migrate-0.2.mjs +90 -0
- package/packages/script/src/edit.ts +28 -60
- package/packages/script/src/lexical.ts +59 -130
- package/packages/script/src/manifest.ts +11 -11
- package/packages/script/src/narrative.ts +4 -2
- package/packages/script/src/parser.ts +140 -193
- package/packages/script/src/types.ts +2 -3
- package/packages/studio/src/preview/runtime-shim.ts +17 -3
- package/packages/studio/src/ui/code.ts +1 -1
- package/packages/studio/src/ui/main.ts +2 -2
- package/packages/studio/src/ui/markers.ts +3 -3
- package/packages/studio/src/ui/selection.ts +1 -1
- package/packages/studio/src/ui/syntax.ts +42 -21
- package/packages/temporal-markup/README.md +1 -1
- package/packages/video-cli/README.md +14 -6
- package/packages/video-cli/package.json +1 -0
- package/packages/video-cli/src/capture.ts +17 -2
- package/packages/video-cli/src/creation.ts +4 -6
- package/packages/video-cli/src/distribution.ts +3 -10
- package/packages/video-cli/src/media.ts +10 -3
- package/packages/video-cli/src/version.ts +1 -1
- package/packages/whisperx/README.md +56 -3
- package/packages/whisperx/src/component.ts +2 -5
- package/packages/whisperx/src/evidence.ts +4 -3
- package/packages/whisperx/src/index.ts +1 -0
- package/packages/whisperx/src/manifest.ts +2 -3
- package/packages/whisperx/src/surface.ts +3 -6
- package/packages/whisperx/src/types.ts +9 -1
- package/packages/yt-dlp/README.md +10 -3
- package/packages/yt-dlp/package.json +5 -0
- package/packages/yt-dlp/src/download.ts +11 -29
- package/packages/yt-dlp/src/environment.ts +36 -0
- package/packages/yt-dlp/src/index.ts +1 -0
- package/services/whisperx/README.md +45 -12
- package/services/whisperx/src/hypit_whisperx_service/application.py +3 -0
- package/services/whisperx/src/hypit_whisperx_service/check.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/config.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/engine.py +21 -5
- package/services/whisperx/src/hypit_whisperx_service/models.py +131 -0
- package/services/whisperx/src/hypit_whisperx_service/prepare.py +21 -3
- package/services/whisperx/src/hypit_whisperx_service/resources.py +19 -3
- package/services/yt-dlp/README.md +14 -15
- package/services/yt-dlp/pyproject.toml +1 -1
- package/services/yt-dlp/uv.lock +513 -2
|
@@ -8,19 +8,22 @@ import type {
|
|
|
8
8
|
VisualColorPaint,
|
|
9
9
|
VisualElement,
|
|
10
10
|
VisualKeyframe,
|
|
11
|
+
VisualProgramElement,
|
|
11
12
|
VisualStyleDeclaration,
|
|
12
13
|
VisualTextElement,
|
|
13
14
|
VisualTextPaintLayer,
|
|
14
15
|
VisualTrack,
|
|
15
16
|
} from "@hypit/composition";
|
|
16
|
-
import type { CaptionAlignmentUnit, CaptionDocument } from "@hypit/narrative";
|
|
17
|
+
import type { CaptionAlignmentUnit, CaptionDocument, CaptionDisplayWord } from "@hypit/narrative";
|
|
17
18
|
import { assertProgramSpaceIdentity, programSpaceFrameCount } from "@hypit/program-space";
|
|
18
19
|
import { assertSpatialRegionTimeline } from "@hypit/spatial";
|
|
19
20
|
import type { CanvasSpace, SpatialFrame, SpatialRegionTimeline } from "@hypit/spatial";
|
|
20
21
|
|
|
21
22
|
import { assertFineCaptionParameters, FINE_CAPTION_FAMILY } from "./style.js";
|
|
22
23
|
import { assertFineCaptionSchedule } from "./schedule.js";
|
|
23
|
-
import {
|
|
24
|
+
import { uniformGap, wordGaps } from "./spacing.js";
|
|
25
|
+
import { browserProgram } from "@hypit/hyperframes";
|
|
26
|
+
import { joinedBoxSetup } from "./joined-box.js";
|
|
24
27
|
import type {
|
|
25
28
|
FineCaptionActiveUnderline,
|
|
26
29
|
FineCaptionGlyphPaint,
|
|
@@ -621,20 +624,17 @@ function trackedPlacementAnimation(
|
|
|
621
624
|
return { keyframes };
|
|
622
625
|
}
|
|
623
626
|
|
|
624
|
-
function
|
|
625
|
-
|
|
626
|
-
maxWordsPerLine: number,
|
|
627
|
-
): number {
|
|
628
|
-
let rows = atoms.length === 0 ? 0 : 1;
|
|
627
|
+
function structuralRowStarts(atoms: readonly CaptionAlignmentUnit[], maxWordsPerLine?: number): ReadonlySet<number> {
|
|
628
|
+
const starts = new Set<number>();
|
|
629
629
|
let wordsOnRow = 0;
|
|
630
|
-
for (const atom of atoms) {
|
|
631
|
-
if (wordsOnRow > 0 && wordsOnRow + atom.wordIds.length > maxWordsPerLine) {
|
|
632
|
-
|
|
630
|
+
for (const [index, atom] of atoms.entries()) {
|
|
631
|
+
if (index === 0 || (maxWordsPerLine !== undefined && wordsOnRow > 0 && wordsOnRow + atom.wordIds.length > maxWordsPerLine)) {
|
|
632
|
+
starts.add(index);
|
|
633
633
|
wordsOnRow = 0;
|
|
634
634
|
}
|
|
635
635
|
wordsOnRow += atom.wordIds.length;
|
|
636
636
|
}
|
|
637
|
-
return
|
|
637
|
+
return starts;
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
/**
|
|
@@ -665,7 +665,7 @@ function cueElements(
|
|
|
665
665
|
atoms: readonly CaptionAlignmentUnit[],
|
|
666
666
|
atomFrames: ReadonlyMap<string, { readonly start: number; readonly end: number }>,
|
|
667
667
|
parameters: FineCaptionParameters,
|
|
668
|
-
wordText: ReadonlyMap<string,
|
|
668
|
+
wordText: ReadonlyMap<string, CaptionDisplayWord>,
|
|
669
669
|
durationFrames: number,
|
|
670
670
|
styleId: string,
|
|
671
671
|
trackedPlacement?: {
|
|
@@ -673,7 +673,7 @@ function cueElements(
|
|
|
673
673
|
readonly canvas: CanvasSpace;
|
|
674
674
|
},
|
|
675
675
|
): VisualElement[] {
|
|
676
|
-
type UnorderedVisualElement = Omit<VisualBoxElement, "order"> | Omit<VisualTextElement, "order">;
|
|
676
|
+
type UnorderedVisualElement = Omit<VisualBoxElement, "order"> | Omit<VisualTextElement, "order"> | Omit<VisualProgramElement, "order">;
|
|
677
677
|
const elements: VisualElement[] = [];
|
|
678
678
|
let order = 0;
|
|
679
679
|
const push = (element: UnorderedVisualElement): void => {
|
|
@@ -694,21 +694,29 @@ function cueElements(
|
|
|
694
694
|
const cueLoop = parameters.motion.loopTarget === "cue" ? loopAnimation(parameters, durationFrames) : undefined;
|
|
695
695
|
const activationFrames = exclusiveActivationFrames(atoms, atomFrames);
|
|
696
696
|
const gapPx = `${compactNumber(parameters.layout.wordGapPx)}px`;
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const cueGap = uniformGap(atomGaps);
|
|
697
|
+
const atomWords = atoms.map(atom => atom.wordIds.map(wordId => {
|
|
698
|
+
const word = wordText.get(wordId);
|
|
699
|
+
if (!word) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
700
|
+
return word;
|
|
701
|
+
}));
|
|
702
|
+
const atomSurfaces = atomWords.map(words => words.map(word => word.text));
|
|
703
|
+
const atomGaps = atomWords.map((words, index) => index > 0 && words[0]!.separatorBefore === " ");
|
|
705
704
|
// The Visual IR carries physical margins, so the leading edge follows the Cue's own direction.
|
|
706
705
|
const marginStart = parameters.layout.direction === "rtl" ? "margin-right" : "margin-left";
|
|
707
706
|
const spacedStyle = (spaced: boolean): readonly VisualStyleDeclaration[] =>
|
|
708
707
|
spaced ? [{ name: marginStart, value: gapPx }] : [];
|
|
708
|
+
const atomFlow = (words: readonly CaptionDisplayWord[]): VisualStyleDeclaration[] => [
|
|
709
|
+
...(uniformGap(wordGaps(words)) === true ? [{ name: "column-gap", value: gapPx }] : []),
|
|
710
|
+
{ name: "display", value: "inline-flex" },
|
|
711
|
+
{ name: "min-width", value: "0" },
|
|
712
|
+
{ name: "overflow-wrap", value: "anywhere" },
|
|
713
|
+
{ name: "white-space", value: "normal" },
|
|
714
|
+
{ name: "word-break", value: parameters.layout.wrap === "grapheme" ? "break-all" : "normal" },
|
|
715
|
+
];
|
|
709
716
|
const fonts = parameters.typography.exactFonts;
|
|
717
|
+
const rowStarts = structuralRowStarts(atoms, parameters.layout.maxWordsPerLine);
|
|
710
718
|
if (parameters.layout.maxLines !== undefined) {
|
|
711
|
-
const rows =
|
|
719
|
+
const rows = rowStarts.size;
|
|
712
720
|
if (rows > parameters.layout.maxLines) {
|
|
713
721
|
throw new Error(`Fine Caption Style ${styleId} constructs ${rows} rows for a Cue whose maximum is ${parameters.layout.maxLines}`);
|
|
714
722
|
}
|
|
@@ -766,10 +774,10 @@ function cueElements(
|
|
|
766
774
|
{ name: "border-radius", value: `${compactNumber(parameters.cueBox.radiusPx)}px` },
|
|
767
775
|
{ name: "border-style", value: "solid" },
|
|
768
776
|
{ name: "border-width", value: `${compactNumber(parameters.cueBox.borderWidthPx)}px` },
|
|
769
|
-
...(cueGap === true ? [{ name: "column-gap", value: gapPx }] as const : []),
|
|
770
777
|
{ name: "direction", value: parameters.layout.direction },
|
|
771
|
-
{ name: "display", value: "
|
|
772
|
-
{ name: "
|
|
778
|
+
{ name: "display", value: "block" },
|
|
779
|
+
{ name: "font-size", value: "0px" },
|
|
780
|
+
{ name: "line-height", value: "0" },
|
|
773
781
|
{ name: "justify-content", value: parameters.layout.textAlign === "left" ? "flex-start"
|
|
774
782
|
: parameters.layout.textAlign === "right" ? "flex-end" : "center" },
|
|
775
783
|
{ name: "max-width", value: "100%" },
|
|
@@ -781,64 +789,73 @@ function cueElements(
|
|
|
781
789
|
],
|
|
782
790
|
});
|
|
783
791
|
|
|
792
|
+
const pushSeparator = (id: string, parent: string): void => push({
|
|
793
|
+
id, parent, kind: "text", text: " ", fonts,
|
|
794
|
+
style: [
|
|
795
|
+
{ name: "display", value: "inline" },
|
|
796
|
+
{ name: "font-size", value: "0px" },
|
|
797
|
+
{ name: "letter-spacing", value: gapPx },
|
|
798
|
+
{ name: "line-height", value: "0" },
|
|
799
|
+
{ name: "white-space", value: "normal" },
|
|
800
|
+
],
|
|
801
|
+
});
|
|
802
|
+
|
|
784
803
|
if (parameters.activeBox.mode === "trail" && parameters.activeBox.continuity === "joined") {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
804
|
+
// Fine owns this drawing program. The renderer only executes its existing browser-program
|
|
805
|
+
// format; it learns nothing about captions, activation units or their layout rules.
|
|
806
|
+
push({
|
|
807
|
+
id: "joined-boxes", parent: "cue", kind: "program",
|
|
808
|
+
style: [
|
|
809
|
+
{ name: "position", value: "absolute" }, { name: "inset", value: "0" },
|
|
810
|
+
{ name: "padding", value: `${compactNumber(parameters.cueBox.paddingYPx)}px ${compactNumber(parameters.cueBox.paddingXPx)}px` },
|
|
811
|
+
],
|
|
812
|
+
program: browserProgram({
|
|
813
|
+
html: "{{joined-layout}}" + atoms.map((_, index) => `{{joined-box-${index + 1}}}`).join(""),
|
|
814
|
+
setup: joinedBoxSetup,
|
|
815
|
+
css: ":scope { pointer-events: none; }",
|
|
816
|
+
data: {
|
|
817
|
+
wordCounts: atomWords.map(words => words.length),
|
|
818
|
+
background: parameters.activeBox.background, borderColor: parameters.activeBox.borderColor,
|
|
819
|
+
borderWidth: parameters.activeBox.borderWidthPx, radius: parameters.activeBox.radiusPx,
|
|
820
|
+
paddingX: parameters.activeBox.paddingXPx, paddingY: parameters.activeBox.paddingYPx,
|
|
821
|
+
},
|
|
822
|
+
}),
|
|
823
|
+
});
|
|
824
|
+
push({ id: "joined-layout", parent: "joined-boxes", kind: "box",
|
|
825
|
+
style: [{ name: "visibility", value: "hidden" }],
|
|
826
|
+
attributes: [{ name: "data-fine-box-layout", value: "" }],
|
|
827
|
+
});
|
|
828
|
+
// The complete stationary Cue supplies layout, including the inactive suffix. Its actual
|
|
829
|
+
// word fragments determine the background; decoration contributes no width to text flow.
|
|
830
|
+
for (const [index, words] of atomWords.entries()) {
|
|
831
|
+
if (index > 0 && rowStarts.has(index)) push({ id: `joined-break-${index}`, parent: "joined-layout", kind: "box",
|
|
832
|
+
style: [{ name: "display", value: "block" }, { name: "height", value: "0" }],
|
|
833
|
+
});
|
|
834
|
+
if (!rowStarts.has(index) && words[0]!.separatorBefore === " ") pushSeparator(`joined-gap-${index}`, "joined-layout");
|
|
835
|
+
const groupId = `joined-group-${index}`;
|
|
836
|
+
push({ id: groupId, parent: "joined-layout", kind: "box", style: [...atomFlow(words), { name: "max-width", value: "100%" }] });
|
|
837
|
+
const gaps = wordGaps(words);
|
|
838
|
+
for (const [wordIndex, word] of words.entries()) {
|
|
839
|
+
push({ id: `${groupId}-word-${wordIndex}`, parent: groupId, kind: "text", text: word.text, fonts,
|
|
840
|
+
style: [...transparentGlyphStyle(parameters),
|
|
841
|
+
...(uniformGap(gaps) === undefined ? spacedStyle(gaps[wordIndex] ?? false) : [])],
|
|
842
|
+
attributes: [{ name: "data-fine-box-word", value: "" }],
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
for (const [index, atom] of atoms.entries()) {
|
|
847
|
+
const timing = atomFrames.get(atom.id)!;
|
|
848
|
+
const next = atoms[index + 1];
|
|
789
849
|
const nextStart = next === undefined ? durationFrames : atomFrames.get(next.id)?.start;
|
|
790
850
|
if (nextStart === undefined) throw new Error("Fine Caption is missing timing for the next Atom");
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const prefixText = joinSurfaces(
|
|
794
|
-
atoms.slice(0, prefixIndex + 1).map((prefixAtom, index) => {
|
|
795
|
-
for (const wordId of prefixAtom.wordIds) {
|
|
796
|
-
if (!wordText.has(wordId)) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
797
|
-
}
|
|
798
|
-
return joinSurfaces(atomSurfaces[index] ?? [], "\u00A0");
|
|
799
|
-
}),
|
|
800
|
-
" ",
|
|
801
|
-
);
|
|
802
|
-
const layerId = `joined-box-${prefixIndex + 1}`;
|
|
803
|
-
push({
|
|
804
|
-
id: layerId,
|
|
805
|
-
parent: "cue",
|
|
806
|
-
kind: "box",
|
|
807
|
-
style: [
|
|
808
|
-
{ name: "box-sizing", value: "border-box" },
|
|
809
|
-
{ name: "direction", value: parameters.layout.direction },
|
|
810
|
-
{ name: "inset", value: "0" },
|
|
811
|
-
{ name: "padding", value: `${compactNumber(parameters.cueBox.paddingYPx)}px ${compactNumber(parameters.cueBox.paddingXPx)}px` },
|
|
812
|
-
{ name: "position", value: "absolute" },
|
|
813
|
-
{ name: "text-align", value: parameters.layout.textAlign },
|
|
814
|
-
],
|
|
851
|
+
push({ id: `joined-box-${index + 1}`, parent: "joined-boxes", kind: "box",
|
|
852
|
+
style: [{ name: "position", value: "absolute" }, { name: "inset", value: "0" }],
|
|
815
853
|
animation: activeBoxAnimation(parameters, timing.start, nextStart, durationFrames, "current"),
|
|
816
854
|
attributes: [{ name: "data-caption-active-box", value: "joined" }],
|
|
817
855
|
});
|
|
818
|
-
push({
|
|
819
|
-
id: `${layerId}-text`,
|
|
820
|
-
parent: layerId,
|
|
821
|
-
kind: "text",
|
|
822
|
-
text: prefixText,
|
|
823
|
-
style: [
|
|
824
|
-
...transparentGlyphStyle(parameters).filter(({ name }) => name !== "white-space"),
|
|
825
|
-
{ name: "-webkit-box-decoration-break", value: "clone" },
|
|
826
|
-
{ name: "background", value: parameters.activeBox.background },
|
|
827
|
-
{ name: "border-color", value: parameters.activeBox.borderColor },
|
|
828
|
-
{ name: "border-radius", value: `${compactNumber(parameters.activeBox.radiusPx)}px` },
|
|
829
|
-
{ name: "border-style", value: "solid" },
|
|
830
|
-
{ name: "border-width", value: `${compactNumber(parameters.activeBox.borderWidthPx)}px` },
|
|
831
|
-
{ name: "box-decoration-break", value: "clone" },
|
|
832
|
-
{ name: "display", value: "inline" },
|
|
833
|
-
{ name: "padding", value: `${compactNumber(parameters.activeBox.paddingYPx)}px ${compactNumber(parameters.activeBox.paddingXPx)}px` },
|
|
834
|
-
{ name: "white-space", value: "normal" },
|
|
835
|
-
],
|
|
836
|
-
fonts,
|
|
837
|
-
});
|
|
838
856
|
}
|
|
839
857
|
}
|
|
840
858
|
|
|
841
|
-
let wordsOnLine = 0;
|
|
842
859
|
for (const [atomIndex, atom] of atoms.entries()) {
|
|
843
860
|
const timing = atomFrames.get(atom.id);
|
|
844
861
|
if (timing === undefined) throw new Error(`Fine Caption is missing timing for Atom ${atom.id}`);
|
|
@@ -848,22 +865,20 @@ function cueElements(
|
|
|
848
865
|
const surfaces = atomSurfaces[atomIndex] ?? [];
|
|
849
866
|
// The base glyphs and the activated copy stacked over them are laid out from this one list, so
|
|
850
867
|
// the karaoke wipe keeps sitting on the letterforms it reveals.
|
|
851
|
-
const gaps = wordGaps(
|
|
868
|
+
const gaps = wordGaps(atomWords[atomIndex]!);
|
|
852
869
|
const wordGap = uniformGap(gaps);
|
|
853
870
|
const entryId = `${atomId}-entry`;
|
|
854
871
|
const loopId = `${atomId}-loop`;
|
|
855
872
|
const responseId = `${atomId}-response`;
|
|
856
|
-
if (
|
|
857
|
-
&& wordsOnLine > 0
|
|
858
|
-
&& wordsOnLine + atom.wordIds.length > parameters.layout.maxWordsPerLine) {
|
|
873
|
+
if (atomIndex > 0 && rowStarts.has(atomIndex)) {
|
|
859
874
|
push({
|
|
860
875
|
id: `line-break-${atomIndex}`,
|
|
861
876
|
parent: "cue",
|
|
862
877
|
kind: "box",
|
|
863
|
-
style: [{ name: "
|
|
878
|
+
style: [{ name: "display", value: "block" }, { name: "height", value: "0" }],
|
|
864
879
|
});
|
|
865
|
-
wordsOnLine = 0;
|
|
866
880
|
}
|
|
881
|
+
if (!rowStarts.has(atomIndex) && atomGaps[atomIndex]) pushSeparator(`gap-${atomIndex}`, "cue");
|
|
867
882
|
const entryAnimation = atomLifecycleAnimation(parameters, timing.start, timing.end, durationFrames);
|
|
868
883
|
const atomLoop = parameters.motion.loopTarget === "active-atom"
|
|
869
884
|
? loopAnimation(parameters, durationFrames, activationTiming) : undefined;
|
|
@@ -880,8 +895,6 @@ function cueElements(
|
|
|
880
895
|
style: [
|
|
881
896
|
{ name: "display", value: "inline-flex" },
|
|
882
897
|
{ name: "min-width", value: "0" },
|
|
883
|
-
// A row that wraps opens at its own edge, so an atom that starts one carries no margin.
|
|
884
|
-
...(cueGap === undefined && wordsOnLine > 0 ? spacedStyle(atomGaps[atomIndex] ?? false) : []),
|
|
885
898
|
{ name: "transform-origin", value: "center center" },
|
|
886
899
|
],
|
|
887
900
|
...(entryAnimation === undefined ? {} : { animation: entryAnimation }),
|
|
@@ -913,13 +926,8 @@ function cueElements(
|
|
|
913
926
|
parent: responseId,
|
|
914
927
|
kind: "box",
|
|
915
928
|
style: [
|
|
916
|
-
...(
|
|
917
|
-
{ name: "display", value: "inline-flex" },
|
|
918
|
-
{ name: "min-width", value: "0" },
|
|
919
|
-
{ name: "overflow-wrap", value: "anywhere" },
|
|
929
|
+
...atomFlow(atomWords[atomIndex]!),
|
|
920
930
|
{ name: "position", value: "relative" },
|
|
921
|
-
{ name: "white-space", value: "normal" },
|
|
922
|
-
{ name: "word-break", value: parameters.layout.wrap === "grapheme" ? "break-all" : "normal" },
|
|
923
931
|
],
|
|
924
932
|
attributes: [{ name: "data-caption-atom", value: atom.id }],
|
|
925
933
|
});
|
|
@@ -996,7 +1004,7 @@ function cueElements(
|
|
|
996
1004
|
}
|
|
997
1005
|
};
|
|
998
1006
|
for (const [wordIndex, wordId] of atom.wordIds.entries()) {
|
|
999
|
-
const text = wordText.get(wordId);
|
|
1007
|
+
const text = wordText.get(wordId)?.text;
|
|
1000
1008
|
if (text === undefined) throw new Error(`Fine Caption Atom references unknown word ${wordId}`);
|
|
1001
1009
|
pushText({
|
|
1002
1010
|
id: `${atomId}-base-${wordIndex + 1}`,
|
|
@@ -1037,7 +1045,7 @@ function cueElements(
|
|
|
1037
1045
|
attributes: [{ name: kind === "glyph" ? "data-caption-karaoke" : "data-caption-active-underline", value: mode }],
|
|
1038
1046
|
});
|
|
1039
1047
|
for (const [wordIndex, wordId] of atom.wordIds.entries()) {
|
|
1040
|
-
const text = wordText.get(wordId)
|
|
1048
|
+
const text = wordText.get(wordId)!.text;
|
|
1041
1049
|
pushText({
|
|
1042
1050
|
id: `${activeId}-${wordIndex + 1}`,
|
|
1043
1051
|
parent: activeId,
|
|
@@ -1060,7 +1068,6 @@ function cueElements(
|
|
|
1060
1068
|
if (parameters.activeUnderline.mode !== "off") {
|
|
1061
1069
|
addActivatedTextLayer("underline", parameters.activeUnderline.mode, "underline");
|
|
1062
1070
|
}
|
|
1063
|
-
wordsOnLine += atom.wordIds.length;
|
|
1064
1071
|
}
|
|
1065
1072
|
return elements;
|
|
1066
1073
|
}
|
|
@@ -1081,7 +1088,7 @@ export function renderFineCaption(
|
|
|
1081
1088
|
throw new Error("Fine Caption inputs belong to different Timelines or Narratives");
|
|
1082
1089
|
}
|
|
1083
1090
|
const styles = new Map(program.styles.map((style) => [style.id, style]));
|
|
1084
|
-
const wordText = new Map(document.words.map((word) => [word.id, word
|
|
1091
|
+
const wordText = new Map(document.words.map((word) => [word.id, word]));
|
|
1085
1092
|
const atomById = new Map(document.units.map((atom) => [atom.id, atom]));
|
|
1086
1093
|
for (const style of styles.values()) {
|
|
1087
1094
|
if (style.rendering === null) continue;
|
|
@@ -1,69 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
* Where a word gap belongs between two adjacent display surfaces.
|
|
3
|
-
*
|
|
4
|
-
* A Display Word is one lexical unit, and in Han, Hiragana and Katakana that unit is a single
|
|
5
|
-
* character. A gap applied to every boundary therefore sets Chinese and Japanese as though every
|
|
6
|
-
* character were a word, so each boundary decides its own gap from the two characters that meet
|
|
7
|
-
* across it.
|
|
8
|
-
*
|
|
9
|
-
* The classes follow pangu.js: a CJK character beside a half-width letter, digit, bracket or
|
|
10
|
-
* operator takes one space. The gap pangu inserts is an ordinary space, so a boundary either
|
|
11
|
-
* carries the Style's `word-gap` or carries nothing.
|
|
12
|
-
*/
|
|
1
|
+
import type { CaptionDisplayWord } from "@hypit/narrative";
|
|
13
2
|
|
|
14
|
-
|
|
15
|
-
const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
3
|
+
type Surface = Pick<CaptionDisplayWord, "text" | "separatorBefore">;
|
|
16
4
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* These glyphs are drawn inside a full-width em box with their own side bearing, so a gap beside
|
|
21
|
-
* one paints a second space over the one the font already carries. The ranges cover CJK
|
|
22
|
-
* punctuation, the full-width ASCII forms, the curly quotes and the ellipsis, and stop short of
|
|
23
|
-
* the full-width digits and letters at `0-9`, `A-Z` and `a-z`, which
|
|
24
|
-
* are ordinary word characters.
|
|
25
|
-
*/
|
|
26
|
-
const FULL_WIDTH_PUNCTUATION =
|
|
27
|
-
/[、-〃〈-】〔-〟!-/:-@[-`{-・‘’“”—…]/u;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Half-width brackets and quotation marks take their space on the outside of the pair, which falls
|
|
31
|
-
* out of reading the boundary characters: `displayWordSurfaces` attaches an opener to the surface
|
|
32
|
-
* that follows it and a closer to the surface before it, so `中文(注)中文` meets `(` on the right
|
|
33
|
-
* of one boundary and `)` on the left of the next, and both take a gap.
|
|
34
|
-
*
|
|
35
|
-
* A boundary with no CJK character on either side keeps the gap it has today. Latin spacing is not
|
|
36
|
-
* what this decides.
|
|
37
|
-
*/
|
|
38
|
-
export function wordGapBetween(previous: string, next: string): boolean {
|
|
39
|
-
const left = [...previous].at(-1);
|
|
40
|
-
const right = [...next].at(0);
|
|
41
|
-
if (left === undefined || right === undefined) return false;
|
|
42
|
-
if (FULL_WIDTH_PUNCTUATION.test(left) || FULL_WIDTH_PUNCTUATION.test(right)) return false;
|
|
43
|
-
return !(CJK.test(left) && CJK.test(right));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Every boundary in order. The first surface opens the run, so its entry is always `false`. */
|
|
47
|
-
export function wordGaps(surfaces: readonly string[]): readonly boolean[] {
|
|
48
|
-
return surfaces.map((surface, index) => {
|
|
49
|
-
const previous = surfaces[index - 1];
|
|
50
|
-
return previous === undefined ? false : wordGapBetween(previous, surface);
|
|
51
|
-
});
|
|
5
|
+
/** The first displayed word starts a line; all other gaps come from authored content. */
|
|
6
|
+
export function wordGaps(words: readonly Surface[]): readonly boolean[] {
|
|
7
|
+
return words.map((word, index) => index > 0 && word.separatorBefore === " ");
|
|
52
8
|
}
|
|
53
9
|
|
|
54
|
-
/**
|
|
55
|
-
* A run whose boundaries all agree carries one `column-gap` on its container. A mixed run cannot,
|
|
56
|
-
* because `column-gap` is uniform, and takes a margin per element instead.
|
|
57
|
-
*/
|
|
10
|
+
/** A uniform run can use column-gap; a mixed run requires individual separators. */
|
|
58
11
|
export function uniformGap(gaps: readonly boolean[]): boolean | undefined {
|
|
59
12
|
const boundaries = gaps.slice(1);
|
|
60
13
|
if (boundaries.length === 0) return false;
|
|
61
|
-
if (boundaries.every(
|
|
62
|
-
return boundaries.some(
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Join surfaces back into prose, spacing only the boundaries that carry a gap. */
|
|
66
|
-
export function joinSurfaces(surfaces: readonly string[], separator: string): string {
|
|
67
|
-
const gaps = wordGaps(surfaces);
|
|
68
|
-
return surfaces.map((surface, index) => (gaps[index] ? `${separator}${surface}` : surface)).join("");
|
|
14
|
+
if (boundaries.every(gap => gap)) return true;
|
|
15
|
+
return boundaries.some(gap => gap) ? undefined : false;
|
|
69
16
|
}
|
|
@@ -114,10 +114,8 @@ function cueText(document: CaptionDocument | undefined, unitIds: readonly string
|
|
|
114
114
|
const selected = new Set(unitIds);
|
|
115
115
|
return document.words
|
|
116
116
|
.filter((word) => selected.has(word.unitId))
|
|
117
|
-
.map((word) => word.text)
|
|
118
|
-
.join("
|
|
119
|
-
.replace(/([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])\s+(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])/gu, "$1")
|
|
120
|
-
.trim();
|
|
117
|
+
.map((word, index) => (index === 0 ? "" : word.separatorBefore) + word.text)
|
|
118
|
+
.join("");
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
export function projectCaptionContents(context: StudioTrackCompanionContext): readonly StudioEntityDraft[] {
|
package/packages/cli/README.md
CHANGED
|
@@ -153,13 +153,14 @@ paths. A deleted Source or directory remains queryable: only its existing ancest
|
|
|
153
153
|
the missing path suffix is retained. This is local argument handling, with no saved alias inventory.
|
|
154
154
|
CLI, Studio and creation tools call that same package; the CLI is not another environment owner.
|
|
155
155
|
|
|
156
|
-
`doctor`, `programs up|status|down`, and `runtime up` accept repeated `--endpoint <instance>` values.
|
|
156
|
+
`doctor`, `programs prepare|up|status|down`, and `runtime up` accept repeated `--endpoint <instance>` values.
|
|
157
157
|
The same Endpoint scope reaches package preparation and Program operations. Omission means the whole
|
|
158
158
|
Profile. Build preflight instead uses the Endpoints resolved for that Build's concrete requests.
|
|
159
159
|
An unrelated offered capability does not add another credential or Program requirement.
|
|
160
160
|
Program rows show the configured Endpoint selector alongside an internal Program ID when they differ.
|
|
161
161
|
The `programs` JSON `ok` field reports whether this command succeeded; `ready` reports service
|
|
162
|
-
readiness
|
|
162
|
+
readiness for `up`/`status`, and preparation readiness for `prepare`. The latter does not start the
|
|
163
|
+
service. Successful stopping can therefore report `ok: true` with `ready: false`. A declined stop remains visible even
|
|
163
164
|
while the service is still preparing and cannot yet answer its health probe.
|
|
164
165
|
|
|
165
166
|
Upstream package installation reports an `install.log` path at preparation time. Exact package
|
|
@@ -349,8 +349,8 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
|
|
|
349
349
|
|
|
350
350
|
function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
|
|
351
351
|
const [action, ...values] = tail;
|
|
352
|
-
if (action !== "up" && action !== "down" && action !== "status") {
|
|
353
|
-
throw new Error("programs takes up, down or status");
|
|
352
|
+
if (action !== "prepare" && action !== "up" && action !== "down" && action !== "status") {
|
|
353
|
+
throw new Error("programs takes prepare, up, down or status");
|
|
354
354
|
}
|
|
355
355
|
const [profile, rest] = optionalPositional(values);
|
|
356
356
|
const options = commandOptions(`programs ${action}`, rest,
|
|
@@ -146,7 +146,7 @@ export type ProgramsCommand =
|
|
|
146
146
|
| (CommandBase & RuntimeOption & {
|
|
147
147
|
readonly command: "programs";
|
|
148
148
|
readonly endpoints?: readonly string[];
|
|
149
|
-
readonly action: "down" | "status";
|
|
149
|
+
readonly action: "prepare" | "down" | "status";
|
|
150
150
|
readonly limit: number;
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -175,7 +175,7 @@ export async function runEnvironmentCommand(input: {
|
|
|
175
175
|
if (runtimeProfile === undefined) throw new Error("programs requires a Runtime Profile");
|
|
176
176
|
const profile = resolve(runtimeProfile);
|
|
177
177
|
const host = await runtimeHost(profile);
|
|
178
|
-
if (args.action === "up") {
|
|
178
|
+
if (args.action === "up" || args.action === "prepare") {
|
|
179
179
|
await host.prepare({ ...(args.endpoints === undefined ? {} : { endpoints: args.endpoints }), ...(reportPackageProgress === undefined ? {} : { onProgress: reportPackageProgress }) });
|
|
180
180
|
}
|
|
181
181
|
const controller = await runtimeController(profile);
|
|
@@ -185,6 +185,11 @@ export async function runEnvironmentCommand(input: {
|
|
|
185
185
|
...(args.maxWaitMs === undefined ? {} : { maxWaitMs: args.maxWaitMs }),
|
|
186
186
|
...(reportProgramProgress === undefined ? {} : { onProgress: reportProgramProgress }),
|
|
187
187
|
})
|
|
188
|
+
: args.action === "prepare"
|
|
189
|
+
? await controller.programs.prepare({
|
|
190
|
+
...(args.endpoints === undefined ? {} : { endpoints: args.endpoints }),
|
|
191
|
+
...(reportProgramProgress === undefined ? {} : { onProgress: reportProgramProgress }),
|
|
192
|
+
})
|
|
188
193
|
: args.action === "down"
|
|
189
194
|
? await controller.programs.down(args.endpoints === undefined ? {} : { endpoints: args.endpoints })
|
|
190
195
|
: await controller.programs.report(args.endpoints === undefined ? {} : { endpoints: args.endpoints });
|
|
@@ -199,7 +204,9 @@ export async function runEnvironmentCommand(input: {
|
|
|
199
204
|
const urgent = relevant.filter(needsAttention);
|
|
200
205
|
const shownPrograms = [...urgent, ...relevant.filter((item) => !needsAttention(item)).slice(0, Math.max(0, args.limit - urgent.length))];
|
|
201
206
|
const omittedPrograms = relevant.length - shownPrograms.length;
|
|
202
|
-
const title = args.action === "
|
|
207
|
+
const title = args.action === "prepare"
|
|
208
|
+
? lifecycleOk ? "External program resources prepared" : "External program preparation needs attention"
|
|
209
|
+
: args.action === "up"
|
|
203
210
|
? lifecycleOk ? "External programs ready" : "External programs need attention"
|
|
204
211
|
: args.action === "down"
|
|
205
212
|
? lifecycleOk ? "External programs stopped" : "External program stop needs attention"
|
|
@@ -364,13 +371,10 @@ export async function runEnvironmentCommand(input: {
|
|
|
364
371
|
}
|
|
365
372
|
const credentialsControl = await (await runtimeHost(runtimeProfile)).openCredentials(args.endpoint);
|
|
366
373
|
try {
|
|
367
|
-
let credentials = await credentialsControl.credentials(args.endpoint);
|
|
368
|
-
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
369
|
-
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
370
|
-
if (args.slot === undefined && credentials.length > 1 && args.action !== "status") {
|
|
371
|
-
throw new Error(`Endpoint ${args.endpoint} has several credentials; select one with --slot`);
|
|
372
|
-
}
|
|
373
374
|
if (args.action === "status") {
|
|
375
|
+
let credentials = await credentialsControl.credentials(args.endpoint);
|
|
376
|
+
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
377
|
+
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
374
378
|
const view = credentials.slice(0, args.limit).map((item) => ({
|
|
375
379
|
endpoint: item.endpoint,
|
|
376
380
|
slot: item.slot,
|
|
@@ -397,7 +401,15 @@ export async function runEnvironmentCommand(input: {
|
|
|
397
401
|
: `login opens OAuth: ${item.acquisition.authorizationEndpoint}`;
|
|
398
402
|
return `${item.slot}: ${item.configured ? "configured" : "missing"} · ${item.writable ? "writable" : "read-only"} · ${entry}`;
|
|
399
403
|
}));
|
|
400
|
-
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
let credentials = await credentialsControl.describeCredentials(args.endpoint);
|
|
407
|
+
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
408
|
+
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
409
|
+
if (args.slot === undefined && credentials.length > 1) {
|
|
410
|
+
throw new Error(`Endpoint ${args.endpoint} has several credentials; select one with --slot`);
|
|
411
|
+
}
|
|
412
|
+
if (args.action === "login") {
|
|
401
413
|
const [item] = credentials;
|
|
402
414
|
if (item === undefined) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
403
415
|
if (!item.writable) {
|
|
@@ -20,7 +20,7 @@ export type OperationalMachineView =
|
|
|
20
20
|
| { readonly format: "hypit.cli-runtime-selection@1"; readonly selected: boolean; readonly removed?: boolean; readonly profile?: string; readonly project?: string }
|
|
21
21
|
| { 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 }
|
|
22
22
|
| { readonly format: "hypit.cli-package@1"; readonly action: "install" | "status"; readonly package: string; readonly ready: boolean }
|
|
23
|
-
| { readonly format: "hypit.cli-programs@1"; readonly action: "up" | "down" | "status"; readonly ok: boolean; readonly ready: boolean; readonly programCount: number; readonly readyCount: number; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
|
|
23
|
+
| { readonly format: "hypit.cli-programs@1"; readonly action: "prepare" | "up" | "down" | "status"; readonly ok: boolean; readonly ready: boolean; readonly programCount: number; readonly readyCount: number; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
|
|
24
24
|
| { 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[] } }
|
|
25
25
|
| { readonly format: "hypit.cli-runtime-logs@1"; readonly lines: readonly string[]; readonly totalLines: number; readonly omittedLines: number; readonly path?: string }
|
|
26
26
|
| { readonly format: "hypit.cli-runtime-down@1"; readonly worker: string }
|
package/packages/cli/src/main.ts
CHANGED
|
@@ -148,7 +148,8 @@ export async function runCli(
|
|
|
148
148
|
}, "Runtime Profile created", "success", [
|
|
149
149
|
["Profile", selected.profile],
|
|
150
150
|
["Project", selected.projectRoot],
|
|
151
|
-
], ["
|
|
151
|
+
], ["Review the Profile’s credential stores and Endpoint settings before login or runtime up.",
|
|
152
|
+
"No package was installed, no service was contacted and no Worker was started."]);
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
155
|
if (args.command === "runtime" && args.action === "use") {
|
|
@@ -28,6 +28,54 @@ function tokenExpiry(value: { readonly expires_at?: unknown; readonly expires_in
|
|
|
28
28
|
return seconds === undefined ? undefined : Date.now() + seconds * 1_000;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export type AuthorizeBrowserLaunch = {
|
|
32
|
+
readonly command: string;
|
|
33
|
+
readonly args: readonly string[];
|
|
34
|
+
readonly windowsVerbatimArguments: boolean;
|
|
35
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Open the authorize URL in a browser. Windows `cmd /c start` treats `&` as a command
|
|
40
|
+
* separator unless the URL is a later quoted token after an explicit window title.
|
|
41
|
+
*/
|
|
42
|
+
export function authorizeBrowserLaunch(
|
|
43
|
+
url: string,
|
|
44
|
+
options: { readonly platform?: NodeJS.Platform; readonly comSpec?: string } = {},
|
|
45
|
+
): AuthorizeBrowserLaunch {
|
|
46
|
+
const platform = options.platform ?? process.platform;
|
|
47
|
+
if (platform === "win32") {
|
|
48
|
+
// Expand one environment value after cmd parses the command. Interpolating the
|
|
49
|
+
// URL into command text would also expand percent sequences inside the URL.
|
|
50
|
+
const serialized = new URL(url).href;
|
|
51
|
+
return {
|
|
52
|
+
command: options.comSpec ?? process.env.ComSpec ?? "cmd.exe",
|
|
53
|
+
args: ["/d", "/s", "/v:off", "/c", 'start "" "%HYPIT_OAUTH_AUTHORIZE_URL%"'],
|
|
54
|
+
windowsVerbatimArguments: true,
|
|
55
|
+
env: { HYPIT_OAUTH_AUTHORIZE_URL: serialized },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
command: platform === "darwin" ? "open" : "xdg-open",
|
|
60
|
+
args: [url],
|
|
61
|
+
windowsVerbatimArguments: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openAuthorizeUrl(url: string): void {
|
|
66
|
+
const launch = authorizeBrowserLaunch(url);
|
|
67
|
+
const child = spawn(launch.command, [...launch.args], {
|
|
68
|
+
stdio: "ignore",
|
|
69
|
+
detached: true,
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
windowsVerbatimArguments: launch.windowsVerbatimArguments,
|
|
72
|
+
...(launch.env === undefined ? {} : { env: { ...process.env, ...launch.env } }),
|
|
73
|
+
});
|
|
74
|
+
// A missing opener must not crash the CLI; the URL is already on the progress line.
|
|
75
|
+
child.once("error", () => undefined);
|
|
76
|
+
child.unref();
|
|
77
|
+
}
|
|
78
|
+
|
|
31
79
|
/** Acquire one OAuth credential from the exact data declared by its Endpoint package. */
|
|
32
80
|
export async function acquireOAuthCredential(
|
|
33
81
|
acquisition: CredentialAcquisition,
|
|
@@ -102,13 +150,8 @@ export async function acquireOAuthCredential(
|
|
|
102
150
|
authorize.searchParams.set("code_challenge", challenge);
|
|
103
151
|
authorize.searchParams.set("code_challenge_method", "S256");
|
|
104
152
|
options.onProgress?.(`Opening sign-in: ${authorize}`);
|
|
105
|
-
if (options.open === undefined)
|
|
106
|
-
|
|
107
|
-
const openerArgs = process.platform === "win32" ? ["/c", "start", "", authorize.toString()] : [authorize.toString()];
|
|
108
|
-
spawn(opener, openerArgs, { stdio: "ignore", detached: true, windowsHide: true }).unref();
|
|
109
|
-
} else {
|
|
110
|
-
options.open(authorize.toString());
|
|
111
|
-
}
|
|
153
|
+
if (options.open === undefined) openAuthorizeUrl(authorize.toString());
|
|
154
|
+
else options.open(authorize.toString());
|
|
112
155
|
const code = await callback;
|
|
113
156
|
options.onProgress?.("Authorization returned. Exchanging token…");
|
|
114
157
|
const deadline = AbortSignal.timeout(acquisition.requestTimeoutMs);
|
|
@@ -878,6 +878,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
|
|
|
878
878
|
colors.accent(colors.strong("hypit programs")),
|
|
879
879
|
colors.dim("Prepare and operate external programs declared by Endpoints in one Runtime Profile."),
|
|
880
880
|
"",
|
|
881
|
+
" hypit programs prepare [--runtime <profile>] [--endpoint <instance>] # resources only; does not start services",
|
|
881
882
|
" hypit programs up [--runtime <profile>] [--workspace <project>] [--max-wait-ms <ms>]",
|
|
882
883
|
" hypit programs status [--runtime <profile>] [--workspace <project>]",
|
|
883
884
|
" hypit programs down [--runtime <profile>] [--workspace <project>]",
|