lineadraw 0.3.0 → 0.5.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 +133 -0
- package/bin/lineadraw.mjs +17 -6
- package/lib/init.mjs +42 -5
- package/lib/manifest.mjs +82 -10
- package/lineadraw.d.ts +46 -2
- package/package.json +5 -8
- package/templates/README.md +1 -1
- package/templates/tsconfig.json +9 -2
- package/main.js +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# lineadraw
|
|
2
|
+
|
|
3
|
+
Type definitions and marketplace tooling for [Linea](https://lineadraw.com)
|
|
4
|
+
blocks and commands — the one devDependency a block/command repository
|
|
5
|
+
needs. Zero runtime: your modules import types from `"lineadraw"`, and the
|
|
6
|
+
Linea editor provides the real implementation when it runs them.
|
|
7
|
+
|
|
8
|
+
- **Typings** — `lineadraw.d.ts` makes `import { defineBlock } from
|
|
9
|
+
"lineadraw"` and `import { … } from "lineadraw/helpers"` type-check in
|
|
10
|
+
any editor/CI, with parameter types inferred into your `draw` function.
|
|
11
|
+
- **CLI** — `lineadraw manifest | check | init` turns any repository into
|
|
12
|
+
a Linea **marketplace**: a git repo the editor can browse and install
|
|
13
|
+
blocks/commands from.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Scaffold a fresh marketplace repository:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm create lineadraw my-blocks
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or add the structure to an existing project:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i -D lineadraw
|
|
27
|
+
npx lineadraw init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Either way you get `blocks/` and `commands/` folders, a `tsconfig.json`
|
|
31
|
+
wired to the typings, a sample block, a CI workflow, and a generated
|
|
32
|
+
catalog (`.lineadraw/marketplace.json`) — the repo is addable in the
|
|
33
|
+
editor from the first push.
|
|
34
|
+
|
|
35
|
+
## Writing a block
|
|
36
|
+
|
|
37
|
+
A block is one module with a single `defineBlock` default export:
|
|
38
|
+
typed parameters in, geometry out.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// blocks/surface.block.ts
|
|
42
|
+
import { defineBlock } from "lineadraw";
|
|
43
|
+
|
|
44
|
+
export default defineBlock({
|
|
45
|
+
// The id is the block's identity everywhere: @<collection>/<slug>.
|
|
46
|
+
// Re-publishing the same id updates installed copies in place.
|
|
47
|
+
id: "@my-blocks/surface",
|
|
48
|
+
name: "Surface",
|
|
49
|
+
version: "1.0.0",
|
|
50
|
+
params: [
|
|
51
|
+
{ name: "kind", type: "enum", default: "rock", options: ["rock", "ground"] },
|
|
52
|
+
],
|
|
53
|
+
place: ["Start point", "End point"], // one point pick per label, in order
|
|
54
|
+
draw: ({ params, inputs: [start, end] }) => {
|
|
55
|
+
params.kind; // "rock" | "ground" — inferred from the params table
|
|
56
|
+
return [{ type: "line", a: start, b: end }];
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Notable optional members: `place` may instead be an interactive
|
|
62
|
+
`({ params, pickPoint, pickObject })` function run once at insertion;
|
|
63
|
+
`dependencies` lists ids of blocks your `draw` emits instances of (installs
|
|
64
|
+
pull them along); `paramVisibility({ params })` hides property rows;
|
|
65
|
+
`previewInputs` curates the inputs used for thumbnails. Units are
|
|
66
|
+
millimeters, Y-up, angles in radians CCW.
|
|
67
|
+
|
|
68
|
+
## Writing a command
|
|
69
|
+
|
|
70
|
+
A command is a macro with a single `defineCommand` default export. Its
|
|
71
|
+
whole run is one undo step; Esc during a pick reverts cleanly.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// commands/circle-here.cmd.ts
|
|
75
|
+
import { defineCommand } from "lineadraw";
|
|
76
|
+
|
|
77
|
+
export default defineCommand({
|
|
78
|
+
id: "@my-blocks/circle-here",
|
|
79
|
+
name: "Circle here",
|
|
80
|
+
run: async ({ document, pickPoint, prompt, showToast }) => {
|
|
81
|
+
const center = await pickPoint("Center point");
|
|
82
|
+
const r = await prompt("Radius");
|
|
83
|
+
document.add([{ type: "circle", center, radius: Number.parseFloat(r) }]);
|
|
84
|
+
showToast("Done", "success");
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Imports are limited to `"lineadraw"` and `"lineadraw/helpers"` (geometry
|
|
90
|
+
helpers) — the editor resolves both when executing your module.
|
|
91
|
+
|
|
92
|
+
## The CLI
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
lineadraw manifest Validate all modules and write .lineadraw/marketplace.json
|
|
96
|
+
lineadraw check Validate without writing; fail if the committed manifest is stale
|
|
97
|
+
lineadraw init Add the marketplace structure to the current npm project
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The manifest is deterministic (repo-relative paths, no git metadata), so
|
|
101
|
+
`check` is a byte-for-byte diff — the included CI workflow runs it on every
|
|
102
|
+
push and PR. With `@lineadraw/sdk` installed as a devDependency,
|
|
103
|
+
`manifest`/`check` additionally **dry-run every block** and render
|
|
104
|
+
thumbnails; without it the CLI still fully works.
|
|
105
|
+
|
|
106
|
+
Collection name and owner come from your package.json:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{ "lineadraw": { "marketplaceName": "My blocks", "marketplaceOwner": "Me" } }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Using a marketplace in the editor
|
|
113
|
+
|
|
114
|
+
In [the editor](https://lineadraw.com): **Library → Marketplace tab → +**
|
|
115
|
+
and enter `owner/repo` (or `owner/repo@branch`). For a private repository,
|
|
116
|
+
create a fine-grained personal access token with read-only Contents access
|
|
117
|
+
to just that repository and paste it in the add dialog — it is stored only
|
|
118
|
+
on that device. Installing embeds the module (and its declared dependency
|
|
119
|
+
closure) in the document, so drawings keep working offline and travel with
|
|
120
|
+
the file.
|
|
121
|
+
|
|
122
|
+
## Ecosystem
|
|
123
|
+
|
|
124
|
+
- [Linea editor](https://lineadraw.com) — the browser CAD app that runs
|
|
125
|
+
these modules.
|
|
126
|
+
- [`lineadraw/lineadraw`](https://github.com/lineadraw/lineadraw) — the
|
|
127
|
+
public collection: real blocks/commands to learn from, agent skill, and
|
|
128
|
+
guides.
|
|
129
|
+
- [`@lineadraw/sdk`](https://www.npmjs.com/package/@lineadraw/sdk) —
|
|
130
|
+
headless Node API over the same kernel (open/edit/render/export
|
|
131
|
+
`.linea` documents; also powers the CLI's block dry-runs).
|
|
132
|
+
- [`@lineadraw/mcp`](https://www.npmjs.com/package/@lineadraw/mcp) — MCP
|
|
133
|
+
server with the full editor as an in-chat app.
|
package/bin/lineadraw.mjs
CHANGED
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
manifestJson,
|
|
12
12
|
MANIFEST_PATH,
|
|
13
13
|
verifyWithSdk,
|
|
14
|
+
withThumbnails,
|
|
14
15
|
} from "../lib/manifest.mjs";
|
|
15
|
-
import { init } from "../lib/init.mjs";
|
|
16
|
+
import { init, tsconfigLibWarning } from "../lib/init.mjs";
|
|
16
17
|
|
|
17
18
|
const USAGE = `Usage: lineadraw <manifest|check|init> [--dir <path>]
|
|
18
19
|
|
|
@@ -21,7 +22,9 @@ const USAGE = `Usage: lineadraw <manifest|check|init> [--dir <path>]
|
|
|
21
22
|
init Add the marketplace structure to the current npm project
|
|
22
23
|
|
|
23
24
|
With @lineadraw/sdk installed (devDependency), manifest and check also
|
|
24
|
-
dry-run every block
|
|
25
|
+
dry-run every block and bake preview thumbnails into the catalog — commit
|
|
26
|
+
the manifest from an environment with the sdk installed (the CI template
|
|
27
|
+
does) so check compares like with like.`;
|
|
25
28
|
|
|
26
29
|
const args = process.argv.slice(2);
|
|
27
30
|
const command = args[0];
|
|
@@ -40,16 +43,21 @@ const reportProblems = (problems) => {
|
|
|
40
43
|
|
|
41
44
|
/** Validate everything (light + optional sdk dry-run); exits on failure. */
|
|
42
45
|
const validatedManifest = async () => {
|
|
43
|
-
const { manifest, sources, problems } = buildManifest(dir);
|
|
44
|
-
const sdkResult = await verifyWithSdk(dir,
|
|
46
|
+
const { manifest: base, sources, problems } = buildManifest(dir);
|
|
47
|
+
const sdkResult = await verifyWithSdk(dir, base, sources);
|
|
45
48
|
const all = [...problems, ...sdkResult.problems];
|
|
46
49
|
if (all.length > 0) {
|
|
47
50
|
reportProblems(all);
|
|
48
51
|
fail(`${all.length} problem(s) — nothing written.`);
|
|
49
52
|
}
|
|
53
|
+
const manifest = withThumbnails(base, sdkResult.thumbnails);
|
|
50
54
|
if (!sdkResult.ran)
|
|
51
55
|
console.warn(
|
|
52
|
-
|
|
56
|
+
sdkResult.reason === "load-failed"
|
|
57
|
+
? `Blocks were not dry-run: @lineadraw/sdk is installed but failed to load (${sdkResult.detail}). Try reinstalling it.`
|
|
58
|
+
: sdkResult.reason === "too-old"
|
|
59
|
+
? "Blocks were not dry-run: the installed @lineadraw/sdk is too old for module validation. Update it to enable this check."
|
|
60
|
+
: "Blocks were not dry-run because @lineadraw/sdk is not installed. Install it as a devDependency to enable this check.",
|
|
53
61
|
);
|
|
54
62
|
return manifest;
|
|
55
63
|
};
|
|
@@ -75,6 +83,8 @@ switch (command) {
|
|
|
75
83
|
fail(
|
|
76
84
|
`${MANIFEST_PATH} is ${committed === null ? "missing" : "stale"} — run \`npx lineadraw manifest\` and commit the result.`,
|
|
77
85
|
);
|
|
86
|
+
const libWarning = tsconfigLibWarning(dir);
|
|
87
|
+
if (libWarning !== null) console.warn(`warning: ${libWarning}`);
|
|
78
88
|
console.log(
|
|
79
89
|
`OK: ${manifest.entries.length} entries, manifest is current.`,
|
|
80
90
|
);
|
|
@@ -82,9 +92,10 @@ switch (command) {
|
|
|
82
92
|
}
|
|
83
93
|
case "init": {
|
|
84
94
|
try {
|
|
85
|
-
const { created, skipped } = init(dir);
|
|
95
|
+
const { created, skipped, warnings } = await init(dir);
|
|
86
96
|
for (const f of created) console.log(`created ${f}`);
|
|
87
97
|
for (const f of skipped) console.log(`kept ${f}`);
|
|
98
|
+
for (const w of warnings) console.warn(`warning ${w}`);
|
|
88
99
|
console.log(
|
|
89
100
|
"\nDone. Commit, push, then add the repo in the editor: Library → Marketplace.",
|
|
90
101
|
);
|
package/lib/init.mjs
CHANGED
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
buildManifest,
|
|
17
|
+
manifestJson,
|
|
18
|
+
MANIFEST_PATH,
|
|
19
|
+
verifyWithSdk,
|
|
20
|
+
withThumbnails,
|
|
21
|
+
} from "./manifest.mjs";
|
|
16
22
|
|
|
17
23
|
const templatesDir = join(
|
|
18
24
|
dirname(fileURLToPath(import.meta.url)),
|
|
@@ -33,8 +39,29 @@ export const collectionSlug = (pkgName) => {
|
|
|
33
39
|
return slug === "" ? "my-collection" : slug;
|
|
34
40
|
};
|
|
35
41
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Warn when an existing tsconfig lets TypeScript default `lib` to include
|
|
44
|
+
* DOM: lib.dom's globals collide with lineadraw's global DTO aliases —
|
|
45
|
+
* DOM's `interface Text` (a Node) silently wins over lineadraw's `Text`
|
|
46
|
+
* (skipLibCheck hides the duplicate-identifier error). Repos scaffolded
|
|
47
|
+
* before lineadraw 0.3.1 carry such a tsconfig. Cheap string test on
|
|
48
|
+
* purpose: tsconfig is JSONC, so JSON.parse can't be trusted here.
|
|
49
|
+
* Returns the warning text, or null when the config is fine or absent.
|
|
50
|
+
*/
|
|
51
|
+
export const tsconfigLibWarning = (dir) => {
|
|
52
|
+
const p = join(dir, "tsconfig.json");
|
|
53
|
+
if (!existsSync(p)) return null;
|
|
54
|
+
const text = readFileSync(p, "utf8");
|
|
55
|
+
if (/"lib"\s*:/.test(text)) return null;
|
|
56
|
+
return (
|
|
57
|
+
'tsconfig.json has no "lib" — TypeScript then includes DOM types, and ' +
|
|
58
|
+
"DOM's global Text/Image/... shadow the lineadraw script types " +
|
|
59
|
+
'(Text becomes a DOM Node). Add to compilerOptions: "lib": ["ES2022"], "types": []'
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Run init in `dir`. Returns `{ created, skipped, warnings }` lists. */
|
|
64
|
+
export const init = async (dir) => {
|
|
38
65
|
const pkgPath = join(dir, "package.json");
|
|
39
66
|
if (!existsSync(pkgPath))
|
|
40
67
|
throw new Error(
|
|
@@ -112,16 +139,26 @@ export const init = (dir) => {
|
|
|
112
139
|
write(".gitignore", "node_modules/\n");
|
|
113
140
|
|
|
114
141
|
// Generate the catalog last, over whatever modules now exist.
|
|
115
|
-
const { manifest, problems } = buildManifest(dir);
|
|
142
|
+
const { manifest: base, sources, problems } = buildManifest(dir);
|
|
116
143
|
if (problems.length > 0) {
|
|
117
144
|
const detail = problems
|
|
118
145
|
.map((p) => `${p.file}: ${p.problems.join("; ")}`)
|
|
119
146
|
.join("\n");
|
|
120
147
|
throw new Error(`existing modules failed validation:\n${detail}`);
|
|
121
148
|
}
|
|
149
|
+
// Bake thumbnails when the sdk is installed, so the very first commit's
|
|
150
|
+
// manifest matches what CI's `check` (sdk installed) regenerates.
|
|
151
|
+
const sdkResult = await verifyWithSdk(dir, base, sources);
|
|
152
|
+
const manifest = withThumbnails(base, sdkResult.thumbnails);
|
|
122
153
|
mkdirSync(join(dir, dirname(MANIFEST_PATH)), { recursive: true });
|
|
123
154
|
writeFileSync(join(dir, MANIFEST_PATH), manifestJson(manifest));
|
|
124
155
|
created.push(`${MANIFEST_PATH} (${manifest.entries.length} entries)`);
|
|
125
156
|
|
|
126
|
-
|
|
157
|
+
// A kept (pre-existing) tsconfig may carry the DOM-collision footgun the
|
|
158
|
+
// template avoids — surface it instead of silently leaving it broken.
|
|
159
|
+
const warnings = [];
|
|
160
|
+
const libWarning = tsconfigLibWarning(dir);
|
|
161
|
+
if (libWarning !== null) warnings.push(libWarning);
|
|
162
|
+
|
|
163
|
+
return { created, skipped, warnings };
|
|
127
164
|
};
|
package/lib/manifest.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// bytes, so `check` is a plain diff.
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
|
|
11
11
|
// sucrase is this package's own dependency — the light path needs nothing
|
|
@@ -202,24 +202,96 @@ export const MANIFEST_PATH = ".lineadraw/marketplace.json";
|
|
|
202
202
|
* `{ ran, problems }` — `ran: false` when the sdk (or a new enough one) is
|
|
203
203
|
* absent; validation-only, so manifest CONTENT never depends on it.
|
|
204
204
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Locate the repo's installed @lineadraw/sdk entry module. require.resolve
|
|
207
|
+
* alone is NOT enough: sdk ≤ 0.11 exports only the `import` condition,
|
|
208
|
+
* which a CJS resolver refuses (ERR_PACKAGE_PATH_NOT_EXPORTED) even though
|
|
209
|
+
* the package is installed — that mislabeled installed sdks as missing.
|
|
210
|
+
* On any resolve failure, fall back to walking node_modules by hand (the
|
|
211
|
+
* same directory climb Node performs) and reading the entry from the sdk's
|
|
212
|
+
* own package.json. Returns an absolute path, or null when truly absent.
|
|
213
|
+
*/
|
|
214
|
+
const resolveSdkEntry = (repoDir) => {
|
|
207
215
|
try {
|
|
208
216
|
const repoRequire = createRequire(join(repoDir, "package.json"));
|
|
209
|
-
|
|
210
|
-
pathToFileURL(repoRequire.resolve("@lineadraw/sdk")).href
|
|
211
|
-
);
|
|
217
|
+
return repoRequire.resolve("@lineadraw/sdk");
|
|
212
218
|
} catch {
|
|
213
|
-
|
|
219
|
+
for (let d = resolve(repoDir); ; d = dirname(d)) {
|
|
220
|
+
const pkgPath = join(d, "node_modules", "@lineadraw", "sdk", "package.json");
|
|
221
|
+
if (existsSync(pkgPath)) {
|
|
222
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
223
|
+
const dot = pkg.exports?.["."];
|
|
224
|
+
const entry =
|
|
225
|
+
(typeof dot === "string" ? dot : (dot?.import ?? dot?.default)) ??
|
|
226
|
+
pkg.main ??
|
|
227
|
+
"./dist/index.js";
|
|
228
|
+
return join(dirname(pkgPath), entry);
|
|
229
|
+
}
|
|
230
|
+
if (dirname(d) === d) return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export const verifyWithSdk = async (repoDir, manifest, sources) => {
|
|
236
|
+
const entryPath = resolveSdkEntry(repoDir);
|
|
237
|
+
if (entryPath === null)
|
|
238
|
+
return { ran: false, problems: [], reason: "not-installed" };
|
|
239
|
+
let sdk;
|
|
240
|
+
try {
|
|
241
|
+
sdk = await import(pathToFileURL(entryPath).href);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
// Installed but not loadable (broken install, incompatible Node, …) —
|
|
244
|
+
// must never be reported as "not installed".
|
|
245
|
+
return {
|
|
246
|
+
ran: false,
|
|
247
|
+
problems: [],
|
|
248
|
+
reason: "load-failed",
|
|
249
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
250
|
+
};
|
|
214
251
|
}
|
|
215
252
|
if (typeof sdk.testBlockDefinition !== "function")
|
|
216
|
-
return { ran: false, problems: [] };
|
|
253
|
+
return { ran: false, problems: [], reason: "too-old" };
|
|
217
254
|
const problems = [];
|
|
255
|
+
// All block sources double as the resolution set for thumbnails, so a
|
|
256
|
+
// block emitting instances of its catalog siblings previews complete.
|
|
257
|
+
const blockSources = manifest.entries
|
|
258
|
+
.filter((e) => e.kind === "block")
|
|
259
|
+
.map((e) => sources.get(e.id));
|
|
260
|
+
const thumbnails = new Map();
|
|
218
261
|
for (const entry of manifest.entries) {
|
|
219
262
|
if (entry.kind !== "block") continue; // commands are interactive — no dry run
|
|
220
263
|
const result = await sdk.testBlockDefinition(sources.get(entry.id));
|
|
221
|
-
if (!result.ok)
|
|
264
|
+
if (!result.ok) {
|
|
222
265
|
problems.push({ file: entry.source, problems: [result.error] });
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
// Bake the preview thumbnail (sdk ≥ 0.12; older sdks still validate).
|
|
269
|
+
// The render is deterministic, so the manifest stays byte-stable.
|
|
270
|
+
if (typeof sdk.renderBlockThumbnailSvg === "function") {
|
|
271
|
+
try {
|
|
272
|
+
const svg = await sdk.renderBlockThumbnailSvg(sources.get(entry.id), {
|
|
273
|
+
definitions: blockSources,
|
|
274
|
+
});
|
|
275
|
+
if (typeof svg === "string") thumbnails.set(entry.id, svg);
|
|
276
|
+
} catch {
|
|
277
|
+
// A block that validates but can't render just ships no thumbnail.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
223
280
|
}
|
|
224
|
-
return { ran: true, problems };
|
|
281
|
+
return { ran: true, problems, thumbnails };
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Manifest with `thumbnailSvg` baked onto the block entries that have one —
|
|
286
|
+
* appended as the LAST key of each entry so serialization stays stable.
|
|
287
|
+
*/
|
|
288
|
+
export const withThumbnails = (manifest, thumbnails) => {
|
|
289
|
+
if (!thumbnails || thumbnails.size === 0) return manifest;
|
|
290
|
+
return {
|
|
291
|
+
...manifest,
|
|
292
|
+
entries: manifest.entries.map((entry) => {
|
|
293
|
+
const svg = thumbnails.get(entry.id);
|
|
294
|
+
return svg === undefined ? entry : { ...entry, thumbnailSvg: svg };
|
|
295
|
+
}),
|
|
296
|
+
};
|
|
225
297
|
};
|
package/lineadraw.d.ts
CHANGED
|
@@ -86,6 +86,12 @@ declare module "lineadraw" {
|
|
|
86
86
|
kind: "lines";
|
|
87
87
|
angle: number;
|
|
88
88
|
spacing: number;
|
|
89
|
+
} | {
|
|
90
|
+
kind: "gradient";
|
|
91
|
+
shape?: "linear" | "radial";
|
|
92
|
+
color1?: string;
|
|
93
|
+
color2?: string;
|
|
94
|
+
angle?: number;
|
|
89
95
|
};
|
|
90
96
|
/** Pattern spacing multiplier. */
|
|
91
97
|
scale?: number;
|
|
@@ -219,6 +225,12 @@ declare module "lineadraw" {
|
|
|
219
225
|
kind: "lines";
|
|
220
226
|
angle: number;
|
|
221
227
|
spacing: number;
|
|
228
|
+
} | {
|
|
229
|
+
kind: "gradient";
|
|
230
|
+
shape: "linear" | "radial";
|
|
231
|
+
color1: string;
|
|
232
|
+
color2: string;
|
|
233
|
+
angle: number;
|
|
222
234
|
};
|
|
223
235
|
export type StoredHatch = StoredShape<{
|
|
224
236
|
type: "hatch";
|
|
@@ -345,6 +357,14 @@ declare module "lineadraw" {
|
|
|
345
357
|
type: "enum";
|
|
346
358
|
default: string;
|
|
347
359
|
options: readonly ParameterOption[];
|
|
360
|
+
}
|
|
361
|
+
/** A section label between rows in the properties panel. Carries no
|
|
362
|
+
* value: it never appears in the `params` record `draw` receives.
|
|
363
|
+
* `name` stays the unique key (paramVisibility can hide it by name). */
|
|
364
|
+
| {
|
|
365
|
+
name: string;
|
|
366
|
+
label?: string;
|
|
367
|
+
type: "label";
|
|
348
368
|
};
|
|
349
369
|
/**
|
|
350
370
|
* The block's parameters table — what the `params` member of a BlockSpec
|
|
@@ -375,7 +395,9 @@ declare module "lineadraw" {
|
|
|
375
395
|
* passes through unchanged.
|
|
376
396
|
*/
|
|
377
397
|
export type ParamValues<P> = P extends () => infer R ? ParamValues<R> : P extends readonly (infer Q extends ParamDef)[] ? {
|
|
378
|
-
readonly [E in Q as E
|
|
398
|
+
readonly [E in Q as E extends {
|
|
399
|
+
type: "label";
|
|
400
|
+
} ? never : E["name"]]: ParamValue<E>;
|
|
379
401
|
} : P;
|
|
380
402
|
|
|
381
403
|
// ——— Script contract ———
|
|
@@ -865,6 +887,12 @@ type Hatch = Styled<{
|
|
|
865
887
|
kind: "lines";
|
|
866
888
|
angle: number;
|
|
867
889
|
spacing: number;
|
|
890
|
+
} | {
|
|
891
|
+
kind: "gradient";
|
|
892
|
+
shape?: "linear" | "radial";
|
|
893
|
+
color1?: string;
|
|
894
|
+
color2?: string;
|
|
895
|
+
angle?: number;
|
|
868
896
|
};
|
|
869
897
|
/** Pattern spacing multiplier. */
|
|
870
898
|
scale?: number;
|
|
@@ -998,6 +1026,12 @@ type StoredHatchFill = {
|
|
|
998
1026
|
kind: "lines";
|
|
999
1027
|
angle: number;
|
|
1000
1028
|
spacing: number;
|
|
1029
|
+
} | {
|
|
1030
|
+
kind: "gradient";
|
|
1031
|
+
shape: "linear" | "radial";
|
|
1032
|
+
color1: string;
|
|
1033
|
+
color2: string;
|
|
1034
|
+
angle: number;
|
|
1001
1035
|
};
|
|
1002
1036
|
type StoredHatch = StoredShape<{
|
|
1003
1037
|
type: "hatch";
|
|
@@ -1124,6 +1158,14 @@ type ParamDef = {
|
|
|
1124
1158
|
type: "enum";
|
|
1125
1159
|
default: string;
|
|
1126
1160
|
options: readonly ParameterOption[];
|
|
1161
|
+
}
|
|
1162
|
+
/** A section label between rows in the properties panel. Carries no
|
|
1163
|
+
* value: it never appears in the `params` record `draw` receives.
|
|
1164
|
+
* `name` stays the unique key (paramVisibility can hide it by name). */
|
|
1165
|
+
| {
|
|
1166
|
+
name: string;
|
|
1167
|
+
label?: string;
|
|
1168
|
+
type: "label";
|
|
1127
1169
|
};
|
|
1128
1170
|
/**
|
|
1129
1171
|
* The block's parameters table — what the `params` member of a BlockSpec
|
|
@@ -1154,7 +1196,9 @@ type ParamValue<Q extends ParamDef> = Q extends {
|
|
|
1154
1196
|
* passes through unchanged.
|
|
1155
1197
|
*/
|
|
1156
1198
|
type ParamValues<P> = P extends () => infer R ? ParamValues<R> : P extends readonly (infer Q extends ParamDef)[] ? {
|
|
1157
|
-
readonly [E in Q as E
|
|
1199
|
+
readonly [E in Q as E extends {
|
|
1200
|
+
type: "label";
|
|
1201
|
+
} ? never : E["name"]]: ParamValue<E>;
|
|
1158
1202
|
} : P;
|
|
1159
1203
|
|
|
1160
1204
|
// Script-contract globals: aliases into the module (not re-declarations),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lineadraw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Type definitions and marketplace tooling for Linea blocks and commands",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mikhail Koshelev",
|
|
@@ -11,17 +11,14 @@
|
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
-
"types": "./lineadraw.d.ts"
|
|
15
|
-
"default": "./main.js"
|
|
14
|
+
"types": "./lineadraw.d.ts"
|
|
16
15
|
},
|
|
17
16
|
"./helpers": {
|
|
18
|
-
"types": "./lineadraw.d.ts"
|
|
19
|
-
"default": "./main.js"
|
|
17
|
+
"types": "./lineadraw.d.ts"
|
|
20
18
|
}
|
|
21
19
|
},
|
|
22
20
|
"files": [
|
|
23
21
|
"lineadraw.d.ts",
|
|
24
|
-
"main.js",
|
|
25
22
|
"bin",
|
|
26
23
|
"lib",
|
|
27
24
|
"templates"
|
|
@@ -30,9 +27,9 @@
|
|
|
30
27
|
"test": "vitest run"
|
|
31
28
|
},
|
|
32
29
|
"dependencies": {
|
|
33
|
-
"sucrase": "^3.35.
|
|
30
|
+
"sucrase": "^3.35.1"
|
|
34
31
|
},
|
|
35
32
|
"devDependencies": {
|
|
36
|
-
"vitest": "^4.1.
|
|
33
|
+
"vitest": "^4.1.11"
|
|
37
34
|
}
|
|
38
35
|
}
|
package/templates/README.md
CHANGED
|
@@ -12,7 +12,7 @@ catalog (`.lineadraw/marketplace.json`).
|
|
|
12
12
|
|
|
13
13
|
## Using it in the editor
|
|
14
14
|
|
|
15
|
-
Library →
|
|
15
|
+
Library → Marketplace tab → + → add this repository
|
|
16
16
|
as `owner/repo`. For a private repository, create a fine-grained personal
|
|
17
17
|
access token with read-only Contents access to just this repository and
|
|
18
18
|
paste it in the add dialog — it is stored only on that device.
|
package/templates/tsconfig.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"ES2022"
|
|
6
|
+
],
|
|
7
|
+
"types": [],
|
|
4
8
|
"module": "ESNext",
|
|
5
9
|
"moduleResolution": "bundler",
|
|
6
10
|
"strict": true,
|
|
7
11
|
"noEmit": true,
|
|
8
12
|
"skipLibCheck": true
|
|
9
13
|
},
|
|
10
|
-
"include": [
|
|
11
|
-
|
|
14
|
+
"include": [
|
|
15
|
+
"blocks",
|
|
16
|
+
"commands"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/main.js
DELETED
|
File without changes
|