@cutcraft/kit 0.1.0 → 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 +40 -56
- package/dist/{index-CLhXqOpF.d.mts → index-BYYpzZuS.d.mts} +351 -327
- package/dist/index.d.mts +4260 -13987
- package/dist/index.mjs +1 -1
- package/dist/source/index-public.d.mts +57 -57
- package/dist/source/index-public.mjs +214 -50
- package/dist/{src-BsBU2JLf.mjs → src-CcFjz_DV.mjs} +2241 -2069
- package/package.json +16 -8
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Jn as ANIMATION_UNITS, Yn as AUTOMATION_LANES, lt as AUTO_GROUP_TYPES, ct as ActionHistory, tn as Adjustment, nn as Angle, rn as Audio, Xn as BLEND_MODES, c as BUNDLED_FONTS, l as BUNDLED_FONT_WEIGHTS, Zn as CAPTION_ANIMATIONS, Qn as CAPTION_POSITIONS, in as Caption, an as Captions, on as Clip, sn as ColorGrade, cn as Composition, ln as Cut, St as DEFAULT_CUE_OPTIONS, Vn as DEFAULT_THEME, $n as EASINGS, kt as EASING_FN, un as ELEMENTS, Ht as EMPTY_MANIFEST, dn as ENGINE_LAYERS, fn as ENGINE_SCHEMAS, pn as Effect, rt as Executor, er as FIT_MODES, Hn as FONT_KEYS, mn as Freeze, tr as GRAPHIC_ANIMATIONS, hn as Gap, gn as Graphic, yt as INTERIOR_PREFIX, nr as KEYFRAME_PROPERTIES, rr as KEYFRAME_PROPERTY_PATTERN, Pr as KIND_CAPABILITIES, _n as Keyframe, vn as Layer, b as MASKED_KINDS, ir as MASK_EASINGS, x as MASK_NOT_RENDERED, ar as MASK_TYPES, u as MODULE_FONTS, Jt as MODULE_SHADERS, Un as MOTIONS, Wn as MOTION_NAMES, yn as Marker, bn as Mask, xn as Motion, Sn as Multicam, S as NOT_RENDERED, Cn as Nested, N as NotImplemented, Gn as PALETTE_KEYS, C as RENDERED_VALUES, wn as SCHEMAS_BY_KIND, or as SHAPE_TYPES, In as STYLED_KINDS, sr as SVG_COLOR_MODES, Tn as Shape, En as SpeedKey, Dn as Sticker, On as Svg, ut as TARGET_PARAM_KEYS, cr as TEXT_ALIGNS, lr as TEXT_ANIMATION_PRESETS, ur as TEXT_DECORATIONS, dr as TEXT_VALIGNS, Kn as THEMES, qn as THEME_NAMES, Ln as TOKEN_PATTERN, fr as TRACK_FLAGS, pr as TRACK_LAYOUTS, mr as TRACK_ROLES, Pn as TRANSITION_PARAMS, hr as TRANSITION_TYPES, kn as Three, An as Title, jn as Track, Mn as Transition, gr as VIDEO_EFFECT_TYPES, zt as applyTheme, Ir as asJson, Ut as assetIdOf, Fr as capabilitiesOf, P as childAt, F as childIndex, o as collect, I as collectErrors, d as collectFonts, s as collectModule, Yt as collectShaders, Lr as createPatchLog, Ct as cueNodes, Fn as defaultTransitionParams, L as define, Xt as defineEffect, jr as defineElement, Zt as defineTransition, dt as describeAction, Nn as engineOf, bt as findParentOf, f as font, p as fontsOf, At as freezeHoldsOf, jt as freezeShift, Mt as freezeUnshift, it as getHandler, m as googleFont, t as graphicControls, E as holdLayerFrame, Nt as holdTotal, R as insertNode, _ as installLayerClock, D as installLayerEngine, A as interpolate, pt as invalid, at as inverseOf, h as isBundledFont, z as isError, _r as isKeyframeProperty, a as isResolveError, Gt as isTimeRefString, Mr as isVocabulary, Nr as kindOf, B as lastChildId, O as layerEngine, k as layerFramesPending, ot as listHandlers, Wt as lookupAsset, V as makePropHandler, w as motionBlurOf, Rn as motionStyleSchema, mt as nodeOfSpec, ht as nodeSpecOf, Rr as nodesEqual, gt as ok, wt as onTimeline, Kt as parseTimeRef, g as parseTranscript, H as placeNode, U as placementOf, W as placementProps, Tt as planCues, Et as playsKey, Pt as rampAdvance, Ft as rampInverse, It as rampSpeed, j as random, G as rawNode, K as rawProp, zr as reduceInto, st as registerHandler, q as requireElement, J as requireLayout, Y as requireLiteralProp, X as requireNotOpaque, Z as requireUnlocked, _t as resolve, i as resolveScene, Bt as resolveTheme, qt as resolveTimeRef, Dt as sameAsset, vt as samePlacement, Q as setProps, Qt as shaderFor, $t as shadersOf, T as shutterTimes, $ as siblingAfter, Lt as speedKeysOf, M as spring, Rt as springAt, Br as stableJson, et as stub, r as subtitles, ft as targetIdOf, zn as themeSchema, Vt as themeToken, Ot as timelineWords, Bn as tokenSchema, tt as uniqueId, v as useLayer, y as useT, en as usesShaders, nt as validateTarget, xt as walk, vr as zBlendMode, yr as zCaptionAnimation, br as zEasing, xr as zFitMode, Sr as zGraphicAnimation, Cr as zKeyframeProperty, wr as zMaskEasing, Tr as zShapeType, Er as zTextAnimationPreset, Dr as zTrackLayout, Or as zTrackRole, kr as zTransitionType, Ar as zVideoEffectType } from "./src-CcFjz_DV.mjs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as NodeSpec, Rn as AssetManifest, V as ResolvedState, an as PatchLog, nn as Json } from "../index-BYYpzZuS.mjs";
|
|
2
2
|
import { ZodObject } from "zod";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
|
-
import { ParseResult } from "@babel/parser";
|
|
5
4
|
import * as t from "@babel/types";
|
|
5
|
+
import { ParseResult } from "@babel/parser";
|
|
6
6
|
interface ParsedSource {
|
|
7
7
|
readonly text: string;
|
|
8
8
|
readonly ast: ParseResult<t.File>;
|
|
@@ -13,6 +13,48 @@ export declare function rangeOf(node: {
|
|
|
13
13
|
start?: number | null;
|
|
14
14
|
end?: number | null;
|
|
15
15
|
}): Range;
|
|
16
|
+
type SourceEdit = {
|
|
17
|
+
readonly kind: "prop";
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly prop: string;
|
|
20
|
+
readonly range: Range;
|
|
21
|
+
readonly text: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "text";
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly range: Range;
|
|
26
|
+
readonly text: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "insert";
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly range: Range;
|
|
31
|
+
readonly text: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "remove";
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly range: Range;
|
|
36
|
+
readonly text: "";
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "move";
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly range: Range;
|
|
41
|
+
readonly text: string;
|
|
42
|
+
readonly from: Range;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "bake";
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly range: Range;
|
|
47
|
+
readonly text: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: "defaults";
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly range: Range;
|
|
52
|
+
readonly text: string;
|
|
53
|
+
};
|
|
54
|
+
/** Splice edits into the text, last range first, refusing overlaps. */
|
|
55
|
+
export declare function applyEdits(text: string, edits: readonly SourceEdit[]): string;
|
|
56
|
+
/** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
|
|
57
|
+
export declare function lineRange(text: string, range: Range): Range;
|
|
16
58
|
interface PropSite {
|
|
17
59
|
readonly name: string;
|
|
18
60
|
/** The whole attribute, including the name. */
|
|
@@ -123,55 +165,12 @@ export declare function literalOf(node: t.Node | null | undefined): {
|
|
|
123
165
|
value: Json | undefined;
|
|
124
166
|
};
|
|
125
167
|
export declare function indentOf(text: string, pos: number): string;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
} | {
|
|
133
|
-
readonly kind: "text";
|
|
134
|
-
readonly id: string;
|
|
135
|
-
readonly range: Range;
|
|
136
|
-
readonly text: string;
|
|
137
|
-
} | {
|
|
138
|
-
readonly kind: "insert";
|
|
139
|
-
readonly id: string;
|
|
140
|
-
readonly range: Range;
|
|
141
|
-
readonly text: string;
|
|
142
|
-
} | {
|
|
143
|
-
readonly kind: "remove";
|
|
144
|
-
readonly id: string;
|
|
145
|
-
readonly range: Range;
|
|
146
|
-
readonly text: "";
|
|
147
|
-
} | {
|
|
148
|
-
readonly kind: "move";
|
|
149
|
-
readonly id: string;
|
|
150
|
-
readonly range: Range;
|
|
151
|
-
readonly text: string;
|
|
152
|
-
readonly from: Range;
|
|
153
|
-
} | {
|
|
154
|
-
readonly kind: "bake";
|
|
155
|
-
readonly id: string;
|
|
156
|
-
readonly range: Range;
|
|
157
|
-
readonly text: string;
|
|
158
|
-
} | {
|
|
159
|
-
readonly kind: "defaults";
|
|
160
|
-
readonly id: string;
|
|
161
|
-
readonly range: Range;
|
|
162
|
-
readonly text: string;
|
|
163
|
-
};
|
|
164
|
-
/** Splice edits into the text, last range first, refusing overlaps. */
|
|
165
|
-
export declare function applyEdits(text: string, edits: readonly SourceEdit[]): string;
|
|
166
|
-
/** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
|
|
167
|
-
export declare function lineRange(text: string, range: Range): Range;
|
|
168
|
-
/** JavaScript source for a JSON value, compact, double-quoted. */
|
|
169
|
-
export declare function printLiteral(value: Json | undefined): string;
|
|
170
|
-
/** One JSX attribute. Strings as `name="…"`, everything else as `name={…}`, `true` as a bare attribute. */
|
|
171
|
-
export declare function printProp(name: string, value: Json | undefined): string;
|
|
172
|
-
export declare function elementName(kind: string): string;
|
|
173
|
-
/** A whole element as JSX text, indented with `indent`, children on their own lines. */
|
|
174
|
-
export declare function printElement(spec: NodeSpec, indent?: string, step?: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* Replace the helper call that produced a generated element with the literal
|
|
170
|
+
* elements that call produced, so the editor can address them. Multicam cuts
|
|
171
|
+
* are baked by appending their generated clips after the generator.
|
|
172
|
+
*/
|
|
173
|
+
export declare function bake(text: string, id: string, state: ResolvedState, index?: SourceIndex): SourceEdit[];
|
|
175
174
|
interface FoldResult {
|
|
176
175
|
readonly text: string;
|
|
177
176
|
readonly edits: readonly SourceEdit[];
|
|
@@ -194,12 +193,6 @@ export declare function fold(log: PatchLog, index: SourceIndex, state: ResolvedS
|
|
|
194
193
|
};
|
|
195
194
|
/** Fold with baking: generated targets are baked into literals, the file is re-indexed, and the fold is retried. */
|
|
196
195
|
export declare function foldToSource(text: string, log: PatchLog, state: ResolvedState, maxRounds?: number): FoldResult;
|
|
197
|
-
/**
|
|
198
|
-
* Replace the helper call that produced a generated element with the literal
|
|
199
|
-
* elements that call produced, so the editor can address them. Multicam cuts
|
|
200
|
-
* are baked by appending their generated clips after the generator.
|
|
201
|
-
*/
|
|
202
|
-
export declare function bake(text: string, id: string, state: ResolvedState, index?: SourceIndex): SourceEdit[];
|
|
203
196
|
interface Rule {
|
|
204
197
|
readonly name: string;
|
|
205
198
|
check(index: SourceIndex, options: LintOptions): Diagnostic[];
|
|
@@ -233,4 +226,11 @@ interface LoadOptions {
|
|
|
233
226
|
readonly imports?: Readonly<Record<string, unknown>>;
|
|
234
227
|
}
|
|
235
228
|
export declare function loadModule(source: string, options?: LoadOptions): CutModule;
|
|
229
|
+
/** JavaScript source for a JSON value, compact, double-quoted. */
|
|
230
|
+
export declare function printLiteral(value: Json | undefined): string;
|
|
231
|
+
/** One JSX attribute. Strings as `name="…"`, everything else as `name={…}`, `true` as a bare attribute. */
|
|
232
|
+
export declare function printProp(name: string, value: Json | undefined): string;
|
|
233
|
+
export declare function elementName(kind: string): string;
|
|
234
|
+
/** A whole element as JSX text, indented with `indent`, children on their own lines. */
|
|
235
|
+
export declare function printElement(spec: NodeSpec, indent?: string, step?: string): string;
|
|
236
236
|
export type { CutModule, Diagnostic, ElementSite, FoldResult, HelperCallSite, HelperSite, LintOptions, LintRule, LoadOptions, PropSite, Range, SourceEdit, SourceIndex };
|
|
@@ -1,12 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as zod from "zod";
|
|
1
|
+
import { Bt as resolveTheme, C as RENDERED_VALUES, Fr as capabilitiesOf, In as STYLED_KINDS, Jt as MODULE_SHADERS, Ln as TOKEN_PATTERN, S as NOT_RENDERED, Vt as themeToken, Wt as lookupAsset, Yt as collectShaders, b as MASKED_KINDS, d as collectFonts, dn as ENGINE_LAYERS, h as isBundledFont, ht as nodeSpecOf, n as src_exports, qt as resolveTimeRef, u as MODULE_FONTS, un as ELEMENTS, x as MASK_NOT_RENDERED, yt as INTERIOR_PREFIX } from "../src-CcFjz_DV.mjs";
|
|
3
2
|
import { GRAPHIC_BASE_PROPS, GRAPHIC_KINDS, GRAPHIC_KIND_NAMES, isIcon } from "@cutcraft/graphics";
|
|
3
|
+
import * as zod from "zod";
|
|
4
4
|
import * as React from "react";
|
|
5
|
-
import { parse } from "@babel/parser";
|
|
6
5
|
import traverseModule from "@babel/traverse";
|
|
7
6
|
import * as t from "@babel/types";
|
|
7
|
+
import { parse } from "@babel/parser";
|
|
8
8
|
import * as jsxRuntime from "react/jsx-runtime";
|
|
9
9
|
import { transform } from "sucrase";
|
|
10
|
+
// src/source/edits.ts
|
|
11
|
+
/** Splice edits into the text, last range first, refusing overlaps. */
|
|
12
|
+
function applyEdits(text, edits) {
|
|
13
|
+
const splices = [];
|
|
14
|
+
for (const e of edits) if (e.kind === "move") {
|
|
15
|
+
splices.push({
|
|
16
|
+
range: e.from,
|
|
17
|
+
text: ""
|
|
18
|
+
});
|
|
19
|
+
splices.push({
|
|
20
|
+
range: e.range,
|
|
21
|
+
text: e.text
|
|
22
|
+
});
|
|
23
|
+
} else splices.push({
|
|
24
|
+
range: e.range,
|
|
25
|
+
text: e.text
|
|
26
|
+
});
|
|
27
|
+
splices.sort((a, b) => b.range[0] - a.range[0] || b.range[1] - a.range[1]);
|
|
28
|
+
let out = text;
|
|
29
|
+
let lastStart = Number.POSITIVE_INFINITY;
|
|
30
|
+
for (const s of splices) {
|
|
31
|
+
if (s.range[1] > lastStart) throw new Error(`overlapping edits at ${s.range[0]}..${s.range[1]}`);
|
|
32
|
+
out = out.slice(0, s.range[0]) + s.text + out.slice(s.range[1]);
|
|
33
|
+
lastStart = s.range[0];
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
|
|
38
|
+
function lineRange(text, range) {
|
|
39
|
+
let start = range[0];
|
|
40
|
+
const lineStart = text.lastIndexOf("\n", start - 1) + 1;
|
|
41
|
+
if (/^\s*$/.test(text.slice(lineStart, start))) start = lineStart;
|
|
42
|
+
let end = range[1];
|
|
43
|
+
const rest = text.slice(end).match(/^[ \t]*\r?\n/);
|
|
44
|
+
if (rest) end += rest[0].length;
|
|
45
|
+
return [start, end];
|
|
46
|
+
}
|
|
10
47
|
// src/source/parse.ts
|
|
11
48
|
function parseSource(text) {
|
|
12
49
|
return {
|
|
@@ -24,7 +61,7 @@ function rangeOf(node) {
|
|
|
24
61
|
}
|
|
25
62
|
// src/source/index.ts
|
|
26
63
|
const traverse$1 = traverseModule.default ?? traverseModule;
|
|
27
|
-
const KIND_BY_NAME = new Map(Object.entries(ELEMENTS).map(([name, c]) => [name, c.kind]));
|
|
64
|
+
const KIND_BY_NAME = new Map([...Object.entries(ELEMENTS), ...Object.entries(ENGINE_LAYERS)].map(([name, c]) => [name, c.kind]));
|
|
28
65
|
/**
|
|
29
66
|
* Index every vocabulary JSX element with a literal id so patches can be
|
|
30
67
|
* written back as text edits. Elements inside helper functions are indexed
|
|
@@ -185,7 +222,7 @@ function buildSourceIndex(text) {
|
|
|
185
222
|
});
|
|
186
223
|
const closing = path.node.closingElement;
|
|
187
224
|
const childrenRange = closing ? [opening.end ?? 0, closing.start ?? 0] : null;
|
|
188
|
-
const interior =
|
|
225
|
+
const interior = name === "Layer" ? interiorOf(path.node, inSchemaFn) : null;
|
|
189
226
|
if (interior) {
|
|
190
227
|
for (const prop of interior.lockedProps) locked.push(`${INTERIOR_PREFIX}${prop}`);
|
|
191
228
|
for (const [prop, key] of Object.entries(interior.boundProps)) bound[`${INTERIOR_PREFIX}${prop}`] = key;
|
|
@@ -447,43 +484,6 @@ function indentOf(text, pos) {
|
|
|
447
484
|
const prefix = text.slice(lineStart, pos);
|
|
448
485
|
return /^\s*$/.test(prefix) ? prefix : "";
|
|
449
486
|
}
|
|
450
|
-
// src/source/edits.ts
|
|
451
|
-
/** Splice edits into the text, last range first, refusing overlaps. */
|
|
452
|
-
function applyEdits(text, edits) {
|
|
453
|
-
const splices = [];
|
|
454
|
-
for (const e of edits) if (e.kind === "move") {
|
|
455
|
-
splices.push({
|
|
456
|
-
range: e.from,
|
|
457
|
-
text: ""
|
|
458
|
-
});
|
|
459
|
-
splices.push({
|
|
460
|
-
range: e.range,
|
|
461
|
-
text: e.text
|
|
462
|
-
});
|
|
463
|
-
} else splices.push({
|
|
464
|
-
range: e.range,
|
|
465
|
-
text: e.text
|
|
466
|
-
});
|
|
467
|
-
splices.sort((a, b) => b.range[0] - a.range[0] || b.range[1] - a.range[1]);
|
|
468
|
-
let out = text;
|
|
469
|
-
let lastStart = Number.POSITIVE_INFINITY;
|
|
470
|
-
for (const s of splices) {
|
|
471
|
-
if (s.range[1] > lastStart) throw new Error(`overlapping edits at ${s.range[0]}..${s.range[1]}`);
|
|
472
|
-
out = out.slice(0, s.range[0]) + s.text + out.slice(s.range[1]);
|
|
473
|
-
lastStart = s.range[0];
|
|
474
|
-
}
|
|
475
|
-
return out;
|
|
476
|
-
}
|
|
477
|
-
/** Widen an element range to swallow its own line(s): leading indentation and the trailing newline. */
|
|
478
|
-
function lineRange(text, range) {
|
|
479
|
-
let start = range[0];
|
|
480
|
-
const lineStart = text.lastIndexOf("\n", start - 1) + 1;
|
|
481
|
-
if (/^\s*$/.test(text.slice(lineStart, start))) start = lineStart;
|
|
482
|
-
let end = range[1];
|
|
483
|
-
const rest = text.slice(end).match(/^[ \t]*\r?\n/);
|
|
484
|
-
if (rest) end += rest[0].length;
|
|
485
|
-
return [start, end];
|
|
486
|
-
}
|
|
487
487
|
// src/source/print.ts
|
|
488
488
|
const NAME_BY_KIND = new Map(Object.entries(ELEMENTS).map(([name, c]) => [c.kind, name]));
|
|
489
489
|
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -514,7 +514,7 @@ function elementName(kind) {
|
|
|
514
514
|
function printElement(spec, indent = "", step = " ") {
|
|
515
515
|
const name = elementName(spec.kind);
|
|
516
516
|
const attrs = Object.entries(spec.props).filter(([k, v]) => v !== void 0 && true).map(([k, v]) => printProp(k, v)).filter(Boolean);
|
|
517
|
-
const open = `${indent}<${name}${attrs.length ?
|
|
517
|
+
const open = `${indent}<${name}${attrs.length ? ` ${attrs.join(" ")}` : ""}`;
|
|
518
518
|
const children = spec.children ?? [];
|
|
519
519
|
if (children.length === 0 && spec.text === void 0) return `${open} />`;
|
|
520
520
|
if (children.length === 0) return `${open}>${escapeText(spec.text ?? "")}</${name}>`;
|
|
@@ -933,7 +933,7 @@ function bindingEdit(index, id, prop, key, value, skipped) {
|
|
|
933
933
|
});
|
|
934
934
|
return null;
|
|
935
935
|
}
|
|
936
|
-
if (!target
|
|
936
|
+
if (!target?.valueRange) {
|
|
937
937
|
skipped.push({
|
|
938
938
|
id,
|
|
939
939
|
reason: `"${prop}" is bound to defaults.${key}, which has no literal`
|
|
@@ -1035,6 +1035,97 @@ function reindent(source, from, to) {
|
|
|
1035
1035
|
if (from === to) return source;
|
|
1036
1036
|
return source.split("\n").map((line, i) => i === 0 ? line : line.startsWith(from) ? to + line.slice(from.length) : line).join("\n");
|
|
1037
1037
|
}
|
|
1038
|
+
// src/source/lint/rules/fonts.ts
|
|
1039
|
+
const DECLARERS = /* @__PURE__ */ new Set([
|
|
1040
|
+
"font",
|
|
1041
|
+
"googleFont",
|
|
1042
|
+
"defineEffect",
|
|
1043
|
+
"defineTransition"
|
|
1044
|
+
]);
|
|
1045
|
+
/** Declarations whose arguments hold code (a zod schema): only their name and shader need be literal. */
|
|
1046
|
+
const SHADERS = /* @__PURE__ */ new Set(["defineEffect", "defineTransition"]);
|
|
1047
|
+
/** Calls to the kit's declarations (`font()`, `googleFont()`, `defineEffect()`, `defineTransition()`), under whatever local names the file imports them as. */
|
|
1048
|
+
function declarations(index) {
|
|
1049
|
+
const local = /* @__PURE__ */ new Map();
|
|
1050
|
+
for (const node of index.ast.program.body) {
|
|
1051
|
+
if (!t.isImportDeclaration(node) || node.source.value !== "@cutcraft/kit") continue;
|
|
1052
|
+
for (const spec of node.specifiers) {
|
|
1053
|
+
if (!t.isImportSpecifier(spec)) continue;
|
|
1054
|
+
const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
1055
|
+
if (DECLARERS.has(imported)) local.set(spec.local.name, imported);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (local.size === 0) return [];
|
|
1059
|
+
const top = /* @__PURE__ */ new Set();
|
|
1060
|
+
for (const node of index.ast.program.body) {
|
|
1061
|
+
const decl = t.isExportNamedDeclaration(node) ? node.declaration : node;
|
|
1062
|
+
if (t.isVariableDeclaration(decl)) {
|
|
1063
|
+
for (const d of decl.declarations) if (d.init) top.add(d.init);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const out = [];
|
|
1067
|
+
t.traverseFast(index.ast.program, (node) => {
|
|
1068
|
+
if (t.isCallExpression(node) && t.isIdentifier(node.callee) && local.has(node.callee.name)) out.push({
|
|
1069
|
+
call: node,
|
|
1070
|
+
topLevel: top.has(node),
|
|
1071
|
+
declarer: local.get(node.callee.name)
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
return out;
|
|
1075
|
+
}
|
|
1076
|
+
/** A value written out in the source: a string, number or boolean, or an array or object of them. */
|
|
1077
|
+
function literal(node) {
|
|
1078
|
+
if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node)) return true;
|
|
1079
|
+
if (t.isTemplateLiteral(node)) return node.expressions.length === 0;
|
|
1080
|
+
if (t.isArrayExpression(node)) return node.elements.every((e) => e !== null && literal(e));
|
|
1081
|
+
if (t.isObjectExpression(node)) return node.properties.every((p) => t.isObjectProperty(p) && !p.computed && (t.isIdentifier(p.key) || t.isStringLiteral(p.key)) && literal(p.value));
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
/** The families a file declares with literal arguments: `font({ family: "…" })` and `googleFont("…")`. */
|
|
1085
|
+
function declaredFamilies(index) {
|
|
1086
|
+
const out = /* @__PURE__ */ new Set();
|
|
1087
|
+
for (const { call, declarer } of declarations(index)) {
|
|
1088
|
+
if (SHADERS.has(declarer)) continue;
|
|
1089
|
+
const [first] = call.arguments;
|
|
1090
|
+
if (t.isStringLiteral(first)) out.add(first.value);
|
|
1091
|
+
else if (t.isObjectExpression(first)) {
|
|
1092
|
+
for (const p of first.properties) if (t.isObjectProperty(p) && t.isIdentifier(p.key, { name: "family" }) && t.isStringLiteral(p.value)) out.add(p.value.value);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return out;
|
|
1096
|
+
}
|
|
1097
|
+
/** A `name` and `shader` written out in a `defineEffect()` / `defineTransition()` call's options. */
|
|
1098
|
+
function literalShader(call) {
|
|
1099
|
+
const [options] = call.arguments;
|
|
1100
|
+
if (!t.isObjectExpression(options)) return false;
|
|
1101
|
+
const field = (key) => options.properties.find((p) => t.isObjectProperty(p) && t.isIdentifier(p.key, { name: key }))?.value;
|
|
1102
|
+
const name = field("name");
|
|
1103
|
+
const shader = field("shader");
|
|
1104
|
+
return !!name && !!shader && literal(name) && literal(shader);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* The kit's declarations (`font()`, `googleFont()`, `defineEffect()`, `defineTransition()`) at the top of the file,
|
|
1108
|
+
* with what names them written out, where the editor and lint can read them.
|
|
1109
|
+
*/
|
|
1110
|
+
const declaration = {
|
|
1111
|
+
name: "declaration",
|
|
1112
|
+
check: (index) => declarations(index).flatMap(({ call, topLevel, declarer }) => {
|
|
1113
|
+
const name = call.callee.name;
|
|
1114
|
+
if (!topLevel) return [{
|
|
1115
|
+
rule: "declaration",
|
|
1116
|
+
severity: "warning",
|
|
1117
|
+
range: rangeOf(call),
|
|
1118
|
+
message: `${name}() inside a function or expression is hidden from the editor; declare it at the top of the file: const x = ${name}(…)`
|
|
1119
|
+
}];
|
|
1120
|
+
if (SHADERS.has(declarer) ? !literalShader(call) : !call.arguments.every(literal)) return [{
|
|
1121
|
+
rule: "declaration",
|
|
1122
|
+
severity: "warning",
|
|
1123
|
+
range: rangeOf(call),
|
|
1124
|
+
message: SHADERS.has(declarer) ? `${name}() takes a literal name and shader, so the editor and lint can read them from the source` : `${name}() takes literal arguments, so the editor and lint can read the font from the source`
|
|
1125
|
+
}];
|
|
1126
|
+
return [];
|
|
1127
|
+
})
|
|
1128
|
+
};
|
|
1038
1129
|
// src/source/lint/rules/free-code.ts
|
|
1039
1130
|
/** HTML straight in a `<Track>` or `<Composition>`: the resolver walks past it and nothing draws it. */
|
|
1040
1131
|
const strayHtml = {
|
|
@@ -1050,6 +1141,27 @@ const strayHtml = {
|
|
|
1050
1141
|
* Free code renders frame by frame against the video's own clock. `useT()`, requestAnimationFrame, Date, performance
|
|
1051
1142
|
* and CSS animations follow it; timers, media elements playing on their own and canvases kept on the CPU do not.
|
|
1052
1143
|
*/
|
|
1144
|
+
/** Libraries with a clock of their own, by module: the imports that run on it, and the adapter that puts them on the video's. */
|
|
1145
|
+
const OWN_CLOCK = {
|
|
1146
|
+
"@react-three/fiber": {
|
|
1147
|
+
names: ["Canvas"],
|
|
1148
|
+
use: "<ThreeScene> from @cutcraft/graphics/three"
|
|
1149
|
+
},
|
|
1150
|
+
motion: {
|
|
1151
|
+
names: ["animate"],
|
|
1152
|
+
use: "useTimeline from @cutcraft/graphics/motion"
|
|
1153
|
+
},
|
|
1154
|
+
"motion/react": {
|
|
1155
|
+
names: ["animate", "useAnimate"],
|
|
1156
|
+
use: "useTimeline or useLayerTime from @cutcraft/graphics/motion"
|
|
1157
|
+
},
|
|
1158
|
+
"framer-motion": {
|
|
1159
|
+
names: ["animate", "useAnimate"],
|
|
1160
|
+
use: "useTimeline or useLayerTime from @cutcraft/graphics/motion"
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
/** Props that start a Motion component's own animation. */
|
|
1164
|
+
const MOTION_PLAYS = /* @__PURE__ */ new Set(["animate", "whileInView"]);
|
|
1053
1165
|
const freeCodeClock = {
|
|
1054
1166
|
name: "free-code-clock",
|
|
1055
1167
|
check: (index) => {
|
|
@@ -1062,13 +1174,38 @@ const freeCodeClock = {
|
|
|
1062
1174
|
message
|
|
1063
1175
|
});
|
|
1064
1176
|
};
|
|
1177
|
+
const ownClock = /* @__PURE__ */ new Map();
|
|
1178
|
+
const motionFactories = /* @__PURE__ */ new Set();
|
|
1179
|
+
for (const node of index.ast.program.body) {
|
|
1180
|
+
if (!t.isImportDeclaration(node)) continue;
|
|
1181
|
+
const lib = OWN_CLOCK[node.source.value];
|
|
1182
|
+
if (!lib) continue;
|
|
1183
|
+
for (const spec of node.specifiers) {
|
|
1184
|
+
if (!t.isImportSpecifier(spec)) continue;
|
|
1185
|
+
const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
1186
|
+
if (lib.names.includes(imported)) ownClock.set(spec.local.name, {
|
|
1187
|
+
imported,
|
|
1188
|
+
use: lib.use
|
|
1189
|
+
});
|
|
1190
|
+
if (node.source.value !== "motion" && (imported === "motion" || imported === "m")) motionFactories.add(spec.local.name);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1065
1193
|
t.traverseFast(index.ast.program, (node) => {
|
|
1066
1194
|
if (t.isCallExpression(node)) {
|
|
1067
1195
|
const callee = node.callee;
|
|
1068
1196
|
const name = t.isIdentifier(callee) ? callee.name : t.isMemberExpression(callee) && t.isIdentifier(callee.property) ? callee.property.name : null;
|
|
1069
1197
|
if (name === "setTimeout" || name === "setInterval") warn(node, `${name} runs on the wall clock, so an export would not repeat it frame for frame; derive the change from useT() instead`);
|
|
1070
|
-
|
|
1071
|
-
|
|
1198
|
+
const own = t.isIdentifier(callee) ? ownClock.get(callee.name) : void 0;
|
|
1199
|
+
if (own) warn(node, `${own.imported}() plays on its library's own clock, so a scrub or an export does not follow it; use ${own.use}`);
|
|
1200
|
+
} else if (t.isJSXOpeningElement(node)) {
|
|
1201
|
+
const el = node.name;
|
|
1202
|
+
if (t.isJSXIdentifier(el) && (el.name === "video" || el.name === "audio")) warn(node, `<${el.name}> plays on its own clock and its sound is not in the export; use a <Clip> or <Audio>, and useClipFrame for a clip's picture in free code`);
|
|
1203
|
+
else if (t.isJSXIdentifier(el) && ownClock.has(el.name)) warn(node, `<${el.name}> draws on its own clock, so a scrub or an export does not follow it; use ${ownClock.get(el.name).use}`);
|
|
1204
|
+
else if (t.isJSXMemberExpression(el) && t.isJSXIdentifier(el.object) && motionFactories.has(el.object.name)) {
|
|
1205
|
+
const plays = node.attributes.find((a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && MOTION_PLAYS.has(a.name.name));
|
|
1206
|
+
if (plays) warn(plays, `<${el.object.name}.${el.property.name}> animates on Motion's own clock, so a scrub or an export does not follow it; drive it with useTimeline or useLayerTime from @cutcraft/graphics/motion`);
|
|
1207
|
+
}
|
|
1208
|
+
} else if (t.isObjectProperty(node) && t.isIdentifier(node.key) && node.key.name === "willReadFrequently" && t.isBooleanLiteral(node.value, { value: true })) warn(node, "a canvas with willReadFrequently is kept on the CPU and the export does not capture it; leave the option out");
|
|
1072
1209
|
});
|
|
1073
1210
|
return out;
|
|
1074
1211
|
}
|
|
@@ -1136,6 +1273,25 @@ const graphicProps = {
|
|
|
1136
1273
|
return out;
|
|
1137
1274
|
}
|
|
1138
1275
|
};
|
|
1276
|
+
/** `<Graphic>` is deprecated: each one warns, pointing to the free code that replaces it. */
|
|
1277
|
+
const deprecatedGraphic = {
|
|
1278
|
+
name: "deprecated-graphic",
|
|
1279
|
+
check: (index) => {
|
|
1280
|
+
const out = [];
|
|
1281
|
+
for (const site of index.elements.values()) {
|
|
1282
|
+
if (site.kind !== "graphic") continue;
|
|
1283
|
+
const kind = site.props.get("kind")?.literal;
|
|
1284
|
+
out.push({
|
|
1285
|
+
rule: "deprecated-graphic",
|
|
1286
|
+
severity: "warning",
|
|
1287
|
+
message: `"${site.id}": <Graphic${typeof kind === "string" ? ` kind="${kind}"` : ""}> is deprecated and will be removed; draw it with <Motion>, <Three> or a <Layer> (see the examples)`,
|
|
1288
|
+
range: site.openingTag,
|
|
1289
|
+
id: site.id
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
return out;
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1139
1295
|
// src/source/lint/rules/not-rendered.ts
|
|
1140
1296
|
/** Values that ask for nothing, so writing them is never reported. */
|
|
1141
1297
|
const OFF = [
|
|
@@ -1431,20 +1587,21 @@ const themeLiterals = {
|
|
|
1431
1587
|
return out;
|
|
1432
1588
|
}
|
|
1433
1589
|
};
|
|
1434
|
-
/** Fonts no renderer ships
|
|
1590
|
+
/** Fonts no renderer ships and the file does not declare (`font()`, `googleFont()`): the export sets them in Inter. */
|
|
1435
1591
|
const fontAvailable = {
|
|
1436
1592
|
name: "font-available",
|
|
1437
1593
|
check: (index) => {
|
|
1438
1594
|
const theme = fileTheme(index) ?? resolveTheme(void 0);
|
|
1595
|
+
const listed = declaredFamilies(index);
|
|
1439
1596
|
const out = [];
|
|
1440
1597
|
const seen = /* @__PURE__ */ new Set();
|
|
1441
1598
|
const check = (family, site, prop, name, path) => {
|
|
1442
|
-
if (isBundledFont(family) || seen.has(`${site.id}.${path}`)) return;
|
|
1599
|
+
if (isBundledFont(family) || listed.has(family) || seen.has(`${site.id}.${path}`)) return;
|
|
1443
1600
|
seen.add(`${site.id}.${path}`);
|
|
1444
1601
|
out.push({
|
|
1445
1602
|
rule: "font-available",
|
|
1446
1603
|
severity: "warning",
|
|
1447
|
-
message: `"${site.id}.${path}" uses font "${family}", which is
|
|
1604
|
+
message: `"${site.id}.${path}" uses font "${family}", which is neither bundled nor declared with font() or googleFont(); the export sets it in Inter`,
|
|
1448
1605
|
range: [prop.range[0], prop.range[1]],
|
|
1449
1606
|
id: site.id,
|
|
1450
1607
|
prop: name
|
|
@@ -1710,7 +1867,9 @@ const rules = [
|
|
|
1710
1867
|
themeRefs,
|
|
1711
1868
|
themeLiterals,
|
|
1712
1869
|
fontAvailable,
|
|
1870
|
+
declaration,
|
|
1713
1871
|
graphicProps,
|
|
1872
|
+
deprecatedGraphic,
|
|
1714
1873
|
notRendered
|
|
1715
1874
|
];
|
|
1716
1875
|
/** The composition's first track in document order that lays out as a sequence and is not hidden: the one under everything else. */
|
|
@@ -1761,8 +1920,13 @@ function loadModule(source, options = {}) {
|
|
|
1761
1920
|
if (!(name in offered)) throw new Error(`The file imports “${name}”, which this host does not offer (it offers ${Object.keys(offered).join(", ")}).`);
|
|
1762
1921
|
return offered[name];
|
|
1763
1922
|
};
|
|
1764
|
-
|
|
1923
|
+
let shaders = [];
|
|
1924
|
+
const fonts = collectFonts(() => {
|
|
1925
|
+
shaders = collectShaders(() => new Function("require", "exports", "module", code)(require, module.exports, module));
|
|
1926
|
+
});
|
|
1765
1927
|
if (typeof module.exports.default !== "function") throw new Error("The file has no default export (the video).");
|
|
1928
|
+
Object.defineProperty(module.exports, MODULE_FONTS, { value: fonts });
|
|
1929
|
+
Object.defineProperty(module.exports, MODULE_SHADERS, { value: shaders });
|
|
1766
1930
|
return module.exports;
|
|
1767
1931
|
}
|
|
1768
1932
|
export { ALLOWED_IMPORTS, applyEdits, bake, buildSourceIndex, elementName, fold, foldToSource, indentOf, lineRange, lint, literalOf, loadModule, parseSource, printElement, printLiteral, printProp, rangeOf, rules };
|