@onmark/cli 0.3.0 → 0.4.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.
Files changed (34) hide show
  1. package/onmark-release.json +83 -53
  2. package/package.json +4 -4
  3. package/packages/authoring/dist/src/frame-motion.d.ts +10 -2
  4. package/packages/authoring/dist/src/frame-motion.js +29 -6
  5. package/packages/authoring/dist/src/index.d.ts +2 -2
  6. package/packages/authoring/dist/src/motion.d.ts +16 -4
  7. package/packages/authoring/dist/src/presentation.js +34 -1
  8. package/packages/authoring/dist/src/types.d.ts +1 -1
  9. package/packages/bundler/dist/src/authored_html.d.ts +17 -7
  10. package/packages/bundler/dist/src/authored_html.js +79 -21
  11. package/packages/bundler/dist/src/bundle_projection.d.ts +9 -0
  12. package/packages/bundler/dist/src/bundle_projection.js +66 -0
  13. package/packages/bundler/dist/src/command.js +28 -8
  14. package/packages/bundler/dist/src/generated/bundle-projection-validator.d.ts +7 -0
  15. package/packages/bundler/dist/src/generated/bundle-projection-validator.js +432 -0
  16. package/packages/bundler/dist/src/generated/bundle-projection.d.ts +25 -0
  17. package/packages/bundler/dist/src/generated/bundle-projection.js +2 -0
  18. package/packages/bundler/dist/src/index.d.ts +1 -1
  19. package/packages/bundler/dist/src/presentation.d.ts +13 -6
  20. package/packages/bundler/dist/src/presentation.js +58 -28
  21. package/packages/motion-gsap/dist/src/index.d.ts +10 -2
  22. package/packages/motion-gsap/dist/src/index.js +27 -5
  23. package/packages/runtime/dist/src/generated/browser-request.d.ts +15 -2
  24. package/packages/runtime/dist/src/generated/browser-response.d.ts +1 -1
  25. package/packages/runtime/dist/src/generated/runtime-contract.d.ts +2 -0
  26. package/packages/runtime/dist/src/generated/runtime-contract.js +2 -0
  27. package/packages/runtime/dist/src/generated/validators.d.ts +2 -2
  28. package/packages/runtime/dist/src/generated/validators.js +331 -128
  29. package/packages/runtime/dist/src/index.d.ts +2 -2
  30. package/packages/runtime/dist/src/index.js +1 -1
  31. package/packages/runtime/dist/src/presentation.d.ts +10 -0
  32. package/packages/runtime/dist/src/presentation.js +18 -3
  33. package/packages/runtime/dist/src/session.js +61 -12
  34. package/packages/runtime/dist/src/types.d.ts +1 -1
@@ -119,39 +119,81 @@ function projectShotStructure(source) {
119
119
  const film = findElement(document, "om-film");
120
120
  if (film === undefined) {
121
121
  return {
122
- regions: Object.freeze([]),
122
+ shots: Object.freeze([]),
123
123
  regionStructure: Object.freeze({ scenes: Object.freeze([]) }),
124
124
  };
125
125
  }
126
- const scenes = childElements(film, "om-scene").map((scene) => {
127
- const shots = childElements(scene, "om-shot").map((shot) => removableElementRange(source, shot));
128
- return Object.freeze({
129
- range: removableElementRange(source, scene),
130
- shots: Object.freeze(shots),
131
- });
132
- });
133
- const regions = scenes.flatMap((scene, sceneIndex) => scene.shots.map((_, shotIndex) => Object.freeze({ scene: sceneIndex, shot: shotIndex })));
126
+ const shots = [];
127
+ const scenes = childElements(film, "om-scene").map((scene, sceneIndex) => projectSceneStructure(source, scene, sceneIndex, shots));
134
128
  return {
135
- regions: Object.freeze(regions),
129
+ shots: Object.freeze(shots),
136
130
  regionStructure: Object.freeze({ scenes: Object.freeze(scenes) }),
137
131
  };
138
132
  }
139
- /** Materializes one shot projection without retaining every projected document. */
140
- export function projectShotDocument(html, region) {
141
- const selectedScene = html.regionStructure.scenes[region.scene];
142
- const selectedShot = selectedScene?.shots[region.shot];
143
- if (selectedScene === undefined || selectedShot === undefined) {
144
- throw new AuthoredHtmlError("browser projection selects an unknown shot");
133
+ function projectSceneStructure(source, scene, sceneIndex, documentShots) {
134
+ const shots = [];
135
+ const transitions = [];
136
+ let outgoingShot;
137
+ let pendingTransition;
138
+ for (const element of directElements(scene)) {
139
+ if (element.tagName === "om-transition") {
140
+ if (outgoingShot === undefined || pendingTransition !== undefined) {
141
+ throw transitionProjectionError();
142
+ }
143
+ pendingTransition = removableElementRange(source, element);
144
+ continue;
145
+ }
146
+ if (element.tagName !== "om-shot") {
147
+ continue;
148
+ }
149
+ const index = documentShots.length;
150
+ const shot = Object.freeze({ scene: sceneIndex, shot: shots.length });
151
+ documentShots.push(shot);
152
+ shots.push(Object.freeze({
153
+ index,
154
+ range: removableElementRange(source, element),
155
+ }));
156
+ if (pendingTransition !== undefined && outgoingShot !== undefined) {
157
+ transitions.push(Object.freeze({
158
+ incomingShot: index,
159
+ outgoingShot,
160
+ range: pendingTransition,
161
+ }));
162
+ pendingTransition = undefined;
163
+ }
164
+ outgoingShot = index;
145
165
  }
166
+ if (pendingTransition !== undefined) {
167
+ throw transitionProjectionError();
168
+ }
169
+ return Object.freeze({
170
+ range: removableElementRange(source, scene),
171
+ shots: Object.freeze(shots),
172
+ transitions: Object.freeze(transitions),
173
+ });
174
+ }
175
+ function transitionProjectionError() {
176
+ return new AuthoredHtmlError("compiled transition boundaries cannot be projected into browser regions");
177
+ }
178
+ /** Materializes one Rust-planned region without retaining every projection. */
179
+ export function projectRegionDocument(html, shotIndices) {
180
+ const selectedShots = selectedShotSet(html, shotIndices);
146
181
  const ranges = [];
147
- for (const [sceneIndex, scene] of html.regionStructure.scenes.entries()) {
148
- if (sceneIndex !== region.scene) {
182
+ for (const scene of html.regionStructure.scenes) {
183
+ if (!scene.shots.some((shot) => selectedShots.has(shot.index))) {
149
184
  ranges.push(scene.range);
150
185
  continue;
151
186
  }
152
- for (const [shotIndex, shot] of scene.shots.entries()) {
153
- if (shotIndex !== region.shot) {
154
- ranges.push(shot);
187
+ for (const shot of scene.shots) {
188
+ if (!selectedShots.has(shot.index)) {
189
+ ranges.push(shot.range);
190
+ }
191
+ }
192
+ for (const transition of scene.transitions) {
193
+ const retained = selectedShots.has(transition.outgoingShot) &&
194
+ selectedShots.has(transition.incomingShot);
195
+ if (!retained) {
196
+ ranges.push(transition.range);
155
197
  }
156
198
  }
157
199
  }
@@ -160,6 +202,16 @@ export function projectShotDocument(html, region) {
160
202
  runtimeOffset: projectOffset(html.runtimeOffset, ranges),
161
203
  });
162
204
  }
205
+ function selectedShotSet(html, shotIndices) {
206
+ const selected = new Set();
207
+ for (const index of shotIndices) {
208
+ if (html.shots[index] === undefined) {
209
+ throw new AuthoredHtmlError(`browser projection selects unknown shot ${index}`);
210
+ }
211
+ selected.add(index);
212
+ }
213
+ return selected;
214
+ }
163
215
  function parseDocument(source) {
164
216
  const errors = [];
165
217
  const document = parse(source, {
@@ -234,6 +286,9 @@ function findElement(node, name) {
234
286
  function childElements(parent, name) {
235
287
  return parent.childNodes.filter((child) => "tagName" in child && child.tagName === name);
236
288
  }
289
+ function directElements(parent) {
290
+ return parent.childNodes.filter((child) => "tagName" in child);
291
+ }
237
292
  function visit(node, visitor) {
238
293
  const pending = [node];
239
294
  while (pending.length > 0) {
@@ -328,9 +383,12 @@ function isCompilerOnlyAttribute(tagName, attributeName) {
328
383
  case "om-title":
329
384
  return attributeName === "cue" || attributeName === "delay";
330
385
  case "om-shot":
386
+ case "om-transition":
331
387
  return attributeName === "duration";
332
388
  case "video":
333
389
  return (attributeName === "delay" ||
390
+ attributeName === "hold-last" ||
391
+ attributeName === "plays" ||
334
392
  attributeName === "speed" ||
335
393
  attributeName === "src" ||
336
394
  attributeName === "trim");
@@ -0,0 +1,9 @@
1
+ import type { BundleProjection } from "./generated/bundle-projection.js";
2
+ /** Invalid or unreadable projection data at the bundler process boundary. */
3
+ export declare class BundleProjectionError extends Error {
4
+ constructor(message: string, options?: ErrorOptions);
5
+ }
6
+ /** Reads and validates one bounded Rust-owned projection snapshot. */
7
+ export declare function readBundleProjection(path: string): Promise<BundleProjection>;
8
+ /** Decodes one checked projection at an in-process or file boundary. */
9
+ export declare function decodeBundleProjection(value: unknown): BundleProjection;
@@ -0,0 +1,66 @@
1
+ // Bounded reader for the Rust-owned render-region projection contract.
2
+ // It validates process input before authored HTML is projected or published.
3
+ import { open } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { validateBundleProjection } from "./generated/bundle-projection-validator.js";
6
+ const MAX_PROJECTION_BYTES = 1024 * 1024;
7
+ /** Invalid or unreadable projection data at the bundler process boundary. */
8
+ export class BundleProjectionError extends Error {
9
+ constructor(message, options) {
10
+ super(message, options);
11
+ this.name = "BundleProjectionError";
12
+ }
13
+ }
14
+ /** Reads and validates one bounded Rust-owned projection snapshot. */
15
+ export async function readBundleProjection(path) {
16
+ const absolute = resolve(path);
17
+ let file;
18
+ try {
19
+ file = await open(absolute, "r");
20
+ }
21
+ catch (error) {
22
+ throw new BundleProjectionError(`cannot open bundle projection ${absolute}`, { cause: error });
23
+ }
24
+ try {
25
+ const bytes = await readBoundedProjection(file);
26
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
27
+ return decodeBundleProjection(value);
28
+ }
29
+ catch (error) {
30
+ if (error instanceof BundleProjectionError) {
31
+ throw error;
32
+ }
33
+ throw new BundleProjectionError(`cannot read bundle projection ${absolute}`, { cause: error });
34
+ }
35
+ finally {
36
+ await file.close();
37
+ }
38
+ }
39
+ /** Decodes one checked projection at an in-process or file boundary. */
40
+ export function decodeBundleProjection(value) {
41
+ if (!validateBundleProjection(value)) {
42
+ throw invalidProjection();
43
+ }
44
+ return value;
45
+ }
46
+ async function readBoundedProjection(file) {
47
+ const bytes = Buffer.allocUnsafe(MAX_PROJECTION_BYTES + 1);
48
+ let length = 0;
49
+ while (length < bytes.length) {
50
+ const { bytesRead } = await file.read(bytes, length, bytes.length - length, length);
51
+ if (bytesRead === 0) {
52
+ break;
53
+ }
54
+ length += bytesRead;
55
+ }
56
+ if (length > MAX_PROJECTION_BYTES) {
57
+ throw new BundleProjectionError(`bundle projection exceeds the ${MAX_PROJECTION_BYTES}-byte limit`);
58
+ }
59
+ return bytes.subarray(0, length);
60
+ }
61
+ function invalidProjection() {
62
+ const error = validateBundleProjection.errors?.[0];
63
+ const path = error?.instancePath ?? "";
64
+ const detail = error?.message ?? "does not match its schema";
65
+ return new BundleProjectionError(`bundle projection${path} ${detail}`.trim());
66
+ }
@@ -3,11 +3,13 @@
3
3
  import process from "node:process";
4
4
  import { parseArgs } from "node:util";
5
5
  import { BundleError, bundlePresentation, } from "./presentation.js";
6
+ import { BundleProjectionError, readBundleProjection, } from "./bundle_projection.js";
6
7
  import { BUNDLE_FRAME_BEHAVIORS, BUNDLE_TEMPORAL_CAPABILITIES, BUNDLE_VISUAL_CAPABILITIES, } from "./generated/bundle-manifest.js";
7
8
  const USAGE = [
8
9
  "Usage: onmark-bundle",
9
10
  " --html <path>",
10
11
  " [--resolve-directory <directory>]",
12
+ " [--projection <path> # required for randomAccess]",
11
13
  " --output <directory>",
12
14
  " --max-output-bytes <bytes>",
13
15
  " --frame-behavior <perFrame|placementBounded>",
@@ -17,7 +19,7 @@ const USAGE = [
17
19
  ].join("\n");
18
20
  // ── Execution ──
19
21
  try {
20
- const command = parseArguments(process.argv.slice(2));
22
+ const command = await parseArguments(process.argv.slice(2));
21
23
  if (command.kind === "help") {
22
24
  process.stdout.write(USAGE);
23
25
  }
@@ -31,7 +33,7 @@ catch (error) {
31
33
  process.exitCode = failure.kind === "configuration" ? 2 : 1;
32
34
  }
33
35
  // ── Arguments ──
34
- function parseArguments(arguments_) {
36
+ async function parseArguments(arguments_) {
35
37
  const values = commandValues(arguments_);
36
38
  if (values.help !== undefined) {
37
39
  if (arguments_.length !== 1) {
@@ -48,16 +50,25 @@ function parseArguments(arguments_) {
48
50
  frameBehavior,
49
51
  maxOutputBytes,
50
52
  outputDirectory,
51
- temporalCapability,
52
53
  visualCapability,
53
54
  };
55
+ const common = {
56
+ ...controls,
57
+ document: oneValue(values.html, "--html"),
58
+ ...optionalResolveDirectory(values["resolve-directory"]),
59
+ };
60
+ if (temporalCapability === "sequential") {
61
+ rejectValue(values.projection, "--projection requires randomAccess");
62
+ return {
63
+ kind: "bundle",
64
+ options: { ...common, temporalCapability },
65
+ };
66
+ }
67
+ const projectionPath = oneValue(values.projection, "--projection");
68
+ const projection = await readBundleProjection(projectionPath);
54
69
  return {
55
70
  kind: "bundle",
56
- options: {
57
- ...controls,
58
- document: oneValue(values.html, "--html"),
59
- ...optionalResolveDirectory(values["resolve-directory"]),
60
- },
71
+ options: { ...common, projection, temporalCapability },
61
72
  };
62
73
  }
63
74
  function commandValues(arguments_) {
@@ -71,6 +82,7 @@ function commandValues(arguments_) {
71
82
  html: { type: "string", multiple: true },
72
83
  "max-output-bytes": { type: "string", multiple: true },
73
84
  output: { type: "string", multiple: true },
85
+ projection: { type: "string", multiple: true },
74
86
  "resolve-directory": { type: "string", multiple: true },
75
87
  "temporal-capability": { type: "string", multiple: true },
76
88
  "visual-capability": { type: "string", multiple: true },
@@ -119,6 +131,11 @@ function oneValue(values, name) {
119
131
  }
120
132
  return value;
121
133
  }
134
+ function rejectValue(values, message) {
135
+ if (values !== undefined) {
136
+ throw configuration(message);
137
+ }
138
+ }
122
139
  function parseByteLimit(value) {
123
140
  const bytes = Number(value);
124
141
  if (!Number.isSafeInteger(bytes) || bytes <= 0) {
@@ -134,5 +151,8 @@ function commandFailure(error) {
134
151
  if (error instanceof BundleError) {
135
152
  return error;
136
153
  }
154
+ if (error instanceof BundleProjectionError) {
155
+ return configuration(error.message, error);
156
+ }
137
157
  return new BundleError("output", "unexpected bundler failure", error);
138
158
  }
@@ -0,0 +1,7 @@
1
+ export declare const validateBundleProjection: typeof validate20;
2
+ declare function validate20(data: any, { instancePath, parentData, parentDataProperty, rootData, dynamicAnchors, }?: {
3
+ dynamicAnchors?: {} | undefined;
4
+ instancePath?: string | undefined;
5
+ rootData?: any;
6
+ }): boolean;
7
+ export {};