@cardanvil/frame-kit 0.5.0 → 0.6.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 (46) hide show
  1. package/README.md +45 -0
  2. package/dist/cli/main.js +16 -0
  3. package/dist/cli/sourceWatcher.d.ts +40 -0
  4. package/dist/cli/sourceWatcher.js +100 -0
  5. package/dist/cli/studio/boxSets.d.ts +33 -0
  6. package/dist/cli/studio/boxSets.js +87 -0
  7. package/dist/cli/studio/edit/applyEdits.d.ts +71 -0
  8. package/dist/cli/studio/edit/applyEdits.js +177 -0
  9. package/dist/cli/studio/edit/impact.d.ts +53 -0
  10. package/dist/cli/studio/edit/impact.js +87 -0
  11. package/dist/cli/studio/edit/resolveModule.d.ts +10 -0
  12. package/dist/cli/studio/edit/resolveModule.js +55 -0
  13. package/dist/cli/studio/edit/resolvePath.d.ts +53 -0
  14. package/dist/cli/studio/edit/resolvePath.js +606 -0
  15. package/dist/cli/studio/edit/sourceIndex.d.ts +29 -0
  16. package/dist/cli/studio/edit/sourceIndex.js +73 -0
  17. package/dist/cli/studio/edit/ts.d.ts +14 -0
  18. package/dist/cli/studio/edit/ts.js +34 -0
  19. package/dist/cli/studio/serve.d.ts +23 -0
  20. package/dist/cli/studio/serve.js +513 -0
  21. package/dist/cli/studio/session.d.ts +73 -0
  22. package/dist/cli/studio/session.js +154 -0
  23. package/dist/cli/watch.d.ts +3 -0
  24. package/dist/cli/watch.js +20 -89
  25. package/dist/layout/assetPlacement.d.ts +71 -0
  26. package/dist/layout/assetPlacement.js +104 -0
  27. package/dist/layout/decorations.d.ts +78 -0
  28. package/dist/layout/decorations.js +108 -0
  29. package/dist/layout/frameLayers.d.ts +136 -0
  30. package/dist/layout/frameLayers.js +291 -0
  31. package/dist/layout/index.d.ts +16 -0
  32. package/dist/layout/index.js +16 -0
  33. package/dist/layout/resolveFrameAsset.d.ts +31 -0
  34. package/dist/layout/resolveFrameAsset.js +58 -0
  35. package/dist/manifest/contract.d.ts +359 -1
  36. package/dist/manifest/contract.js +3 -1
  37. package/dist/schema/frame.d.ts +907 -0
  38. package/dist/schema/frame.js +16 -0
  39. package/dist/schema/frameAssets.d.ts +3 -1
  40. package/dist/schema/frameAssets.js +23 -1
  41. package/dist/studio/assets/PlantinMTProRgIt-D0FB51kb.woff2 +0 -0
  42. package/dist/studio/assets/beleren-b-B3lFvodX.ttf +0 -0
  43. package/dist/studio/assets/beleren-bsc-yvL6mU7X.ttf +0 -0
  44. package/dist/studio/assets/index-CNzyV22G.js +76 -0
  45. package/dist/studio/index.html +12 -0
  46. package/package.json +12 -3
@@ -0,0 +1,87 @@
1
+ import { resolveFramePath } from "./resolvePath.js";
2
+ /** Splits a leaf path into its box-set prefix and the tail inside that set. */
3
+ function splitTarget(target, boxSets) {
4
+ for (const set of boxSets) {
5
+ if (target.length > set.path.length &&
6
+ set.path.every((segment, i) => target[i] === segment)) {
7
+ return { set, tail: target.slice(set.path.length) };
8
+ }
9
+ }
10
+ return undefined;
11
+ }
12
+ /**
13
+ * Every box set that reads the same literal as `target`.
14
+ *
15
+ * Frames share geometry heavily — `frame-borderless` spreads
16
+ * `borderlessNormalBoxes` into most of its layouts, and
17
+ * `withCollectorInfoDefaults` hands one `collectorInfo` to all ten box sets of
18
+ * every layout. Moving such a box moves it everywhere, so the studio has to be
19
+ * able to say so *before* it writes, rather than surprising the author after.
20
+ *
21
+ * Identity is the exact source range: two paths are the same value only when
22
+ * they resolve to the same characters in the same file.
23
+ *
24
+ * The scan is per-frame. A literal exported through `/extend` and read by a
25
+ * different frame package is not counted here; `resolveFramePath` reports that
26
+ * separately by way of the file it landed in.
27
+ */
28
+ export function impactOf(options) {
29
+ const { ctx, entryFile, exportName, boxSets, target } = options;
30
+ const split = splitTarget(target, boxSets);
31
+ if (!split) {
32
+ return [];
33
+ }
34
+ const anchor = resolveFramePath(ctx, entryFile, exportName, target);
35
+ if (!anchor.editable) {
36
+ return [];
37
+ }
38
+ const shared = [];
39
+ for (const set of boxSets) {
40
+ const path = [...set.path, ...split.tail];
41
+ if (path.length === target.length &&
42
+ path.every((s, i) => s === target[i])) {
43
+ continue; // the value being edited
44
+ }
45
+ const resolution = resolveFramePath(ctx, entryFile, exportName, path);
46
+ if (resolution.editable &&
47
+ resolution.file === anchor.file &&
48
+ resolution.start === anchor.start &&
49
+ resolution.end === anchor.end) {
50
+ shared.push({
51
+ variant: set.variant,
52
+ layout: set.layout,
53
+ boxSet: set.key,
54
+ path,
55
+ });
56
+ }
57
+ }
58
+ return shared;
59
+ }
60
+ /**
61
+ * Memoises impact scans for one loaded revision of a frame.
62
+ *
63
+ * The scan resolves the same tail against every box set, which for a frame
64
+ * like `frame-borderless` is ~100 resolutions; the inspector asks for one per
65
+ * field. Parses are already cached by `sourceIndex`, so this only has to stop
66
+ * the walk itself from repeating.
67
+ */
68
+ export function createImpactCache() {
69
+ let cachedRevision = -1;
70
+ let entries = new Map();
71
+ return {
72
+ get(revision, options) {
73
+ if (revision !== cachedRevision) {
74
+ cachedRevision = revision;
75
+ entries = new Map();
76
+ }
77
+ const key = options.target.join(".");
78
+ const hit = entries.get(key);
79
+ if (hit) {
80
+ return hit;
81
+ }
82
+ const computed = impactOf(options);
83
+ entries.set(key, computed);
84
+ return computed;
85
+ },
86
+ };
87
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Absolute path a specifier resolves to from `importer`, or `undefined`.
3
+ *
4
+ * Relative specifiers are probed on disk; bare ones go through Node's own
5
+ * resolver, which honours `exports` maps and pnpm's workspace links. That
6
+ * matters because frame packages point their exports straight at TypeScript
7
+ * source (`"./extend": "./src/extend.ts"`), so `@cardanvil/frame-m15/extend`
8
+ * resolves to a file this can actually parse.
9
+ */
10
+ export declare function resolveModuleFile(specifier: string, importer: string): string | undefined;
@@ -0,0 +1,55 @@
1
+ import { statSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { normalizeFile } from "./sourceIndex.js";
5
+ /**
6
+ * Extensions tried for a relative import, in order.
7
+ *
8
+ * Frame packages import without extensions by convention (`./boxes`,
9
+ * `./base`), so a specifier names either a sibling module or a directory with
10
+ * a barrel. `.js` is mapped back to `.ts` for authors who write NodeNext-style
11
+ * specifiers.
12
+ */
13
+ const CANDIDATES = [
14
+ (base) => `${base}.ts`,
15
+ (base) => `${base}.tsx`,
16
+ (base) => path.join(base, "index.ts"),
17
+ (base) => path.join(base, "index.tsx"),
18
+ (base) => base,
19
+ ];
20
+ function firstExisting(base) {
21
+ for (const candidate of CANDIDATES) {
22
+ const file = candidate(base);
23
+ try {
24
+ if (statSync(file).isFile()) {
25
+ return normalizeFile(file);
26
+ }
27
+ }
28
+ catch {
29
+ /* not there; try the next candidate */
30
+ }
31
+ }
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Absolute path a specifier resolves to from `importer`, or `undefined`.
36
+ *
37
+ * Relative specifiers are probed on disk; bare ones go through Node's own
38
+ * resolver, which honours `exports` maps and pnpm's workspace links. That
39
+ * matters because frame packages point their exports straight at TypeScript
40
+ * source (`"./extend": "./src/extend.ts"`), so `@cardanvil/frame-m15/extend`
41
+ * resolves to a file this can actually parse.
42
+ */
43
+ export function resolveModuleFile(specifier, importer) {
44
+ if (specifier.startsWith(".")) {
45
+ const base = path.resolve(path.dirname(importer), specifier);
46
+ const stripped = base.replace(/\.js$/, "");
47
+ return firstExisting(base) ?? firstExisting(stripped);
48
+ }
49
+ try {
50
+ return normalizeFile(createRequire(importer).resolve(specifier));
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
@@ -0,0 +1,53 @@
1
+ import { type FramePath } from "../../../manifest/walk.js";
2
+ import { type SourceIndex } from "./sourceIndex.js";
3
+ import type { TypeScriptApi } from "./ts.js";
4
+ /** Why a value cannot be edited where it is written. */
5
+ export type UnsupportedReason = "computed" | "helper-call" | "not-a-literal" | "outside-root" | "missing-property" | "unsupported-expression" | "no-export" | "unreadable";
6
+ export interface SourceLocation {
7
+ /** Absolute, normalized. */
8
+ readonly file: string;
9
+ /** 1-based. */
10
+ readonly line: number;
11
+ /** 1-based. */
12
+ readonly column: number;
13
+ }
14
+ export interface ResolvedLiteral {
15
+ readonly editable: true;
16
+ readonly file: string;
17
+ /** Offsets of the literal itself, ready to splice. */
18
+ readonly start: number;
19
+ readonly end: number;
20
+ /** The literal exactly as written, e.g. `2446` or `-12`. */
21
+ readonly text: string;
22
+ readonly location: SourceLocation;
23
+ /**
24
+ * True when the value came from a default handed to a frame-kit helper
25
+ * rather than from the box set itself, so every set that omits it reads
26
+ * this one literal.
27
+ */
28
+ readonly sharedDefault: boolean;
29
+ }
30
+ export interface UnresolvedValue {
31
+ readonly editable: false;
32
+ readonly reason: UnsupportedReason;
33
+ /** A sentence naming the construct, for the inspector's lock tooltip. */
34
+ readonly detail: string;
35
+ /** Where the value really comes from, when that is known. */
36
+ readonly source?: SourceLocation;
37
+ }
38
+ export type Resolution = ResolvedLiteral | UnresolvedValue;
39
+ export interface ResolveContext {
40
+ readonly api: TypeScriptApi;
41
+ readonly index: SourceIndex;
42
+ /** Nothing outside this directory may be reported as editable. */
43
+ readonly root: string;
44
+ }
45
+ /**
46
+ * Where the value at `framePath` is written in source, if it is written as a
47
+ * literal at all.
48
+ *
49
+ * Returns the exact text range so a caller can splice a new value in without
50
+ * reprinting anything — comments, formatting and import order survive because
51
+ * nothing else is touched.
52
+ */
53
+ export declare function resolveFramePath(ctx: ResolveContext, entryFile: string, exportName: string, framePath: FramePath): Resolution;