@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.
- package/README.md +45 -0
- package/dist/cli/main.js +16 -0
- package/dist/cli/sourceWatcher.d.ts +40 -0
- package/dist/cli/sourceWatcher.js +100 -0
- package/dist/cli/studio/boxSets.d.ts +33 -0
- package/dist/cli/studio/boxSets.js +87 -0
- package/dist/cli/studio/edit/applyEdits.d.ts +71 -0
- package/dist/cli/studio/edit/applyEdits.js +177 -0
- package/dist/cli/studio/edit/impact.d.ts +53 -0
- package/dist/cli/studio/edit/impact.js +87 -0
- package/dist/cli/studio/edit/resolveModule.d.ts +10 -0
- package/dist/cli/studio/edit/resolveModule.js +55 -0
- package/dist/cli/studio/edit/resolvePath.d.ts +53 -0
- package/dist/cli/studio/edit/resolvePath.js +606 -0
- package/dist/cli/studio/edit/sourceIndex.d.ts +29 -0
- package/dist/cli/studio/edit/sourceIndex.js +73 -0
- package/dist/cli/studio/edit/ts.d.ts +14 -0
- package/dist/cli/studio/edit/ts.js +34 -0
- package/dist/cli/studio/serve.d.ts +23 -0
- package/dist/cli/studio/serve.js +513 -0
- package/dist/cli/studio/session.d.ts +73 -0
- package/dist/cli/studio/session.js +154 -0
- package/dist/cli/watch.d.ts +3 -0
- package/dist/cli/watch.js +20 -89
- package/dist/layout/assetPlacement.d.ts +71 -0
- package/dist/layout/assetPlacement.js +104 -0
- package/dist/layout/decorations.d.ts +78 -0
- package/dist/layout/decorations.js +108 -0
- package/dist/layout/frameLayers.d.ts +136 -0
- package/dist/layout/frameLayers.js +291 -0
- package/dist/layout/index.d.ts +16 -0
- package/dist/layout/index.js +16 -0
- package/dist/layout/resolveFrameAsset.d.ts +31 -0
- package/dist/layout/resolveFrameAsset.js +58 -0
- package/dist/manifest/contract.d.ts +359 -1
- package/dist/manifest/contract.js +3 -1
- package/dist/schema/frame.d.ts +907 -0
- package/dist/schema/frame.js +16 -0
- package/dist/schema/frameAssets.d.ts +3 -1
- package/dist/schema/frameAssets.js +23 -1
- package/dist/studio/assets/PlantinMTProRgIt-D0FB51kb.woff2 +0 -0
- package/dist/studio/assets/beleren-b-B3lFvodX.ttf +0 -0
- package/dist/studio/assets/beleren-bsc-yvL6mU7X.ttf +0 -0
- package/dist/studio/assets/index-CNzyV22G.js +76 -0
- package/dist/studio/index.html +12 -0
- 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;
|